edc59d2b2a37
add Firefox MAIN-world passkey detector + manifest content_scripts entry
- time
- 2026-04-28 18:29 UTC · 6 months ago
- machine
- koh2
- parent
- 698feaf6f97c
↓ peach-browser-edc59d2b.tar.gzatlas at this save
files changed2 files
manifest.firefox.json+13 −0src/content/passkeys-firefox.ts+70 −0manifest.firefox.json+13 −0
@@ -29,7 +29,20 @@2929 "src/content/index.ts"3030 ],3131 "run_at": "document_idle"32+ },33+ {34+ "matches": [35+ "<all_urls>"36+ ],37+ "js": [38+ "src/content/passkeys-firefox.ts"39+ ],40+ "run_at": "document_start",41+ "world": "MAIN"3242 }43+ ],44+ "host_permissions": [45+ "<all_urls>"3346 ],3447 "commands": {3548 "_execute_action": {src/content/passkeys-firefox.ts+70 −0
@@ -0,0 +1,70 @@1+/**2+ * passkeys-firefox.ts — MAIN-world detection layer for Firefox 150+.3+ *4+ * Firefox does not expose `chrome.webAuthenticationProxy`, so Peach cannot5+ * intercept WebAuthn calls the way it does on Chrome. Instead, this script6+ * runs in the page's main world (per `manifest.firefox.json`'s `world: "MAIN"`)7+ * and monkey-patches `navigator.credentials.create` / `.get` to *observe*8+ * passkey activity and emit CustomEvents the rest of Peach can react to.9+ *10+ * The original calls always proceed unchanged. Peach never blocks, modifies,11+ * or replaces the page's own WebAuthn flow from here. Registration and12+ * authentication on behalf of the user happen separately, from the popup13+ * context, where Firefox 150+ allows extensions to call `navigator.credentials`14+ * directly with their own host-permitted RP ID.15+ *16+ * Events emitted:17+ * `peach:passkey-create` — page invoked navigator.credentials.create with18+ * a publicKey option set. detail = { rpId, userName }19+ * `peach:passkey-get` — page invoked navigator.credentials.get with a20+ * publicKey option set. detail = { rpId, allowCredentials }21+ */22+23+(() => {24+ const credentials = navigator.credentials;25+ if (!credentials || typeof credentials.create !== "function" || typeof credentials.get !== "function") {26+ return;27+ }28+29+ const FLAG = "__peachPasskeysFirefoxAttached";30+ const w = window as unknown as Record<string, unknown>;31+ if (w[FLAG]) return;32+ w[FLAG] = true;33+34+ const originalCreate = credentials.create.bind(credentials);35+ const originalGet = credentials.get.bind(credentials);36+37+ credentials.create = async function patchedCreate(options) {38+ const publicKey = options?.publicKey;39+ if (publicKey) {40+ try {41+ window.dispatchEvent(42+ new CustomEvent("peach:passkey-create", {43+ detail: {44+ rpId: publicKey.rp?.id ?? location.hostname,45+ userName: publicKey.user?.name,46+ },47+ })48+ );49+ } catch { /* dispatching must never break the page */ }50+ }51+ return originalCreate(options);52+ };53+54+ credentials.get = async function patchedGet(options) {55+ const publicKey = options?.publicKey;56+ if (publicKey) {57+ try {58+ window.dispatchEvent(59+ new CustomEvent("peach:passkey-get", {60+ detail: {61+ rpId: publicKey.rpId ?? location.hostname,62+ allowCredentials: publicKey.allowCredentials,63+ },64+ })65+ );66+ } catch { /* dispatching must never break the page */ }67+ }68+ return originalGet(options);69+ };70+})();