Skip to content

Commit

Permalink
feat: use heart beat to check if it is ready, especially on buggy hua…
Browse files Browse the repository at this point in the history
…wei os
  • Loading branch information
linonetwo committed Mar 12, 2024
1 parent 91b4bd2 commit 0356540
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 12 deletions.
7 changes: 7 additions & 0 deletions assets/preload/streamChunksPreloadScript.js.html
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -23,6 +24,7 @@
}
// @ts-ignore
window.onStreamChunksToWebView = function (event) {
var _a, _b, _c;
switch (event.type) {
case OnStreamChunksToWebViewEventTypes.TIDDLYWIKI_HTML: {
resetUseStreamChunksToWebViewWebviewSideReceiverIIFE();
Expand All @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions scripts/buildPreload.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion src/pages/WikiWebView/WikiViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
17 changes: 14 additions & 3 deletions src/pages/WikiWebView/useStreamChunksToWebView/index.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<WebView | null>) {
export function useStreamChunksToWebView(webViewReference: MutableRefObject<WebView | null>, 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,
Expand All @@ -38,14 +44,19 @@ export function useStreamChunksToWebView(webViewReference: MutableRefObject<WebV
// start using `window.onStreamChunksToWebView` only when webviewLoaded, which means preload script is loaded.
if (webViewReference.current !== null) {
try {
// Huawei HONOR device need to wait for a while before sending large data, otherwise first message (send HTML) will lost, cause white screen (no HTML loaded). Maybe its webview has bug.
// Instead of `if (brand === 'HONOR') await new Promise<void>(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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -45,6 +46,11 @@ export enum OnStreamChunksToWebViewEventTypes {
startInjectTiddlerIfHTMLDone();
break;
}
case OnStreamChunksToWebViewEventTypes.CHECK_RECEIVER_READY: {
// @ts-ignore
window.service?.wikiHookService?.setWebviewReceiverReady?.();
break;
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
} | {
Expand Down
1 change: 1 addition & 0 deletions src/services/WikiHookService/descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const WikiHookServiceIPCDescriptor: ProxyDescriptor = {
channel: WikiHookServiceChannel.name,
properties: {
triggerFullReload: ProxyPropertyType.Function,
setWebviewReceiverReady: ProxyPropertyType.Function,
saveLocationInfo: ProxyPropertyType.Function,
},
};
29 changes: 27 additions & 2 deletions src/services/WikiHookService/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<WebView | null>;
#wikiStore = useWorkspaceStore;
readonly #wikiStore = useWorkspaceStore;

constructor(workspace: IWikiWorkspace) {
this.#workspace = workspace;
Expand Down Expand Up @@ -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<void> {
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 {
Expand Down

0 comments on commit 0356540

Please sign in to comment.