Files
onenote-tools/src/worker/onenote.worker.ts

272 lines
7.3 KiB
TypeScript

import { OneNoteExportError } from '../onenote/export/index.js';
import type {
OneNotePackageDto,
OneNotePageDto,
OneNoteSectionDto,
} from '../onenote/model/dto.js';
import type { OneNoteResource } from '../onenote/one/content-model.js';
import { createWorkerExportArtifact } from './export-adapter.js';
import {
failureFromUnknown,
openPackageForWorker,
openPackageSetForWorker,
openSectionForWorker,
resourcePayload,
workerPageKey,
workerResourceKey,
} from './parser-adapter.js';
import type {
WorkerFailure,
WorkerRequest,
WorkerResponse,
} from './worker-protocol.js';
interface ModuleWorkerScope {
onmessage: ((event: MessageEvent<WorkerRequest>) => void) | null;
postMessage(response: WorkerResponse, transfer?: Transferable[]): void;
}
interface ParserSession {
source: OneNoteSectionDto | OneNotePackageDto;
pages: Map<string, OneNotePageDto>;
resources: Map<string, OneNoteResource>;
}
const workerScope = globalThis as unknown as ModuleWorkerScope;
const sessions = new Map<string, ParserSession>();
let sessionSequence = 0;
function createSession(
source: OneNoteSectionDto | OneNotePackageDto,
pages: Map<string, OneNotePageDto>,
resources: Map<string, OneNoteResource>
): string {
const id = `session-${++sessionSequence}`;
sessions.set(id, { source, pages, resources });
return id;
}
async function handleRequest(data: WorkerRequest): Promise<void> {
switch (data.type) {
case 'parse-one': {
try {
const parsed = openSectionForWorker(
new Uint8Array(data.bytes),
data.fileName
);
const sessionId = createSession(
parsed.section,
parsed.pages,
parsed.resources
);
workerScope.postMessage({
type: 'section',
requestId: data.requestId,
sessionId,
section: parsed.section,
});
} catch (error) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: failureFromUnknown(error),
});
}
return;
}
case 'parse-onepkg':
try {
const parsed =
data.cabinetParts && data.cabinetParts.length > 0
? await openPackageSetForWorker(
[
{
fileName: data.fileName,
bytes: new Uint8Array(data.bytes),
},
...data.cabinetParts.map((part) => ({
fileName: part.fileName,
bytes: new Uint8Array(part.bytes),
})),
],
data.fileName
)
: await openPackageForWorker(
new Uint8Array(data.bytes),
data.fileName
);
const sessionId = createSession(
parsed.notebook,
parsed.pages,
parsed.resources
);
workerScope.postMessage({
type: 'package',
requestId: data.requestId,
sessionId,
notebook: parsed.notebook,
});
} catch (error) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: failureFromUnknown(error),
});
}
return;
case 'get-page': {
const session = sessions.get(data.sessionId);
if (!session) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: {
code: 'session-not-available',
message: 'The parser session is no longer available.',
recoverable: false,
},
});
return;
}
const page = session.pages.get(
workerPageKey(data.pageId, data.sectionId)
);
if (!page) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: {
code: 'page-not-available',
message: 'The selected page is not available in this source.',
recoverable: true,
},
});
return;
}
workerScope.postMessage({
type: 'page',
requestId: data.requestId,
sessionId: data.sessionId,
page,
});
return;
}
case 'get-resource': {
const session = sessions.get(data.sessionId);
if (!session) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: {
code: 'session-not-available',
message: 'The parser session is no longer available.',
recoverable: false,
},
});
return;
}
const resource = session.resources.get(
workerResourceKey(data.resourceId, data.sectionId)
);
const payload = resource
? resourcePayload(resource, data.intent)
: undefined;
if (!payload) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: {
code: 'resource-not-available',
message:
data.intent === 'preview' && resource
? 'This image can be downloaded but is not approved for automatic preview.'
: 'The selected embedded resource is not available.',
recoverable: true,
},
});
return;
}
workerScope.postMessage(
{
type: 'resource',
requestId: data.requestId,
sessionId: data.sessionId,
resource: payload,
},
[payload.bytes]
);
return;
}
case 'export-session': {
const session = sessions.get(data.sessionId);
if (!session) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: {
code: 'session-not-available',
message: 'The parser session is no longer available.',
recoverable: false,
},
});
return;
}
try {
const artifact = createWorkerExportArtifact(
session.source,
session.pages,
session.resources,
data.format
);
workerScope.postMessage(
{
type: 'export-artifact',
requestId: data.requestId,
sessionId: data.sessionId,
artifact,
},
[artifact.bytes]
);
} catch (error) {
workerScope.postMessage({
type: 'failure',
requestId: data.requestId,
error: exportFailureFromUnknown(error),
});
}
return;
}
case 'dispose':
sessions.delete(data.sessionId);
workerScope.postMessage({
type: 'disposed',
sessionId: data.sessionId,
});
}
}
function exportFailureFromUnknown(error: unknown): WorkerFailure {
if (error instanceof OneNoteExportError) {
return {
code:
error.code === 'limit-exceeded' ? 'limit-exceeded' : 'export-failed',
message: error.message,
recoverable: true,
structure: 'OneNote export',
};
}
return {
code: 'export-failed',
message:
error instanceof Error
? error.message
: 'The local notebook export could not be created.',
recoverable: true,
structure: 'OneNote export',
};
}
workerScope.onmessage = ({ data }) => {
void handleRequest(data);
};