diff --git a/extension/src/service-worker/__tests__/session-timer.test.ts b/extension/src/service-worker/__tests__/session-timer.test.ts index 39a56fc..93a04aa 100644 --- a/extension/src/service-worker/__tests__/session-timer.test.ts +++ b/extension/src/service-worker/__tests__/session-timer.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; import * as timer from '../session-timer'; +import { READ_ONLY_CONTENT_CALLABLE } from '../session-timer'; describe('session-timer', () => { beforeEach(() => { @@ -97,3 +98,29 @@ describe('session-timer', () => { expect(cb).not.toHaveBeenCalled(); }); }); + +describe('READ_ONLY_CONTENT_CALLABLE — inversion exclusion set', () => { + // The SW handler invokes resetTimer() on every message whose type is NOT + // in this set. These cases encode the documented inversion contract from + // Plan C Phase 5: popup-only messages reset, content-callable writes + // reset, only passive content reads (currently just get_autofill_candidates) + // do NOT reset. + + it('popup-only message would reset the timer (not in exclusion set)', () => { + // e.g. list_items — popup interaction is unambiguously active use + expect(READ_ONLY_CONTENT_CALLABLE.has('list_items')).toBe(false); + }); + + it('content-callable get_autofill_candidates does NOT reset (in exclusion set)', () => { + expect(READ_ONLY_CONTENT_CALLABLE.has('get_autofill_candidates')).toBe(true); + }); + + it('content-callable capture_save_login DOES reset (write op = active use)', () => { + expect(READ_ONLY_CONTENT_CALLABLE.has('capture_save_login')).toBe(false); + }); + + it('content-callable check_credential DOES reset', () => { + // Asking "is this credential already saved" is user-initiated. + expect(READ_ONLY_CONTENT_CALLABLE.has('check_credential')).toBe(false); + }); +}); diff --git a/extension/src/service-worker/index.ts b/extension/src/service-worker/index.ts index e3dec89..cab9a83 100644 --- a/extension/src/service-worker/index.ts +++ b/extension/src/service-worker/index.ts @@ -2,12 +2,12 @@ /// forwards every message into router/index.route(). import type { Request, Response, SessionTimeoutConfig } from '../shared/messages'; -import { CONTENT_CALLABLE_TYPES } from '../shared/messages'; import type { RouterState } from './router/index'; import { route } from './router/index'; import * as vault from './vault'; import { clearCurrent } from './session'; import * as sessionTimer from './session-timer'; +import { READ_ONLY_CONTENT_CALLABLE } from './session-timer'; // @ts-ignore TS2307 — resolved by webpack alias / copy import initDefault, { initSync } from '../../wasm/relicario_wasm.js'; @@ -73,7 +73,10 @@ chrome.commands.onCommand.addListener((command) => { chrome.runtime.onMessage.addListener( (request: Request, sender: chrome.runtime.MessageSender, sendResponse: (r: Response) => void) => { (async () => { - if (!CONTENT_CALLABLE_TYPES.has(request.type as never)) { + // Plan C Phase 5: invert the reset rule. Reset on every message + // except a documented passive-read exclusion set, so an active + // autofiller / content-driven flow keeps the vault alive. + if (!READ_ONLY_CONTENT_CALLABLE.has(request.type)) { sessionTimer.resetTimer(); } if (!state.wasm) { diff --git a/extension/src/service-worker/session-timer.ts b/extension/src/service-worker/session-timer.ts index 9948586..2a7800a 100644 --- a/extension/src/service-worker/session-timer.ts +++ b/extension/src/service-worker/session-timer.ts @@ -48,3 +48,17 @@ export function stopTimer(): void { 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', +]);