Files
workinf_Blender_Wasm/web/protocol/wasm-modules.ts
mes123456 a72b984f4c
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): preload completed and load debug wasm modules on demand
2026-09-04 10:56:34 +08:00

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 };
}