05/19 Enhance codes

This commit is contained in:
2026-05-19 09:31:33 -04:00
parent 5ede6326c8
commit f95461aee2
3 changed files with 151 additions and 99 deletions
+61 -48
View File
@@ -471,8 +471,8 @@ const Vault = (() => {
_sortOrder === "folder" _sortOrder === "folder"
? Object.keys(groups).sort() ? Object.keys(groups).sort()
: ["(No folder)", ..._folders.map((f) => f.name)].filter( : ["(No folder)", ..._folders.map((f) => f.name)].filter(
(k) => groups[k], (k) => groups[k],
); );
Object.keys(groups).forEach((k) => { Object.keys(groups).forEach((k) => {
if (!keys.includes(k)) keys.push(k); if (!keys.includes(k)) keys.push(k);
}); });
@@ -540,7 +540,7 @@ const Vault = (() => {
case "card": case "card":
subText = item.plain.card_number subText = item.plain.card_number
? "•••• " + ? "•••• " +
String(item.plain.card_number).replace(/\s/g, "").slice(-4) String(item.plain.card_number).replace(/\s/g, "").slice(-4)
: ""; : "";
break; break;
case "bank": case "bank":
@@ -920,9 +920,9 @@ const Vault = (() => {
0, 0,
Math.round( Math.round(
100 - 100 -
(weak.length / total) * 40 - (weak.length / total) * 40 -
(reused.length / total) * 30 - (reused.length / total) * 30 -
(old.length / total) * 15, (old.length / total) * 15,
), ),
); );
const cls = const cls =
@@ -943,24 +943,22 @@ const Vault = (() => {
</div>`; </div>`;
sectionsEl.innerHTML = ""; sectionsEl.innerHTML = "";
const makeSection = (title, icon, items, desc) => { const makeSection = (title, icon, items, desc, renderItem) => {
if (!items.length) return; if (!items.length) return;
const sec = document.createElement("div"); const sec = document.createElement("div");
sec.className = "sec-section"; sec.className = "sec-section";
const defaultRender = (i) => `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(i.plain?.username || "")}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`;
const renderFn = renderItem || defaultRender;
sec.innerHTML = ` sec.innerHTML = `
<div class="sec-section-header"><span class="sec-section-icon">${icon}</span> <div class="sec-section-header"><span class="sec-section-icon">${icon}</span>
<div><div class="sec-section-title">${title} (${items.length})</div><div class="sec-section-desc">${desc}</div></div> <div><div class="sec-section-title">${title} (${items.length})</div><div class="sec-section-desc">${desc}</div></div>
</div> </div>
<ul class="sec-item-list"> <ul class="sec-item-list">
${items ${items.map(renderFn).join("")}
.map(
(i) => `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(i.plain?.username || "")}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`,
)
.join("")}
</ul>`; </ul>`;
sec.querySelectorAll("[data-sec-edit]").forEach((btn) => { sec.querySelectorAll("[data-sec-edit]").forEach((btn) => {
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
@@ -995,6 +993,20 @@ const Vault = (() => {
"🕐", "🕐",
old, old,
"Weak or reused passwords not changed in over 180 days.", "Weak or reused passwords not changed in over 180 days.",
(i) => {
const ref = i.plain?.password_changed_at || i.created_at;
const daysAgo = ref
? Math.floor((Date.now() - new Date(ref).getTime()) / 86400000)
: null;
const ageLabel = daysAgo !== null
? `Last changed ${daysAgo} day${daysAgo !== 1 ? "s" : ""} ago`
: "Age unknown";
return `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(ageLabel)}</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Edit</button>
</li>`;
},
); );
// ── Missing 2FA warning ────────────────────────────────────────────────── // ── Missing 2FA warning ──────────────────────────────────────────────────
@@ -1060,16 +1072,16 @@ const Vault = (() => {
</div> </div>
<ul class="sec-item-list"> <ul class="sec-item-list">
${breached ${breached
.map((i) => { .map((i) => {
const count = const count =
hibpResults.find((r) => r.item.id === i.id)?.count || 0; hibpResults.find((r) => r.item.id === i.id)?.count || 0;
return `<li class="sec-item"> return `<li class="sec-item">
<span class="sec-item-name">${escHtml(i.name)}</span> <span class="sec-item-name">${escHtml(i.name)}</span>
<span class="sec-item-sub">${escHtml(i.plain?.username || "")} — seen ${count.toLocaleString()} time${count !== 1 ? "s" : ""} in breaches</span> <span class="sec-item-sub">${escHtml(i.plain?.username || "")} — seen ${count.toLocaleString()} time${count !== 1 ? "s" : ""} in breaches</span>
<button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Change</button> <button class="btn-secondary btn-sm" data-sec-edit="${i.id}">Change</button>
</li>`; </li>`;
}) })
.join("")} .join("")}
</ul>`; </ul>`;
hibpSection.querySelectorAll("[data-sec-edit]").forEach((btn) => { hibpSection.querySelectorAll("[data-sec-edit]").forEach((btn) => {
btn.addEventListener("click", () => { btn.addEventListener("click", () => {
@@ -1423,6 +1435,9 @@ const Vault = (() => {
payload = _importRows; payload = _importRows;
} else { } else {
// Plaintext CSV rows — encrypt each one now. // Plaintext CSV rows — encrypt each one now.
// Use the import timestamp as password_changed_at — best available
// approximation since CSV exports don't carry a change date.
const importedAt = new Date().toISOString();
payload = await Promise.all( payload = await Promise.all(
_importRows.map(async (row) => { _importRows.map(async (row) => {
const plain = { const plain = {
@@ -1430,6 +1445,7 @@ const Vault = (() => {
username: row.username || "", username: row.username || "",
password: row.password, password: row.password,
notes: row.notes || "", notes: row.notes || "",
password_changed_at: importedAt,
}; };
const { enc_data, iv } = await Crypto.encryptItem( const { enc_data, iv } = await Crypto.encryptItem(
vaultKey, vaultKey,
@@ -1607,8 +1623,7 @@ const Vault = (() => {
<span class="share-name">${escHtml(s.item_name)}</span> <span class="share-name">${escHtml(s.item_name)}</span>
<span class="share-meta">From ${escHtml(s.owner_email)}</span> <span class="share-meta">From ${escHtml(s.owner_email)}</span>
</div> </div>
${ ${!s.accepted
!s.accepted
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>` ? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
: `<button class="btn-secondary btn-sm" : `<button class="btn-secondary btn-sm"
data-view-share="${s.id}" data-view-share="${s.id}"
@@ -1619,7 +1634,7 @@ const Vault = (() => {
data-iv-name="${escHtml(s.iv_name || "")}" data-iv-name="${escHtml(s.iv_name || "")}"
data-name="${escHtml(s.item_name)}" data-name="${escHtml(s.item_name)}"
data-type="${escHtml(s.item_type || "")}">View</button>` data-type="${escHtml(s.item_type || "")}">View</button>`
} }
</li>`, </li>`,
) )
.join(""); .join("");
@@ -2155,10 +2170,9 @@ const Vault = (() => {
<span class="em-vault-chevron">▸</span> <span class="em-vault-chevron">▸</span>
</button> </button>
<div class="em-vault-item-body hidden" id="em-vault-body-${idx}"> <div class="em-vault-item-body hidden" id="em-vault-body-${idx}">
${ ${hasFields
hasFields ? '<div class="detail-body em-detail-body"></div>'
? '<div class="detail-body em-detail-body"></div>' : '<p class="vault-empty">Could not decrypt this item.</p>'
: '<p class="vault-empty">Could not decrypt this item.</p>'
} }
</div> </div>
</li>`; </li>`;
@@ -2487,8 +2501,8 @@ const Vault = (() => {
const typeLabel = transports.includes("internal") const typeLabel = transports.includes("internal")
? "📱 Device" ? "📱 Device"
: transports.some((t) => ["usb", "nfc", "ble", "smart-card"].includes(t)) : transports.some((t) => ["usb", "nfc", "ble", "smart-card"].includes(t))
? "🔑 Security key" ? "🔑 Security key"
: "🔑 Passkey"; : "🔑 Passkey";
return `<div class="passkey-item" data-cred-id="${c.id}"> return `<div class="passkey-item" data-cred-id="${c.id}">
<div class="passkey-info"> <div class="passkey-info">
<span class="passkey-name">${escHtml(c.name)}</span> <span class="passkey-name">${escHtml(c.name)}</span>
@@ -2751,16 +2765,15 @@ const Vault = (() => {
<h3 style="margin:0 0 8px;font-size:16px;color:#111827;"> <h3 style="margin:0 0 8px;font-size:16px;color:#111827;">
${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"} ${isFirstTime ? "🔐 MFA enabled — save your backup codes" : "🔐 New backup codes"}
</h3> </h3>
${ ${isFirstTime
isFirstTime ? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
? `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
These codes let you sign in if you lose access to your authenticator app. These codes let you sign in if you lose access to your authenticator app.
<strong>Save them now — they will not be shown again.</strong> <strong>Save them now — they will not be shown again.</strong>
</p>` </p>`
: `<p style="font-size:13px;color:#374151;margin-bottom:12px;"> : `<p style="font-size:13px;color:#374151;margin-bottom:12px;">
Your previous codes have been invalidated. Save these new codes securely. Your previous codes have been invalidated. Save these new codes securely.
</p>` </p>`
} }
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:12px;display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px;"> <div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:8px;padding:12px;display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px;">
${codesHtml} ${codesHtml}
</div> </div>
@@ -3269,14 +3282,14 @@ const Vault = (() => {
const pool = !_activeFilter const pool = !_activeFilter
? _items ? _items
: _activeFilter.type === "itemType" : _activeFilter.type === "itemType"
? _items.filter((i) => i.item_type === _activeFilter.value) ? _items.filter((i) => i.item_type === _activeFilter.value)
: _activeFilter.type === "folder" : _activeFilter.type === "folder"
? _items.filter((i) => i.folder_id === _activeFilter.value) ? _items.filter((i) => i.folder_id === _activeFilter.value)
: _activeFilter.type === "tag" : _activeFilter.type === "tag"
? _items.filter((i) => ? _items.filter((i) =>
(i.plain?.tags || []).includes(_activeFilter.value), (i.plain?.tags || []).includes(_activeFilter.value),
) )
: _items; : _items;
renderItemList( renderItemList(
pool.filter( pool.filter(
(item) => (item) =>
@@ -3427,7 +3440,7 @@ const Vault = (() => {
if ( if (
(mode === "add" && (mode === "add" &&
(document.getElementById("field-type").value || "password") === (document.getElementById("field-type").value || "password") ===
"password") || "password") ||
(mode === "edit" && (item?.item_type || "password") === "password") (mode === "edit" && (item?.item_type || "password") === "password")
) { ) {
initPasswordFieldEnhancements(); initPasswordFieldEnhancements();
@@ -3684,7 +3697,7 @@ const Vault = (() => {
"X-CSRFToken": csrfToken(), "X-CSRFToken": csrfToken(),
}, },
body: JSON.stringify({ refresh_token: refreshToken }), body: JSON.stringify({ refresh_token: refreshToken }),
}).catch(() => {}); }).catch(() => { });
VaultSession.clear(); VaultSession.clear();
SharingSession.clear(); SharingSession.clear();
sessionStorage.removeItem("access_token"); sessionStorage.removeItem("access_token");
@@ -3789,11 +3802,11 @@ const Vault = (() => {
// Auto-clear clipboard after 30 seconds — industry-standard hygiene. // Auto-clear clipboard after 30 seconds — industry-standard hygiene.
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer); if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
_clipboardClearTimer = setTimeout(() => { _clipboardClearTimer = setTimeout(() => {
navigator.clipboard.writeText("").catch(() => {}); navigator.clipboard.writeText("").catch(() => { });
_clipboardClearTimer = null; _clipboardClearTimer = null;
}, 30_000); }, 30_000);
}) })
.catch(() => {}); .catch(() => { });
} }
function escHtml(str) { function escHtml(str) {
+17 -3
View File
@@ -932,6 +932,7 @@ body {
background: #e0f2fe; background: #e0f2fe;
color: #0369a1; color: #0369a1;
} }
/* ── Three-dot flyout menu ───────────────────────────────────────────────── */ /* ── Three-dot flyout menu ───────────────────────────────────────────────── */
.pk-flyout { .pk-flyout {
@@ -940,7 +941,7 @@ body {
background: #ffffff; background: #ffffff;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 16px rgba(0,0,0,0.13); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.13);
min-width: 150px; min-width: 150px;
padding: 4px 0; padding: 4px 0;
display: flex; display: flex;
@@ -972,9 +973,20 @@ body {
color: #6b7280; color: #6b7280;
} }
.pk-flyout-info {
padding: 6px 12px 7px;
font-size: 11px;
color: #9ca3af;
border-top: 1px solid #f3f4f6;
margin-top: 2px;
user-select: none;
}
/* ── Collapsible folder groups ────────────────────────────────────────────── */ /* ── Collapsible folder groups ────────────────────────────────────────────── */
.pk-group { margin-bottom: 2px; } .pk-group {
margin-bottom: 2px;
}
.pk-group-header { .pk-group-header {
display: flex; display: flex;
@@ -988,7 +1000,9 @@ body {
transition: background 0.1s; transition: background 0.1s;
} }
.pk-group-header:hover { background: #f0f4ff; } .pk-group-header:hover {
background: #f0f4ff;
}
.pk-group-name { .pk-group-name {
font-size: 12px; font-size: 12px;
+72 -47
View File
@@ -69,10 +69,10 @@ function escHtml(str) {
// Auto-clear clipboard 30 s after a sensitive copy. // Auto-clear clipboard 30 s after a sensitive copy.
let _clipTimer = null; let _clipTimer = null;
function _copyWithAutoClear(text) { function _copyWithAutoClear(text) {
navigator.clipboard.writeText(text).catch(() => {}); navigator.clipboard.writeText(text).catch(() => { });
if (_clipTimer) clearTimeout(_clipTimer); if (_clipTimer) clearTimeout(_clipTimer);
_clipTimer = setTimeout(() => { _clipTimer = setTimeout(() => {
navigator.clipboard.writeText("").catch(() => {}); navigator.clipboard.writeText("").catch(() => { });
_clipTimer = null; _clipTimer = null;
}, 30_000); }, 30_000);
} }
@@ -186,7 +186,7 @@ function siteLabel(item) {
if (item.plain?.url) { if (item.plain?.url) {
try { try {
return new URL(item.plain.url).hostname.replace(/^www\./, ""); return new URL(item.plain.url).hostname.replace(/^www\./, "");
} catch {} } catch { }
} }
return item.name; return item.name;
} }
@@ -441,7 +441,7 @@ async function completeLogin(data, masterPassword) {
refresh_token: data.refresh_token, refresh_token: data.refresh_token,
enc_key_salt: data.enc_key_salt, enc_key_salt: data.enc_key_salt,
}) })
.catch(() => {}); .catch(() => { });
showView("vault"); showView("vault");
await checkPendingSave(); await checkPendingSave();
@@ -502,9 +502,9 @@ async function signOut() {
Authorization: `Bearer ${access_token}`, Authorization: `Bearer ${access_token}`,
}, },
body: JSON.stringify({ refresh_token }), body: JSON.stringify({ refresh_token }),
}).catch(() => {}); }).catch(() => { });
} }
} catch {} } catch { }
await chrome.storage.session.clear(); await chrome.storage.session.clear();
await chrome.storage.local.remove(["refresh_token", "enc_key_salt"]); await chrome.storage.local.remove(["refresh_token", "enc_key_salt"]);
// vault_items_cs is now in session storage — cleared by the session.clear() call above. // vault_items_cs is now in session storage — cleared by the session.clear() call above.
@@ -616,7 +616,7 @@ async function fetchAndDecryptVault() {
type: "VAULT_UPDATED", type: "VAULT_UPDATED",
vault_items: itemsForContentScript, vault_items: itemsForContentScript,
}) })
.catch(() => {}); .catch(() => { });
// Run health checks in the background — sync metrics first, then HIBP. // Run health checks in the background — sync metrics first, then HIBP.
// Results are sent to the background SW via HEALTH_UPDATE so the toolbar // Results are sent to the background SW via HEALTH_UPDATE so the toolbar
@@ -645,7 +645,7 @@ async function _runPopupHealthCheck() {
if (!pwItems.length) { if (!pwItems.length) {
chrome.runtime chrome.runtime
.sendMessage({ type: "HEALTH_UPDATE", breached: 0, weak: 0, reused: 0 }) .sendMessage({ type: "HEALTH_UPDATE", breached: 0, weak: 0, reused: 0 })
.catch(() => {}); .catch(() => { });
return; return;
} }
@@ -676,7 +676,7 @@ async function _runPopupHealthCheck() {
weak: weak.length, weak: weak.length,
reused: reused.length, reused: reused.length,
}) })
.catch(() => {}); .catch(() => { });
// HIBP — k-anonymity, parallel. // HIBP — k-anonymity, parallel.
const hibpResults = await Promise.all( const hibpResults = await Promise.all(
@@ -695,7 +695,7 @@ async function _runPopupHealthCheck() {
weak: weak.length, weak: weak.length,
reused: reused.length, reused: reused.length,
}) })
.catch(() => {}); .catch(() => { });
} catch (err) { } catch (err) {
console.error("[PassKeeper] Popup health check failed:", err); console.error("[PassKeeper] Popup health check failed:", err);
} }
@@ -1053,7 +1053,7 @@ function renderList() {
password: item.plain.password || "", password: item.plain.password || "",
autologin: !!item.plain.autologin, autologin: !!item.plain.autologin,
}) })
.catch(() => {}); .catch(() => { });
} }
window.close(); window.close();
}), }),
@@ -1093,44 +1093,64 @@ function renderList() {
const menuItems = [ const menuItems = [
item.plain?.url item.plain?.url
? { ? {
label: "Open URL", label: "Open URL",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>', icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M15 3h6v6M10 14L21 3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
action: async () => { action: async () => {
chrome.tabs.create({ url: item.plain.url }); chrome.tabs.create({ url: item.plain.url });
flyout.remove(); flyout.remove();
}, },
} }
: null, : null,
item.plain?.username item.plain?.username
? { ? {
label: "Copy username", label: "Copy username",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>', icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><circle cx="12" cy="8" r="4" stroke="currentColor" stroke-width="1.7"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
action: async () => { action: async () => {
if (item.plain?.reprompt) { if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword(); const ok = await _repromptMasterPassword();
if (!ok) return; if (!ok) return;
} }
_copyWithAutoClear(item.plain.username); _copyWithAutoClear(item.plain.username);
flyout.remove(); flyout.remove();
}, },
} }
: null, : null,
item.plain?.password item.plain?.password
? { ? {
label: "Copy password", label: "Copy password",
icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>', icon: '<svg viewBox="0 0 24 24" fill="none" width="14" height="14"><rect x="3" y="10" width="18" height="11" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 10V7a4 4 0 018 0v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>',
action: async () => { action: async () => {
if (item.plain?.reprompt) { if (item.plain?.reprompt) {
const ok = await _repromptMasterPassword(); const ok = await _repromptMasterPassword();
if (!ok) return; if (!ok) return;
} }
_copyWithAutoClear(item.plain.password); _copyWithAutoClear(item.plain.password);
flyout.remove(); flyout.remove();
}, },
} }
: null, : null,
].filter(Boolean); ].filter(Boolean);
// Add a non-action info row showing when the password was last changed.
// Only shown for password items that have password_changed_at set.
if (item.item_type === "password" && item.plain?.password_changed_at) {
const daysAgo = Math.floor(
(Date.now() - new Date(item.plain.password_changed_at).getTime()) /
86400000,
);
const ageText =
daysAgo === 0
? "Changed today"
: daysAgo === 1
? "Changed yesterday"
: `Changed ${daysAgo} days ago`;
const infoRow = document.createElement("div");
infoRow.className = "pk-flyout-info";
infoRow.textContent = ageText;
// Append after action rows are added.
flyout._ageInfoRow = infoRow;
}
menuItems.forEach((mi) => { menuItems.forEach((mi) => {
const row = document.createElement("button"); const row = document.createElement("button");
row.className = "pk-flyout-item"; row.className = "pk-flyout-item";
@@ -1142,6 +1162,11 @@ function renderList() {
flyout.appendChild(row); flyout.appendChild(row);
}); });
// Append age info row if present.
if (flyout._ageInfoRow) {
flyout.appendChild(flyout._ageInfoRow);
}
// Position flyout using fixed coords so it escapes vault-list overflow clipping. // Position flyout using fixed coords so it escapes vault-list overflow clipping.
const appEl = document.getElementById("app"); const appEl = document.getElementById("app");
appEl.appendChild(flyout); appEl.appendChild(flyout);
@@ -1246,7 +1271,7 @@ async function checkPendingSave() {
function _clearPendingSave() { function _clearPendingSave() {
$("save-prompt-overlay").classList.add("hidden"); $("save-prompt-overlay").classList.add("hidden");
chrome.storage.local.remove("pending_save"); chrome.storage.local.remove("pending_save");
chrome.runtime.sendMessage({ type: "CLEAR_SAVE_BADGE" }).catch(() => {}); chrome.runtime.sendMessage({ type: "CLEAR_SAVE_BADGE" }).catch(() => { });
} }
$("btn-save-yes").onclick = async () => { $("btn-save-yes").onclick = async () => {
@@ -1484,7 +1509,7 @@ async function initAddView() {
: new URL(tab.url).hostname.replace(/^www\./, ""); : new URL(tab.url).hostname.replace(/^www\./, "");
} }
} }
} catch (e) {} } catch (e) { }
// Load folders into the add-folder select. // Load folders into the add-folder select.
const addFolderSel = $("add-folder"); const addFolderSel = $("add-folder");
@@ -1500,7 +1525,7 @@ async function initAddView() {
addFolderSel.appendChild(opt); addFolderSel.appendChild(opt);
}); });
} }
} catch (e) {} } catch (e) { }
// Wire one-time event listeners. // Wire one-time event listeners.
if (!_addViewInitialised) { if (!_addViewInitialised) {
@@ -1635,7 +1660,7 @@ async function initAccountView() {
// Tell background to re-apply the new interval. // Tell background to re-apply the new interval.
chrome.runtime chrome.runtime
.sendMessage({ type: "SET_IDLE_TIMEOUT", seconds: val }) .sendMessage({ type: "SET_IDLE_TIMEOUT", seconds: val })
.catch(() => {}); .catch(() => { });
// Brief "Saved" confirmation. // Brief "Saved" confirmation.
const saved = $("acct-idle-saved"); const saved = $("acct-idle-saved");
saved.classList.remove("hidden"); saved.classList.remove("hidden");
@@ -1656,7 +1681,7 @@ async function init() {
currentWindow: true, currentWindow: true,
}); });
_currentUrl = tab?.url || ""; _currentUrl = tab?.url || "";
} catch {} } catch { }
// Set vault link // Set vault link
$("btn-open-vault").href = VAULT_URL; $("btn-open-vault").href = VAULT_URL;
@@ -1672,7 +1697,7 @@ async function init() {
showView("generator"); showView("generator");
initGenerator(); initGenerator();
// Still load vault data in background so the Vault tab is ready. // Still load vault data in background so the Vault tab is ready.
fetchAndDecryptVault().then(() => {}); fetchAndDecryptVault().then(() => { });
// Wire event listeners below, then return early from vault-specific setup. // Wire event listeners below, then return early from vault-specific setup.
} else { } else {
showView("vault"); showView("vault");
@@ -1752,7 +1777,7 @@ async function init() {
// browser is open. We ping it every 20 s; the ping itself is a no-op but // browser is open. We ping it every 20 s; the ping itself is a no-op but
// prevents the SW from being killed between popup openings. // prevents the SW from being killed between popup openings.
const _keepalive = setInterval(() => { const _keepalive = setInterval(() => {
chrome.runtime.sendMessage({ type: "KEEPALIVE" }).catch(() => {}); chrome.runtime.sendMessage({ type: "KEEPALIVE" }).catch(() => { });
}, 20_000); }, 20_000);
// Search // Search