04/26 New features, new for Firefox
This commit is contained in:
+99
-3
@@ -9,6 +9,41 @@ const Vault = (() => {
|
||||
let _items = [];
|
||||
let _folders = [];
|
||||
let _currentView = "vault";
|
||||
|
||||
// ── Web-app inactivity / session timeout ──────────────────────────────────
|
||||
// Mirrors the extension idle lock. Default 15 min; user can change in
|
||||
// Account Settings. Zero means never. Stored in localStorage so it persists
|
||||
// across page reloads without a server round-trip.
|
||||
|
||||
const WEB_IDLE_KEY = 'web_idle_minutes';
|
||||
const WEB_IDLE_DEFAULT = 15; // minutes; 0 = never
|
||||
let _webIdleTimer = null;
|
||||
|
||||
function _getWebIdleMinutes() {
|
||||
const v = parseInt(localStorage.getItem(WEB_IDLE_KEY), 10);
|
||||
return isNaN(v) ? WEB_IDLE_DEFAULT : v;
|
||||
}
|
||||
|
||||
function _resetWebIdleTimer() {
|
||||
if (_webIdleTimer) clearTimeout(_webIdleTimer);
|
||||
const mins = _getWebIdleMinutes();
|
||||
if (mins === 0) return;
|
||||
_webIdleTimer = setTimeout(() => {
|
||||
// Only lock if the vault is currently unlocked.
|
||||
if (!VaultSession.getKey()) return;
|
||||
console.log('[PassKeeper] Web inactivity lock after', mins, 'min');
|
||||
VaultSession.clear();
|
||||
showUnlockOverlay();
|
||||
showToast('Vault locked due to inactivity.', 'info');
|
||||
}, mins * 60_000);
|
||||
}
|
||||
|
||||
function _startWebIdleTracking() {
|
||||
const events = ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll'];
|
||||
const handler = () => _resetWebIdleTimer();
|
||||
events.forEach((e) => document.addEventListener(e, handler, { passive: true }));
|
||||
_resetWebIdleTimer();
|
||||
}
|
||||
let _activeFilter = null;
|
||||
let _sortOrder = "name-asc";
|
||||
|
||||
@@ -197,7 +232,7 @@ const Vault = (() => {
|
||||
|
||||
// ── View switching ────────────────────────────────────────────────────────
|
||||
|
||||
function switchView(view) {
|
||||
function switchView(view, { pushState = true } = {}) {
|
||||
_currentView = view;
|
||||
["vault", "security", "sharing", "emergency", "import-export"].forEach((v) => {
|
||||
document
|
||||
@@ -215,6 +250,12 @@ const Vault = (() => {
|
||||
.forEach((el) => el.classList.remove("active"));
|
||||
}
|
||||
|
||||
// Push to browser history so the back button navigates between views.
|
||||
if (pushState) {
|
||||
const hash = view === "vault" ? "" : `#${view}`;
|
||||
history.pushState({ view }, "", hash || window.location.pathname);
|
||||
}
|
||||
|
||||
if (view === "security") renderSecurityDashboard();
|
||||
if (view === "sharing") loadSharingView();
|
||||
if (view === "emergency") loadEmergencyView();
|
||||
@@ -643,6 +684,19 @@ const Vault = (() => {
|
||||
);
|
||||
makeSection("Old Passwords", "🕐", old, "Weak or reused passwords not changed in over 180 days.");
|
||||
|
||||
// ── Missing 2FA warning ──────────────────────────────────────────────────
|
||||
// Flag password items that have a URL but no TOTP URI saved.
|
||||
// These accounts likely support 2FA but the user hasn't stored it.
|
||||
const noTotp = pwItems.filter((i) =>
|
||||
i.plain?.url && !extractTotpSecret(i.plain?.totp_uri)
|
||||
);
|
||||
makeSection(
|
||||
'No 2FA Saved',
|
||||
'🔓',
|
||||
noTotp,
|
||||
'These accounts may support two-factor authentication but have no TOTP code stored.',
|
||||
);
|
||||
|
||||
// ── HaveIBeenPwned breach check ──────────────────────────────────────────
|
||||
// Run after the synchronous sections are rendered so the UI is immediately
|
||||
// useful. The HIBP API is queried in parallel for all passwords.
|
||||
@@ -1798,6 +1852,9 @@ const Vault = (() => {
|
||||
|
||||
async function openSettingsModal() {
|
||||
document.getElementById("settings-modal").classList.add("open");
|
||||
// Populate web-app session timeout select with saved value.
|
||||
const idleSel = document.getElementById("web-idle-select");
|
||||
if (idleSel) idleSel.value = String(_getWebIdleMinutes());
|
||||
// Reset change password fields
|
||||
["cp-current", "cp-new", "cp-confirm"].forEach((id) => {
|
||||
const el = document.getElementById(id);
|
||||
@@ -2717,10 +2774,21 @@ const Vault = (() => {
|
||||
return 30 - (Math.floor(Date.now() / 1000) % 30);
|
||||
}
|
||||
|
||||
// Track the clipboard clear timer so multiple rapid copies don't stack.
|
||||
let _clipboardClearTimer = null;
|
||||
|
||||
function copyToClipboard(text, msg) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => showToast(msg))
|
||||
.then(() => {
|
||||
showToast(msg);
|
||||
// Auto-clear clipboard after 30 seconds — industry-standard hygiene.
|
||||
if (_clipboardClearTimer) clearTimeout(_clipboardClearTimer);
|
||||
_clipboardClearTimer = setTimeout(() => {
|
||||
navigator.clipboard.writeText('').catch(() => {});
|
||||
_clipboardClearTimer = null;
|
||||
}, 30_000);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
@@ -2979,6 +3047,16 @@ const Vault = (() => {
|
||||
document
|
||||
.getElementById("btn-close-settings")
|
||||
?.addEventListener("click", () => closeModal("settings-modal"));
|
||||
|
||||
// Web-app session timeout setting.
|
||||
document.getElementById("web-idle-select")?.addEventListener("change", (e) => {
|
||||
const mins = parseInt(e.target.value, 10);
|
||||
localStorage.setItem(WEB_IDLE_KEY, mins);
|
||||
_resetWebIdleTimer();
|
||||
const label = mins === 0 ? 'never' : mins + ' min';
|
||||
showToast('Auto-lock set to ' + label + '.');
|
||||
console.log('[PassKeeper] Web idle timeout set to', label);
|
||||
});
|
||||
document
|
||||
.getElementById("settings-modal")
|
||||
?.addEventListener("click", (e) => {
|
||||
@@ -3161,10 +3239,28 @@ const Vault = (() => {
|
||||
document.getElementById("btn-add-item")?.click();
|
||||
});
|
||||
|
||||
// ── Browser history / back-button support ─────────────────────────────────
|
||||
const _hashToView = (hash) => {
|
||||
const v = (hash || '').replace('#', '').trim();
|
||||
const valid = ['vault', 'security', 'sharing', 'emergency', 'import-export'];
|
||||
return valid.includes(v) ? v : 'vault';
|
||||
};
|
||||
const _initialView = _hashToView(window.location.hash);
|
||||
history.replaceState({ view: _initialView }, '', window.location.href);
|
||||
window.addEventListener('popstate', (e) => {
|
||||
const view = e.state?.view || _hashToView(window.location.hash);
|
||||
switchView(view, { pushState: false });
|
||||
});
|
||||
|
||||
// Start web-app inactivity tracking.
|
||||
_startWebIdleTracking();
|
||||
|
||||
if (!VaultSession.getKey()) {
|
||||
showUnlockOverlay();
|
||||
} else {
|
||||
loadVault();
|
||||
// Restore view from hash after vault loads (avoids rendering before decrypt).
|
||||
if (_initialView !== 'vault') switchView(_initialView, { pushState: false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3268,4 +3364,4 @@ const Vault = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
|
||||
Reference in New Issue
Block a user