passkeys-firefox-relay.ts
1/**2 * passkeys-firefox-relay.ts — extension-world relay for the MAIN-world3 * passkey proxy (passkeys-firefox.ts).4 *5 * The MAIN-world script cannot call chrome.runtime.sendMessage — it lives in6 * the page's JS context, not the extension's. This relay listens for the7 * MAIN-world's PEACH_PASSKEY_REQUEST window messages, forwards each payload8 * to the background, and posts the result back as PEACH_PASSKEY_RESPONSE9 * with the same request id.10 *11 * Trust boundary: anything the page can synthesize on `window.message` lands12 * here. The `type` and `id` fields gate dispatch; the `payload` is forwarded13 * verbatim to the background, which validates required fields and runs the14 * Pro/vault checks before performing any cryptographic work. Vault data is15 * never sent the other direction over postMessage — only the credential16 * shape the RP needs.17 *18 * Removal: deleted alongside the MAIN-world proxy when Firefox ships19 * chrome.webAuthenticationProxy. See src/shared/passkey-flags.ts.20 */2122window.addEventListener("message", async (event: MessageEvent) => {23 if (event.source !== window) return;24 const data = event.data as25 | { type?: string; id?: string; payload?: unknown }26 | undefined;27 if (!data || data.type !== "PEACH_PASSKEY_REQUEST") return;28 const { id, payload } = data;29 if (typeof id !== "string" || !payload || typeof payload !== "object") return;3031 const reply = (replyPayload: unknown) => {32 window.postMessage(33 { type: "PEACH_PASSKEY_RESPONSE", id, payload: replyPayload },34 window.location.origin35 );36 };3738 try {39 const response = await chrome.runtime.sendMessage(payload);40 reply(response);41 } catch {42 // The background may be momentarily unreachable (service worker spin-up,43 // extension reload). Tell the proxy to fall through to the platform44 // rather than throwing into the page.45 reply({ ok: false, error: "extension_error" });46 }47});