/** * passkeys-firefox-relay.ts — extension-world relay for the MAIN-world * passkey proxy (passkeys-firefox.ts). * * The MAIN-world script cannot call chrome.runtime.sendMessage — it lives in * the page's JS context, not the extension's. This relay listens for the * MAIN-world's PEACH_PASSKEY_REQUEST window messages, forwards each payload * to the background, and posts the result back as PEACH_PASSKEY_RESPONSE * with the same request id. * * Trust boundary: anything the page can synthesize on `window.message` lands * here. The `type` and `id` fields gate dispatch; the `payload` is forwarded * verbatim to the background, which validates required fields and runs the * Pro/vault checks before performing any cryptographic work. Vault data is * never sent the other direction over postMessage — only the credential * shape the RP needs. * * Removal: deleted alongside the MAIN-world proxy when Firefox ships * chrome.webAuthenticationProxy. See src/shared/passkey-flags.ts. */ window.addEventListener("message", async (event: MessageEvent) => { if (event.source !== window) return; const data = event.data as | { type?: string; id?: string; payload?: unknown } | undefined; if (!data || data.type !== "PEACH_PASSKEY_REQUEST") return; const { id, payload } = data; if (typeof id !== "string" || !payload || typeof payload !== "object") return; const reply = (replyPayload: unknown) => { window.postMessage( { type: "PEACH_PASSKEY_RESPONSE", id, payload: replyPayload }, window.location.origin ); }; try { const response = await chrome.runtime.sendMessage(payload); reply(response); } catch { // The background may be momentarily unreachable (service worker spin-up, // extension reload). Tell the proxy to fall through to the platform // rather than throwing into the page. reply({ ok: false, error: "extension_error" }); } });