diff --git a/.gitignore b/.gitignore index 7231f1b..63373dd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ node_modules/ *.zip popup-test-*.png +popup-test.png test-*.html test-*.js diff --git a/manifest.json b/manifest.json index 821cb09..c2ade26 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "gnexus-creds", "version": "0.1.0", "description": "Browser extension for gnexus-creds secret manager.", - "permissions": ["storage", "activeTab"], + "permissions": ["storage", "activeTab", "alarms"], "host_permissions": ["https://*/*", "http://*/*"], "background": { "service_worker": "src/background.js", diff --git a/src/background.js b/src/background.js index ff460c2..5636a3d 100644 --- a/src/background.js +++ b/src/background.js @@ -51,6 +51,17 @@ }); } +chrome.alarms.create("refresh-secrets-cache", { periodInMinutes: 15 }); + +chrome.alarms.onAlarm.addListener(async (alarm) => { + if (alarm.name !== "refresh-secrets-cache") return; + try { + await getSecretsCached(true); + } catch { + // ignore — no token or network error + } +}); + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { (async () => { try { diff --git a/src/content.css b/src/content.css index 87b74b7..5e28d67 100644 --- a/src/content.css +++ b/src/content.css @@ -19,12 +19,12 @@ border-top: 2px solid #58a6ff; color: #c9d1d9; font-family: 'IBM Plex Mono', ui-monospace, monospace; - font-size: 12px; - border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.4); - padding: 12px 16px; - min-width: 260px; - max-width: 400px; + font-size: 14px; + border-radius: 0; + box-shadow: 0 6px 16px rgba(0,0,0,0.5); + padding: 16px 20px; + min-width: 300px; + max-width: 460px; width: 100%; opacity: 0; transform: translateY(-12px); @@ -46,12 +46,38 @@ font-weight: 600; color: #58a6ff; word-break: break-word; - margin-bottom: 10px; + margin-bottom: 14px; + font-size: 15px; +} + +.gnexus-creds-autofill-list { + max-height: 200px; + overflow-y: auto; + margin-bottom: 14px; +} + +.gnexus-creds-autofill-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 0; + border-bottom: 1px solid #30363d; +} + +.gnexus-creds-autofill-row:last-child { + border-bottom: none; +} + +.gnexus-creds-autofill-row-title { + font-size: 13px; + color: #c9d1d9; + word-break: break-word; } .gnexus-creds-autofill-actions { display: flex; - gap: 8px; + gap: 10px; justify-content: flex-end; } @@ -59,12 +85,13 @@ background: #238636; border: none; color: #fff; - padding: 5px 12px; - border-radius: 4px; + padding: 7px 16px; + border-radius: 0; font-family: inherit; - font-size: 11px; + font-size: 13px; cursor: pointer; line-height: 1; + flex-shrink: 0; } .gnexus-creds-autofill-btn-secondary { diff --git a/src/content.js b/src/content.js index 47b95a4..9f428e7 100644 --- a/src/content.js +++ b/src/content.js @@ -132,7 +132,7 @@ }, 280); } -function showAutofillCard(form, usernameInput, passwordInput, secret) { +function showAutofillCard(form, usernameInput, passwordInput, secrets) { const key = form || passwordInput; if (!key || shownCards.has(key)) return; shownCards.add(key); @@ -140,22 +140,43 @@ const container = getAutofillContainer(); const card = document.createElement("div"); card.className = "gnexus-creds-autofill-card"; - card.innerHTML = ` -
${escapeHtml(secret.title)}
-
- - -
- `; + + if (secrets.length === 1) { + const secret = secrets[0]; + card.innerHTML = ` +
${escapeHtml(secret.title)}
+
+ + +
+ `; + } else { + const rows = secrets.map((s) => ` +
+
${escapeHtml(s.title)}
+ +
+ `).join(""); + card.innerHTML = ` +
Select account
+
${rows}
+
+ +
+ `; + } container.appendChild(card); requestAnimationFrame(() => { card.classList.add("gnexus-creds-autofill-card-visible"); }); - card.querySelector('[data-action="use"]').addEventListener("click", async () => { + card.addEventListener("click", async (e) => { + const btn = e.target.closest('[data-action="use"]'); + if (!btn) return; + const secretId = btn.dataset.id; try { - const revealed = await send("REVEAL_SECRET", { id: secret.id }); + const revealed = await send("REVEAL_SECRET", { id: secretId }); const fields = revealed.fields || []; const userField = fields.find((f) => /user|login|email/i.test(f.name)); const passField = fields.find((f) => /pass|password|пароль/i.test(f.name)); @@ -173,9 +194,12 @@ removeAutofillCard(card, key); }); - card.querySelector('[data-action="dismiss"]').addEventListener("click", () => { - removeAutofillCard(card, key); - }); + const dismissBtn = card.querySelector('[data-action="dismiss"]'); + if (dismissBtn) { + dismissBtn.addEventListener("click", () => { + removeAutofillCard(card, key); + }); + } } function triggerInputEvents(input) { @@ -271,8 +295,7 @@ for (const { form, usernameInput, passwordInput } of forms) { // Autofill card if (secretsForDomain.length > 0 && usernameInput) { - const bestMatch = secretsForDomain[0]; - showAutofillCard(form, usernameInput, passwordInput, bestMatch); + showAutofillCard(form, usernameInput, passwordInput, secretsForDomain); } // Save interceptor attachSubmitInterceptor(form, usernameInput, passwordInput); @@ -311,3 +334,13 @@ div.textContent = String(text); return div.innerHTML; } + +function escapeAttr(text) { + if (text == null) return ""; + return String(text) + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(//g, ">"); +}