Skip to content

Commit

Permalink
Extract WasmContextProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
bouzuya committed Aug 31, 2023
1 parent 983fd70 commit a091376
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 37 deletions.
59 changes: 22 additions & 37 deletions android/App.tsx
@@ -1,48 +1,39 @@
import Constants from "expo-constants";
import { randomUUID } from "expo-crypto";
import { StatusBar } from "expo-status-bar";
import { useCallback, useRef, useState } from "react";
import { useCallback, useState } from "react";
import { Button, StyleSheet, Text, View } from "react-native";
import WebView, { WebViewMessageEvent } from "react-native-webview";
import { WasmContextProvider, useWasm } from "./components/WasmContextProvider";
import Constants from "expo-constants";

export default function App() {
function MyApp(): JSX.Element {
const { call } = useWasm();
const [count, setCount] = useState<number>(0);
const backendBaseUrl = Constants.expoConfig?.extra?.backendBaseUrl ?? null;
if (backendBaseUrl === null)
throw new Error("BACKEND_BASE_URL is not defined");
const ref = useRef<WebView>(null);
const uri = `${backendBaseUrl}/assets/index.html`;
const handleOnMessage = useCallback((event: WebViewMessageEvent): void => {
const message = JSON.parse(event.nativeEvent.data);
console.log("onMessage", message);
setCount(message.result);
}, []);
const handleOnPress = useCallback((): void => {
const id = randomUUID();
const message = {
id,
name: "add",
args: [count, 1],
};
console.log("postMessage", message);
ref.current?.postMessage(JSON.stringify(message));
}, [count, ref]);
(async () => {
const result = await call("add", [count, 1]);
setCount(result as number);
})();
}, [count]);
return (
<View style={styles.container}>
<StatusBar style="auto" />
<WebView
cacheEnabled={false}
containerStyle={styles.webView}
onMessage={handleOnMessage}
ref={ref}
source={{ uri }}
/>
<Text>{count}</Text>
<Button onPress={handleOnPress} title="Increment" />
</View>
);
}

export default function App() {
const backendBaseUrl = Constants.expoConfig?.extra?.backendBaseUrl ?? null;
if (backendBaseUrl === null)
throw new Error("BACKEND_BASE_URL is not defined");
const uri = `${backendBaseUrl}/assets/index.html`;
return (
<WasmContextProvider uri={uri}>
<MyApp />
</WasmContextProvider>
);
}

const styles = StyleSheet.create({
container: {
alignContent: "flex-start",
Expand All @@ -55,10 +46,4 @@ const styles = StyleSheet.create({
padding: 0,
width: "100%",
},
webView: {
height: "100%",
margin: 0,
padding: 0,
width: "100%",
},
});
99 changes: 99 additions & 0 deletions android/components/WasmContextProvider.tsx
@@ -0,0 +1,99 @@
import { randomUUID } from "expo-crypto";
import {
RefObject,
createContext,
createRef,
useCallback,
useContext,
useRef,
} from "react";
import { StyleSheet } from "react-native";
import WebView, { WebViewMessageEvent } from "react-native-webview";

type Call = (name: string, args: unknown[]) => Promise<unknown>;
type Calls = Record<string, Deferred | undefined>;

type Deferred = {
promise: Promise<unknown>;
reject: (reason: unknown) => void;
resolve: (value: unknown) => void;
};

function defer(): Deferred {
let reject: ((reason: unknown) => void) | null = null;
let resolve: ((value: unknown) => void) | null = null;
const promise = new Promise((ok, ng) => {
reject = ng;
resolve = ok;
});
if (reject === null) throw new Error("outerReject is null");
if (resolve === null) throw new Error("outerResolve is null");
return { promise, reject, resolve };
}

const WasmContext = createContext<{
call: Call;
calls: RefObject<Calls>;
}>({ call: () => Promise.resolve(null), calls: createRef<Calls>() });

export function useWasm(): {
call: Call;
} {
const { call } = useContext(WasmContext);
return { call };
}

type Props = {
children: React.ReactNode;
uri: string;
};

export function WasmContextProvider({ children, uri }: Props): JSX.Element {
const ref = useRef<WebView>(null);
const calls = useRef<Calls>({});
const handleOnMessage = useCallback(
(event: WebViewMessageEvent): void => {
const message = JSON.parse(event.nativeEvent.data);
console.log("onMessage", message);
const call = calls.current[message.id];
if (typeof call === "undefined") throw new Error("Unknown call");
call.resolve(message.result);
calls.current[message.id] = undefined;
},
[calls]
);
const call = useCallback(
(name: string, args: unknown[]): Promise<unknown> => {
const id = randomUUID();
const message = {
id,
name,
args,
};
const deferred = defer();
calls.current[id] = deferred;
console.log("postMessage", message);
ref.current?.postMessage(JSON.stringify(message));
return deferred.promise;
},
[ref]
);
return (
<WasmContext.Provider value={{ call, calls }}>
<WebView
cacheEnabled={false}
containerStyle={styles.webView}
onMessage={handleOnMessage}
ref={ref}
source={{ uri }}
/>
{children}
</WasmContext.Provider>
);
}

const styles = StyleSheet.create({
webView: {
display: "none",
},
});

0 comments on commit a091376

Please sign in to comment.