From f212b31a8305547d32e58989b5656ab5227e7fae Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Wed, 3 Jan 2024 04:02:45 +0800 Subject: [PATCH] fix: webviewSideReceiver can't load async in WikiViewer --- .../fix-location-info.ts | 2 +- src/pages/WikiWebView/WikiViewer.tsx | 48 +++---- .../{useWindowMeta.ts => getWindowMeta.ts} | 2 +- src/pages/WikiWebView/index.tsx | 16 ++- .../useStreamChunksToWebView/index.ts | 11 +- .../webviewSideReceiver.ts | 125 +----------------- 6 files changed, 45 insertions(+), 159 deletions(-) rename src/pages/WikiWebView/{useWindowMeta.ts => getWindowMeta.ts} (81%) diff --git a/plugins/src/expo-file-system-syncadaptor/fix-location-info.ts b/plugins/src/expo-file-system-syncadaptor/fix-location-info.ts index b441655..4a948ab 100644 --- a/plugins/src/expo-file-system-syncadaptor/fix-location-info.ts +++ b/plugins/src/expo-file-system-syncadaptor/fix-location-info.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ -import type { WindowMeta } from '../../../src/pages/WikiWebView/useWindowMeta'; +import type { WindowMeta } from '../../../src/pages/WikiWebView/getWindowMeta'; declare global { interface Window { diff --git a/src/pages/WikiWebView/WikiViewer.tsx b/src/pages/WikiWebView/WikiViewer.tsx index 945c552..6adf3df 100644 --- a/src/pages/WikiWebView/WikiViewer.tsx +++ b/src/pages/WikiWebView/WikiViewer.tsx @@ -12,10 +12,10 @@ import { useRegisterService } from '../../services/registerServiceOnWebView'; import { useSetWebViewReferenceToService } from '../../services/WikiHookService/hooks'; import { useConfigStore } from '../../store/config'; import { IWikiWorkspace } from '../../store/workspace'; +import { getWindowMeta } from './getWindowMeta'; import { useStreamChunksToWebView } from './useStreamChunksToWebView'; import { onErrorHandler } from './useStreamChunksToWebView/onErrorHandler'; import { useTiddlyWiki } from './useTiddlyWiki'; -import { useWindowMeta } from './useWindowMeta'; const WebViewContainer = styled.View` flex: 2; @@ -30,10 +30,15 @@ const ErrorText = styled(Text)` `; export interface WikiViewerProps { + /** + * Preload script for `injectHtmlAndTiddlersStore`. + * This can't load async in this component. This component need to load at once. + */ + webviewSideReceiver: string; wikiWorkspace: IWikiWorkspace; } -export const WikiViewer = ({ wikiWorkspace }: WikiViewerProps) => { +export const WikiViewer = ({ wikiWorkspace, webviewSideReceiver }: WikiViewerProps) => { const { t } = useTranslation(); const theme = useTheme(); // TODO: prevent swipe back work, then enable "use notification go back", maybe make this a config option. And let swipe go back become navigate back in the webview @@ -58,31 +63,33 @@ export const WikiViewer = ({ wikiWorkspace }: WikiViewerProps) => { servicesOfWorkspace.wikiHookService.setLatestTriggerFullReloadCallback(triggerFullReload); /** * 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, webviewSideReceiver } = useStreamChunksToWebView(webViewReference); + const injectHtmlAndTiddlersStore = useStreamChunksToWebView(webViewReference); useEffect(() => { void backgroundSyncService.updateServerOnlineStatus(); }, [webViewKeyToReloadAfterRecycleByOS]); const { loadHtmlError } = useTiddlyWiki(wikiWorkspace, injectHtmlAndTiddlersStore, loaded && webViewReference.current !== null, webViewKeyToReloadAfterRecycleByOS); - const windowMetaScript = useWindowMeta(wikiWorkspace); - const preloadScript = useMemo(() => - webviewSideReceiver === undefined ? undefined : ` - var lastLocationHash = \`${rememberLastVisitState ? wikiWorkspace.lastLocationHash ?? '' : ''}\`; - location.hash = lastLocationHash; + const preloadScript = useMemo(() => { + const windowMetaScript = getWindowMeta(wikiWorkspace); + return ` + var lastLocationHash = \`${rememberLastVisitState ? wikiWorkspace.lastLocationHash ?? '' : ''}\`; + location.hash = lastLocationHash; - ${windowMetaScript} + ${windowMetaScript} - ${onErrorHandler} - - ${webviewPreloadedJS} + ${onErrorHandler} + + ${webviewPreloadedJS} - ${registerWikiStorageServiceOnWebView} + ${registerWikiStorageServiceOnWebView} - ${webviewSideReceiver} - - true; // note: this is required, or you'll sometimes get silent failures - `, [registerWikiStorageServiceOnWebView, rememberLastVisitState, webviewSideReceiver, wikiWorkspace.lastLocationHash, windowMetaScript]); + ${webviewSideReceiver} + + true; // note: this is required, or you'll sometimes get silent failures + `; + }, [registerWikiStorageServiceOnWebView, rememberLastVisitState, webviewSideReceiver, wikiWorkspace]); if (loadHtmlError) { return ( @@ -91,13 +98,6 @@ export const WikiViewer = ({ wikiWorkspace }: WikiViewerProps) => { ); } - if (preloadScript === undefined) { - return ( - - {t('Loading')} - - ); - } return ( state.workspaces.find(wiki => wiki.id === id)); useCloseSQLite(activeWikiWorkspace); + const [webviewSideReceiver, webviewSideReceiverSetter] = useState(undefined); + useEffect(() => { + void getWebviewSideReceiver().then(webviewSideReceiverSetter); + }, []); + if (webviewSideReceiver === undefined) { + return ( + + {t('Loading')} + + ); + } switch (activeWikiWorkspace?.type) { case undefined: case 'wiki': { return ( - {(activeWikiWorkspace !== undefined) && } + {(activeWikiWorkspace !== undefined) && } ); } diff --git a/src/pages/WikiWebView/useStreamChunksToWebView/index.ts b/src/pages/WikiWebView/useStreamChunksToWebView/index.ts index b639a33..50e5aa9 100644 --- a/src/pages/WikiWebView/useStreamChunksToWebView/index.ts +++ b/src/pages/WikiWebView/useStreamChunksToWebView/index.ts @@ -1,7 +1,7 @@ -import { MutableRefObject, useCallback, useEffect, useState } from 'react'; +import { MutableRefObject, useCallback } from 'react'; import { WebView } from 'react-native-webview'; import { IHtmlContent } from '../useTiddlyWiki'; -import { getWebviewSideReceiver, OnStreamChunksToWebViewEventTypes, webviewSideReceiver } from './webviewSideReceiver'; +import { OnStreamChunksToWebViewEventTypes } from './webviewSideReceiver'; const CHUNK_SIZE = 1_000_000; @@ -72,10 +72,5 @@ export function useStreamChunksToWebView(webViewReference: MutableRefObject(undefined); - // useEffect(() => { - // void getWebviewSideReceiver().then(webviewSideReceiverSetter); - // }, []); - - return { injectHtmlAndTiddlersStore, webviewSideReceiver } as const; + return injectHtmlAndTiddlersStore; } diff --git a/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts b/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts index 2760033..02dc5a8 100644 --- a/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts +++ b/src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts @@ -31,127 +31,6 @@ export const getWebviewSideReceiver = async () => { if (!streamChunksPreloadScriptFileUri) { throw new Error(`streamChunksPreloadScript failed to load, ID: ${streamChunksPreloadScriptAssetID}`); } - return await fs.readAsStringAsync(streamChunksPreloadScriptFileUri); + const webviewSideReceiver = await fs.readAsStringAsync(streamChunksPreloadScriptFileUri); + return webviewSideReceiver; }; -export const webviewSideReceiver = `// Initialize an empty string to start with -(function useStreamChunksToWebViewWebviewSideReceiverIIFE() { - let tiddlersStoreAccumulatedContent = ''; - let skinnyTiddlersStoreAccumulatedContent = ''; - let wikiHTML = ''; - let scriptCompleteCount = 0; - function resetUseStreamChunksToWebViewWebviewSideReceiverIIFE() { - tiddlersStoreAccumulatedContent = ''; - skinnyTiddlersStoreAccumulatedContent = ''; - wikiHTML = ''; - scriptCompleteCount = 0; - } - - window.onStreamChunksToWebView = function (event) { - const data = event.data; - - switch (event.type) { - case 'TIDDLYWIKI_HTML': { - wikiHTML += data; - break; - } - case 'TIDDLER_STORE_SCRIPT_CHUNK': { - tiddlersStoreAccumulatedContent += data; - break; - } - case 'TIDDLER_SKINNY_STORE_SCRIPT_CHUNK': { - skinnyTiddlersStoreAccumulatedContent += data; - break; - } - case 'TIDDLER_SKINNY_STORE_SCRIPT_CHUNK_END': - case 'TIDDLER_STORE_SCRIPT_CHUNK_END': { - scriptCompleteCount += 1; - if (scriptCompleteCount === 2) { - // start jobs - startInjectHTML(); - } - break; - } - } - }; - - function startInjectHTML() { - console.log('startInjectHTML'); - /** - * All information needed are collected. - * Start using html and store. - */ - - /** - * Use MutationObserver to watch if wikiHTML is loaded. - * We execute the script tags after this. - */ - const observer = new MutationObserver((mutationsList, observer) => { - let hasChange = false; - for (let mutation of mutationsList) { - if (mutation.type === 'childList') { - hasChange = true; - } - } - if (hasChange) { - observer.disconnect(); // Important: disconnect the observer once done. - // use timeout to give splash screen a chance to execute and show - setTimeout(executeScriptsAfterInjectHTML, 100); - } - }); - - // Start observing the body with the configured parameters - observer.observe(document.body, { childList: true }); - - // this ignores all script tags, so we need 'executeScriptsAfterInjectHTML()' later. - document.body.innerHTML = wikiHTML; - } - - function appendStoreScript(storeJSON, name) { - const tiddlersStoreScript = document.createElement('script'); - tiddlersStoreScript.type = 'application/json'; - tiddlersStoreScript.classList.add('tiddlywiki-tiddler-store', name); - tiddlersStoreScript.textContent = storeJSON; - const styleAreaDiv = document.getElementById('styleArea'); - styleAreaDiv?.insertAdjacentElement('afterend', tiddlersStoreScript); - } - - /** - * Manually execute each of the script tags. - * Delay the script execution slightly, until MutationObserver found document.body is ready. - */ - function executeScriptsAfterInjectHTML() { - console.log('executeScriptsAfterInjectHTML'); - try { - // load tiddlers store, place it after
where it used to belong to. - appendStoreScript(skinnyTiddlersStoreAccumulatedContent, 'skinnyTiddlers'); - appendStoreScript(tiddlersStoreAccumulatedContent, 'pluginsAndJS'); - - // load other scripts - const scriptElements = document.querySelectorAll('script'); - for (let script of scriptElements) { - // skip tiddlersStoreScript we just added - if (script.classList.contains('tiddlywiki-tiddler-store')) continue; - // activate other scripts in the HTML - const newScript = document.createElement('script'); - // copy all attributes from the original script to the new one - for (const { name, value } of script.attributes) { - newScript.setAttribute(name, value); - } - if (script.src) { - // if the original script has a 'src' url, load it - newScript.src = script.src; - } else { - // if the script has inline content, set it - newScript.textContent = script.textContent; - } - // replace the old script element with the new one - script.parentNode?.replaceChild(newScript, script); - } - } catch (e) { - console.error('executeScriptsAfterInjectHTML error', e); - } - resetUseStreamChunksToWebViewWebviewSideReceiverIIFE(); - } -})(); - -`;