import { ensureSettingsLoaded, getSettings, lock } from "./vault"; import { isRecoveryConfirmPending } from "./recovery-pending"; const AUTO_LOCK_ALARM = "autolock"; const DEFAULT_AUTO_LOCK_MINUTES = 5; const MIN_IDLE_SECONDS = 15; let autoLockDeferredUntil = 0; /** Keep a bounded foreground security ceremony alive across its normal lock deadline. */ export function deferAutoLockForOperation(durationMs: number): () => void { const deadline = Date.now() + Math.max(0, durationMs); autoLockDeferredUntil = Math.max(autoLockDeferredUntil, deadline); return () => { if (autoLockDeferredUntil !== deadline) return; autoLockDeferredUntil = 0; touchAutoLock(); }; } function rescheduleDeferredAutoLock(): boolean { const remainingMs = autoLockDeferredUntil - Date.now(); if (remainingMs <= 0) return false; chrome.alarms.create(AUTO_LOCK_ALARM, { delayInMinutes: remainingMs / 60_000, }); return true; } function resolveAutoLockMinutes(): number { const setting = getSettings().autoLockMinutes; if (typeof setting !== "number") return DEFAULT_AUTO_LOCK_MINUTES; return setting; // 0 = never } export function touchAutoLock(): void { if (!chrome.alarms?.clear || !chrome.alarms?.create) return; chrome.alarms.clear(AUTO_LOCK_ALARM); const minutes = resolveAutoLockMinutes(); if (minutes <= 0) return; // "never" chrome.alarms.create(AUTO_LOCK_ALARM, { delayInMinutes: minutes }); } export function clearAutoLock(): void { if (!chrome.alarms?.clear) return; chrome.alarms.clear(AUTO_LOCK_ALARM); } export async function setupAutoLock(): Promise { await ensureSettingsLoaded(); if (!chrome.idle?.setDetectionInterval) return; const minutes = resolveAutoLockMinutes(); const seconds = minutes > 0 ? Math.max(MIN_IDLE_SECONDS, minutes * 60) : MIN_IDLE_SECONDS; chrome.idle.setDetectionInterval(seconds); } function broadcastLocked(): void { // Reach the popup (and any other extension UI context) via runtime. chrome.runtime?.sendMessage?.({ type: "VAULT_LOCKED" })?.catch?.(() => {}); // Reach content scripts via tabs. Tabs without Peach content scripts // loaded will reject the sendMessage; ignore those. try { chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { if (tab.id === undefined) continue; chrome.tabs.sendMessage(tab.id, { type: "VAULT_LOCKED" }).catch(() => {}); } }); } catch { // tabs permission may not be available in all contexts } } if (chrome.alarms?.onAlarm?.addListener) { chrome.alarms.onAlarm.addListener(async (alarm) => { if (alarm.name !== AUTO_LOCK_ALARM) return; if (rescheduleDeferredAutoLock()) return; if (await isRecoveryConfirmPending()) return; await lock(); broadcastLocked(); }); } if (chrome.idle?.onStateChanged) { chrome.idle.onStateChanged.addListener(async (state) => { if (resolveAutoLockMinutes() <= 0) return; if (state !== "idle" && state !== "locked") return; if (state === "idle" && rescheduleDeferredAutoLock()) return; if (await isRecoveryConfirmPending()) return; await lock(); broadcastLocked(); }); }