05/17 enhance codes

This commit is contained in:
Nguyen Ngo
2026-05-17 18:29:13 -04:00
parent eee5f62689
commit afc3250c48
6 changed files with 246 additions and 52 deletions
+22 -7
View File
@@ -235,6 +235,13 @@ function isMatch(item) {
// ── API helpers ───────────────────────────────────────────────────────────────
// Singleton promise for the in-flight token refresh.
// When a 401 triggers a refresh, all concurrent requests that also receive a
// 401 await this same promise instead of starting their own — preventing the
// second refresh from using an already-rotated (and therefore blacklisted)
// refresh token, which would cause an unexpected sign-out.
let _refreshPromise = null;
async function apiFetch(path, options = {}) {
const { access_token } = await chrome.storage.session.get("access_token");
const headers = {
@@ -246,7 +253,13 @@ async function apiFetch(path, options = {}) {
let res = await fetch(`${API_BASE}${path}`, { ...options, headers });
if (res.status === 401) {
const refreshed = await tryRefreshToken();
// Coalesce concurrent 401 retries onto a single refresh attempt.
if (!_refreshPromise) {
_refreshPromise = tryRefreshToken().finally(() => {
_refreshPromise = null;
});
}
const refreshed = await _refreshPromise;
if (!refreshed) {
signOut();
return null;
@@ -899,7 +912,9 @@ function renderList() {
if (!item?.plain?.totp_uri) return;
const code = await getTotpCode(item.plain.totp_uri).catch(() => null);
if (code) {
navigator.clipboard.writeText(code);
// Use _copyWithAutoClear so the 2FA code is wiped from the clipboard
// after 30 s, consistent with password copy behaviour.
_copyWithAutoClear(code);
btn.title = "Copied!";
btn.style.color = "#16a34a";
setTimeout(() => {
@@ -1163,7 +1178,7 @@ async function saveCredential(data) {
const res = await apiFetch("/api/vault", {
method: "POST",
body: JSON.stringify({
name: "password",
name, // actual site name (plaintext fallback for legacy clients)
item_type: "password",
folder_id,
enc_data,
@@ -1232,9 +1247,9 @@ function generatePassword(length, useLower, useUpper, useNumbers, useSymbols) {
if (useSymbols)
required.push(GEN_SETS.symbols[_cryptoRandInt(GEN_SETS.symbols.length)]);
const arr = new Uint32Array(length);
crypto.getRandomValues(arr);
const rest = Array.from(arr).map((n) => pool[n % pool.length]);
// Use _cryptoRandInt() for each character to avoid modulo bias that arises
// when pool.length is not a power of 2.
const rest = Array.from({ length }, () => pool[_cryptoRandInt(pool.length)]);
// Splice required chars into random positions and trim to length.
const combined = [...rest];
@@ -1462,7 +1477,7 @@ async function addItemToVault() {
const res = await apiFetch("/api/vault", {
method: "POST",
body: JSON.stringify({
name: "password",
name, // actual site name (plaintext fallback for legacy clients)
item_type: "password",
folder_id,
enc_data,