/** * browser.ts — feature-detected browser identifier shared by background, * popup, and content scripts. * * The discriminator is feature presence + extension URL scheme, not user * agent string. `chrome.webAuthenticationProxy` is Chrome-only; the * `moz-extension://` URL scheme tells us we are running under Firefox. */ export type Browser = "chrome" | "firefox" | "unknown"; export function detectBrowser(): Browser { if (typeof chrome === "undefined") return "unknown"; // chrome.webAuthenticationProxy exists on Chrome 122+ regardless of // whether it has been attached. Treat its mere presence as the discriminator. if ((chrome as unknown as { webAuthenticationProxy?: unknown }).webAuthenticationProxy) { return "chrome"; } try { const url = chrome.runtime?.getURL?.("") ?? ""; if (url.startsWith("moz-extension://")) return "firefox"; } catch { /* extension context unavailable */ } return "unknown"; } /** * Best-effort Firefox version detection. Used to gate the Firefox passkey * path on Firefox 150+. Returns NaN for non-Firefox or when we can't parse. */ export function firefoxVersion(): number { if (detectBrowser() !== "firefox") return NaN; const match = navigator.userAgent.match(/Firefox\/(\d+)/); return match ? Number(match[1]) : NaN; }