Skip to content

Commit 8455b02

Browse files
committed
0.0.3 added react-native-webview support
1 parent f30407f commit 8455b02

File tree

8 files changed

+1820
-960
lines changed

8 files changed

+1820
-960
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# WebApiBridge
22

3-
`WebApiBridge` is a plain JavaScript class that can be used in a React Native application and in a web app running in a React Native [WebView](https://facebook.github.io/react-native/docs/webview.html) to support a function call API between the two. It can also be used as an IPC mechanism between a web page and an iframe. The intention is for this code to be used in a web app that is using either a framework or pure JavaScript so framework code was kept out of this class.
3+
`WebApiBridge` is a plain JavaScript class that can be used in a React Native application and in a web app using [react-native-community/react-native-webview](https://github.com/react-native-community/react-native-webview) to support a function call API between the two. The original [React Native WebView](https://facebook.github.io/react-native/docs/webview.html) has been deprecated but will still work. The api can also be used as an IPC mechanism between other windows, for example, a web page and an iframe. The intention is for this code to be used in a web app that is using either a framework or pure JavaScript so framework code was kept out of this class.
44

55
## Features
66

77
* Provides support for API calls between JavaScript processes. Each process can support API calls in an array of JavaScript objects (inluding React components)
88
* Marshalls plain JavaScript parameters and return values via `JSON.stringify()`
9-
* Works for React Native webviews
9+
* Works with [react-native-community/react-native-webview](https://github.com/react-native-community/react-native-webview), including support of [ReactNativeWebView.postMessage](https://github.com/react-native-community/react-native-webview/blob/cdbfc19cd20a0d96c9cbd13fcb8a32fcde77943b/docs/Guide.md#the-windowreactnativewebviewpostmessage-method-and-onmessage-prop)
1010
* Works for communication between a web page and iframe based window
1111
* Provides promise support for asynchronous API calls that need to return results
1212
* Marshalls exceptions thown in API calls to the caller
@@ -42,7 +42,7 @@ import { Message } from '@precor/web-api-bridge/types/shapes'
4242

4343
## Gotchas
4444

45-
* Beware that messages passed before `onLoad` is called will probably not get through.
45+
* Beware that messages passed before `onLoad` is called may not get through.
4646
* Type files for Flow and TypeScript are included but not used by our projects so they may have issues. Please submit an issue or better yet a pull request with corrections.
4747

4848
## History

docs/WEBVIEWBRIDGE.md

+61-39
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,23 @@
66
- [Examples][2]
77
- [apis][3]
88
- [ipc][4]
9-
- [listener][5]
10-
- [origin][6]
11-
- [targetOrigin][7]
12-
- [onMessage][8]
13-
- [Parameters][9]
14-
- [send][10]
15-
- [Parameters][11]
9+
- [useReactNativeWebView][5]
10+
- [listener][6]
11+
- [origin][7]
12+
- [targetOrigin][8]
13+
- [onMessage][9]
14+
- [Parameters][10]
15+
- [send][11]
16+
- [Parameters][12]
1617

1718
## WebApiBridge
1819

19-
`WebApiBridge` is a JavaScript class that can be used in a React Native application
20-
and in a web app running in a React Native [WebView][12]
21-
to support a function call interface between the two. It can also be used as an IPC mechanism
22-
between a web site and content running in an iframe.
20+
`WebApiBridge` is a plain JavaScript class that can be used in a React Native application and
21+
in a web app running in a [react-native-webview][13]
22+
to support a function call API between the two. [React Native WebView][14]
23+
will also still work. It can also be used as an IPC mechanism between other windows, for example,
24+
a web page and an iframe. The intention is for this code to be used in a web app that is using
25+
either a framework or pure JavaScript so framework code was kept out of this class.
2326

2427
WebApiBridge works by passing `Message` objects between Javascript processes.
2528

@@ -30,7 +33,7 @@ Example React Native API implementation using a `WebApiBridge`.
3033

3134
```javascript
3235
import React from 'react';
33-
import { WebView } from 'react-native';
36+
import { WebView } from 'react-native-webview';
3437
import WebApiBridge from '@precor/web-api-bridge';
3538

3639
class WebViewApi extends React.Component {
@@ -63,6 +66,7 @@ class WebViewApi extends React.Component {
6366
render() {
6467
return (
6568
<WebView
69+
originWhitelist={['*']}
6670
javaScriptEnabled
6771
ref={(webview) => { this.webview = webview; }}
6872
onMessage={this.onMessage}
@@ -82,15 +86,21 @@ Example `window` API implementation using a `WebApiBridge`.
8286
```javascript
8387
import WebApiBridge from '@precor/web-api-bridge';
8488

89+
const iOS = (process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent));
8590
class MyApi
8691
constructor() {
8792
webApiBridge = new WebApiBridge();
8893
webApiBridge.apis = [this];
89-
// webApiBridge.origin = 'https://www.mydom.com'; // should enable if in iframe
90-
// webApiBridge.targetOrigin = 'https://www.mydom.com'; // should enable if in iframe
91-
// and following would add to `window` if running in iframe instead of webview
92-
document.addEventListener('message', event => webApiBridge.onMessage(event, event.data));
93-
webApiBridge.ipc = window; // window.parent if running in iframe
94+
// set-up orgins if not in a webview:
95+
// webApiBridge.origin = 'https://www.mydom.com';
96+
// webApiBridge.targetOrigin = 'https://www.mydom.com';
97+
const eventObj = (iOS) ? window : document; // window if not in a webview
98+
eventObj.addEventListener('message', event => webApiBridge.onMessage(event, event.data));
99+
// for webview:
100+
webApiBridge.ipc = window;
101+
webApiBridge.useReactNativeWebView = true; // webview side only
102+
// enable this for non-webview:
103+
// webApiBridge.ipc = window; // use window.parent for an iframe
94104
}
95105

96106
// call with myApi.myApiCall(thing1, thing2).then(result => console.log(result));
@@ -117,9 +127,17 @@ in more than one API.
117127

118128
### ipc
119129

120-
Property for the Inter Process Communications object that will handle
121-
messages from the other-side. This is the `window` object on a web page
122-
and the `ref` for the `WebView` component on the React Native side.
130+
Property for ipc object to post messages to the other side. Typically the `window`,
131+
or a `window.parent` for an iframe in a normal web page. For the `WebView`
132+
component on the React Native side use the `ref`, and for the web side of
133+
[react-native-webview][13]
134+
use `window.parent` for iOS and `window` for Android.
135+
136+
### useReactNativeWebView
137+
138+
Property that should be truthy for a webview using
139+
[react-native-webview][13]. When set
140+
`ipc.ReactNativeWebView.postMessage` will be used instead of `ipc.postMessage`.
123141

124142
### listener
125143

@@ -150,8 +168,8 @@ from the other side.
150168

151169
#### Parameters
152170

153-
- `event` **[object][13]** Incomming event.
154-
- `data` **[string][14]** The incoming data received, which is a stingified JSON
171+
- `event` **[object][15]** Incomming event.
172+
- `data` **[string][16]** The incoming data received, which is a stingified JSON
155173
message. Defaults to `event.nativeEvent.data`, which is correct for React Native
156174
but needs to be overridden for the web app with `event.data`. (optional, default `event.nativeEvent.data`)
157175

@@ -162,13 +180,13 @@ Returns a `Promise` object.
162180

163181
#### Parameters
164182

165-
- `targetFunc` **[string][14]** A string of the name of the api function to execute.
166-
- `args` **[Array][15]** An array of parameters to be passsed to the `targetFun`.
167-
- `wantResult` **[boolean][16]** Boolean to indicate if a `Promise` should be `fullfilled`
183+
- `targetFunc` **[string][16]** A string of the name of the api function to execute.
184+
- `args` **[Array][17]** An array of parameters to be passsed to the `targetFun`.
185+
- `wantResult` **[boolean][18]** Boolean to indicate if a `Promise` should be `fullfilled`
168186
or `rejected` after the remote api completes the call. If `false` then no `Promise`
169187
will be `fullfilled`. (optional, default `false`)
170188

171-
Returns **[Promise][17]** Promise object if `wantResult` is `true`, `null` if not.
189+
Returns **[Promise][19]** Promise object if `wantResult` is `true`, `null` if not.
172190

173191
[1]: #webapibridge
174192

@@ -178,28 +196,32 @@ Returns **[Promise][17]** Promise object if `wantResult` is `true`, `null` if no
178196

179197
[4]: #ipc
180198

181-
[5]: #listener
199+
[5]: #usereactnativewebview
200+
201+
[6]: #listener
202+
203+
[7]: #origin
182204

183-
[6]: #origin
205+
[8]: #targetorigin
184206

185-
[7]: #targetorigin
207+
[9]: #onmessage
186208

187-
[8]: #onmessage
209+
[10]: #parameters
188210

189-
[9]: #parameters
211+
[11]: #send
190212

191-
[10]: #send
213+
[12]: #parameters-1
192214

193-
[11]: #parameters-1
215+
[13]: https://github.com/react-native-community/react-native-webview
194216

195-
[12]: https://facebook.github.io/react-native/docs/webview.html
217+
[14]: https://facebook.github.io/react-native/docs/webview.html
196218

197-
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
219+
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
198220

199-
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
221+
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
200222

201-
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
223+
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
202224

203-
[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
225+
[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
204226

205-
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
227+
[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@precor/web-api-bridge",
3-
"version": "0.0.2",
3+
"version": "0.0.3",
44
"description": "Provides api marshalling for React Native WebvViews and web page iframes.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/__tests__/index.test.js

+22
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ class TestAPI {
2626
class TestIPC {
2727
constructor() {
2828
this.postMessage = jest.fn();
29+
this.ReactNativeWebView = {
30+
postMessage: jest.fn(),
31+
};
2932
}
3033
}
3134

@@ -90,6 +93,18 @@ describe('WebApiBridge', () => {
9093
expect(testIPC.postMessage).toHaveBeenCalledWith(JSON.stringify(expectedResultMsg), '*');
9194
});
9295

96+
it('sends a result using ReactNativeWebView, given a response coming from a ReactNativeWebView', () => {
97+
const wantResultMsg = { ...testFuncMsg, wantResult: true };
98+
const expectedResultMsg = {
99+
...wantResultMsg, type: 'response', args: [], error: 'Cannot read property \'then\' of undefined',
100+
};
101+
wab.useReactNativeWebView = true;
102+
wab.onMessage('message', JSON.stringify(wantResultMsg));
103+
expect(testAPI.testFunc).toHaveBeenCalled();
104+
expect(testIPC.ReactNativeWebView.postMessage)
105+
.toHaveBeenCalledWith(JSON.stringify(expectedResultMsg));
106+
});
107+
93108
it('calls an api function with params, given an incoming request has params', () => {
94109
const args = [5, { key: 'value' }];
95110
const argRequestMsg = {
@@ -275,6 +290,13 @@ describe('WebApiBridge', () => {
275290
expect(JSON.parse(testIPC.postMessage.mock.calls[0][0])).toEqual(testFuncMsg);
276291
});
277292

293+
it('sends an api request using using ReactNativeWebView, given request from a ReactNativeWebView', () => {
294+
wab.useReactNativeWebView = true;
295+
expect(wab.send('testFunc', [], false)).toBeNull();
296+
expect(JSON.parse(testIPC.ReactNativeWebView.postMessage.mock.calls[0][0]))
297+
.toEqual(testFuncMsg);
298+
});
299+
278300
it('sends an api request without requesting a result by default', () => {
279301
expect(wab.send('testFunc', [])).toBeNull();
280302
expect(JSON.parse(testIPC.postMessage.mock.calls[0][0])).toEqual(testFuncMsg);

src/index.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { PostMessage } from "../lib";
2+
13
/*
24
* WebApiBridge should be left as a pure JS implementation. In order to
35
* support typescript these declarations are supported in a separate .flow file.
@@ -29,6 +31,7 @@ export type OnMessage = (event: string, data: {}) => void
2931
export default class WebApiBridge {
3032
listener: Listener | null;
3133
ipc: {};
34+
useReactNativeWebView: boolean;
3235
send: Send;
3336
apis: {}[];
3437
onMessage: OnMessage;

src/index.js

+41-15
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
/**
22
* @class
33
* @description
4-
* `WebApiBridge` is a JavaScript class that can be used in a React Native application
5-
* and in a web app running in a React Native [WebView](https://facebook.github.io/react-native/docs/webview.html)
6-
* to support a function call interface between the two. It can also be used as an IPC mechanism
7-
* between a web site and content running in an iframe.
4+
* `WebApiBridge` is a plain JavaScript class that can be used in a React Native application and
5+
* in a web app running in a [react-native-webview](https://github.com/react-native-community/react-native-webview)
6+
* to support a function call API between the two. [React Native WebView](https://facebook.github.io/react-native/docs/webview.html)
7+
* will also still work. It can also be used as an IPC mechanism between other windows, for example,
8+
* a web page and an iframe. The intention is for this code to be used in a web app that is using
9+
* either a framework or pure JavaScript so framework code was kept out of this class.
810
*
911
* WebApiBridge works by passing `Message` objects between Javascript processes.
1012
*
1113
* @example <caption>Example React Native API implementation using a `WebApiBridge`.</caption>
1214
* import React from 'react';
13-
* import { WebView } from 'react-native';
15+
* import { WebView } from 'react-native-webview';
1416
* import WebApiBridge from '@precor/web-api-bridge';
1517
*
1618
* class WebViewApi extends React.Component {
@@ -43,6 +45,7 @@
4345
* render() {
4446
* return (
4547
* <WebView
48+
* originWhitelist={['*']}
4649
* javaScriptEnabled
4750
* ref={(webview) => { this.webview = webview; }}
4851
* onMessage={this.onMessage}
@@ -58,15 +61,21 @@
5861
* @example <caption>Example `window` API implementation using a `WebApiBridge`.</caption>
5962
* import WebApiBridge from '@precor/web-api-bridge';
6063
*
64+
* const iOS = (process.browser && /iPad|iPhone|iPod/.test(navigator.userAgent));
6165
* class MyApi
6266
* constructor() {
6367
* webApiBridge = new WebApiBridge();
6468
* webApiBridge.apis = [this];
65-
* // webApiBridge.origin = 'https://www.mydom.com'; // should enable if in iframe
66-
* // webApiBridge.targetOrigin = 'https://www.mydom.com'; // should enable if in iframe
67-
* // and following would add to `window` if running in iframe instead of webview
68-
* document.addEventListener('message', event => webApiBridge.onMessage(event, event.data));
69-
* webApiBridge.ipc = window; // window.parent if running in iframe
69+
* // set-up orgins if not in a webview:
70+
* // webApiBridge.origin = 'https://www.mydom.com';
71+
* // webApiBridge.targetOrigin = 'https://www.mydom.com';
72+
* const eventObj = (iOS) ? window : document; // window if not in a webview
73+
* eventObj.addEventListener('message', event => webApiBridge.onMessage(event, event.data));
74+
* // for webview:
75+
* webApiBridge.ipc = window;
76+
* webApiBridge.useReactNativeWebView = true; // webview side only
77+
* // enable this for non-webview:
78+
* // webApiBridge.ipc = window; // use window.parent for an iframe
7079
* }
7180
*
7281
* // call with myApi.myApiCall(thing1, thing2).then(result => console.log(result));
@@ -95,12 +104,21 @@ class WebApiBridge {
95104
this.apis = [];
96105

97106
/**
98-
* Property for the Inter Process Communications object that will handle
99-
* messages from the other-side. This is the `window` object on a web page
100-
* and the `ref` for the `WebView` component on the React Native side.
107+
* Property for ipc object to post messages to the other side. Typically the `window`,
108+
* or a `window.parent` for an iframe in a normal web page. For the `WebView`
109+
* component on the React Native side use the `ref`, and for the web side of
110+
* [react-native-webview](https://github.com/react-native-community/react-native-webview)
111+
* use `window.parent` for iOS and `window` for Android.
101112
*/
102113
this.ipc = {};
103114

115+
/**
116+
* Property that should be truthy for a webview using
117+
* [react-native-webview](https://github.com/react-native-community/react-native-webview). When set
118+
* `ipc.ReactNativeWebView.postMessage` will be used instead of `ipc.postMessage`.
119+
*/
120+
this.useReactNativeWebView = undefined;
121+
104122
/**
105123
* Listener functions can monitor all `Message` objects exchanged
106124
* between `WebApiBridge` objects. Listener functions are attached to `WebApiBridge` instances
@@ -155,7 +173,11 @@ class WebApiBridge {
155173
sendResponse(response) {
156174
response.type = 'response';
157175
if (this.listener) this.listener(response);
158-
this.ipc.postMessage(JSON.stringify(response), this.targetOrigin);
176+
if (this.useReactNativeWebView) {
177+
this.ipc.ReactNativeWebView.postMessage(JSON.stringify(response));
178+
} else {
179+
this.ipc.postMessage(JSON.stringify(response), this.targetOrigin);
180+
}
159181
}
160182

161183
handleRequest(request) {
@@ -230,7 +252,11 @@ class WebApiBridge {
230252

231253
doSend(message) {
232254
if (this.listener) this.listener(message);
233-
this.ipc.postMessage(JSON.stringify(message), this.targetOrigin);
255+
if (this.useReactNativeWebView) {
256+
this.ipc.ReactNativeWebView.postMessage(JSON.stringify(message));
257+
} else {
258+
this.ipc.postMessage(JSON.stringify(message), this.targetOrigin);
259+
}
234260
}
235261

236262
/**

src/index.js.flow

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type OnMessage = (event: string, data: {}) => void
2929
export default class WebApiBridge {
3030
listener: Listener | null;
3131
ipc: {};
32+
useReactNativeWebView: boolean;
3233
send: Send;
3334
apis: {}[];
3435
onMessage: OnMessage;

0 commit comments

Comments
 (0)