mirror of
https://github.com/status-im/react-native-webview-bridge.git
synced 2026-08-30 21:01:12 +00:00
wip for android
This commit is contained in:
+48
-5
@@ -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<String, Integer> 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) {
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ public class WebViewBridgePackage implements ReactPackage {
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
|
||||
return Arrays.<ViewManager>asList(
|
||||
new WebViewBridgeManager()
|
||||
new WebViewBridgeManager(reactApplicationContext)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage()
|
||||
new MainReactPackage(),
|
||||
new WebViewBridgePackage()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
}())
|
||||
@@ -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)
|
||||
}
|
||||
}())
|
||||
@@ -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')
|
||||
|
||||
@@ -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 (
|
||||
<WebViewBridgeRPC
|
||||
ref={(ref) => 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',
|
||||
}
|
||||
});
|
||||
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>
|
||||
Welcome to React Native!
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
To get started, edit index.android.js
|
||||
</Text>
|
||||
<Text style={styles.instructions}>
|
||||
Double tap R on your keyboard to reload,{'\n'}
|
||||
Shake or press menu button for dev menu
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 (
|
||||
<WebViewBridgeRPC
|
||||
ref={(ref) => 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);
|
||||
|
||||
+250
-104
@@ -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 = () => (
|
||||
<View style={styles.loadingView}>
|
||||
<ActivityIndicator
|
||||
style={styles.loadingProgressBar}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
/**
|
||||
* 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 =
|
||||
<RCTWebViewBridge
|
||||
ref={RCT_WEBVIEWBRIDGE_REF}
|
||||
ref={RCT_WEBVIEW_REF}
|
||||
key="webViewKey"
|
||||
javaScriptEnabled={true}
|
||||
{...props}
|
||||
source={resolveAssetSource(source)}
|
||||
style={webViewStyles}
|
||||
source={resolveAssetSource(source)}
|
||||
scalesPageToFit={this.props.scalesPageToFit}
|
||||
injectedJavaScript={this.props.injectedJavaScript}
|
||||
userAgent={this.props.userAgent}
|
||||
javaScriptEnabled={this.props.javaScriptEnabled}
|
||||
domStorageEnabled={this.props.domStorageEnabled}
|
||||
contentInset={this.props.contentInset}
|
||||
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
|
||||
onContentSizeChange={this.props.onContentSizeChange}
|
||||
onLoadingStart={this.onLoadingStart}
|
||||
onLoadingFinish={this.onLoadingFinish}
|
||||
onLoadingError={this.onLoadingError}
|
||||
onChange={this.onMessage}
|
||||
testID={this.props.testID}
|
||||
mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
|
||||
onBridgeMessage={this.onBridgeMessage}
|
||||
rpc={!!this.props.rpc}
|
||||
/>;
|
||||
|
||||
return (
|
||||
@@ -136,89 +247,116 @@ var WebViewBridge = React.createClass({
|
||||
{otherView}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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;
|
||||
module.exports = WebView;
|
||||
|
||||
Reference in New Issue
Block a user