Skip to content

Add RPC capabilities to a React Native WebView component

License

Notifications You must be signed in to change notification settings

ronhe/rn-webview-rpc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React-Native WebView RPC

NPM version Build Status

RN-WebView-RPC's goal is to allow calls to native API from a web application that runs inside a WebView component, and vice versa. It can be used as a bridge between the native and the web parts of hybrid apps, for example, requesting native geo-location permissions from withing a WebView.

RN-WebView-RPC integrates React-Native's WebView component together with Google's Comlink library into an easy-to-use package.

For example, the snippet below allows opening a native alert that lets the user to choose the background color of an html web page.

// App.js

import React from 'react';
import { View, Alert } from 'react-native';
import WebViewRpc from 'rn-webview-rpc/native';
import html from './index.html';

export default class App extends React.Component {
  render() {
    return (
      <View style={{ flex: 1 }}>
        <WebViewRpc
          style={{ marginTop: 0, flex: 1 }}
          source={html}
          exposedObj={{ Alert }}
          injectScriptTag
        />
      </View>
    );
  }
}
// index.html

const proxy = rnRpc.proxy();
await proxy.Alert.alert(
    'What is your favorite color?',
    'We got green and blue',
    [
      {text: 'Ask me later'},
      {text: 'Green', onPress: rnRpc.proxyValue(() => setBgColor('green'))},
      {text: 'Blue', onPress: rnRpc.proxyValue(() => setBgColor('blue'))},
    ],
    { cancelable: false }
);
    
function setBgColor(color) {
  document.body.style.backgroundColor = color;
}

Installation

React-Native End Installation

First, install the react-native-webview peer dependency, by following the instruction here. For most projects, that would be:

$ npm install --save react-native-webview
$ react-native link react-native-webview

Then, install rn-webview-rpc from NPM:

$ npm install --save rn-webview-rpc

Finally, in your code, import the WebViewRpc component:

import WebViewRpc from 'rn-webview-rpc/native';

Note: If you encounter this error:

Objects are not valid as a React child...

error screenshot

then you should try adding the following imports

import 'core-js/es6/map';
import 'core-js/es6/symbol';

at the top of your app's JavaScript entry point. See this thread for more info.

Web End Installation

You can either install from NPM or from a CDN.

Install from NPM

Install rn-webview-rpc from NPM (exactly as for the React-Native project):

$ npm install --save rn-webview-rpc

Then, in your code, import the rnRpc object:

import rnRpc from 'rn-webview-rpc/web';

Install from a CDN

Install from a CDN automatically

Let the React-Native's WebViewRpc inject an HTML script tag to the website, by setting the injectScriptTag prop to true:

<WebViewRpc
  ...
  injectScriptTag
/>
Install from a CDN manually

Add a script tag to your HTML's head:

<script src="https://cdn.jsdelivr.net/npm/rn-webview-rpc@2.0.0></script>

Either way (after a manual or automatic installation) the rnRpc object becomes available globally.

Warning: When manually installing rn-webview-rpc on the web end (either from NPM or CDN), it's your responsibility to make sure that both ends (React-Native and web) use compatible versions of rn-webview-rpc. However, when installing automatically from a CDN by using the injectScriptTag, this burden is inherently taken care of for you.

React-Native API - The WebViewRpc component

A React-Native component that renders a WebView component with a bi-directional RPC bridge.

Props

The following props are consumed by the rn-webview-rpc component. Additional props are forwarded to the react-native-webview's WebView component.

exposedObj

The React-Native object to be exposed to the web end.

  • Type: object
  • Default: {}

injectScriptTag

Controls whether to inject a script tag to automatically load the rn-webview-rpc module to the website.

  • Type: bool
  • Default: false

onMessage(listener)

Allows an extra custom listener to message events (except for the one that is already configured in order to take care of the RPC functionality).

  • Type: function
  • Default: undefined

In most cases, this prop should not be used.

target

An interface object for proxy calls. It is required for invoking calls from the React-Native end to the web end (using the rnRpc's proxy attribute).

  • Type: object
  • Default: {}

Methods and Attributes

Hint: A React class component instance is accessible using the ref prop, for example:

<WebViewRpc
    ref={(ref) => {
      this.webViewRpc = ref;
    }}
/>

proxy

An ES6 proxy polyfill that sends all operations performed on it to the web side. It is essentially a proxy object returned by Comlink's proxy function. However, due to limitations of the proxy polyfill (with respect to the ES6 proxy), it is limited to the interface defined beforehand by the target prop. This limitation will be resolved once React-Native updates its JavaScript engine to a modern one, probably in React-Native@0.60.

webView

A reference to the native WebView component instance.

Static Class Methods

proxyValue(value)

A wrapper function to let a parameter or a return value be proxied rather than copied. This is just a reference to Comlink's proxyValue function.

Web API - rnRpc

An object at the web end that provides a bi-directional RPC bridge to the native end.

Functions

proxy(target)

Creates an ES6 proxy that sends all operations performed on it to the native side. proxy() returns an ES6 proxy object, exactly like Comlink's proxy function.

In older browsers the proxy polyfill is used instead of the ES6 proxy. In that case, the target argument should define the interface object for proxy calls.

proxyValue

A wrapper function to let a parameter or a return value be proxied rather than copied. This is just a reference to Comlink's proxyValue function.

expose(obj)

Exposes an object to the native end. It just wraps Comlink's expose function.

Limitations

Exposing an object in the native side requires providing a target interface at creation time. This is because the JavaScript engine JavaScriptCore used by React-Native does not support the ES6 proxy object, and its polyfill proxy-polyfill is limited. Same applies for the web end for old browsers.

Example

See the example directory.

example gif

Under the Hood

Reading this section is most definitely optionally :)

The major pain coding this package was helping the delightful Comlink library to work in the native environment (and old browsers). Comlink is designed for web workers environment, and unfortunately the native JavaScript engine is more limited.

  1. WebView's messaging interface supports only string messages. Thankfully, solving this issue was easy, since the Comlink library already includes a MessageChannelAdapter, to support string based message channels, such as WebRTC. What remained to be done is to translate the postMessage/onMessage WebView's message API to a send/addEventListener endpoint.

  2. The ES6 proxy object is unsupported natively. This was solved using the Proxy Polyfill (with some limitations). Moreover, to allow proxy to work in React-Native, a few changes were required in Comlink.

  3. The MessageChannel and MessagePort objects are missing in the native environment. Since no polyfills are available, to address this problem I had to write pretty simple degenerated polyfills.

  4. More polyfills: The Object and ArrayBuffer behave slightly inconsistently in different environments. Hence, I have them overridden by polyfills when necessary.