76fd3d0637f7
M3/QUA-005: surface clipboard write failures to user
- time
- 2026-05-16 03:59 UTC · 5 months ago
- machine
- koh2
- parent
- 3b81fc8ed88e
↓ peach-browser-76fd3d06.tar.gzatlas at this save
files changed2 files
src/content/index.ts+21 −5src/content/inject.ts+82 −24src/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) {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