04/20 Update extension UI and functions: add item, form filler

This commit is contained in:
2026-04-20 08:15:49 -04:00
parent 0320e4f8eb
commit bb000e0aff
5 changed files with 672 additions and 212 deletions
+91 -10
View File
@@ -341,7 +341,11 @@ async function signOut() {
}
} catch {}
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",
]);
_vaultKey = null;
_items = [];
showView("login");
@@ -411,8 +415,27 @@ async function fetchAndDecryptVault() {
}),
);
// Write to session for the popup's own use (badge, rendering).
await chrome.storage.session.set({ vault_items: _items });
chrome.runtime.sendMessage({ type: "VAULT_UPDATED" }).catch(() => {});
// Write a lightweight copy to local storage — this is what content scripts
// read, since chrome.storage.local works reliably across all Chrome versions
// and does not require message delivery from the background service worker.
const itemsForContentScript = _items.map((item) => ({
id: item.id,
name: item.name,
item_type: item.item_type,
plain: item.plain,
}));
await chrome.storage.local.set({ vault_items_cs: itemsForContentScript });
// Notify background to refresh badges and forward to content scripts.
chrome.runtime
.sendMessage({
type: "VAULT_UPDATED",
vault_items: itemsForContentScript,
})
.catch(() => {});
} catch (err) {
console.error("fetchAndDecryptVault:", err);
} finally {
@@ -581,6 +604,30 @@ function initTabs() {
// ── Save-prompt modal ─────────────────────────────────────────────────────────
/**
* Populate the folder <select> inside the save modal.
* Fetches /api/folders and rebuilds the option list.
* The first option ("— No folder —") with value "" is always kept.
*/
async function loadFoldersIntoSaveModal() {
const select = $("save-folder");
// Reset to just the placeholder option.
select.innerHTML = '<option value="">— No folder —</option>';
try {
const res = await apiFetch("/api/folders");
if (!res?.ok) return;
const folders = await res.json();
folders.forEach((f) => {
const opt = document.createElement("option");
opt.value = f.id;
opt.textContent = f.name;
select.appendChild(opt);
});
} catch (err) {
console.error("[PassKeeper] loadFoldersIntoSaveModal failed:", err);
}
}
/**
* Show the save-prompt overlay. It is a blocking modal — no backdrop click,
* no ✕ button — so the user MUST click "Save" or "Not now".
@@ -602,6 +649,10 @@ async function checkPendingSave() {
? "Update in PassKeeper?"
: "Save to PassKeeper?";
// Load folders (non-blocking — modal shows immediately, folders populate async).
$("save-folder").value = "";
loadFoldersIntoSaveModal();
// Show the overlay.
$("save-prompt-overlay").classList.remove("hidden");
$("save-name").focus();
@@ -635,6 +686,9 @@ async function saveCredential(data) {
notes: "",
};
const name = $("save-name").value.trim() || data.siteName || "Untitled";
// Read selected folder — empty string means no folder (null).
const folderVal = $("save-folder").value;
const folder_id = folderVal ? parseInt(folderVal, 10) : null;
const { enc_data, iv } = await ExtCrypto.encryptItem(_vaultKey, plain);
try {
const res = await apiFetch("/api/vault", {
@@ -642,12 +696,16 @@ async function saveCredential(data) {
body: JSON.stringify({
name,
item_type: "password",
folder_id: null,
folder_id,
enc_data,
iv,
}),
});
if (res?.ok) {
console.log(
"[PassKeeper] Credential saved to vault, folder_id:",
folder_id,
);
await fetchAndDecryptVault();
renderList();
}
@@ -821,14 +879,37 @@ async function init() {
const sessionState = await restoreSessionIfAvailable();
if (sessionState === "vault") {
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
// Check whether the content script requested a specific view (e.g. generator).
const { popup_nav } = await chrome.storage.session.get("popup_nav");
if (popup_nav) {
await chrome.storage.session.remove("popup_nav");
if (popup_nav === "generator") {
showView("generator");
initGenerator();
// Still load vault data in background so the Vault tab is ready.
fetchAndDecryptVault().then(() => {});
// Wire event listeners below, then return early from vault-specific setup.
} else {
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
} else {
await fetchAndDecryptVault();
renderList();
}
}
} else {
await fetchAndDecryptVault();
renderList();
showView("vault");
await checkPendingSave();
if (_items.length) {
renderList();
fetchAndDecryptVault().then(() => renderList());
} else {
await fetchAndDecryptVault();
renderList();
}
}
} else if (sessionState === "unlock") {
showView("unlock");