fix: use static import + initSync for WASM in service worker

Chrome MV3 service workers do not support dynamic import().
Switch to static import of the wasm-pack JS glue and use
initSync() with fetch() to load the WASM binary at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
adlee-was-taken
2026-04-12 11:37:44 -04:00
parent 8236a18433
commit 336e90fc84
2 changed files with 23 additions and 18 deletions

View File

@@ -21,27 +21,30 @@ const totpSecretCache: Map<string, string> = new Map();
// --- WASM initialization --- // --- WASM initialization ---
// We use a dynamic import so webpack treats idfoto_wasm.js as a separate chunk. // Chrome MV3 service workers do NOT support dynamic import().
// The WASM file (idfoto_wasm_bg.wasm) is loaded by the JS glue code. // Instead we load the WASM binary via fetch() and use the synchronous
type WasmModule = typeof import('idfoto-wasm'); // initSync() function exported by wasm-pack's --target web output.
// The JS glue is imported statically so webpack bundles it into
// the service worker script.
// @ts-ignore TS2307 — resolved by webpack alias / copy
import initSync, * as wasmBindings from '../../wasm/idfoto_wasm.js';
type WasmModule = typeof wasmBindings;
let wasm: WasmModule | null = null; let wasm: WasmModule | null = null;
async function initWasm(): Promise<WasmModule> { async function initWasm(): Promise<WasmModule> {
if (wasm) return wasm; if (wasm) return wasm;
// wasm-pack --target web produces an ES module with an `default` init function // Fetch the .wasm binary and instantiate synchronously
// that loads the .wasm file. In a Chrome MV3 service worker we import the JS const wasmResponse = await fetch(chrome.runtime.getURL('idfoto_wasm_bg.wasm'));
// glue and call init() with the wasm URL. const wasmBytes = await wasmResponse.arrayBuffer();
const mod = await import( initSync({ module: new WebAssembly.Module(wasmBytes) });
// @ts-ignore TS2307 — resolved at runtime by the service worker, not by TS/webpack
/* webpackIgnore: true */ './idfoto_wasm.js'
) as WasmModule & { default: (input?: string | URL) => Promise<void> };
await mod.default('./idfoto_wasm_bg.wasm'); vault.setWasm(wasmBindings);
vault.setWasm(mod); wasm = wasmBindings;
wasm = mod;
wasmReady = true; wasmReady = true;
return mod; return wasm;
} }
// --- Storage helpers --- // --- Storage helpers ---

View File

@@ -8,14 +8,16 @@ import type { GitHost } from './git-host';
import type { Entry, Manifest, ManifestEntry } from '../shared/types'; import type { Entry, Manifest, ManifestEntry } from '../shared/types';
// WASM module reference — set once during init. // WASM module reference — set once during init.
let wasm: typeof import('idfoto-wasm') | null = null; // eslint-disable-next-line @typescript-eslint/no-explicit-any
let wasm: any = null;
/// Store the WASM module reference after dynamic import. /// Store the WASM module reference after initialization.
export function setWasm(w: typeof import('idfoto-wasm')): void { // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function setWasm(w: any): void {
wasm = w; wasm = w;
} }
function requireWasm(): NonNullable<typeof wasm> { function requireWasm(): any {
if (!wasm) throw new Error('WASM module not initialized'); if (!wasm) throw new Error('WASM module not initialized');
return wasm; return wasm;
} }