feat(web): preload completed and load debug wasm modules on demand
This commit is contained in:
69
web/protocol/wasm-modules.ts
Normal file
69
web/protocol/wasm-modules.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user