alarms.ts

3.1 KB · raw

1import { ensureSettingsLoaded, getSettings, lock } from "./vault";2import { isRecoveryConfirmPending } from "./recovery-pending";34const AUTO_LOCK_ALARM = "autolock";5const DEFAULT_AUTO_LOCK_MINUTES = 5;6const MIN_IDLE_SECONDS = 15;7let autoLockDeferredUntil = 0;89/** Keep a bounded foreground security ceremony alive across its normal lock deadline. */10export function deferAutoLockForOperation(durationMs: number): () => void {11  const deadline = Date.now() + Math.max(0, durationMs);12  autoLockDeferredUntil = Math.max(autoLockDeferredUntil, deadline);13  return () => {14    if (autoLockDeferredUntil !== deadline) return;15    autoLockDeferredUntil = 0;16    touchAutoLock();17  };18}1920function rescheduleDeferredAutoLock(): boolean {21  const remainingMs = autoLockDeferredUntil - Date.now();22  if (remainingMs <= 0) return false;23  chrome.alarms.create(AUTO_LOCK_ALARM, {24    delayInMinutes: remainingMs / 60_000,25  });26  return true;27}2829function resolveAutoLockMinutes(): number {30  const setting = getSettings().autoLockMinutes;31  if (typeof setting !== "number") return DEFAULT_AUTO_LOCK_MINUTES;32  return setting; // 0 = never33}3435export function touchAutoLock(): void {36  if (!chrome.alarms?.clear || !chrome.alarms?.create) return;37  chrome.alarms.clear(AUTO_LOCK_ALARM);38  const minutes = resolveAutoLockMinutes();39  if (minutes <= 0) return; // "never"40  chrome.alarms.create(AUTO_LOCK_ALARM, { delayInMinutes: minutes });41}4243export function clearAutoLock(): void {44  if (!chrome.alarms?.clear) return;45  chrome.alarms.clear(AUTO_LOCK_ALARM);46}4748export async function setupAutoLock(): Promise<void> {49  await ensureSettingsLoaded();50  if (!chrome.idle?.setDetectionInterval) return;51  const minutes = resolveAutoLockMinutes();52  const seconds = minutes > 0 ? Math.max(MIN_IDLE_SECONDS, minutes * 60) : MIN_IDLE_SECONDS;53  chrome.idle.setDetectionInterval(seconds);54}5556function broadcastLocked(): void {57  // Reach the popup (and any other extension UI context) via runtime.58  chrome.runtime?.sendMessage?.({ type: "VAULT_LOCKED" })?.catch?.(() => {});59  // Reach content scripts via tabs. Tabs without Peach content scripts60  // loaded will reject the sendMessage; ignore those.61  try {62    chrome.tabs.query({}, (tabs) => {63      for (const tab of tabs) {64        if (tab.id === undefined) continue;65        chrome.tabs.sendMessage(tab.id, { type: "VAULT_LOCKED" }).catch(() => {});66      }67    });68  } catch {69    // tabs permission may not be available in all contexts70  }71}7273if (chrome.alarms?.onAlarm?.addListener) {74  chrome.alarms.onAlarm.addListener(async (alarm) => {75    if (alarm.name !== AUTO_LOCK_ALARM) return;76    if (rescheduleDeferredAutoLock()) return;77    if (await isRecoveryConfirmPending()) return;78    await lock();79    broadcastLocked();80  });81}8283if (chrome.idle?.onStateChanged) {84  chrome.idle.onStateChanged.addListener(async (state) => {85    if (resolveAutoLockMinutes() <= 0) return;86    if (state !== "idle" && state !== "locked") return;87    if (state === "idle" && rescheduleDeferredAutoLock()) return;88    if (await isRecoveryConfirmPending()) return;89    await lock();90    broadcastLocked();91  });92}