76fd3d0637f7

M3/QUA-005: surface clipboard write failures to user

time
2026-05-16 03:59 UTC · 5 months ago
machine
koh2
parent
3b81fc8ed88e
2 files src/content/index.ts+21 −5src/content/inject.ts+82 −24
src/content/index.ts+21 −5
@@ -249,12 +249,21 @@249249         const r = resp as { ok: boolean; data?: { password?: string } };250250         if (r.ok && r.data?.password) {251251           let alreadyWritten = false;252-          try { await navigator.clipboard.writeText(r.data.password); alreadyWritten = true; } catch {}253-          await chrome.runtime.sendMessage({252+          try {253+            await navigator.clipboard.writeText(r.data.password);254+            alreadyWritten = true;255+          } catch (err) {256+            console.warn("[Peach] clipboard write denied:", err);257+          }258+          const copyResp = await chrome.runtime.sendMessage({254259             type: "COPY",255260             payload: { text: r.data.password, label: "Password", alreadyWritten },256-          });257-          showToast("Password copied");261+          }) as { ok?: boolean } | undefined;262+          if (alreadyWritten || copyResp?.ok) {263+            showToast("Password copied");264+          } else {265+            showToast("Could not copy password — clipboard blocked");266+          }258267         } else {259268           showToast("Could not copy password");260269         }@@ -344,13 +353,20 @@344353   if (entry.totp) {345354     const { code } = generateTOTP(entry.totp);346355     let alreadyWritten = false;347-    try { await navigator.clipboard.writeText(code); alreadyWritten = true; } catch {}356+    try {357+      await navigator.clipboard.writeText(code);358+      alreadyWritten = true;359+    } catch (err) {360+      console.warn("[Peach] clipboard write denied:", err);361+    }348362     const response = await chrome.runtime.sendMessage({349363       type: "COPY",350364       payload: { text: code, label: "TOTP", alreadyWritten },351365     });352366     if (alreadyWritten || (response as { ok?: boolean }).ok) {353367       showToast("TOTP copied · " + code);368+    } else {369+      showToast("Could not copy TOTP — clipboard blocked");354370     }355371     const totpField = findTOTPField();356372     if (totpField) {

view file

src/content/inject.ts+82 −24
@@ -1939,6 +1939,18 @@19391939     setTimeout(() => btn.classList.remove("flashed"), 700);19401940   }19411941 1942+  // M3/QUA-005: when both the direct clipboard write and the background COPY1943+  // fallback fail, briefly show the user that the copy did not happen.1944+  function flashCopyError(btn: HTMLButtonElement) {1945+    const original = btn.textContent ?? "";1946+    btn.textContent = "Blocked";1947+    btn.classList.add("flashed");1948+    setTimeout(() => {1949+      btn.classList.remove("flashed");1950+      if (btn.isConnected) btn.textContent = original;1951+    }, 1200);1952+  }1953+19421954   function renderSilverPlatter(entry: SelectorEntry) {19431955     container.innerHTML = "";19441956     searchInput = null;@@ -2033,12 +2045,18 @@20332045       copyEmail.addEventListener("click", async (e) => {20342046         e.stopPropagation();20352047         let alreadyWritten = false;2036-        try { await navigator.clipboard.writeText(entry.email!); alreadyWritten = true; } catch {}2037-        await chrome.runtime.sendMessage({2048+        try {2049+          await navigator.clipboard.writeText(entry.email!);2050+          alreadyWritten = true;2051+        } catch (err) {2052+          console.warn("[Peach] clipboard write denied:", err);2053+        }2054+        const resp = await chrome.runtime.sendMessage({20382055           type: "COPY",20392056           payload: { text: entry.email, label: "Email", alreadyWritten },2040-        }).catch(() => {});2041-        flashActionBtn(copyEmail);2057+        }).catch(() => null) as { ok?: boolean } | null;2058+        if (alreadyWritten || resp?.ok) flashActionBtn(copyEmail);2059+        else flashCopyError(copyEmail);20422060       });20432061       actions.appendChild(copyEmail);20442062     }@@ -2054,12 +2072,18 @@20542072       e.stopPropagation();20552073       if (!entry.username) return;20562074       let alreadyWritten = false;2057-      try { await navigator.clipboard.writeText(entry.username); alreadyWritten = true; } catch {}2058-      await chrome.runtime.sendMessage({2075+      try {2076+        await navigator.clipboard.writeText(entry.username);2077+        alreadyWritten = true;2078+      } catch (err) {2079+        console.warn("[Peach] clipboard write denied:", err);2080+      }2081+      const resp = await chrome.runtime.sendMessage({20592082         type: "COPY",20602083         payload: { text: entry.username, label: entry.username.includes("@") ? "Email" : "Username", alreadyWritten },2061-      }).catch(() => {});2062-      flashActionBtn(copyU);2084+      }).catch(() => null) as { ok?: boolean } | null;2085+      if (alreadyWritten || resp?.ok) flashActionBtn(copyU);2086+      else flashCopyError(copyU);20632087     });20642088     actions.appendChild(copyU);20652089 @@ -2076,12 +2100,18 @@20762100       }).catch(() => null) as { ok: boolean; data?: { password?: string } } | null;20772101       if (!resp?.ok || !resp.data?.password) return;20782102       let alreadyWritten = false;2079-      try { await navigator.clipboard.writeText(resp.data.password); alreadyWritten = true; } catch {}2080-      await chrome.runtime.sendMessage({2103+      try {2104+        await navigator.clipboard.writeText(resp.data.password);2105+        alreadyWritten = true;2106+      } catch (err) {2107+        console.warn("[Peach] clipboard write denied:", err);2108+      }2109+      const copyResp = await chrome.runtime.sendMessage({20812110         type: "COPY",20822111         payload: { text: resp.data.password, label: "Password", alreadyWritten },2083-      }).catch(() => {});2084-      flashActionBtn(copyP);2112+      }).catch(() => null) as { ok?: boolean } | null;2113+      if (alreadyWritten || copyResp?.ok) flashActionBtn(copyP);2114+      else flashCopyError(copyP);20852115     });20862116     actions.appendChild(copyP);20872117 @@ -2161,12 +2191,17 @@21612191         e.stopPropagation();21622192         const { code } = generateTOTP(secret!);21632193         let alreadyWritten = false;2164-        try { await navigator.clipboard.writeText(code); alreadyWritten = true; } catch {}2165-        await chrome.runtime.sendMessage({2194+        try {2195+          await navigator.clipboard.writeText(code);2196+          alreadyWritten = true;2197+        } catch (err) {2198+          console.warn("[Peach] clipboard write denied:", err);2199+        }2200+        const resp = await chrome.runtime.sendMessage({21662201           type: "COPY",21672202           payload: { text: code, label: "TOTP", alreadyWritten },2168-        }).catch(() => {});2169-        copyBtn.textContent = "Copied";2203+        }).catch(() => null) as { ok?: boolean } | null;2204+        copyBtn.textContent = alreadyWritten || resp?.ok ? "Copied" : "Blocked";21702205         setTimeout(() => { if (copyBtn.isConnected) copyBtn.textContent = "Copy"; }, 1200);21712206       });21722207 @@ -3328,6 +3363,17 @@33283363     setTimeout(() => b.classList.remove("flashed"), 700);33293364   }33303365 3366+  // M3/QUA-005: clipboard + background COPY both failed — show user.3367+  function flashCopyError(b: HTMLButtonElement) {3368+    const original = b.textContent ?? "";3369+    b.textContent = "Blocked";3370+    b.classList.add("flashed");3371+    setTimeout(() => {3372+      b.classList.remove("flashed");3373+      if (b.isConnected) b.textContent = original;3374+    }, 1200);3375+  }3376+33313377   async function fetchFullCard(): Promise<import("../shared/types").VaultEntry | null> {33323378     const resp = await chrome.runtime.sendMessage({33333379       type: "GET_ENTRY_FOR_FILL",@@ -3346,12 +3392,18 @@33463392     const full = await fetchFullCard();33473393     if (!full?.cardNumber) return;33483394     let alreadyWritten = false;3349-    try { await navigator.clipboard.writeText(full.cardNumber); alreadyWritten = true; } catch {}3350-    await chrome.runtime.sendMessage({3395+    try {3396+      await navigator.clipboard.writeText(full.cardNumber);3397+      alreadyWritten = true;3398+    } catch (err) {3399+      console.warn("[Peach] clipboard write denied:", err);3400+    }3401+    const resp = await chrome.runtime.sendMessage({33513402       type: "COPY",33523403       payload: { text: full.cardNumber, label: "Card number", alreadyWritten },3353-    }).catch(() => {});3354-    flashBtn(copyNum);3404+    }).catch(() => null) as { ok?: boolean } | null;3405+    if (alreadyWritten || resp?.ok) flashBtn(copyNum);3406+    else flashCopyError(copyNum);33553407   });33563408   actions.appendChild(copyNum);33573409 @@ -3365,12 +3417,18 @@33653417     const full = await fetchFullCard();33663418     if (!full?.cvc) return;33673419     let alreadyWritten = false;3368-    try { await navigator.clipboard.writeText(full.cvc); alreadyWritten = true; } catch {}3369-    await chrome.runtime.sendMessage({3420+    try {3421+      await navigator.clipboard.writeText(full.cvc);3422+      alreadyWritten = true;3423+    } catch (err) {3424+      console.warn("[Peach] clipboard write denied:", err);3425+    }3426+    const resp = await chrome.runtime.sendMessage({33703427       type: "COPY",33713428       payload: { text: full.cvc, label: "CVC", alreadyWritten },3372-    }).catch(() => {});3373-    flashBtn(copyCvc);3429+    }).catch(() => null) as { ok?: boolean } | null;3430+    if (alreadyWritten || resp?.ok) flashBtn(copyCvc);3431+    else flashCopyError(copyCvc);33743432   });33753433   actions.appendChild(copyCvc);33763434 

view file