├── src
├── index.js
└── WebViewJSContext.js
├── android
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── shaynesweeney
│ │ └── react_native_webview_js_context
│ │ ├── RNWebViewJSContextPackage.java
│ │ └── RNWebViewJSContextModule.java
└── build.gradle
├── ios
├── RNWebViewJSContext
│ ├── RNWebViewJSContext.h
│ └── RNWebViewJSContext.m
└── RNWebViewJSContext.xcodeproj
│ └── project.pbxproj
├── .gitignore
├── react-native-webview-js-context.podspec
├── package.json
├── LICENSE
├── .flowconfig
└── README.md
/src/index.js:
--------------------------------------------------------------------------------
1 | import WebViewJSContext from './WebViewJSContext';
2 |
3 | module.exports = WebViewJSContext;
4 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/RNWebViewJSContext/RNWebViewJSContext.h:
--------------------------------------------------------------------------------
1 | //
2 | // RNWebViewJSContext.h
3 | // RNWebViewJSContext
4 | //
5 | // Created by Shayne Sweeney on 10/21/15.
6 | // Copyright © 2015 Shayne Sweeney. All rights reserved.
7 | //
8 |
9 | #import "RCTBridgeModule.h"
10 |
11 | @interface RNWebViewJSContext : NSObject
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | .DS_Store
3 |
4 | # node.js
5 | node_modules/
6 | npm-debug.log
7 |
8 | # Xcode
9 | build/
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | *.xcworkspace
19 | !default.xcworkspace
20 | xcuserdata
21 | profile
22 | *.moved-aside
23 | DerivedData
24 | .idea/
25 |
26 | # Pods
27 | Pods
28 |
--------------------------------------------------------------------------------
/react-native-webview-js-context.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "react-native-webview-js-context"
3 | s.version = "0.2.0"
4 | s.summary = "Utilize the JavaScript VM running inside an iOS UIWebView to exploit libraries targeting the DOM (e.g. Google Charts)"
5 |
6 | s.homepage = "https://github.com/shayne/react-native-webview-js-context"
7 |
8 | s.license = "MIT"
9 | s.platform = :ios, "8.0"
10 |
11 | s.source = { :git => "https://github.com/shayne/react-native-webview-js-context" }
12 |
13 | s.source_files = "ios/*.{h,m}"
14 |
15 | s.dependency 'React'
16 | end
17 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "0.2.0"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 |
21 | // needed for https://github.com/square/okio/issues/58
22 | lintOptions {
23 | warning 'InvalidPackage'
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: ['*.jar'])
29 | compile 'com.facebook.react:react-native:0.11.+'
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-webview-js-context",
3 | "version": "0.2.3",
4 | "description": "Utilize the JavaScript VM running inside an iOS UIWebView to exploit libraries targeting the DOM (e.g. Google Charts).",
5 | "main": "src/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/shayne/react-native-webview-js-context"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/shayne/react-native-webview-js-context/issues"
12 | },
13 | "homepage": {
14 | "url": "https://github.com/shayne/react-native-webview-js-context#readme"
15 | },
16 | "keywords": [
17 | "dom",
18 | "web",
19 | "jsc",
20 | "ios",
21 | "android",
22 | "react",
23 | "react-native",
24 | "react-component"
25 | ],
26 | "author": "Shayne Sweeney ",
27 | "license": "MIT",
28 | "peerDependencies": {
29 | "react-native": "*"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Shayne Sweeney
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/WebViewJSContext.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @flow
3 | */
4 |
5 | var React = require('react-native');
6 |
7 | var { Platform, NativeModules: { RNWebViewJSContext } } = React;
8 |
9 | export default class WebViewJSContext {
10 | ctx: any;
11 |
12 | static async createWithHTML(html: string): Promise {
13 | if (Platform.OS === 'android') {
14 | return new Promise((resolve, reject) => {
15 | RNWebViewJSContext.loadHTML(html, ctx => {
16 | resolve(new WebViewJSContext(ctx));
17 | }, reject);
18 | });
19 | }
20 | const ctx = await RNWebViewJSContext.loadHTML(html);
21 | return new WebViewJSContext(ctx);
22 | }
23 |
24 | constructor(ctx: any) {
25 | this.ctx = ctx;
26 | }
27 |
28 | evaluateScript(script: string): Promise {
29 | console.log('HERE');
30 | if (Platform.OS === 'android') {
31 | return new Promise((resolve, reject) => {
32 | RNWebViewJSContext.evaluateScript(this.ctx, script, resolve, reject);
33 | });
34 | }
35 | return RNWebViewJSContext.evaluateScript(this.ctx, script);
36 | }
37 |
38 | destroy() {
39 | RNWebViewJSContext.destroy(this.ctx);
40 | this.ctx = null;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/android/src/main/java/com/shaynesweeney/react_native_webview_js_context/RNWebViewJSContextPackage.java:
--------------------------------------------------------------------------------
1 | package com.shaynesweeney.react_native_webview_js_context;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 |
14 | public final class RNWebViewJSContextPackage implements ReactPackage {
15 |
16 | @Override
17 | public List createNativeModules(
18 | ReactApplicationContext reactContext) {
19 | List modules = new ArrayList<>();
20 |
21 | modules.add(new RNWebViewJSContextModule(reactContext));
22 |
23 | return modules;
24 | }
25 |
26 | @Override
27 | public List> createJSModules() {
28 | return Collections.emptyList();
29 | }
30 |
31 | @Override
32 | public List createViewManagers(
33 | ReactApplicationContext reactContext) {
34 | return Collections.emptyList();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | # We fork some components by platform.
4 | .*/*.web.js
5 | .*/*.android.js
6 |
7 | # Some modules have their own node_modules with overlap
8 | .*/node_modules/node-haste/.*
9 |
10 | # Ignore react-tools where there are overlaps, but don't ignore anything that
11 | # react-native relies on
12 | .*/node_modules/react-tools/src/React.js
13 | .*/node_modules/react-tools/src/renderers/shared/event/EventPropagators.js
14 | .*/node_modules/react-tools/src/renderers/shared/event/eventPlugins/ResponderEventPlugin.js
15 | .*/node_modules/react-tools/src/shared/vendor/core/ExecutionEnvironment.js
16 |
17 | # Ignore commoner tests
18 | .*/node_modules/commoner/test/.*
19 |
20 | # See https://github.com/facebook/flow/issues/442
21 | .*/react-tools/node_modules/commoner/lib/reader.js
22 |
23 | # Ignore jest
24 | .*/node_modules/jest-cli/.*
25 |
26 | # Ignore Website
27 | .*/website/.*
28 |
29 | [include]
30 |
31 | [libs]
32 | node_modules/react-native/Libraries/react-native/react-native-interface.js
33 |
34 | [options]
35 | module.system=haste
36 |
37 | munge_underscores=true
38 |
39 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
40 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
41 |
42 | suppress_type=$FlowIssue
43 | suppress_type=$FlowFixMe
44 | suppress_type=$FixMe
45 |
46 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
47 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(1[0-6]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)? #[0-9]+
48 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
49 |
50 | [version]
51 | 0.16.0
52 |
--------------------------------------------------------------------------------
/ios/RNWebViewJSContext/RNWebViewJSContext.m:
--------------------------------------------------------------------------------
1 | //
2 | // RNWebViewJSContext.m
3 | // RNWebViewJSContext
4 | //
5 | // Created by Shayne Sweeney on 10/21/15.
6 | // Copyright © 2015 Shayne Sweeney. All rights reserved.
7 | //
8 |
9 | @import UIKit;
10 | @import JavaScriptCore;
11 |
12 | #import "RNWebViewJSContext.h"
13 |
14 | #pragma mark - UIWebView (JSContext)
15 |
16 | @interface UIWebView (JSContext)
17 |
18 | - (JSContext *)JSContext;
19 |
20 | @end
21 |
22 |
23 | @implementation UIWebView (JSContext)
24 |
25 | - (JSContext *)JSContext {
26 | return [self valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
27 | }
28 |
29 | @end
30 |
31 | #pragma mark -
32 |
33 |
34 | @interface RNWebViewJSContext ()
35 |
36 | @property (nonatomic, strong) NSMutableDictionary *webViews;
37 |
38 | @end
39 |
40 | @implementation RNWebViewJSContext
41 |
42 | RCT_EXPORT_MODULE()
43 |
44 | - (instancetype)init {
45 | if (self = [super init]) {
46 | self.webViews = [NSMutableDictionary dictionary];
47 | }
48 | return self;
49 | }
50 |
51 |
52 | RCT_EXPORT_METHOD(destroy:(nonnull NSNumber *)contextID)
53 | {
54 | [self.webViews removeObjectForKey:contextID];
55 | }
56 |
57 |
58 | RCT_EXPORT_METHOD(loadHTML:(NSString *)htmlString resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
59 | {
60 |
61 | NSUInteger hash = [htmlString hash];
62 |
63 | __weak RNWebViewJSContext *weakSelf = self;
64 | dispatch_async(dispatch_get_main_queue(), ^{
65 | __strong RNWebViewJSContext *strongSelf = weakSelf;
66 | UIWebView *webView = self.webViews[@(hash)];
67 | if (!webView) {
68 | webView = [[UIWebView alloc] initWithFrame:CGRectZero];
69 | strongSelf.webViews[@(hash)] = webView;
70 | }
71 |
72 | JSContext *context = [webView JSContext];
73 |
74 | RCTPromiseResolveBlock resolveWrapper = ^(id obj) {
75 | resolve(@(hash));
76 | };
77 |
78 | [self generateCallbacksInContext:context resolver:resolveWrapper rejecter:reject UUIDSuffix:false];
79 |
80 | __weak UIWebView *weakWebView = webView;
81 | dispatch_async(dispatch_get_main_queue(), ^{
82 | __strong UIWebView *strongWebView = weakWebView;
83 | [strongWebView loadHTMLString:htmlString baseURL:nil];
84 | });
85 | });
86 | }
87 |
88 | RCT_REMAP_METHOD(loadModule, loadModuleInContext:(nonnull NSNumber *)contextID script:(NSString *)script resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
89 | {
90 |
91 | }
92 |
93 |
94 | RCT_REMAP_METHOD(evaluateScript, evaluateScriptInContext:(nonnull NSNumber *)contextID script:(NSString *)script resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
95 | {
96 | UIWebView *webView = self.webViews[contextID];
97 | NSAssert(webView, @"WebView was nil!");
98 | JSContext *context = [webView JSContext];
99 |
100 | NSDictionary *callbacks = [self generateCallbacksInContext:context resolver:resolve rejecter:reject UUIDSuffix:true];
101 |
102 | NSString *jsWrapper = [NSString stringWithFormat:@"\
103 | setTimeout(function(){ \
104 | var resolve = %@, reject = %@; \
105 | try { \
106 | %@ \
107 | } catch (e) { \
108 | reject(e); \
109 | } \
110 | }, 0)", callbacks[@"resolveName"], callbacks[@"rejectName"], script];
111 |
112 | dispatch_async(dispatch_get_main_queue(), ^{
113 | [context evaluateScript:jsWrapper];
114 | });
115 |
116 | }
117 |
118 |
119 | - (NSDictionary *)generateCallbacksInContext:(JSContext *)context resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject UUIDSuffix:(BOOL)withSuffix {
120 | NSString *resolveName = @"resolve";
121 | NSString *rejectName = @"reject";
122 |
123 | if (withSuffix) {
124 | NSString *uniquePrefix = [[NSUUID UUID].UUIDString substringToIndex:7];
125 | resolveName = [NSString stringWithFormat:@"resolve_%@", uniquePrefix];
126 | rejectName = [NSString stringWithFormat:@"reject_%@", uniquePrefix];
127 | }
128 |
129 | context[resolveName] = ^(JSValue *val) {
130 | resolve([val toString]);
131 | };
132 |
133 | context[rejectName] = ^(JSValue *val) {
134 | NSString *description = [val toString];
135 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: description };
136 | NSError *error = [NSError errorWithDomain:@"JSWebViewManagerErrorDomain"
137 | code:-57
138 | userInfo:userInfo];
139 |
140 | reject(@"-57", description, error);
141 | };
142 |
143 | return @{@"resolveName": resolveName, @"rejectName": rejectName};
144 | }
145 |
146 | @end
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-webview-js-context [](http://badge.fury.io/js/react-native-webview-js-context)
2 |
3 | Interactive JavaScript between a UIWebView and React Native.
4 |
5 | **Example:** Google Charts used to render a chart (base64 encoded image) in a `` component
6 |
7 |
8 |
9 | ```javascript
10 | const GC_HTML = `
11 |
12 |
13 |
14 |
18 |
19 |
20 | `;
21 |
22 | const CHART_JS = `
23 | var data = new google.visualization.DataTable();
24 | data.addColumn('date', 'Day');
25 | data.addColumn('number', 'Weight');
26 | data.addColumn({ type: 'string', role: 'annotation' });
27 | data.addRows([
28 | [new Date(2015, 2, 1), 150, '150'],
29 | [new Date(2015, 2, 2), 152, null],
30 | [new Date(2015, 2, 3), 146, '146'],
31 | [new Date(2015, 2, 4), 150, null],
32 | [new Date(2015, 2, 5), 157, '157'],
33 | [new Date(2015, 2, 06), 147, null],
34 | [new Date(2015, 2, 07), 147.5, '147'],
35 | ]);
36 |
37 | var options = { enableInteractivity: false,
38 | legend: {position: 'none'},
39 | lineWidth: 3, width:750, height:420,
40 | pointShape: 'circle', pointSize: 8,
41 | chartArea: { left: 30, width: 690 }, areaOpacity: 0.07,
42 | colors: ['#e14c4d'], backgroundColor: { 'fill': '#34343f' },
43 | annotations: {
44 | textStyle: { fontSize: 26, bold: true, color: '#bbbbbd', auroColor: '#3f3f3f' },
45 | },
46 | hAxis: {
47 | format: 'MMM d',
48 | textStyle: {color: '#bbbbbd', fontSize: 16,}, gridlines: { color: 'transparent' },
49 | },
50 | vAxis: { gridlines: { count: 3, color: '#3f414f' } },
51 | };
52 |
53 | var chart = new google.visualization.AreaChart(document.getElementById('chart_div'));
54 | chart.draw(data, options);
55 |
56 | resolve(chart.getImageURI()); /* <--- resolve() is called by RNWebViewJSContext */`;
57 |
58 | import WebViewJSContext from 'react-native-webview-js-context';
59 |
60 | class RNCharts {
61 | state: { imageUri: null };
62 |
63 | componentWillMount() {
64 | WebViewJSContext.createWithHTML(GC_HTML)
65 | .then(context => {
66 | this.ctx = context;
67 | this.loadChart();
68 | });
69 | }
70 |
71 | componentWillUnmount() {
72 | this.ctx && this.ctx.destroy();
73 | },
74 |
75 | render() {
76 | return this.state.imageUri ?
77 |
78 | : ;
79 | }
80 |
81 | async loadChart() {
82 | var imageUri = await this.ctx.evaluateScript(CHART_JS);
83 | this.setState({ imageUri });
84 | }
85 | }
86 | ```
87 |
88 | ## Usage
89 |
90 | First you need to install react-native-webview-js-context:
91 |
92 | ```javascript
93 | npm install react-native-webview-js-context --save
94 | ```
95 |
96 |
97 | ## `iOS`
98 |
99 | 1. In XCode, in the project navigator, right click `Libraries` ➜ `Add Files to [your project's name]`
100 | 2. Go to `node_modules` ➜ `react-native-webview-js-context` ➜ `ios` and add `RNWebViewJSContext.xcodeproj`
101 | 3. In XCode, in the project navigator, select your project. Add `libRNWebViewJSContext.a` to your project's `Build Phases` ➜ `Link Binary With Libraries`
102 | 4. Run your project (`Cmd+R`)
103 |
104 | ### `Android`
105 |
106 | * `android/settings.gradle`
107 |
108 | ```gradle
109 | ...
110 | include ':react-native-webview-js-context'
111 | project(':react-native-webview-js-context').projectDir = new File(settingsDir, '../node_modules/react-native-webview-js-context/android')
112 | ```
113 | * `android/app/build.gradle`
114 |
115 | ```gradle
116 | dependencies {
117 | ...
118 | compile project(':react-native-webview-js-context')
119 | }
120 | ```
121 |
122 | * register module (in MainActivity.java)
123 |
124 | ```java
125 | ...
126 |
127 | import com.shaynesweeney.react_native_webview_js_context.RNWebViewJSContextPackage; // <--- IMPORT
128 |
129 | public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {
130 | ...
131 |
132 | @Override
133 | protected void onCreate(Bundle savedInstanceState) {
134 | super.onCreate(savedInstanceState);
135 | mReactRootView = new ReactRootView(this);
136 |
137 | mReactInstanceManager = ReactInstanceManager.builder()
138 | .setApplication(getApplication())
139 | .setBundleAssetName("index.android.bundle")
140 | .setJSMainModuleName("index.android")
141 | .addPackage(new MainReactPackage())
142 | .addPackage(new RNWebViewJSContextPackage()) // <- ADD HERE
143 | .setUseDeveloperSupport(BuildConfig.DEBUG)
144 | .setInitialLifecycleState(LifecycleState.RESUMED)
145 | .build();
146 |
147 | mReactRootView.startReactApplication(mReactInstanceManager, "YourProject", null);
148 |
149 | setContentView(mReactRootView);
150 | }
151 | }
152 | ```
153 |
154 |
--------------------------------------------------------------------------------
/android/src/main/java/com/shaynesweeney/react_native_webview_js_context/RNWebViewJSContextModule.java:
--------------------------------------------------------------------------------
1 | package com.shaynesweeney.react_native_webview_js_context;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Handler;
5 | import android.util.SparseArray;
6 | import android.webkit.JavascriptInterface;
7 | import android.webkit.WebView;
8 |
9 | import com.facebook.react.bridge.Callback;
10 | import com.facebook.react.bridge.ReactApplicationContext;
11 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
12 | import com.facebook.react.bridge.ReactMethod;
13 |
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.UUID;
17 |
18 | interface JSCallback {
19 | void invoke(String response);
20 | }
21 |
22 | interface JSCallbacks {
23 | @JavascriptInterface
24 | void global_resolver();
25 |
26 | @JavascriptInterface
27 | void global_resolver(String response);
28 |
29 | @JavascriptInterface
30 | void global_rejecter(String response);
31 |
32 | @JavascriptInterface
33 | void global_rejecter();
34 |
35 | @JavascriptInterface
36 | void callback_resolver(String uuid, String response);
37 |
38 | @JavascriptInterface
39 | void callback_resolver(String uuid);
40 |
41 | @JavascriptInterface
42 | void callback_rejecter(String uuid, String response);
43 |
44 | @JavascriptInterface
45 | void callback_rejecter(String uuid);
46 | }
47 |
48 | public class RNWebViewJSContextModule extends ReactContextBaseJavaModule {
49 | @SuppressWarnings("unused")
50 | private static final String TAG = "RNWebViewJSContext";
51 | public static final String GLOBAL_REJECT_KEY = "global-reject";
52 | private final SparseArray mWebViews;
53 | private final Map mCallbacks;
54 | public static final String GLOBAL_RESOLVE_KEY = "global-resolve";
55 |
56 | public RNWebViewJSContextModule(ReactApplicationContext reactContext) {
57 | super(reactContext);
58 |
59 | mWebViews = new SparseArray<>();
60 | mCallbacks = new HashMap<>();
61 | }
62 |
63 | @Override
64 | public String getName() {
65 | return "RNWebViewJSContext";
66 | }
67 |
68 | @ReactMethod
69 | public void loadHTML(final String html, final Callback resolveCallback, final Callback rejectCallback) {
70 | final Integer contextID = html.hashCode();
71 |
72 | final ReactApplicationContext context = getReactApplicationContext();
73 | Handler mainHandler = new Handler(context.getMainLooper());
74 | Runnable myRunnable = new Runnable() {
75 | @Override
76 | public void run() {
77 |
78 | WebView webView = mWebViews.get(contextID);
79 | if (webView == null) {
80 | webView = createWebView(contextID);
81 | }
82 |
83 | mCallbacks.put("global-resolve", new JSCallback() {
84 | @Override
85 | public void invoke(String response) {
86 | resolveCallback.invoke(contextID);
87 | }
88 | });
89 |
90 | mCallbacks.put("global-reject", new JSCallback() {
91 | @Override
92 | public void invoke(String response) {
93 | rejectCallback.invoke();
94 | }
95 | });
96 |
97 | webView.loadData(html, "text/html", "UTF-8");
98 | // webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null);
99 | }
100 | };
101 | mainHandler.post(myRunnable);
102 | }
103 |
104 | @ReactMethod
105 | public void evaluateScript(final Integer contextID, String script, final Callback resolveCallback, final Callback rejectCallback) {
106 | UUID uuid = UUID.randomUUID();
107 | String uuidString = uuid.toString().substring(0, 7);
108 |
109 | final String resolverName = "resolve_" + uuidString;
110 | final String rejecterName = "reject_" + uuidString;
111 |
112 | final String jsWrapper = String.format(
113 | "setTimeout(function(){"
114 | + "var resolve = function(response) {"
115 | + " $RNWebViewJSContext.callback_resolver(\"%s\", response);"
116 | + "},"
117 | + "reject = function(uuid, response) {"
118 | + " $RNWebViewJSContext.callback_rejecter(\"%s\", response);"
119 | + "};"
120 | + "try {"
121 | + " %s"
122 | + "} catch (e) {"
123 | + " reject(e);"
124 | + "}"
125 | + "}, 0)",
126 | resolverName, rejecterName, script
127 | );
128 |
129 | final ReactApplicationContext context = getReactApplicationContext();
130 | Handler mainHandler = new Handler(context.getMainLooper());
131 | Runnable myRunnable = new Runnable() {
132 | @Override
133 | public void run() {
134 | WebView webView = mWebViews.get(contextID);
135 | assert webView != null;
136 |
137 | mCallbacks.put(resolverName, new JSCallback() {
138 | @Override
139 | public void invoke(String response) {
140 | resolveCallback.invoke(response);
141 | }
142 | });
143 |
144 | mCallbacks.put(rejecterName, new JSCallback() {
145 | @Override
146 | public void invoke(String response) {
147 | rejectCallback.invoke();
148 | }
149 | });
150 |
151 | webView.evaluateJavascript(jsWrapper, null);
152 | }
153 | };
154 | mainHandler.post(myRunnable);
155 | }
156 |
157 | @ReactMethod
158 | public void destroy(Integer contextID) {
159 | mWebViews.delete(contextID);
160 | }
161 |
162 |
163 | @SuppressLint("SetJavaScriptEnabled")
164 | private WebView createWebView(Integer contextID) {
165 |
166 | WebView webView = new WebView(getReactApplicationContext());
167 | webView.getSettings().setJavaScriptEnabled(true);
168 | mWebViews.put(contextID, webView);
169 |
170 | webView.addJavascriptInterface(new JSCallbacks() {
171 |
172 | @JavascriptInterface
173 | public void global_resolver() {
174 | JSCallback resolver = mCallbacks.get(GLOBAL_RESOLVE_KEY);
175 | resolver.invoke(null);
176 | }
177 |
178 | @JavascriptInterface
179 | public void global_resolver(String response) {
180 | JSCallback resolver = mCallbacks.get(GLOBAL_RESOLVE_KEY);
181 | resolver.invoke(response);
182 | }
183 |
184 | @JavascriptInterface
185 | public void global_rejecter() {
186 | JSCallback rejecter = mCallbacks.get(GLOBAL_REJECT_KEY);
187 | rejecter.invoke(null);
188 | }
189 |
190 | @JavascriptInterface
191 | public void global_rejecter(String response) {
192 | JSCallback rejecter = mCallbacks.get(GLOBAL_REJECT_KEY);
193 | rejecter.invoke(response);
194 | }
195 |
196 | @JavascriptInterface
197 | public void callback_resolver(String callbackKey) {
198 | JSCallback resolver = mCallbacks.get(callbackKey);
199 | resolver.invoke(null);
200 | }
201 |
202 | @JavascriptInterface
203 | public void callback_resolver(String callbackKey, String response) {
204 | JSCallback resolver = mCallbacks.get(callbackKey);
205 | resolver.invoke(response);
206 | }
207 |
208 | @JavascriptInterface
209 | public void callback_rejecter(String callbackKey) {
210 | JSCallback rejecter = mCallbacks.get(callbackKey);
211 | rejecter.invoke(null);
212 | }
213 |
214 | @JavascriptInterface
215 | public void callback_rejecter(String callbackKey, String response) {
216 | JSCallback rejecter = mCallbacks.get(callbackKey);
217 | rejecter.invoke(response);
218 | }
219 | }, "$RNWebViewJSContext");
220 |
221 |
222 | webView.evaluateJavascript(
223 | "(function($w){" +
224 | "$w.resolve = $RNWebViewJSContext.global_resolver.bind($RNWebViewJSContext);" +
225 | "$w.reject = $RNWebViewJSContext.global_rejecter.bind($RNWebViewJSContext);" +
226 | "})(window);", null);
227 |
228 |
229 | return webView;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/ios/RNWebViewJSContext.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1C5F19C91BD87D96000069D7 /* RNWebViewJSContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1C5F19C81BD87D96000069D7 /* RNWebViewJSContext.h */; };
11 | 1C5F19CB1BD87D96000069D7 /* RNWebViewJSContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C5F19CA1BD87D96000069D7 /* RNWebViewJSContext.m */; };
12 | /* End PBXBuildFile section */
13 |
14 | /* Begin PBXCopyFilesBuildPhase section */
15 | 1C5F19C31BD87D96000069D7 /* CopyFiles */ = {
16 | isa = PBXCopyFilesBuildPhase;
17 | buildActionMask = 2147483647;
18 | dstPath = "include/$(PRODUCT_NAME)";
19 | dstSubfolderSpec = 16;
20 | files = (
21 | 1C5F19C91BD87D96000069D7 /* RNWebViewJSContext.h in CopyFiles */,
22 | );
23 | runOnlyForDeploymentPostprocessing = 0;
24 | };
25 | /* End PBXCopyFilesBuildPhase section */
26 |
27 | /* Begin PBXFileReference section */
28 | 1C5F19C51BD87D96000069D7 /* libRNWebViewJSContext.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNWebViewJSContext.a; sourceTree = BUILT_PRODUCTS_DIR; };
29 | 1C5F19C81BD87D96000069D7 /* RNWebViewJSContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNWebViewJSContext.h; sourceTree = ""; };
30 | 1C5F19CA1BD87D96000069D7 /* RNWebViewJSContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNWebViewJSContext.m; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 1C5F19C21BD87D96000069D7 /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | );
39 | runOnlyForDeploymentPostprocessing = 0;
40 | };
41 | /* End PBXFrameworksBuildPhase section */
42 |
43 | /* Begin PBXGroup section */
44 | 1C5F19BC1BD87D96000069D7 = {
45 | isa = PBXGroup;
46 | children = (
47 | 1C5F19C71BD87D96000069D7 /* RNWebViewJSContext */,
48 | 1C5F19C61BD87D96000069D7 /* Products */,
49 | );
50 | sourceTree = "";
51 | };
52 | 1C5F19C61BD87D96000069D7 /* Products */ = {
53 | isa = PBXGroup;
54 | children = (
55 | 1C5F19C51BD87D96000069D7 /* libRNWebViewJSContext.a */,
56 | );
57 | name = Products;
58 | sourceTree = "";
59 | };
60 | 1C5F19C71BD87D96000069D7 /* RNWebViewJSContext */ = {
61 | isa = PBXGroup;
62 | children = (
63 | 1C5F19C81BD87D96000069D7 /* RNWebViewJSContext.h */,
64 | 1C5F19CA1BD87D96000069D7 /* RNWebViewJSContext.m */,
65 | );
66 | path = RNWebViewJSContext;
67 | sourceTree = "";
68 | };
69 | /* End PBXGroup section */
70 |
71 | /* Begin PBXNativeTarget section */
72 | 1C5F19C41BD87D96000069D7 /* RNWebViewJSContext */ = {
73 | isa = PBXNativeTarget;
74 | buildConfigurationList = 1C5F19CE1BD87D96000069D7 /* Build configuration list for PBXNativeTarget "RNWebViewJSContext" */;
75 | buildPhases = (
76 | 1C5F19C11BD87D96000069D7 /* Sources */,
77 | 1C5F19C21BD87D96000069D7 /* Frameworks */,
78 | 1C5F19C31BD87D96000069D7 /* CopyFiles */,
79 | );
80 | buildRules = (
81 | );
82 | dependencies = (
83 | );
84 | name = RNWebViewJSContext;
85 | productName = RNWebViewJSContext;
86 | productReference = 1C5F19C51BD87D96000069D7 /* libRNWebViewJSContext.a */;
87 | productType = "com.apple.product-type.library.static";
88 | };
89 | /* End PBXNativeTarget section */
90 |
91 | /* Begin PBXProject section */
92 | 1C5F19BD1BD87D96000069D7 /* Project object */ = {
93 | isa = PBXProject;
94 | attributes = {
95 | LastUpgradeCheck = 0700;
96 | ORGANIZATIONNAME = "Shayne Sweeney";
97 | TargetAttributes = {
98 | 1C5F19C41BD87D96000069D7 = {
99 | CreatedOnToolsVersion = 7.0;
100 | };
101 | };
102 | };
103 | buildConfigurationList = 1C5F19C01BD87D96000069D7 /* Build configuration list for PBXProject "RNWebViewJSContext" */;
104 | compatibilityVersion = "Xcode 3.2";
105 | developmentRegion = English;
106 | hasScannedForEncodings = 0;
107 | knownRegions = (
108 | en,
109 | );
110 | mainGroup = 1C5F19BC1BD87D96000069D7;
111 | productRefGroup = 1C5F19C61BD87D96000069D7 /* Products */;
112 | projectDirPath = "";
113 | projectRoot = "";
114 | targets = (
115 | 1C5F19C41BD87D96000069D7 /* RNWebViewJSContext */,
116 | );
117 | };
118 | /* End PBXProject section */
119 |
120 | /* Begin PBXSourcesBuildPhase section */
121 | 1C5F19C11BD87D96000069D7 /* Sources */ = {
122 | isa = PBXSourcesBuildPhase;
123 | buildActionMask = 2147483647;
124 | files = (
125 | 1C5F19CB1BD87D96000069D7 /* RNWebViewJSContext.m in Sources */,
126 | );
127 | runOnlyForDeploymentPostprocessing = 0;
128 | };
129 | /* End PBXSourcesBuildPhase section */
130 |
131 | /* Begin XCBuildConfiguration section */
132 | 1C5F19CC1BD87D96000069D7 /* Debug */ = {
133 | isa = XCBuildConfiguration;
134 | buildSettings = {
135 | ALWAYS_SEARCH_USER_PATHS = NO;
136 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
137 | CLANG_CXX_LIBRARY = "libc++";
138 | CLANG_ENABLE_MODULES = YES;
139 | CLANG_ENABLE_OBJC_ARC = YES;
140 | CLANG_WARN_BOOL_CONVERSION = YES;
141 | CLANG_WARN_CONSTANT_CONVERSION = YES;
142 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
143 | CLANG_WARN_EMPTY_BODY = YES;
144 | CLANG_WARN_ENUM_CONVERSION = YES;
145 | CLANG_WARN_INT_CONVERSION = YES;
146 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
147 | CLANG_WARN_UNREACHABLE_CODE = YES;
148 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
149 | COPY_PHASE_STRIP = NO;
150 | DEBUG_INFORMATION_FORMAT = dwarf;
151 | ENABLE_STRICT_OBJC_MSGSEND = YES;
152 | ENABLE_TESTABILITY = YES;
153 | GCC_C_LANGUAGE_STANDARD = gnu99;
154 | GCC_DYNAMIC_NO_PIC = NO;
155 | GCC_NO_COMMON_BLOCKS = YES;
156 | GCC_OPTIMIZATION_LEVEL = 0;
157 | GCC_PREPROCESSOR_DEFINITIONS = (
158 | "DEBUG=1",
159 | "$(inherited)",
160 | );
161 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
162 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
163 | GCC_WARN_UNDECLARED_SELECTOR = YES;
164 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
165 | GCC_WARN_UNUSED_FUNCTION = YES;
166 | GCC_WARN_UNUSED_VARIABLE = YES;
167 | HEADER_SEARCH_PATHS = (
168 | "$(SRCROOT)/../../react-native/React/**",
169 | "$(SRCROOT)/node_modules/react-native/React/**",
170 | );
171 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
172 | MTL_ENABLE_DEBUG_INFO = YES;
173 | ONLY_ACTIVE_ARCH = YES;
174 | SDKROOT = iphoneos;
175 | };
176 | name = Debug;
177 | };
178 | 1C5F19CD1BD87D96000069D7 /* Release */ = {
179 | isa = XCBuildConfiguration;
180 | buildSettings = {
181 | ALWAYS_SEARCH_USER_PATHS = NO;
182 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
183 | CLANG_CXX_LIBRARY = "libc++";
184 | CLANG_ENABLE_MODULES = YES;
185 | CLANG_ENABLE_OBJC_ARC = YES;
186 | CLANG_WARN_BOOL_CONVERSION = YES;
187 | CLANG_WARN_CONSTANT_CONVERSION = YES;
188 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
189 | CLANG_WARN_EMPTY_BODY = YES;
190 | CLANG_WARN_ENUM_CONVERSION = YES;
191 | CLANG_WARN_INT_CONVERSION = YES;
192 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
193 | CLANG_WARN_UNREACHABLE_CODE = YES;
194 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
195 | COPY_PHASE_STRIP = NO;
196 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
197 | ENABLE_NS_ASSERTIONS = NO;
198 | ENABLE_STRICT_OBJC_MSGSEND = YES;
199 | GCC_C_LANGUAGE_STANDARD = gnu99;
200 | GCC_NO_COMMON_BLOCKS = YES;
201 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
202 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
203 | GCC_WARN_UNDECLARED_SELECTOR = YES;
204 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
205 | GCC_WARN_UNUSED_FUNCTION = YES;
206 | GCC_WARN_UNUSED_VARIABLE = YES;
207 | HEADER_SEARCH_PATHS = (
208 | "$(SRCROOT)/../../react-native/React/**",
209 | "$(SRCROOT)/node_modules/react-native/React/**",
210 | );
211 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
212 | MTL_ENABLE_DEBUG_INFO = NO;
213 | SDKROOT = iphoneos;
214 | VALIDATE_PRODUCT = YES;
215 | };
216 | name = Release;
217 | };
218 | 1C5F19CF1BD87D96000069D7 /* Debug */ = {
219 | isa = XCBuildConfiguration;
220 | buildSettings = {
221 | OTHER_LDFLAGS = "-ObjC";
222 | PRODUCT_NAME = "$(TARGET_NAME)";
223 | SKIP_INSTALL = YES;
224 | };
225 | name = Debug;
226 | };
227 | 1C5F19D01BD87D96000069D7 /* Release */ = {
228 | isa = XCBuildConfiguration;
229 | buildSettings = {
230 | OTHER_LDFLAGS = "-ObjC";
231 | PRODUCT_NAME = "$(TARGET_NAME)";
232 | SKIP_INSTALL = YES;
233 | };
234 | name = Release;
235 | };
236 | /* End XCBuildConfiguration section */
237 |
238 | /* Begin XCConfigurationList section */
239 | 1C5F19C01BD87D96000069D7 /* Build configuration list for PBXProject "RNWebViewJSContext" */ = {
240 | isa = XCConfigurationList;
241 | buildConfigurations = (
242 | 1C5F19CC1BD87D96000069D7 /* Debug */,
243 | 1C5F19CD1BD87D96000069D7 /* Release */,
244 | );
245 | defaultConfigurationIsVisible = 0;
246 | defaultConfigurationName = Release;
247 | };
248 | 1C5F19CE1BD87D96000069D7 /* Build configuration list for PBXNativeTarget "RNWebViewJSContext" */ = {
249 | isa = XCConfigurationList;
250 | buildConfigurations = (
251 | 1C5F19CF1BD87D96000069D7 /* Debug */,
252 | 1C5F19D01BD87D96000069D7 /* Release */,
253 | );
254 | defaultConfigurationIsVisible = 0;
255 | defaultConfigurationName = Release;
256 | };
257 | /* End XCConfigurationList section */
258 | };
259 | rootObject = 1C5F19BD1BD87D96000069D7 /* Project object */;
260 | }
261 |
--------------------------------------------------------------------------------