feat: add local OneNote and ONEPKG reader
This commit is contained in:
137
src/worker/onenote.client.ts
Normal file
137
src/worker/onenote.client.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user