diff --git a/assets/preload/streamChunksPreloadScript.js.html b/assets/preload/streamChunksPreloadScript.js.html index 337af32..fff4a27 100644 --- a/assets/preload/streamChunksPreloadScript.js.html +++ b/assets/preload/streamChunksPreloadScript.js.html @@ -4,6 +4,7 @@ /* eslint-disable unicorn/prefer-spread */ var OnStreamChunksToWebViewEventTypes; (function (OnStreamChunksToWebViewEventTypes) { + OnStreamChunksToWebViewEventTypes["CHECK_RECEIVER_READY"] = "CHECK_RECEIVER_READY"; OnStreamChunksToWebViewEventTypes["TIDDLER_STORE_SCRIPT_CHUNK"] = "TIDDLER_STORE_SCRIPT_CHUNK"; OnStreamChunksToWebViewEventTypes["TIDDLER_STORE_SCRIPT_CHUNK_END"] = "TIDDLER_STORE_SCRIPT_CHUNK_END"; OnStreamChunksToWebViewEventTypes["TIDDLYWIKI_HTML"] = "TIDDLYWIKI_HTML"; @@ -23,6 +24,7 @@ } // @ts-ignore window.onStreamChunksToWebView = function (event) { + var _a, _b, _c; switch (event.type) { case OnStreamChunksToWebViewEventTypes.TIDDLYWIKI_HTML: { resetUseStreamChunksToWebViewWebviewSideReceiverIIFE(); @@ -45,6 +47,11 @@ startInjectTiddlerIfHTMLDone_1(); break; } + case OnStreamChunksToWebViewEventTypes.CHECK_RECEIVER_READY: { + // @ts-ignore + (_c = (_b = (_a = window.service) === null || _a === void 0 ? void 0 : _a.wikiHookService) === null || _b === void 0 ? void 0 : _b.setWebviewReceiverReady) === null || _c === void 0 ? void 0 : _c.call(_b); + break; + } } }; function startInjectHTML(newInnerHTML) { diff --git a/package.json b/package.json index b357c99..d16308a 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "expo-barcode-scanner": "12.9.3", "expo-camera": "14.0.6", "expo-clipboard": "5.0.1", + "expo-dev-client": "~3.3.9", "expo-device": "5.9.3", "expo-file-system": "16.0.8", "expo-haptics": "12.8.1", @@ -44,6 +45,7 @@ "expo-sqlite": "13.3.0", "expo-status-bar": "1.11.1", "expo-task-manager": "11.7.2", + "exponential-backoff": "^3.1.1", "i18next": "^23.10.1", "immer": "^10.0.3", "lodash": "^4.17.21", @@ -70,8 +72,7 @@ "stream-json": "^1.8.0", "styled-components": "^6.1.8", "type-fest": "^4.12.0", - "zustand": "^4.5.2", - "expo-dev-client": "~3.3.9" + "zustand": "^4.5.2" }, "devDependencies": { "@babel/core": "^7.24.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ada544..3375451 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,6 +92,9 @@ dependencies: expo-task-manager: specifier: 11.7.2 version: 11.7.2(expo@50.0.11) + exponential-backoff: + specifier: ^3.1.1 + version: 3.1.1 i18next: specifier: ^23.10.1 version: 23.10.1 @@ -6198,6 +6201,10 @@ packages: - utf-8-validate dev: false + /exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + dev: false + /ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} dependencies: diff --git a/scripts/buildPreload.mjs b/scripts/buildPreload.mjs index a88454c..7e53324 100644 --- a/scripts/buildPreload.mjs +++ b/scripts/buildPreload.mjs @@ -2,9 +2,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { readFile, writeFile, mkdir } from 'fs/promises'; -import os from 'os'; -import path from 'path'; +import { mkdir, readFile, writeFile } from 'fs/promises'; import { $ } from 'zx'; if (process.platform === 'win32') { @@ -20,7 +18,9 @@ const modifiedTsContent = tsContent.replaceAll('export ', ''); // Write the modified content to a temporary file const tmpTsFilePath = 'build/streamChunksPreloadScript.ts'; -await mkdir('build') +try { + await mkdir('build'); +} catch {} await writeFile(tmpTsFilePath, modifiedTsContent); // Use TypeScript compiler to compile the temporary file diff --git a/src/pages/WikiWebView/WikiViewer.tsx b/src/pages/WikiWebView/WikiViewer.tsx index 002a144..96a1c93 100644 --- a/src/pages/WikiWebView/WikiViewer.tsx +++ b/src/pages/WikiWebView/WikiViewer.tsx @@ -80,12 +80,16 @@ export const WikiViewer = ({ wikiWorkspace, webviewSideReceiver, quickLoad }: Wi setWebViewKeyToReloadAfterRecycleByOS(latest => latest + 1); }, []); servicesOfWorkspace.wikiHookService.setLatestTriggerFullReloadCallback(triggerFullReload); + useEffect(() => { + servicesOfWorkspace.wikiHookService.resetWebviewReceiverReady(); + }, [servicesOfWorkspace.wikiHookService]); + /** * Webview can't load html larger than 20M, we stream the html to webview, and set innerHTML in webview using preloadScript. * This need to use with `webviewSideReceiver`. * @url https://github.com/react-native-webview/react-native-webview/issues/3126 */ - const { injectHtmlAndTiddlersStore, streamChunksToWebViewPercentage } = useStreamChunksToWebView(webViewReference); + const { injectHtmlAndTiddlersStore, streamChunksToWebViewPercentage } = useStreamChunksToWebView(webViewReference, servicesOfWorkspace); const loading = streamChunksToWebViewPercentage > 0 && streamChunksToWebViewPercentage < 1; useEffect(() => { void backgroundSyncService.updateServerOnlineStatus(); diff --git a/src/pages/WikiWebView/useStreamChunksToWebView/index.ts b/src/pages/WikiWebView/useStreamChunksToWebView/index.ts index 82066c1..a97daf4 100644 --- a/src/pages/WikiWebView/useStreamChunksToWebView/index.ts +++ b/src/pages/WikiWebView/useStreamChunksToWebView/index.ts @@ -1,6 +1,9 @@ +import { brand } from 'expo-device'; import { MutableRefObject, useCallback, useState } from 'react'; import { WebView } from 'react-native-webview'; import { Writable } from 'readable-stream'; +import type { WikiHookService } from '../../../services/WikiHookService'; +import type { WikiStorageService } from '../../../services/WikiStorageService'; import { IHtmlContent } from '../useTiddlyWiki'; import { OnStreamChunksToWebViewEventTypes } from './streamChunksPreloadScript'; @@ -9,11 +12,14 @@ import { OnStreamChunksToWebViewEventTypes } from './streamChunksPreloadScript'; * @url https://github.com/react-native-webview/react-native-webview/issues/3126 * @returns */ -export function useStreamChunksToWebView(webViewReference: MutableRefObject) { +export function useStreamChunksToWebView(webViewReference: MutableRefObject, servicesOfWorkspace: { + wikiHookService: WikiHookService; + wikiStorageService: WikiStorageService; +}) { const [streamChunksToWebViewPercentage, setStreamChunksToWebViewPercentage] = useState(0); const sendDataToWebView = useCallback((messageType: OnStreamChunksToWebViewEventTypes, data?: string) => { console.log(`sendDataToWebView ${messageType}`); - if (webViewReference.current === null) return; + if (webViewReference.current === null) throw new Error('WebView is not ready when sendDataToWebView'); const stringifiedData = JSON.stringify({ type: messageType, data, @@ -38,14 +44,19 @@ export function useStreamChunksToWebView(webViewReference: MutableRefObject(resolve => setTimeout(resolve, 1000));}`, we use heart beat to check if it is ready. + await servicesOfWorkspace.wikiHookService.waitForWebviewReceiverReady(() => { + sendDataToWebView(OnStreamChunksToWebViewEventTypes.CHECK_RECEIVER_READY); + }); /** * First sending the html content, including empty html and preload scripts and preload style sheets, this is rather small, down to 100kB (132161 chars from string length) */ sendDataToWebView(OnStreamChunksToWebViewEventTypes.TIDDLYWIKI_HTML, html); + await tiddlersStream.init(); /** * Sending tiddlers store to WebView, this might be very big, up to 20MB (239998203 chars from string length) */ - await tiddlersStream.init(); tiddlersStream.on('progress', (percentage: number) => { setStreamChunksToWebViewPercentage(percentage); }); diff --git a/src/pages/WikiWebView/useStreamChunksToWebView/streamChunksPreloadScript.ts b/src/pages/WikiWebView/useStreamChunksToWebView/streamChunksPreloadScript.ts index 4d7a8ae..d4590dc 100644 --- a/src/pages/WikiWebView/useStreamChunksToWebView/streamChunksPreloadScript.ts +++ b/src/pages/WikiWebView/useStreamChunksToWebView/streamChunksPreloadScript.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ /* eslint-disable unicorn/prefer-spread */ export enum OnStreamChunksToWebViewEventTypes { + CHECK_RECEIVER_READY = 'CHECK_RECEIVER_READY', TIDDLER_STORE_SCRIPT_CHUNK = 'TIDDLER_STORE_SCRIPT_CHUNK', TIDDLER_STORE_SCRIPT_CHUNK_END = 'TIDDLER_STORE_SCRIPT_CHUNK_END', TIDDLYWIKI_HTML = 'TIDDLYWIKI_HTML', @@ -45,6 +46,11 @@ export enum OnStreamChunksToWebViewEventTypes { startInjectTiddlerIfHTMLDone(); break; } + case OnStreamChunksToWebViewEventTypes.CHECK_RECEIVER_READY: { + // @ts-ignore + window.service?.wikiHookService?.setWebviewReceiverReady?.(); + break; + } } }; diff --git a/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts b/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts index 6784fde..2d0d934 100644 --- a/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts +++ b/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts @@ -7,6 +7,7 @@ import { OnStreamChunksToWebViewEventTypes } from './streamChunksPreloadScript'; type OnStreamChunksToWebViewEvents = { data: string; type: + | OnStreamChunksToWebViewEventTypes.CHECK_RECEIVER_READY | OnStreamChunksToWebViewEventTypes.TIDDLYWIKI_HTML | OnStreamChunksToWebViewEventTypes.TIDDLER_STORE_SCRIPT_CHUNK; } | { diff --git a/src/services/WikiHookService/descriptor.ts b/src/services/WikiHookService/descriptor.ts index d816bc4..07c9f8b 100644 --- a/src/services/WikiHookService/descriptor.ts +++ b/src/services/WikiHookService/descriptor.ts @@ -8,6 +8,7 @@ export const WikiHookServiceIPCDescriptor: ProxyDescriptor = { channel: WikiHookServiceChannel.name, properties: { triggerFullReload: ProxyPropertyType.Function, + setWebviewReceiverReady: ProxyPropertyType.Function, saveLocationInfo: ProxyPropertyType.Function, }, }; diff --git a/src/services/WikiHookService/index.ts b/src/services/WikiHookService/index.ts index d8b6e27..d0d915d 100644 --- a/src/services/WikiHookService/index.ts +++ b/src/services/WikiHookService/index.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ /* eslint-disable @typescript-eslint/require-await */ +import { backOff } from 'exponential-backoff'; import { MutableRefObject } from 'react'; import { WebView } from 'react-native-webview'; import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace'; @@ -10,9 +11,9 @@ import { IWikiWorkspace, useWorkspaceStore } from '../../store/workspace'; export class WikiHookService { #triggerFullReloadCallback: () => void = () => {}; /** value in this maybe outdated, use #wikiStore for latest data. */ - #workspace: IWikiWorkspace; + readonly #workspace: IWikiWorkspace; #webViewReference?: MutableRefObject; - #wikiStore = useWorkspaceStore; + readonly #wikiStore = useWorkspaceStore; constructor(workspace: IWikiWorkspace) { this.#workspace = workspace; @@ -54,6 +55,30 @@ export class WikiHookService { public saveLocationInfo(hash: string) { this.#wikiStore.getState().update(this.#workspace.id, { lastLocationHash: hash }); } + + #webViewReceiverReady = false; + public setWebviewReceiverReady() { + this.#webViewReceiverReady = true; + } + + public resetWebviewReceiverReady() { + this.#webViewReceiverReady = false; + } + + public async waitForWebviewReceiverReady(tryGetReady: () => void): Promise { + await backOff( + async () => { + console.log(`backoff retry waitForWebviewReceiverReady`); + if (this.#webViewReceiverReady) { + return true; + } else { + tryGetReady(); + throw new Error('Webview receiver not ready'); + } + }, + { numOfAttempts: 100, jitter: 'full' }, + ); + } } export function wrapScriptToWaitTwReady(script: string): string {