/** * Popup-side copy helper. * * MV3 service workers can't reliably write the clipboard from a non-focused * tab — the SW path's `chrome.scripting.executeScript` into the active tab * loses the popup's user gesture and the page document is not focused, so * `navigator.clipboard.writeText` rejects with NotAllowed. The popup itself * is a focused document with a fresh user gesture and `clipboardWrite` * permission, so it can call the API directly. We then notify the SW with * `alreadyWritten: true` so it skips its own write and only schedules the * auto-clear timer. */ import type { Canary } from "../shared/types"; import { sendMessage } from "./App"; export async function popupCopy( text: string, label: string, canary?: { webhookUrl: Canary["webhookUrl"]; entryTitle: string }, ): Promise { let alreadyWritten = false; try { await navigator.clipboard.writeText(text); alreadyWritten = true; } catch { // Some popup contexts (jsdom in tests, or rare permission-denied edge // cases) reject the direct write. Fall through to the SW path. } const response = await sendMessage({ type: "COPY", payload: { text, label, alreadyWritten }, }); const ok = alreadyWritten || Boolean((response as { ok?: boolean })?.ok); const canaryLabel = label.toLowerCase(); if (ok && canary && (canaryLabel === "password" || canaryLabel === "username")) { void sendMessage({ type: "FIRE_CANARY_PING", payload: { webhookUrl: canary.webhookUrl, entryTitle: canary.entryTitle, trigger: canaryLabel === "password" ? "password_copied" : "username_copied", }, }); } return ok; }