// Kestrove vault worker: owns the wasm VaultCore so Argon2id (createAccount, unlock, login, // encrypted export/import) never blocks the page. Started by `connectVault()` in client.js. // // Protocol (structured clone): {id, method, args} -> {id, ok: value} | {id, err: {name, message, code, ...}}. // The first message must be {id, method: "open", args: [dbName | null, inMemory]}. import init, * as kv from "./kv_wasm.js"; let core = null; let releaseLock = null; function serializeError(e) { if (e && typeof e === "object") { return { name: e.name || "Error", message: e.message || String(e), code: e.code ?? "Internal", status: e.status, vaultId: e.vaultId, reason: e.reason, }; } return { name: "CoreError", message: String(e), code: "Internal" }; } // One tab per vault: two in-memory replicas over the same IndexedDB database would overwrite // each other's writes. The lock is held for the worker's lifetime. async function acquireLock(dbName) { if (!self.navigator?.locks) return true; // very old browsers: best effort return new Promise((resolve) => { self.navigator.locks.request(`kestrove-vault:${dbName}`, { ifAvailable: true }, (lock) => { if (!lock) { resolve(false); return undefined; } resolve(true); return new Promise((release) => (releaseLock = release)); }); }); } async function open(dbName, inMemory) { await init({ module_or_path: new URL("./kv_wasm_bg.wasm", import.meta.url) }); if (inMemory) { core = await kv.VaultCore.newInMemory(); return null; } const name = dbName || "kestrove"; if (!(await acquireLock(name))) { const e = new Error("the vault is open in another tab"); e.name = "CoreError"; e.code = "InUse"; throw e; } core = await kv.VaultCore.open(name); return null; } const FREE_FUNCTIONS = new Set(["generatePassword", "totpCode", "matchUrl", "coreVersion"]); self.onmessage = async ({ data: { id, method, args } }) => { try { let value; if (method === "open") { value = await open(...args); } else if (method === "close") { core?.free(); core = null; releaseLock?.(); value = null; } else if (FREE_FUNCTIONS.has(method)) { value = kv[method](...args); } else if (!core) { throw Object.assign(new Error("vault is not open"), { name: "CoreError", code: "NotConfigured" }); } else if (typeof core[method] !== "function" || method.startsWith("_") || method === "free") { throw Object.assign(new Error(`unknown method ${method}`), { name: "CoreError", code: "Invalid" }); } else { value = await core[method](...args); } self.postMessage({ id, ok: value }); } catch (e) { self.postMessage({ id, err: serializeError(e) }); } };