├── .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 | 166 | 171 | console.error(Object.keys(a), a.type, a.nativeEvent.description) 172 | } 173 | onMessage={ev => sendToWorker(ev.nativeEvent.data)} 174 | ref={this.webViewRef} 175 | originWhitelist={["*"]} 176 | source={{ html: html, baseUrl: 'https://localhost' }} 177 | /> 178 | 179 | ); 180 | } 181 | } 182 | 183 | if (typeof global.crypto !== "object") { 184 | global.crypto = {}; 185 | } 186 | 187 | //required for webview-crypto serializer fromObject 188 | global.crypto.fake = true; 189 | 190 | if (typeof global.crypto.subtle !== "object") { 191 | global.crypto.subtle = subtle; 192 | } 193 | 194 | Object.defineProperty(exports, "__esModule", { value: true }); 195 | exports.default = PolyfillCrypto; 196 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-webview-crypto", 3 | "version": "0.0.25", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "encode-utf8": { 8 | "version": "1.0.2", 9 | "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.2.tgz", 10 | "integrity": "sha512-uZHa/WomOJYUuRznENTRZZI7DCpwC6rdKIXPYJU79Oeo5+QN24bL0uisuTm1TeWtmncS7J5or2Z0c/3k29yz3A==" 11 | }, 12 | "fast-base64-encode": { 13 | "version": "1.0.0", 14 | "resolved": "https://registry.npmjs.org/fast-base64-encode/-/fast-base64-encode-1.0.0.tgz", 15 | "integrity": "sha512-z2XCzVK4fde2cuTEHu2QGkLD6BPtJNKJPn0Z7oINvmhq/quUuIIVPYKUdN0gYeZqOyurjJjBH/bUzK5gafyHvw==" 16 | }, 17 | "serialize-error": { 18 | "version": "2.1.0", 19 | "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", 20 | "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=" 21 | }, 22 | "webview-crypto": { 23 | "version": "0.1.11", 24 | "resolved": "https://registry.npmjs.org/webview-crypto/-/webview-crypto-0.1.11.tgz", 25 | "integrity": "sha512-j+g9bGfgmSMYLLYWtLTVkXLuQQzBb9zcbvSlNhcIb16w880dmrnSpmVca6L6XUgDw0NrQ3oHP63G3ctEAOp3Qw==", 26 | "requires": { 27 | "lodash": "4.17.3", 28 | "serialize-error": "2.1.0" 29 | }, 30 | "dependencies": { 31 | "lodash": { 32 | "version": "4.17.3", 33 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.3.tgz", 34 | "integrity": "sha1-VX7X0qlDjKxf1aQwQ8pgy0VeAfc=" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-webview-crypto", 3 | "version": "0.0.26", 4 | "license": "MIT", 5 | "repository": "https://github.com/webview-crypto/react-native-webview-crypto", 6 | "keywords": [ 7 | "crypto", 8 | "webcrypto", 9 | "cryptography", 10 | "react-native" 11 | ], 12 | "dependencies": { 13 | "encode-utf8": "^1.0.2", 14 | "fast-base64-encode": "^1.0.0", 15 | "webview-crypto": "^0.1.13" 16 | }, 17 | "peerDependencies": { 18 | "react": ">=16.4", 19 | "react-native": ">=0.56", 20 | "react-native-webview": ">=8.*" 21 | }, 22 | "files": [ 23 | "index.d.ts", 24 | "index.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # react-native-webview-crypto 2 | 3 | [![npm](https://img.shields.io/npm/v/react-native-webview-crypto?style=flat-square)](https://www.npmjs.com/package/react-native-webview-crypto) 4 | 5 | Working version based on /saulshanabrook/react-native-webview-crypto and PR https://github.com/webview-crypto/react-native-webview-crypto/pull/9 6 | 7 | This brings `window.crypto.subtle` to your React Native application. It does this by communicating with a hidden WebView, which performs the actual computation. 8 | 9 | Refer to the [`webview-crypto`](https://github.com/webview-crypto/webview-crypto) repo for most of the code and some caveats. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install --save react-native-webview react-native-webview-crypto 15 | react-native link 16 | ``` 17 | 18 | ### known issues 19 | 20 | if you use react-native-crypto and get warnings about cyclic require make sure to import it before the bridge 21 | 22 | ``` 23 | import 'react-native-crypto' 24 | import WebviewCrypto from 'react-native-webview-crypto' 25 | ``` 26 | 27 | ### getRandomValues 28 | 29 | for complete compatability with webcrypto (window.crypto.getRandomValues) you can install react-native-crytpo or react-native-get-random-values 30 | 31 | ## Usage 32 | 33 | Rendering the `PolyfillCrypto` will start up a WebView to transparently proxy all the crypto calls to. 34 | 35 | ```javascript 36 | import React, { Component } from 'react' 37 | import { View } from 'react-native' 38 | 39 | import App from './app' 40 | 41 | import PolyfillCrypto from 'react-native-webview-crypto' 42 | 43 | class TopLevelComponent extends Component { 44 | render() { 45 | return ( 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | } 53 | 54 | AppRegistry.registerComponent('WhateverName', () => TopLevelComponent) 55 | ``` 56 | 57 | Now, in any of your code, you can access `window.crypto.subtle`, just like you would in a browser. 58 | 59 | [Example repo](https://github.com/gooddollar/gun-webcrypto-react-native) 60 | There is also an [example repo](https://github.com/webview-crypto/react-native-webview-crypto-example) which runs some example crypto using this library. 61 | 62 | _This project was funded by [Burke Software and Consulting LLC](http://burkesoftware.com/) for [passit](http://passit.io/)._ 63 | --------------------------------------------------------------------------------