From 9d0be0374079805c6cc4bd92cbcb6cfd1b2b28dc Mon Sep 17 00:00:00 2001 From: im-adithya Date: Wed, 18 Sep 2024 15:35:08 +0530 Subject: [PATCH 1/2] chore: add todo in crypto polyfill --- app/_layout.tsx | 2 +- {polyfill => polyfill-crypto}/index.js | 83 ++++++++++++++++---------- 2 files changed, 52 insertions(+), 33 deletions(-) rename {polyfill => polyfill-crypto}/index.js (65%) diff --git a/app/_layout.tsx b/app/_layout.tsx index 0e054fe..cd64108 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -13,7 +13,7 @@ import * as React from "react"; import { SafeAreaView } from "react-native"; import { NAV_THEME } from "~/lib/constants"; import { useColorScheme } from "~/lib/useColorScheme"; -import PolyfillCrypto from "~/polyfill"; +import PolyfillCrypto from "~/polyfill-crypto"; import { SWRConfig } from "swr"; import { swrConfiguration } from "lib/swr"; import Toast from "react-native-toast-message"; diff --git a/polyfill/index.js b/polyfill-crypto/index.js similarity index 65% rename from polyfill/index.js rename to polyfill-crypto/index.js index cd56fc9..d2e3a43 100644 --- a/polyfill/index.js +++ b/polyfill-crypto/index.js @@ -1,3 +1,10 @@ +// This code modifies `react-native-webview-crypto` package to handle +// `onContentProcessDidTerminate` by remounting the `WebView` and re-initializing +// the worker. + +// TODO: replace this with webview-crypto/react-native-webview-crypto package once +// https://github.com/webview-crypto/react-native-webview-crypto/pull/30 is merged + const React = require("react"); const { StyleSheet, View } = require("react-native"); const { WebView } = require("react-native-webview"); @@ -7,11 +14,13 @@ const styles = StyleSheet.create({ hide: { display: "none", position: "absolute", + width: 0, height: 0, + flexGrow: 0, - flexShrink: 1, - }, + flexShrink: 1 + } }); const internalLibrary = ` @@ -24,72 +33,72 @@ const internalLibrary = ` } } var wvw = new WebViewWorker(postMessage) - // for Android + // for android window.document.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) - // for iOS + // for ios window.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) }()) `; let resolveWorker; -let workerPromise = new Promise((resolve) => { +let workerPromise = new Promise(resolve => { resolveWorker = resolve; }); function sendToWorker(message) { - workerPromise.then((worker) => worker.onWebViewMessage(message)); + workerPromise.then(worker => worker.onWebViewMessage(message)); } const subtle = { fake: true, decrypt(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.decrypt(...args)); + return workerPromise.then(worker => worker.crypto.subtle.decrypt(...args)); }, deriveBits(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.deriveBits(...args) ); }, deriveKey(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.deriveKey(...args) ); }, digest(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.digest(...args)); + return workerPromise.then(worker => worker.crypto.subtle.digest(...args)); }, encrypt(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.encrypt(...args)); + return workerPromise.then(worker => worker.crypto.subtle.encrypt(...args)); }, exportKey(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.exportKey(...args) ); }, generateKey(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.generateKey(...args) ); }, importKey(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.importKey(...args) ); }, sign(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.sign(...args)); + return workerPromise.then(worker => worker.crypto.subtle.sign(...args)); }, unwrapKey(...args) { - return workerPromise.then((worker) => + return workerPromise.then(worker => worker.crypto.subtle.unwrapKey(...args) ); }, verify(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.verify(...args)); + return workerPromise.then(worker => worker.crypto.subtle.verify(...args)); }, wrapKey(...args) { - return workerPromise.then((worker) => worker.crypto.subtle.wrapKey(...args)); - }, + return workerPromise.then(worker => worker.crypto.subtle.wrapKey(...args)); + } }; class PolyfillCrypto extends React.Component { @@ -106,12 +115,20 @@ class PolyfillCrypto extends React.Component { return nextState.webViewKey !== this.state.webViewKey; } + // reset promise so it can be resolved on next re-mount + componentWillUnmount() { + resolveWorker = undefined; + workerPromise = new Promise((resolve) => { + resolveWorker = resolve; + }); + } + componentDidMount() { const webView = this.webViewRef.current; resolveWorker( new MainWorker(msg => { - webView.postMessage(msg); + webView.postMessage(msg); }, this.props.debug) ); } @@ -130,13 +147,6 @@ class PolyfillCrypto extends React.Component { } } - componentWillUnmount() { - resolveWorker = undefined; - workerPromise = new Promise((resolve) => { - resolveWorker = resolve; - }); - } - onContentProcessDidTerminate = (event) => { const { nativeEvent } = event; console.warn("Content process terminated, reloading", nativeEvent); @@ -149,20 +159,28 @@ class PolyfillCrypto extends React.Component { render() { const code = `((function () {${webViewWorkerString};${internalLibrary}})())`; - const html = ``; + const html = `` + // The uri 'about:blank' doesn't have access to crypto.subtle on android +// const uri = "file:///android_asset/blank.html"; + + // Base64 dance is to work around https://github.com/facebook/react-native/issues/20365 +// const source = Platform.select({ +// android: { source: { uri } }, +// ios: undefined +// }); return ( + onContentProcessDidTerminate={this.onContentProcessDidTerminate} + onError={a => console.error(Object.keys(a), a.type, a.nativeEvent.description) } - onMessage={(ev) => sendToWorker(ev.nativeEvent.data)} + onMessage={ev => sendToWorker(ev.nativeEvent.data)} ref={this.webViewRef} originWhitelist={["*"]} - onContentProcessDidTerminate={this.onContentProcessDidTerminate} - source={{ html: html, baseUrl: "https://localhost" }} + source={{ html: html, baseUrl: 'https://localhost' }} /> ); @@ -173,6 +191,7 @@ if (typeof global.crypto !== "object") { global.crypto = {}; } +//required for webview-crypto serializer fromObject global.crypto.fake = true; if (typeof global.crypto.subtle !== "object") { From ea448676e64f307060bf655441f566e8740a6e36 Mon Sep 17 00:00:00 2001 From: im-adithya Date: Wed, 18 Sep 2024 17:35:30 +0530 Subject: [PATCH 2/2] chore: replace with webview crypto package --- app/_layout.tsx | 2 +- package.json | 4 +- polyfill-crypto/index.js | 202 --------------------------------------- yarn.lock | 16 +++- 4 files changed, 18 insertions(+), 206 deletions(-) delete mode 100644 polyfill-crypto/index.js diff --git a/app/_layout.tsx b/app/_layout.tsx index cd64108..2021613 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -13,7 +13,7 @@ import * as React from "react"; import { SafeAreaView } from "react-native"; import { NAV_THEME } from "~/lib/constants"; import { useColorScheme } from "~/lib/useColorScheme"; -import PolyfillCrypto from "~/polyfill-crypto"; +import PolyfillCrypto from "react-native-webview-crypto"; import { SWRConfig } from "swr"; import { swrConfiguration } from "lib/swr"; import Toast from "react-native-toast-message"; diff --git a/package.json b/package.json index 0d53372..b56fe95 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "react-native-toast-message": "^2.2.0", "react-native-url-polyfill": "^2.0.0", "react-native-webview": "13.8.6", - "webview-crypto": "^0.1.13", + "react-native-webview-crypto": "^0.0.26", "swr": "^2.2.5", "tailwind-merge": "^2.3.0", "text-encoding": "^0.7.0", @@ -66,4 +66,4 @@ "typescript": "~5.3.3" }, "private": true -} \ No newline at end of file +} diff --git a/polyfill-crypto/index.js b/polyfill-crypto/index.js deleted file mode 100644 index d2e3a43..0000000 --- a/polyfill-crypto/index.js +++ /dev/null @@ -1,202 +0,0 @@ -// This code modifies `react-native-webview-crypto` package to handle -// `onContentProcessDidTerminate` by remounting the `WebView` and re-initializing -// the worker. - -// TODO: replace this with webview-crypto/react-native-webview-crypto package once -// https://github.com/webview-crypto/react-native-webview-crypto/pull/30 is merged - -const React = require("react"); -const { StyleSheet, View } = require("react-native"); -const { WebView } = require("react-native-webview"); -const { MainWorker, webViewWorkerString } = require("webview-crypto"); - -const styles = StyleSheet.create({ - hide: { - display: "none", - position: "absolute", - - width: 0, - height: 0, - - flexGrow: 0, - flexShrink: 1 - } -}); - -const internalLibrary = ` -(function () { - function postMessage (message) { - if (window.ReactNativeWebView.postMessage === undefined) { - setTimeout(postMessage, 200, message) - } else { - window.ReactNativeWebView.postMessage(message) - } - } - var wvw = new WebViewWorker(postMessage) - // for android - window.document.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) - // for ios - window.addEventListener('message', function (e) {wvw.onMainMessage(e.data);}) -}()) -`; - -let resolveWorker; -let workerPromise = new Promise(resolve => { - resolveWorker = resolve; -}); - -function sendToWorker(message) { - workerPromise.then(worker => worker.onWebViewMessage(message)); -} - -const subtle = { - fake: true, - decrypt(...args) { - return workerPromise.then(worker => worker.crypto.subtle.decrypt(...args)); - }, - deriveBits(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.deriveBits(...args) - ); - }, - deriveKey(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.deriveKey(...args) - ); - }, - digest(...args) { - return workerPromise.then(worker => worker.crypto.subtle.digest(...args)); - }, - encrypt(...args) { - return workerPromise.then(worker => worker.crypto.subtle.encrypt(...args)); - }, - exportKey(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.exportKey(...args) - ); - }, - generateKey(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.generateKey(...args) - ); - }, - importKey(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.importKey(...args) - ); - }, - sign(...args) { - return workerPromise.then(worker => worker.crypto.subtle.sign(...args)); - }, - unwrapKey(...args) { - return workerPromise.then(worker => - worker.crypto.subtle.unwrapKey(...args) - ); - }, - verify(...args) { - return workerPromise.then(worker => worker.crypto.subtle.verify(...args)); - }, - wrapKey(...args) { - return workerPromise.then(worker => worker.crypto.subtle.wrapKey(...args)); - } -}; - -class PolyfillCrypto extends React.Component { - constructor(props) { - super(props); - this.props = props; - this.webViewRef = React.createRef(); - this.state = { - webViewKey: 0, - }; - } - - shouldComponentUpdate(nextProps, nextState) { - return nextState.webViewKey !== this.state.webViewKey; - } - - // reset promise so it can be resolved on next re-mount - componentWillUnmount() { - resolveWorker = undefined; - workerPromise = new Promise((resolve) => { - resolveWorker = resolve; - }); - } - - componentDidMount() { - const webView = this.webViewRef.current; - - resolveWorker( - new MainWorker(msg => { - webView.postMessage(msg); - }, this.props.debug) - ); - } - - componentDidUpdate(prevProps, prevState) { - if (prevState.webViewKey !== this.state.webViewKey) { - const webView = this.webViewRef.current; - resolveWorker( - new MainWorker( - (msg) => { - webView.postMessage(msg); - }, - this.props.debug - ) - ); - } - } - - onContentProcessDidTerminate = (event) => { - const { nativeEvent } = event; - console.warn("Content process terminated, reloading", nativeEvent); - resolveWorker = undefined; - workerPromise = new Promise((resolve) => { - resolveWorker = resolve; - }); - this.setState((prevState) => ({ webViewKey: prevState.webViewKey + 1 })); - }; - - render() { - const code = `((function () {${webViewWorkerString};${internalLibrary}})())`; - const html = `` - // The uri 'about:blank' doesn't have access to crypto.subtle on android -// const uri = "file:///android_asset/blank.html"; - - // Base64 dance is to work around https://github.com/facebook/react-native/issues/20365 -// const source = Platform.select({ -// android: { source: { uri } }, -// ios: undefined -// }); - return ( - - - console.error(Object.keys(a), a.type, a.nativeEvent.description) - } - onMessage={ev => sendToWorker(ev.nativeEvent.data)} - ref={this.webViewRef} - originWhitelist={["*"]} - source={{ html: html, baseUrl: 'https://localhost' }} - /> - - ); - } -} - -if (typeof global.crypto !== "object") { - global.crypto = {}; -} - -//required for webview-crypto serializer fromObject -global.crypto.fake = true; - -if (typeof global.crypto.subtle !== "object") { - global.crypto.subtle = subtle; -} - -Object.defineProperty(exports, "__esModule", { value: true }); -exports.default = PolyfillCrypto; diff --git a/yarn.lock b/yarn.lock index c213af6..2813cc5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3541,7 +3541,7 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -encode-utf8@^1.0.3: +encode-utf8@^1.0.2, encode-utf8@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== @@ -3911,6 +3911,11 @@ fast-base64-decode@^1.0.0: resolved "https://registry.yarnpkg.com/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz#b434a0dd7d92b12b43f26819300d2dafb83ee418" integrity sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q== +fast-base64-encode@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-base64-encode/-/fast-base64-encode-1.0.0.tgz#883945eb67e139dbf5a877bcca57a89e6824c7d4" + integrity sha512-z2XCzVK4fde2cuTEHu2QGkLD6BPtJNKJPn0Z7oINvmhq/quUuIIVPYKUdN0gYeZqOyurjJjBH/bUzK5gafyHvw== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -6542,6 +6547,15 @@ react-native-url-polyfill@^2.0.0: dependencies: whatwg-url-without-unicode "8.0.0-3" +react-native-webview-crypto@^0.0.26: + version "0.0.26" + resolved "https://registry.yarnpkg.com/react-native-webview-crypto/-/react-native-webview-crypto-0.0.26.tgz#bce360876ed3367e677cb5cfa88564ca892ba72f" + integrity sha512-RshjQDik60LOhh7Q+SKIsXjnCgCIBZqZOB+v4MWa7l+0uAEyeZyMkWKL0xJRzWfTQ9vnLu4nHyn6qjEEi0uHnA== + dependencies: + encode-utf8 "^1.0.2" + fast-base64-encode "^1.0.0" + webview-crypto "^0.1.13" + react-native-webview@13.8.6: version "13.8.6" resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-13.8.6.tgz#5d4a62cb311d5ef8d910a8e112b3f1f2807bcd18"