Skip to content

Commit

Permalink
fix: webviewSideReceiver can't load async in WikiViewer
Browse files Browse the repository at this point in the history
  • Loading branch information
linonetwo committed Jan 2, 2024
1 parent a168b76 commit f212b31
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 159 deletions.
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
48 changes: 24 additions & 24 deletions src/pages/WikiWebView/WikiViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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 (
Expand All @@ -91,13 +98,6 @@ export const WikiViewer = ({ wikiWorkspace }: WikiViewerProps) => {
</WebViewContainer>
);
}
if (preloadScript === undefined) {
return (
<WebViewContainer>
<Text>{t('Loading')}</Text>
</WebViewContainer>
);
}
return (
<WebView
style={{ backgroundColor: theme.colors.background }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface WindowMeta {
workspaceID: string;
}

export function useWindowMeta(workspace: IWikiWorkspace) {
export function getWindowMeta(workspace: IWikiWorkspace) {
return `
window.isInTidGi = true;
Expand Down
16 changes: 14 additions & 2 deletions src/pages/WikiWebView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StackScreenProps } from '@react-navigation/stack';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Text } from 'react-native-paper';
import { WebView } from 'react-native-webview';
Expand All @@ -8,6 +8,7 @@ import { RootStackParameterList } from '../../App';
import { useCloseSQLite } from '../../services/SQLiteService/hooks';
import { useWorkspaceStore } from '../../store/workspace';
import { WikiViewer } from './WikiViewer';
import { getWebviewSideReceiver } from './useStreamChunksToWebView/webviewSideReceiver';

const Container = styled.View`
height: 100%;
Expand All @@ -25,13 +26,24 @@ export const WikiWebView: React.FC<StackScreenProps<RootStackParameterList, 'Wik
const { id } = route.params;
const activeWikiWorkspace = useWorkspaceStore(state => state.workspaces.find(wiki => wiki.id === id));
useCloseSQLite(activeWikiWorkspace);
const [webviewSideReceiver, webviewSideReceiverSetter] = useState<string | undefined>(undefined);
useEffect(() => {
void getWebviewSideReceiver().then(webviewSideReceiverSetter);
}, []);
if (webviewSideReceiver === undefined) {
return (
<Container>
<Text>{t('Loading')}</Text>
</Container>
);
}

switch (activeWikiWorkspace?.type) {
case undefined:
case 'wiki': {
return (
<Container>
{(activeWikiWorkspace !== undefined) && <WikiViewer wikiWorkspace={activeWikiWorkspace} />}
{(activeWikiWorkspace !== undefined) && <WikiViewer wikiWorkspace={activeWikiWorkspace} webviewSideReceiver={webviewSideReceiver} />}
</Container>
);
}
Expand Down
11 changes: 3 additions & 8 deletions src/pages/WikiWebView/useStreamChunksToWebView/index.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -72,10 +72,5 @@ export function useStreamChunksToWebView(webViewReference: MutableRefObject<WebV
}
}, [webViewReference, sendDataToWebView, sendChunkedDataToWebView]);

// const [webviewSideReceiver, webviewSideReceiverSetter] = useState<string | undefined>(undefined);
// useEffect(() => {
// void getWebviewSideReceiver().then(webviewSideReceiverSetter);
// }, []);

return { injectHtmlAndTiddlersStore, webviewSideReceiver } as const;
return injectHtmlAndTiddlersStore;
}
125 changes: 2 additions & 123 deletions src/pages/WikiWebView/useStreamChunksToWebView/webviewSideReceiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div id="styleArea"> 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();
}
})();
`;

0 comments on commit f212b31

Please sign in to comment.