diff --git a/app/_layout.tsx b/app/_layout.tsx index 2021613..0e054fe 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 "react-native-webview-crypto"; +import PolyfillCrypto from "~/polyfill"; 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 ccd6748..810547b 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", - "react-native-webview-crypto": "^0.0.25", + "webview-crypto": "^0.1.13", "swr": "^2.2.5", "tailwind-merge": "^2.3.0", "text-encoding": "^0.7.0", diff --git a/polyfill/index.js b/polyfill/index.js new file mode 100644 index 0000000..cd56fc9 --- /dev/null +++ b/polyfill/index.js @@ -0,0 +1,183 @@ +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; + } + + 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 + ) + ); + } + } + + componentWillUnmount() { + resolveWorker = undefined; + workerPromise = new Promise((resolve) => { + resolveWorker = resolve; + }); + } + + 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 = `
`; + return ( +