/** * Worker environment is not part of electron environment, so don't import "@/constants/paths" here, as its process.resourcesPath will become undefined and throw Errors. * * Don't use i18n and logger in worker thread. For example, 12b93020, will throw error "Electron failed to install correctly, please delete node_modules/electron and try installing again ...worker.js..." */ import './preload'; import 'source-map-support/register'; import { uninstall } from '@/helpers/installV8Cache'; import { handleWorkerMessages } from '@services/libs/workerAdapter'; import { mkdtemp } from 'fs-extra'; import { tmpdir } from 'os'; import path from 'path'; import { Observable } from 'rxjs'; import type { IWikiWorkspace } from '@services/workspaces/interface'; import type { SyncAdaptor } from 'tiddlywiki'; import { IZxWorkerMessage, ZxWorkerControlActions } from '../interface'; import { executeScriptInTWContext, executeScriptInZxScriptContext, extractTWContextScripts, type IVariableContextList } from '../plugin/zxPlugin'; import { wikiOperationsInWikiWorker } from '../wikiOperations/executor/wikiOperationInServer'; import { getWikiInstance } from './globals'; import { extractWikiHTML, packetHTMLFromWikiFolder } from './htmlWiki'; import { ipcServerRoutesMethods } from './ipcServerRoutes'; import { notifyServicesReady } from './servicesReady'; import { startNodeJSWiki } from './startNodeJSWiki'; export interface IStartNodeJSWikiConfigs { authToken?: string; constants: { TIDDLYWIKI_PACKAGE_FOLDER: string }; enableHTTPAPI: boolean; excludedPlugins: string[]; homePath: string; https?: { enabled: boolean; tlsCert?: string | undefined; tlsKey?: string | undefined; }; isDev: boolean; openDebugger?: boolean; readOnlyMode?: boolean; rootTiddler?: string; shouldUseDarkColors: boolean; tiddlyWikiHost: string; tiddlyWikiPort: number; tokenAuth?: boolean; userName: string; workspace: IWikiWorkspace; } export type IZxFileInput = { fileContent: string; fileName: string } | { filePath: string }; function executeZxScript(file: IZxFileInput, zxPath: string): Observable { /** this will be observed in src/services/native/index.ts */ return new Observable((observer) => { observer.next({ type: 'control', actions: ZxWorkerControlActions.start }); let filePathToExecute: string; void (async function executeZxScriptIIFE() { try { if ('fileName' in file) { // codeblock mode, eval a string that might have different contexts separated by TW_SCRIPT_SEPARATOR const temporaryDirectory = await mkdtemp(`${tmpdir()}${path.sep}`); filePathToExecute = path.join(temporaryDirectory, file.fileName); const scriptsInDifferentContext = extractTWContextScripts(file.fileContent); /** * Store each script's variable context in an array, so that we can restore them later in next context. * Key is the variable name, value is the variable value. */ const variableContextList: IVariableContextList = []; for (const [index, scriptInContext] of scriptsInDifferentContext.entries()) { switch (scriptInContext.context) { case 'zx': { await executeScriptInZxScriptContext({ zxPath, filePathToExecute }, observer, scriptInContext.content, variableContextList, index); break; } case 'tw-server': { const wikiInstance = getWikiInstance(); if (wikiInstance === undefined) { observer.next({ type: 'stderr', message: `Error in executeZxScript(): $tw is undefined` }); break; } executeScriptInTWContext(scriptInContext.content, observer, wikiInstance, variableContextList, index); break; } } } } else if ('filePath' in file) { // simple mode, only execute a designated file filePathToExecute = file.filePath; await executeScriptInZxScriptContext({ zxPath, filePathToExecute }, observer); } } catch (error) { const message = `zx script's executeZxScriptIIFE() failed with error ${(error as Error).message} ${(error as Error).stack ?? ''}`; observer.next({ type: 'control', actions: ZxWorkerControlActions.error, message }); } })(); }); } async function beforeExit(): Promise { uninstall?.uninstall(); // Cleanup watch-filesystem adaptor const wikiInstance = getWikiInstance(); // Call our custom cleanup method if it exists `src/services/wiki/plugin/watchFileSystemAdaptor/watch-filesystem-adaptor.ts` const syncAdaptor = wikiInstance?.syncadaptor as SyncAdaptor & { cleanup?: () => Promise }; if (syncAdaptor?.cleanup) { await syncAdaptor.cleanup(); } } // All exposed methods should be async. const wikiWorker = { startNodeJSWiki, getTiddlerFileMetadata: async (tiddlerTitle: string) => getWikiInstance()?.boot.files[tiddlerTitle], executeZxScript, extractWikiHTML, packetHTMLFromWikiFolder, beforeExit, notifyServicesReady, wikiOperation: wikiOperationsInWikiWorker.wikiOperation.bind(wikiOperationsInWikiWorker), ...ipcServerRoutesMethods, }; export type WikiWorker = typeof wikiWorker; // Initialize worker message handling handleWorkerMessages(wikiWorker);