Files
relicario/extension/src/service-worker/index.ts
2026-04-27 15:53:53 -04:00

101 lines
3.4 KiB
TypeScript

/// Thin service-worker entry: loads WASM, constructs the router state, and
/// forwards every message into router/index.route().
import type { Request, Response, SessionTimeoutConfig } from '../shared/messages';
import { CONTENT_CALLABLE_TYPES } from '../shared/messages';
import type { RouterState } from './router/index';
import { route } from './router/index';
import * as vault from './vault';
import { clearCurrent } from './session';
import * as sessionTimer from './session-timer';
// @ts-ignore TS2307 — resolved by webpack alias / copy
import initDefault, { initSync } from '../../wasm/relicario_wasm.js';
// @ts-ignore TS2307
import * as wasmBindings from '../../wasm/relicario_wasm.js';
type WasmModule = typeof wasmBindings;
let wasm: WasmModule | null = null;
async function initWasm(): Promise<WasmModule> {
if (wasm) return wasm;
// 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) {
const wasmResponse = await fetch(chrome.runtime.getURL('relicario_wasm_bg.wasm'));
const wasmBytes = await wasmResponse.arrayBuffer();
initSync({ module: new WebAssembly.Module(wasmBytes) });
} else {
const wasmUrl = chrome.runtime.getURL('relicario_wasm_bg.wasm');
await initDefault(wasmUrl);
}
vault.setWasm(wasmBindings);
wasm = wasmBindings;
return wasm;
}
// Single router-state object shared by all messages for this SW instance.
const state: RouterState = {
manifest: null,
gitHost: null,
wasm: null,
};
// --- Session timer wiring ---
sessionTimer.onExpired(() => {
// eslint-disable-next-line no-console
console.log('[relicario sw] session expired — locking vault');
clearCurrent();
state.manifest = null;
// Best-effort broadcast — receiver may not exist yet.
chrome.runtime.sendMessage({ type: 'session_expired' }).catch(() => {});
});
// Restore saved session config from chrome.storage.local on SW startup.
chrome.storage.local.get('session_timeout').then((r) => {
if (r.session_timeout) {
sessionTimer.setConfig(r.session_timeout as SessionTimeoutConfig);
}
}).catch(() => {});
chrome.commands.onCommand.addListener((command) => {
if (command === 'open-vault') {
chrome.tabs.create({ url: chrome.runtime.getURL('vault.html') });
}
});
chrome.runtime.onMessage.addListener(
(request: Request, sender: chrome.runtime.MessageSender, sendResponse: (r: Response) => void) => {
(async () => {
if (!CONTENT_CALLABLE_TYPES.has(request.type as never)) {
sessionTimer.resetTimer();
}
if (!state.wasm) {
// eslint-disable-next-line no-console
console.log('[relicario sw] initializing WASM on first message');
state.wasm = await initWasm();
}
return route(request, state, sender);
})()
.then((r) => {
if (!r.ok) {
// eslint-disable-next-line no-console
console.warn(`[relicario sw] ${request.type} -> error:`, r.error);
}
sendResponse(r);
})
.catch((err: Error) => {
// eslint-disable-next-line no-console
console.error(`[relicario sw] ${request.type} threw:`, err);
sendResponse({ ok: false, error: err.message });
});
return true; // async response
},
);