Files
relicario/extension/src/service-worker/session-timer.ts
adlee-was-taken ba5d218841 fix(ext/sw): inactivity timer resets on all non-passive messages (Plan C Phase 5)
DEV-C P2: an active autofiller never opens the popup, so under the old
rule it got force-locked despite continuous use. Inverts the rule:
reset on all messages except a documented exclusion set (only
get_autofill_candidates today).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:42:44 -04:00

65 lines
2.0 KiB
TypeScript

/// 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<typeof setTimeout> | 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<string> = new Set([
'get_autofill_candidates',
]);