import type { PageWorkerResponse, ParseWorkerRequest, ParseWorkerResponse, WorkerRequest, WorkerFailure, WorkerResponse, } from './worker-protocol.js'; export type SourceKind = 'one' | 'onepkg'; export interface WorkerPort { onmessage: ((event: MessageEvent) => void) | null; onerror: ((event: ErrorEvent) => void) | null; postMessage(message: WorkerRequest, transfer?: Transferable[]): void; terminate(): void; } type PendingRequest = { resolve: (response: ParseWorkerResponse | PageWorkerResponse) => void; reject: (error: WorkerClientError) => void; }; export class WorkerClientError extends Error { constructor( readonly code: WorkerFailure['code'], message: string ) { super(message); this.name = 'WorkerClientError'; } } function createModuleWorker(): WorkerPort { return new Worker(new URL('./onenote.worker.ts', import.meta.url), { type: 'module', }) as WorkerPort; } export class OneNoteWorkerClient { private readonly worker: WorkerPort; private readonly pending = new Map(); private requestSequence = 0; private terminated = false; constructor(workerFactory: () => WorkerPort = createModuleWorker) { this.worker = workerFactory(); this.worker.onmessage = ({ data }) => { if (!('requestId' in data)) return; const pending = this.pending.get(data.requestId); if (!pending) return; this.pending.delete(data.requestId); pending.resolve(data); }; this.worker.onerror = () => { const error = new WorkerClientError( 'worker-failure', 'The local parser worker stopped unexpectedly.' ); for (const request of this.pending.values()) request.reject(error); this.pending.clear(); }; } parseSource( kind: SourceKind, fileName: string, bytes: ArrayBuffer ): Promise { if (this.terminated) { return Promise.reject( new WorkerClientError( 'worker-failure', 'The local parser worker has already been terminated.' ) ); } const requestId = `parse-${++this.requestSequence}`; const request: ParseWorkerRequest = { type: kind === 'one' ? 'parse-one' : 'parse-onepkg', requestId, fileName, bytes, }; return new Promise((resolve, reject) => { this.pending.set(requestId, { resolve: (response) => resolve(response as ParseWorkerResponse), reject, }); this.worker.postMessage(request, [bytes]); }); } getPage( sessionId: string, pageId: string, sectionId?: string ): Promise { if (this.terminated) { return Promise.reject( new WorkerClientError( 'worker-failure', 'The local parser worker has already been terminated.' ) ); } const requestId = `page-${++this.requestSequence}`; return new Promise((resolve, reject) => { this.pending.set(requestId, { resolve: (response) => resolve(response as PageWorkerResponse), reject, }); this.worker.postMessage({ type: 'get-page', requestId, sessionId, pageId, ...(sectionId === undefined ? {} : { sectionId }), }); }); } terminate(): void { if (this.terminated) return; this.terminated = true; this.worker.terminate(); const error = new WorkerClientError( 'worker-failure', 'The local parser request was cancelled.' ); for (const request of this.pending.values()) request.reject(error); this.pending.clear(); } }