copy.ts

1.6 KB · raw

1/**2 * Popup-side copy helper.3 *4 * MV3 service workers can't reliably write the clipboard from a non-focused5 * tab — the SW path's `chrome.scripting.executeScript` into the active tab6 * loses the popup's user gesture and the page document is not focused, so7 * `navigator.clipboard.writeText` rejects with NotAllowed. The popup itself8 * is a focused document with a fresh user gesture and `clipboardWrite`9 * permission, so it can call the API directly. We then notify the SW with10 * `alreadyWritten: true` so it skips its own write and only schedules the11 * auto-clear timer.12 */1314import type { Canary } from "../shared/types";15import { sendMessage } from "./App";1617export async function popupCopy(18  text: string,19  label: string,20  canary?: { webhookUrl: Canary["webhookUrl"]; entryTitle: string },21): Promise<boolean> {22  let alreadyWritten = false;23  try {24    await navigator.clipboard.writeText(text);25    alreadyWritten = true;26  } catch {27    // Some popup contexts (jsdom in tests, or rare permission-denied edge28    // cases) reject the direct write. Fall through to the SW path.29  }3031  const response = await sendMessage({32    type: "COPY",33    payload: { text, label, alreadyWritten },34  });35  const ok = alreadyWritten || Boolean((response as { ok?: boolean })?.ok);36  const canaryLabel = label.toLowerCase();37  if (ok && canary && (canaryLabel === "password" || canaryLabel === "username")) {38    void sendMessage({39      type: "FIRE_CANARY_PING",40      payload: {41        webhookUrl: canary.webhookUrl,42        entryTitle: canary.entryTitle,43        trigger: canaryLabel === "password" ? "password_copied" : "username_copied",44      },45    });46  }47  return ok;48}