diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index 8988bb7..f3c9d75 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -1,12 +1,16 @@ package com.github.alinz.reactnativewebviewbridge; +import android.content.Context; import android.webkit.WebView; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.views.webview.ReactWebViewManager; import com.facebook.react.uimanager.annotations.ReactProp; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Map; @@ -15,9 +19,17 @@ import javax.annotation.Nullable; public class WebViewBridgeManager extends ReactWebViewManager { private static final String REACT_CLASS = "RCTWebViewBridge"; - public static final int COMMAND_SEND_TO_BRIDGE = 101; - public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 102; - public static final int COMMAND_INJECT_RPC = 103; + public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 101; + public static final int COMMAND_INJECT_RPC = 102; + public static final int COMMAND_SEND_TO_BRIDGE = 103; + + private ReactApplicationContext reactApplicationContext; + + public WebViewBridgeManager(ReactApplicationContext reactApplicationContext) { + super(); + //we need to know the context because we need to load files from asset + this.reactApplicationContext = reactApplicationContext; + } @Override public String getName() { @@ -31,6 +43,8 @@ public class WebViewBridgeManager extends ReactWebViewManager { Map commandsMap = super.getCommandsMap(); commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE); + commandsMap.put("injectWebViewBridge", COMMAND_INJECT_WEBVIEW_BRIDGE); + commandsMap.put("injectRPC", COMMAND_INJECT_RPC); return commandsMap; } @@ -61,12 +75,41 @@ public class WebViewBridgeManager extends ReactWebViewManager { } } - private void injectWebViewBridgeScript(WebView root) { + private static String inputStreamToString(InputStream input) throws IOException { + StringBuilder builder = new StringBuilder(); + int ch; + while((ch = input.read()) != -1){ + builder.append((char)ch); + } + input.close(); + return builder.toString(); + } + private static String loadAsset(String filename, final Context context) { + String output = null; + + try { + InputStream inputStream = context.getAssets().open(filename); + output = inputStreamToString(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + + return output; + } + + private void injectWebViewBridgeScript(WebView root) { + String injectContent = loadAsset("WebViewBridge.js", this.reactApplicationContext); + if (injectContent != null) { + evaluateJavascript(root, injectContent); + } } private void injectWebViewBridgeRPCScript(WebView root) { - + String injectContent = loadAsset("WebViewBridgeRPC.js", this.reactApplicationContext); + if (injectContent != null) { + evaluateJavascript(root, injectContent); + } } private void sendToBridge(WebView root, String message) { diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java index 1e189c4..de48b19 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java @@ -19,7 +19,7 @@ public class WebViewBridgePackage implements ReactPackage { @Override public List createViewManagers(ReactApplicationContext reactApplicationContext) { return Arrays.asList( - new WebViewBridgeManager() + new WebViewBridgeManager(reactApplicationContext) ); } diff --git a/examples/rpc/android/app/build.gradle b/examples/rpc/android/app/build.gradle index 8ba8b8a..c34cb8d 100644 --- a/examples/rpc/android/app/build.gradle +++ b/examples/rpc/android/app/build.gradle @@ -128,6 +128,7 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" + compile project(':react-native-webview-bridge') compile "com.facebook.react:react-native:+" // From node_modules } diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java index 774c206..ad076df 100644 --- a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java +++ b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java @@ -8,6 +8,7 @@ import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; +import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; import java.util.Arrays; import java.util.List; @@ -23,7 +24,8 @@ public class MainApplication extends Application implements ReactApplication { @Override protected List getPackages() { return Arrays.asList( - new MainReactPackage() + new MainReactPackage(), + new WebViewBridgePackage() ); } }; diff --git a/examples/rpc/android/app/src/main/res/WebViewBridge.js b/examples/rpc/android/app/src/main/res/WebViewBridge.js new file mode 100644 index 0000000..92ed4b8 --- /dev/null +++ b/examples/rpc/android/app/src/main/res/WebViewBridge.js @@ -0,0 +1,105 @@ +(function () { + var WebViewBridge = window.WebViewBridge ? window.WebViewBridge : {} + var queue = [] + var onMessageListeners = {} + + //base64 encode and decode polyfil + //modified version of https://github.com/davidchambers/Base64.js + //you can replace the b64Encode and b64Decode for atob and btoa. + var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + function b64Encode(input) { + var output = '' + for ( + var block, charCode, idx = 0, map = b64ch; + input.charAt(idx | 0) || (map = '=', idx % 1); + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = input.charCodeAt(idx += 3/4) + block = block << 8 | charCode + } + return output + } + + function b64Decode(input) { + var str = input.replace(/=+$/, '') + var output = '' + for ( + var bc = 0, bs, buffer, idx = 0; + buffer = str.charAt(idx++); + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + buffer = b64ch.indexOf(buffer) + } + return output + } + + function encode(input) { + if (typeof input !== 'string') { + try { + input = JSON.stringify(input) + } catch(e) {} + } + input = unescape(encodeURIComponent(input)) + return b64Encode(input) + } + + function decode(input) { + var result = decodeURIComponent(escape(b64Decode(input))) + try { + result = JSON.parse(result) + } catch(e){} + return result + } + + function signalNative() { + if (WebViewBridge.nativeAndroidSend) { + WebViewBridge.nativeAndroidSend(WebViewBridge.__fetch__()) + } else { + window.location = 'rnwb://message' + new Date().getTime() + } + } + + function dispatch(name, value) { + var event = new CustomEvent(name, { + detail: value, + bubbles: true + }); + + setTimeout(function () { + window.document.dispatchEvent(event) + }, 15) + } + + WebViewBridge.__dispatch__ = dispatch + WebViewBridge.__push__ = function (encoded) { + //we need to release native caller as soon as possible + //that's why we are wrap this on setTimeout + setTimeout(function () { + var fn = null + var decoded = decode(encoded) + Object.keys(onMessageListeners).forEach(function (onMessage) { + fn = onMessageListeners[onMessage] + fn(decoded) + }) + }, 15) + } + WebViewBridge.__fetch__ = function () { + var val = JSON.stringify(queue) + queue = [] + return val + } + WebViewBridge.send = function (input) { + queue.push(encode(input)) + setTimeout(signalNative, 15) + } + WebViewBridge.addMessageListener = function(fn) { + onMessageListeners[fn] = fn + } + WebViewBridge.removeMessageListener = function (fn) { + delete onMessageListeners[fn] + } + + window.WebViewBridge = WebViewBridge + dispatch('webviewbridge:init', WebViewBridge) +}()) diff --git a/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js b/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js new file mode 100644 index 0000000..fb0e820 --- /dev/null +++ b/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js @@ -0,0 +1,141 @@ +(function () { + 'use strict' + + var ids = 0 + var invokers = {} + var responseCallbacks = {} + + //invoker has accept 2 arguments + //fn(args, result). result is a function which accept an argument. + //that argument is being used to send to caller as a result value. + //by using this function, we can support both sync and async operation + function register(sender, name, fn) { + invokers[name] = function (id, args) { + fn(args, function (result) { + sender({ + id: id, + type: 'resolve', + result: result + }) + }, function (err) { + sender({ + id: id, + type: 'reject', + result: err + }) + }) + } + } + + function invoke(sender, name, args, opt, callback) { + var id = ++ids + var target = { + callback: callback + } + + if (opt) { + opt = {} + } + + if (!opt.timeout) { + opt.timeout = 0 + } + + if (opt.timeout) { + target.timeoutHandler = setTimeout(function () { + onReject(id, 'timeout') + }, opt.timeout) + } + + responseCallbacks[id] = target + + sender({ + id: id, + type: 'invoke', + name: name, + args: args + }) + } + + function onInvoke(payload) { + var invoker = invokers[payload.name] + if (invoker) { + setTimeout(function () { + invoker(payload.id, payload.args) + }, 15) + } + } + + function onResolve(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target.callback(null, result) + }, 15) + } + } + + function onReject(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target(result, null) + }, 15) + } + } + + function onMessage(payload) { + if (typeof payload === 'string') { + return + } + + // there are two types of payload + // invoke: { type: 'payload', id, name, args } + // resolve: { type: 'resolve', id, result } + // reject: { type: 'reject', id, result } + switch(payload.type) { + case 'invoke': + onInvoke(payload) + break + case 'resolve': + onResolve(payload.id, payload.result) + break + case 'reject': + onReject(payload.id, payload.result) + default: + //ignore + } + } + + //init this method register and attaches rpc to WebViewBridge. + //you can either check whether WebViewBridge.rpc is availebe or + //simply register to `webviewbridge:rpc` event. + function init(WebViewBridge) { + var rpc = {} + var sender = WebViewBridge.send + + window.removeEventListener('webviewbridge:init', init) + WebViewBridge.addMessageListener(onMessage) + + rpc.register = function (name, fn) { + register(sender, name, fn) + } + + rpc.invoke = function (name, args, opt, callback) { + invoke(sender, name, args, opt, callback) + } + + WebViewBridge.rpc = rpc + WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) + } + + if (window.WebViewBridge) { + init(window.WebViewBridge) + } else { + window.addEventListener('webviewbridge:init', init) + } +}()) diff --git a/examples/rpc/android/settings.gradle b/examples/rpc/android/settings.gradle index 756c08a..62831da 100644 --- a/examples/rpc/android/settings.gradle +++ b/examples/rpc/android/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'rpc' -include ':app' +include ':app', ':react-native-webview-bridge' +project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') diff --git a/examples/rpc/app.js b/examples/rpc/app.js new file mode 100644 index 0000000..ab25949 --- /dev/null +++ b/examples/rpc/app.js @@ -0,0 +1,79 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * @flow + */ + +import React, { Component } from 'react'; +import { + StyleSheet, + Text, + View +} from 'react-native'; + +import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' + +const injectScript = ` + (function () { + if (window.WebViewBridge && window.WebViewBridge.rpc) { + var rpc = WebViewBridge.rpc + + rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { + alert(resp) + }) + + rpc.register('onWebViewCall', function (args, resolve, reject) { + resolve('this is web call') + }) + + // WebViewBridge.addMessageListener(function (payload) { + // alert(payload.message) + // }); + // + // WebViewBridge.send({ message: 'This is working' }); + } + }()); +`; + +export default class app extends Component { + constructor(props, context) { + super(props, context) + + this.bridgeRef = null + } + + componentDidMount() { + this.bridgeRef.register('onNativeCall', this.onNativeCall) + } + + onNativeCall = (args, resolve, reject) => { + resolve('native call') + + setTimeout(() => { + this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { + console.log(err, resp) + }) + }, 3000) + } + + render() { + return ( + this.bridgeRef = ref} + source={{uri: 'https://github.com/facebook/react-native'}} + style={{marginTop: 20}} + javaScriptEnabled={true} + injectedJavaScript={injectScript} + /> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + } +}); diff --git a/examples/rpc/index.android.js b/examples/rpc/index.android.js index 7b23b33..f0e056d 100644 --- a/examples/rpc/index.android.js +++ b/examples/rpc/index.android.js @@ -4,50 +4,7 @@ * @flow */ -import React, { Component } from 'react'; -import { - AppRegistry, - StyleSheet, - Text, - View -} from 'react-native'; +import { AppRegistry } from 'react-native'; +import app from './app' -class rpc extends Component { - render() { - return ( - - - Welcome to React Native! - - - To get started, edit index.android.js - - - Double tap R on your keyboard to reload,{'\n'} - Shake or press menu button for dev menu - - - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - }, -}); - -AppRegistry.registerComponent('rpc', () => rpc); +AppRegistry.registerComponent('rpc', () => app); diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index 5b76b35..f0e056d 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -4,79 +4,7 @@ * @flow */ -import React, { Component } from 'react'; -import { - AppRegistry, - StyleSheet, - Text, - View -} from 'react-native'; +import { AppRegistry } from 'react-native'; +import app from './app' -import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' - -const injectScript = ` - (function () { - if (window.WebViewBridge && window.WebViewBridge.rpc) { - var rpc = WebViewBridge.rpc - - rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { - alert(resp) - }) - - rpc.register('onWebViewCall', function (args, resolve, reject) { - resolve('this is web call') - }) - - // WebViewBridge.addMessageListener(function (payload) { - // alert(payload.message) - // }); - // - // WebViewBridge.send({ message: 'This is working' }); - } - }()); -`; - -class rpc extends Component { - constructor(props, context) { - super(props, context) - - this.bridgeRef = null - } - - componentDidMount() { - this.bridgeRef.register('onNativeCall', this.onNativeCall) - } - - onNativeCall = (args, resolve, reject) => { - resolve('native call') - - setTimeout(() => { - this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { - console.log(err, resp) - }) - }, 3000) - } - - render() { - return ( - this.bridgeRef = ref} - source={{uri: 'https://github.com/facebook/react-native'}} - style={{marginTop: 20}} - javaScriptEnabled={true} - injectedJavaScript={injectScript} - /> - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - } -}); - -AppRegistry.registerComponent('rpc', () => rpc); +AppRegistry.registerComponent('rpc', () => app); diff --git a/lib/WebViewBridge/index.android.js b/lib/WebViewBridge/index.android.js index bdfe818..e004482 100644 --- a/lib/WebViewBridge/index.android.js +++ b/lib/WebViewBridge/index.android.js @@ -2,132 +2,243 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule WebViewBridge + * @providesModule WebView */ 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var invariant = require('invariant'); -var keyMirror = require('keymirror'); -var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); - -var { - ReactNativeViewAttributes, - UIManager, +import React, { PropTypes } from 'react' +import { + ActivityIndicator, EdgeInsetsPropType, StyleSheet, Text, + UIManager, View, - WebView, + ScrollView, + NativeModules, requireNativeComponent, - DeviceEventEmitter, - NativeModules: { - WebViewBridgeManager - } -} = ReactNative; -var { PropTypes } = React; + findNodeHandle +} from 'react-native' -var RCT_WEBVIEWBRIDGE_REF = 'webviewbridge'; +import invariant from 'fbjs/lib/invariant' +import keyMirror from 'fbjs/lib/keyMirror' -var WebViewBridgeState = keyMirror({ +import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; +import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' +import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' + +import { encode, decode } from '../core/base64' + +const { WebViewBridgeManager } = NativeModules + +var RCT_WEBVIEW_REF = 'webview'; + +var WebViewState = keyMirror({ IDLE: null, LOADING: null, ERROR: null, }); -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebViewBridge); +var defaultRenderLoading = () => ( + + + +); /** * Renders a native WebView. */ -var WebViewBridge = React.createClass({ +class WebView extends React.Component { + static propTypes = { + ...View.propTypes, + renderError: PropTypes.func, + renderLoading: PropTypes.func, + onLoad: PropTypes.func, + onLoadEnd: PropTypes.func, + onLoadStart: PropTypes.func, + onError: PropTypes.func, + automaticallyAdjustContentInsets: PropTypes.bool, + contentInset: EdgeInsetsPropType, + onNavigationStateChange: PropTypes.func, + onContentSizeChange: PropTypes.func, + startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load + style: View.propTypes.style, - propTypes: { - ...RCTWebViewBridge.propTypes, + html: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), + + url: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), /** - * Will be called once the message is being sent from webview + * Loads static html or a uri (with optional headers) in the WebView. */ + source: PropTypes.oneOfType([ + PropTypes.shape({ + /* + * The URI to load in the WebView. Can be a local or remote file. + */ + uri: PropTypes.string, + /* + * The HTTP Method to use. Defaults to GET if not specified. + * NOTE: On Android, only GET and POST are supported. + */ + method: PropTypes.oneOf(['GET', 'POST']), + /* + * Additional HTTP headers to send with the request. + * NOTE: On Android, this can only be used with GET requests. + */ + headers: PropTypes.object, + /* + * The HTTP body to send with the request. This must be a valid + * UTF-8 string, and will be sent exactly as specified, with no + * additional encoding (e.g. URL-escaping or base64) applied. + * NOTE: On Android, this can only be used with POST requests. + */ + body: PropTypes.string, + }), + PropTypes.shape({ + /* + * A static HTML page to display in the WebView. + */ + html: PropTypes.string, + /* + * The base URL to be used for any relative links in the HTML. + */ + baseUrl: PropTypes.string, + }), + /* + * Used internally by packager. + */ + PropTypes.number, + ]), + + /** + * Used on Android only, JS is enabled by default for WebView on iOS + * @platform android + */ + javaScriptEnabled: PropTypes.bool, + + /** + * Used on Android only, controls whether DOM Storage is enabled or not + * @platform android + */ + domStorageEnabled: PropTypes.bool, + + /** + * Sets the JS to be injected when the webpage loads. + */ + injectedJavaScript: PropTypes.string, + + /** + * Sets whether the webpage scales to fit the view and the user can change the scale. + */ + scalesPageToFit: PropTypes.bool, + + /** + * Sets the user-agent for this WebView. The user-agent can also be set in native using + * WebViewConfig. This prop will overwrite that config. + */ + userAgent: PropTypes.string, + + /** + * Used to locate this view in end-to-end tests. + */ + testID: PropTypes.string, + + /** + * Determines whether HTML5 audio & videos require the user to tap before they can + * start playing. The default value is `false`. + */ + mediaPlaybackRequiresUserAction: PropTypes.bool, + onBridgeMessage: PropTypes.func, - }, + rpc: PropTypes.bool + }; - getInitialState: function() { - return { - viewState: WebViewBridgeState.IDLE, - lastErrorEvent: null, - startInLoadingState: true, - }; - }, + static defaultProps = { + javaScriptEnabled : true, + scalesPageToFit: true, + }; - - componentWillMount: function() { - DeviceEventEmitter.addListener("webViewBridgeMessage", (body) => { - const { onBridgeMessage } = this.props; - const message = body.message; - if (onBridgeMessage) { - onBridgeMessage(message); - } - }); + state = { + viewState: WebViewState.IDLE, + lastErrorEvent: null, + startInLoadingState: true, + }; + componentWillMount() { if (this.props.startInLoadingState) { - this.setState({viewState: WebViewBridgeState.LOADING}); + this.setState({viewState: WebViewState.LOADING}); } - }, + } - render: function() { + render() { var otherView = null; - if (this.state.viewState === WebViewBridgeState.LOADING) { - otherView = this.props.renderLoading && this.props.renderLoading(); - } else if (this.state.viewState === WebViewBridgeState.ERROR) { + if (this.state.viewState === WebViewState.LOADING) { + otherView = (this.props.renderLoading || defaultRenderLoading)(); + } else if (this.state.viewState === WebViewState.ERROR) { var errorEvent = this.state.lastErrorEvent; otherView = this.props.renderError && this.props.renderError( errorEvent.domain, errorEvent.code, errorEvent.description); - } else if (this.state.viewState !== WebViewBridgeState.IDLE) { + } else if (this.state.viewState !== WebViewState.IDLE) { console.error('RCTWebViewBridge invalid state encountered: ' + this.state.loading); } var webViewStyles = [styles.container, this.props.style]; - if (this.state.viewState === WebViewBridgeState.LOADING || - this.state.viewState === WebViewBridgeState.ERROR) { + if (this.state.viewState === WebViewState.LOADING || + this.state.viewState === WebViewState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView webViewStyles.push(styles.hidden); } - var {javaScriptEnabled, domStorageEnabled} = this.props; - if (this.props.javaScriptEnabledAndroid) { - console.warn('javaScriptEnabledAndroid is deprecated. Use javaScriptEnabled instead'); - javaScriptEnabled = this.props.javaScriptEnabledAndroid; - } - if (this.props.domStorageEnabledAndroid) { - console.warn('domStorageEnabledAndroid is deprecated. Use domStorageEnabled instead'); - domStorageEnabled = this.props.domStorageEnabledAndroid; + var source = this.props.source || {}; + if (this.props.html) { + source.html = this.props.html; + } else if (this.props.url) { + source.uri = this.props.url; } - let {source, ...props} = {...this.props}; + if (source.method === 'POST' && source.headers) { + console.warn('WebView: `source.headers` is not supported when using POST.'); + } else if (source.method === 'GET' && source.body) { + console.warn('WebView: `source.body` is not supported when using GET.'); + } var webView = ; return ( @@ -136,89 +247,116 @@ var WebViewBridge = React.createClass({ {otherView} ); - }, + } - onMessage(event) { - if (this.props.onBridgeMessage != null && event.nativeEvent != null) { - this.props.onBridgeMessage(event.nativeEvent.message) - } - }, - - goForward: function() { + goForward = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goForward, null ); - }, + }; - goBack: function() { + goBack = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goBack, null ); - }, + }; - reload: function() { + reload = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.reload, null ); - }, + }; - sendToBridge: function (message: string) { + stopLoading = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.sendToBridge, - [message] + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.stopLoading, + null ); - }, + }; /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward */ - updateNavigationState: function(event) { + updateNavigationState = (event) => { if (this.props.onNavigationStateChange) { this.props.onNavigationStateChange(event.nativeEvent); } - }, + }; - getWebViewBridgeHandle: function() { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEWBRIDGE_REF]); - }, + onBridgeMessage = (event: Event) => { + const { onBridgeMessage } = this.props; + const messages = event.nativeEvent.messages; + if (onBridgeMessage) { + messages.forEach((message) => { + onBridgeMessage(decode(message)); + }); + } + }; - onLoadingStart: function(event) { + sendToBridge = (message: any) => { + WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message)); + }; + + getWebViewHandle = () => { + return findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + }; + + onLoadingStart = (event) => { var onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); this.updateNavigationState(event); - }, + }; - onLoadingError: function(event) { + onLoadingError = (event) => { event.persist(); // persist this event because we need to store it var {onError, onLoadEnd} = this.props; onError && onError(event); onLoadEnd && onLoadEnd(event); + console.warn('Encountered an error loading page', event.nativeEvent); this.setState({ lastErrorEvent: event.nativeEvent, - viewState: WebViewBridgeState.ERROR + viewState: WebViewState.ERROR }); - }, + }; + + onLoadingFinish = (event) => { + var { onLoad, onLoadEnd, rpc } = this.props; + + //we need to inject WebViewBridge and RPC once the page is loaded + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.injectWebViewBridge, + null + ); + + //check if RPC falg is also on + if (rps) { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.injectRPC, + null + ); + } - onLoadingFinish: function(event) { - var {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ - viewState: WebViewBridgeState.IDLE, + viewState: WebViewState.IDLE, }); this.updateNavigationState(event); - }, -}); + }; +} +var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView); var styles = StyleSheet.create({ container: { @@ -228,6 +366,14 @@ var styles = StyleSheet.create({ height: 0, flex: 0, // disable 'flex:1' when hiding a View }, + loadingView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingProgressBar: { + height: 20, + }, }); -module.exports = WebViewBridge; \ No newline at end of file +module.exports = WebView;