TidGi-Desktop/src/services/wiki/wikiOperations.ts
lin onetwo cd836cce9a fix: sendToMainWindowAndAwait error when app on background
fixes #398 #396

browserView.webContents is undefined
2023-05-28 00:20:55 +08:00

87 lines
4.5 KiB
TypeScript

/**
* Can't use logger in this file:
* ERROR in Circular dependency detected: src/services/libs/log/index.ts -> src/services/libs/log/rendererTransport.ts -> src/services/wiki/wikiOperations.ts -> src/services/libs/log/index.ts
*/
import { ipcMain } from 'electron';
import { WikiChannel } from '@/constants/channels';
import { WikiStateKey } from '@/constants/wiki';
import { container } from '@services/container';
import serviceIdentifier from '@services/serviceIdentifier';
import { IViewService } from '@services/view/interface';
import { IWindowService } from '@services/windows/interface';
import { WindowNames } from '@services/windows/WindowProperties';
/**
* Send to main window renderer (preload script) and not wait for response (fire and forget)
*/
function sendToMainWindowNoWait(type: WikiChannel, workspaceID: string, messages: string[]): void {
const viewService = container.get<IViewService>(serviceIdentifier.View);
const browserView = viewService.getView(workspaceID, WindowNames.main);
browserView?.webContents?.send?.(type, ...messages);
}
/**
* Send to main window renderer (preload script) and wait for response.
*
* Will throw error when on Windows and App is at background (BrowserView will disappear and not accessible.) https://github.com/tiddly-gittly/TidGi-Desktop/issues/398
*
* @param type The handler on renderer (preload script) side should implement `ipcRenderer.send(WikiChannel.xxx, nonceReceived, result);`, where `result` is usually `string[]` (the default type for `<T>` in function signature)
* @returns undefined if main window webContents is not found
*/
async function sendToMainWindowAndAwait<T = string[]>(type: WikiChannel, workspaceID: string, messages: string[], options?: { timeout?: number }): Promise<T | undefined> {
const viewService = container.get<IViewService>(serviceIdentifier.View);
const browserView = viewService.getView(workspaceID, WindowNames.main);
if ((browserView?.webContents) === undefined) {
throw new Error(`browserView.webContents is undefined in sendToMainWindowAndAwait ${workspaceID} when running ${type}`);
}
return await new Promise<T>((resolve, reject) => {
const nonce = Math.random();
browserView?.webContents?.send?.(type, nonce, ...messages);
let timeoutHandle: NodeJS.Timeout;
if (options?.timeout !== undefined) {
timeoutHandle = setTimeout(() => {
reject(new Error(`${type} for ${workspaceID} in sendToMainWindowAndAwait Timeout after ${String(options.timeout)}ms`));
}, options.timeout);
}
/**
* Use nonce to prevent data racing
*/
const listener = (_event: Electron.IpcMainEvent, nonceReceived: number, value: T): void => {
if (nonce === nonceReceived) {
clearTimeout(timeoutHandle);
ipcMain.removeListener(type, listener);
resolve(value);
}
};
ipcMain.on(type, listener);
});
}
/**
* Handle sending message to trigger operations defined in `src/preload/wikiOperation.ts`
*/
export const wikiOperations = {
[WikiChannel.createProgress]: (workspaceID: string, message: string): void => {
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
const createWorkspaceWindow = windowService.get(WindowNames.addWorkspace);
createWorkspaceWindow?.webContents?.send(WikiChannel.createProgress, message);
},
[WikiChannel.syncProgress]: (workspaceID: string, message: string): void => {
sendToMainWindowNoWait(WikiChannel.syncProgress, workspaceID, [message]);
},
[WikiChannel.generalNotification]: (workspaceID: string, message: string): void => {
sendToMainWindowNoWait(WikiChannel.generalNotification, workspaceID, [message]);
},
[WikiChannel.openTiddler]: (workspaceID: string, tiddlerName: string): void => {
sendToMainWindowNoWait(WikiChannel.openTiddler, workspaceID, [tiddlerName]);
},
[WikiChannel.setState]: (workspaceID: string, stateKey: WikiStateKey, content: string): void => {
sendToMainWindowNoWait(WikiChannel.setState, workspaceID, [stateKey, content]);
},
[WikiChannel.runFilter]: async <T extends string[]>(workspaceID: string, filterString: string): Promise<T | undefined> => {
return await sendToMainWindowAndAwait<T>(WikiChannel.runFilter, workspaceID, [filterString]);
},
[WikiChannel.setTiddlerText]: async (workspaceID: string, title: string, value: string, options?: { timeout?: number }): Promise<void> => {
await sendToMainWindowAndAwait(WikiChannel.setTiddlerText, workspaceID, [title, value], options);
},
};
export type IWikiOperations = typeof wikiOperations;