/// Session inactivity timer. /// /// Two modes: /// - `inactivity`: fires the expiry callback after N minutes of no /// resetTimer() calls (i.e. no popup/vault-tab messages). /// - `every_time`: no timer — the session is cleared on every popup close /// (handled elsewhere). resetTimer() is a no-op. import type { SessionTimeoutConfig } from '../shared/messages'; let config: SessionTimeoutConfig = { mode: 'inactivity', minutes: 15 }; let timerId: ReturnType | null = null; let expiredCallback: (() => void) | null = null; /** Register the callback invoked when the inactivity timer fires. */ export function onExpired(cb: () => void): void { expiredCallback = cb; } /** Return the current session timeout config. */ export function getConfig(): SessionTimeoutConfig { return config; } /** Update the config. Also stops any running timer (caller should * resetTimer() afterwards if the session is still active). */ export function setConfig(c: SessionTimeoutConfig): void { config = c; stopTimer(); } /** Clear and restart the inactivity timer. No-op when mode is `every_time`. */ export function resetTimer(): void { stopTimer(); if (config.mode !== 'inactivity') return; const ms = config.minutes * 60 * 1000; timerId = setTimeout(() => { timerId = null; if (expiredCallback) expiredCallback(); }, ms); } /** Cancel any pending timer without changing config. */ export function stopTimer(): void { if (timerId !== null) { clearTimeout(timerId); timerId = null; } } /** * Content-callable message types that should NOT reset the inactivity timer. * * Rationale: a content script reading available autofill candidates is a * passive query — it shouldn't keep the vault alive indefinitely while the * user isn't actually interacting with it. * * Today this is the only known passive read; if a future content message * is also passive, add it here with a one-line justification. */ export const READ_ONLY_CONTENT_CALLABLE: ReadonlySet = new Set([ 'get_autofill_candidates', ]);