diff --git a/extension/src/service-worker/index.ts b/extension/src/service-worker/index.ts index 1d6b703..c3fc4c7 100644 --- a/extension/src/service-worker/index.ts +++ b/extension/src/service-worker/index.ts @@ -1,4 +1,7 @@ -/// Service worker entry point for the idfoto Chrome extension. +/// Background script entry point for the idfoto browser extension. +/// +/// In Chrome this runs as a service worker (MV3). In Firefox this runs +/// as a persistent background script. WASM loading adapts automatically. /// /// Loads the WASM module, manages vault state (master key, manifest, git host), /// and routes all messages from the popup and content scripts. @@ -22,14 +25,14 @@ const totpSecretCache: Map = new Map(); // --- WASM initialization --- -// Chrome MV3 service workers do NOT support dynamic import(). -// Instead we load the WASM binary via fetch() and use the synchronous -// 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. +// Chrome MV3 uses service workers which do NOT support dynamic import(). +// Firefox MV3 uses background scripts which DO support dynamic import(). +// We detect the environment at runtime and use the appropriate loading strategy. // @ts-ignore TS2307 — resolved by webpack alias / copy -import initSync, * as wasmBindings from '../../wasm/idfoto_wasm.js'; +import initDefault, { initSync } from '../../wasm/idfoto_wasm.js'; +// @ts-ignore TS2307 +import * as wasmBindings from '../../wasm/idfoto_wasm.js'; type WasmModule = typeof wasmBindings; let wasm: WasmModule | null = null; @@ -37,10 +40,21 @@ let wasm: WasmModule | null = null; async function initWasm(): Promise { if (wasm) return wasm; - // Fetch the .wasm binary and instantiate synchronously - const wasmResponse = await fetch(chrome.runtime.getURL('idfoto_wasm_bg.wasm')); - const wasmBytes = await wasmResponse.arrayBuffer(); - initSync({ module: new WebAssembly.Module(wasmBytes) }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const SWGlobalScope = (globalThis as any).ServiceWorkerGlobalScope as (new () => ServiceWorker) | undefined; + const isServiceWorker = typeof SWGlobalScope !== 'undefined' + && self instanceof (SWGlobalScope as unknown as typeof EventTarget); + + if (isServiceWorker) { + // Chrome: fetch WASM binary and instantiate synchronously + const wasmResponse = await fetch(chrome.runtime.getURL('idfoto_wasm_bg.wasm')); + const wasmBytes = await wasmResponse.arrayBuffer(); + initSync({ module: new WebAssembly.Module(wasmBytes) }); + } else { + // Firefox: background script — async init works + const wasmUrl = chrome.runtime.getURL('idfoto_wasm_bg.wasm'); + await initDefault(wasmUrl); + } vault.setWasm(wasmBindings); wasm = wasmBindings;