138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
import type {
|
|
PageWorkerResponse,
|
|
ParseWorkerRequest,
|
|
ParseWorkerResponse,
|
|
WorkerRequest,
|
|
WorkerFailure,
|
|
WorkerResponse,
|
|
} from './worker-protocol.js';
|
|
|
|
export type SourceKind = 'one' | 'onepkg';
|
|
|
|
export interface WorkerPort {
|
|
onmessage: ((event: MessageEvent<WorkerResponse>) => 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<string, PendingRequest>();
|
|
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<ParseWorkerResponse> {
|
|
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<PageWorkerResponse> {
|
|
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();
|
|
}
|
|
}
|