70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
export const WASM_MODULE_MANIFEST_SCHEMA = 1 as const;
|
|
|
|
export type WasmModuleLifecycle = "complete" | "debug";
|
|
export type WasmModuleVariant = "single" | "pthread";
|
|
|
|
export interface WasmModuleResource {
|
|
fileName: string;
|
|
url: string;
|
|
sha256: string;
|
|
}
|
|
|
|
export interface WasmModuleEntry {
|
|
id: string;
|
|
lifecycle: WasmModuleLifecycle;
|
|
preload: boolean;
|
|
resources: Record<WasmModuleVariant, WasmModuleResource>;
|
|
}
|
|
|
|
export interface WasmModuleManifest {
|
|
schemaVersion: typeof WASM_MODULE_MANIFEST_SCHEMA;
|
|
releaseId: string;
|
|
modules: WasmModuleEntry[];
|
|
}
|
|
|
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
|
|
function record(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
|
|
function resource(value: unknown, path: string): WasmModuleResource {
|
|
if (!record(value) || typeof value.fileName !== "string" || !NAME.test(value.fileName) || !value.fileName.endsWith(".wasm") ||
|
|
typeof value.url !== "string" || !value.url.startsWith("/") || /[?#\\]/.test(value.url) ||
|
|
typeof value.sha256 !== "string" || !SHA256.test(value.sha256)) {
|
|
throw new Error(`WASM_MODULE_MANIFEST_INVALID: ${path}`);
|
|
}
|
|
const segments = value.url.slice(1).split("/");
|
|
if (segments.some((segment) => !NAME.test(segment)) || segments.at(-1) !== value.fileName) {
|
|
throw new Error(`WASM_MODULE_MANIFEST_INVALID: ${path}.url`);
|
|
}
|
|
return { fileName: value.fileName, url: value.url, sha256: value.sha256 };
|
|
}
|
|
|
|
export function validateWasmModuleManifest(value: unknown): WasmModuleManifest {
|
|
if (!record(value) || value.schemaVersion !== WASM_MODULE_MANIFEST_SCHEMA || typeof value.releaseId !== "string" || !NAME.test(value.releaseId) || !Array.isArray(value.modules)) {
|
|
throw new Error("WASM_MODULE_MANIFEST_INVALID");
|
|
}
|
|
const ids = new Set<string>();
|
|
const modules = value.modules.map((entry, index): WasmModuleEntry => {
|
|
if (!record(entry) || typeof entry.id !== "string" || !NAME.test(entry.id) || ids.has(entry.id) ||
|
|
(entry.lifecycle !== "complete" && entry.lifecycle !== "debug") || typeof entry.preload !== "boolean" ||
|
|
!record(entry.resources) || !entry.resources.single || !entry.resources.pthread) {
|
|
throw new Error(`WASM_MODULE_MANIFEST_INVALID: modules[${index}]`);
|
|
}
|
|
ids.add(entry.id);
|
|
if (entry.lifecycle === "debug" && entry.preload) throw new Error(`WASM_MODULE_MANIFEST_INVALID: modules[${index}].preload`);
|
|
return {
|
|
id: entry.id,
|
|
lifecycle: entry.lifecycle,
|
|
preload: entry.preload,
|
|
resources: {
|
|
single: resource(entry.resources.single, `modules[${index}].resources.single`),
|
|
pthread: resource(entry.resources.pthread, `modules[${index}].resources.pthread`),
|
|
},
|
|
};
|
|
});
|
|
return { schemaVersion: WASM_MODULE_MANIFEST_SCHEMA, releaseId: value.releaseId, modules };
|
|
}
|