export interface WasmResource { id: string; fileName: string; url: string; sha256: string; required: boolean; } export interface WebEngineManifest { schemaVersion: number; protocolVersion: number; engineVersion: string; engine: "mock" | "blender-wasm"; memory: { initialPages: number; maximumPages: number; shared: boolean; }; wasm: WasmResource[]; } export async function loadWebEngineManifest(url = "/engine-manifest.json"): Promise { const response = await fetch(url, { cache: "no-store" }); if (!response.ok) throw new Error(`Engine manifest request failed: ${response.status}`); const manifest = await response.json() as Partial; if (manifest.schemaVersion !== 1 || manifest.protocolVersion !== 1) { throw new Error("Unsupported engine manifest version"); } if (!manifest.engine || !manifest.memory || !Array.isArray(manifest.wasm)) { throw new Error("Invalid engine manifest shape"); } return manifest as WebEngineManifest; } export async function verifyWasmResource(resource: WasmResource): Promise { const response = await fetch(resource.url, { cache: "no-store" }); if (!response.ok) throw new Error(`WASM resource request failed: ${resource.id}`); const bytes = await response.arrayBuffer(); const digest = await crypto.subtle.digest("SHA-256", bytes); const actual = [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); if (actual !== resource.sha256.toLowerCase()) { throw new Error(`WASM resource hash mismatch: ${resource.id}`); } }