├── .gitignore ├── .travis.yml ├── index.d.ts ├── index.js ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typings 3 | dist 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | 5 | # https://docs.travis-ci.com/user/languages/javascript-with-nodejs#Node.js-v4-(or-io.js-v3)-compiler-requirements 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.8 12 | env: 13 | global: 14 | - CXX=g++-4.8 15 | matrix: 16 | - BROWSER_NAME=chrome BROWSER_VERSION=latest 17 | - BROWSER_NAME=iphone BROWSER_VERSION=9.3 18 | - BROWSER_NAME=android BROWSER_VERSION=5.1 19 | # not working 20 | # - BROWSER_NAME=android BROWSER_VERSION=4.4 21 | # - BROWSER_NAME=android BROWSER_VERSION=5.0 22 | 23 | before_script: 24 | - npm run test:typings:install 25 | script: 26 | - npm run test:lint 27 | - npm run test 28 | 29 | # doesn't help that much with speed, maybe saves 40 seconds, and potentially 30 | # introduces some more bugs 31 | # cache: 32 | # directories: 33 | # - node_modules 34 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | export default class PolyfillCrypto extends React.Component {} 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const { StyleSheet, View } = require("react-native"); 3 | const { WebView } = require("react-native-webview"); 4 | const { MainWorker, webViewWorkerString } = require("webview-crypto"); 5 | 6 | const styles = StyleSheet.create({ 7 | hide: { 8 | display: "none", 9 | position: "absolute", 10 | 11 | width: 0, 12 | height: 0, 13 | 14 | flexGrow: 0, 15 | flexShrink: 1 16 | } 17 | }); 18 | 19 | const internalLibrary = ` 20 | (function () { 21 | function postMessage (message) { 22 | if (window.ReactNativeWebView.postMessage === undefined) { 23 | setTimeout(postMessage, 200, message) 24 | } else { 25 | window.ReactNativeWebView.postMessage(message) 26 | } 27 | } 28 | var wvw = new WebViewWorker(postMessage) 29 | // for android 30 | window.document.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) 31 | // for ios 32 | window.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) 33 | }()) 34 | `; 35 | 36 | let resolveWorker; 37 | let workerPromise = new Promise(resolve => { 38 | resolveWorker = resolve; 39 | }); 40 | 41 | function sendToWorker(message) { 42 | workerPromise.then(worker => worker.onWebViewMessage(message)); 43 | } 44 | 45 | const subtle = { 46 | fake: true, 47 | decrypt(...args) { 48 | return workerPromise.then(worker => worker.crypto.subtle.decrypt(...args)); 49 | }, 50 | deriveBits(...args) { 51 | return workerPromise.then(worker => 52 | worker.crypto.subtle.deriveBits(...args) 53 | ); 54 | }, 55 | deriveKey(...args) { 56 | return workerPromise.then(worker => 57 | worker.crypto.subtle.deriveKey(...args) 58 | ); 59 | }, 60 | digest(...args) { 61 | return workerPromise.then(worker => worker.crypto.subtle.digest(...args)); 62 | }, 63 | encrypt(...args) { 64 | return workerPromise.then(worker => worker.crypto.subtle.encrypt(...args)); 65 | }, 66 | exportKey(...args) { 67 | return workerPromise.then(worker => 68 | worker.crypto.subtle.exportKey(...args) 69 | ); 70 | }, 71 | generateKey(...args) { 72 | return workerPromise.then(worker => 73 | worker.crypto.subtle.generateKey(...args) 74 | ); 75 | }, 76 | importKey(...args) { 77 | return workerPromise.then(worker => 78 | worker.crypto.subtle.importKey(...args) 79 | ); 80 | }, 81 | sign(...args) { 82 | return workerPromise.then(worker => worker.crypto.subtle.sign(...args)); 83 | }, 84 | unwrapKey(...args) { 85 | return workerPromise.then(worker => 86 | worker.crypto.subtle.unwrapKey(...args) 87 | ); 88 | }, 89 | verify(...args) { 90 | return workerPromise.then(worker => worker.crypto.subtle.verify(...args)); 91 | }, 92 | wrapKey(...args) { 93 | return workerPromise.then(worker => worker.crypto.subtle.wrapKey(...args)); 94 | } 95 | }; 96 | 97 | class PolyfillCrypto extends React.Component { 98 | constructor(props) { 99 | super(props); 100 | this.props = props; 101 | this.webViewRef = React.createRef(); 102 | this.state = { 103 | webViewKey: 0, 104 | }; 105 | } 106 | 107 | shouldComponentUpdate(nextProps, nextState) { 108 | return nextState.webViewKey !== this.state.webViewKey; 109 | } 110 | 111 | // reset promise so it can be resolved on next re-mount 112 | componentWillUnmount() { 113 | resolveWorker = undefined; 114 | workerPromise = new Promise((resolve) => { 115 | resolveWorker = resolve; 116 | }); 117 | } 118 | 119 | componentDidMount() { 120 | const webView = this.webViewRef.current; 121 | 122 | resolveWorker( 123 | new MainWorker(msg => { 124 | webView.postMessage(msg); 125 | }, this.props.debug) 126 | ); 127 | } 128 | 129 | componentDidUpdate(prevProps, prevState) { 130 | if (prevState.webViewKey !== this.state.webViewKey) { 131 | const webView = this.webViewRef.current; 132 | resolveWorker( 133 | new MainWorker( 134 | (msg) => { 135 | webView.postMessage(msg); 136 | }, 137 | this.props.debug 138 | ) 139 | ); 140 | } 141 | } 142 | 143 | onContentProcessDidTerminate = (event) => { 144 | const { nativeEvent } = event; 145 | console.warn("Content process terminated, reloading", nativeEvent); 146 | resolveWorker = undefined; 147 | workerPromise = new Promise((resolve) => { 148 | resolveWorker = resolve; 149 | }); 150 | this.setState((prevState) => ({ webViewKey: prevState.webViewKey + 1 })); 151 | }; 152 | 153 | render() { 154 | const code = `((function () {${webViewWorkerString};${internalLibrary}})())`; 155 | const html = `
` 156 | // The uri 'about:blank' doesn't have access to crypto.subtle on android 157 | // const uri = "file:///android_asset/blank.html"; 158 | 159 | // Base64 dance is to work around https://github.com/facebook/react-native/issues/20365 160 | // const source = Platform.select({ 161 | // android: { source: { uri } }, 162 | // ios: undefined 163 | // }); 164 | return ( 165 |