6c6828006b
Adds opt-in (default OFF) auto-lock: after X minutes of no user input the app calls signOut() (full memory wipe + PIN re-entry on next open). Settings dropdown (Aus / 5 / 15 / 30 / 60 min) lives in SecurityCenter below the existing wipe-on-close toggle. The idle timer is mounted in AppShell via useIdleAutoLock; activity events are throttled to 1 Hz to avoid timer thrash on rapid mouse movement. The localStorage key is added to PRESERVE_LOCAL_STORAGE so a wipe never silently disables the feature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
57 lines
2.1 KiB
TypeScript
57 lines
2.1 KiB
TypeScript
import { clearConvKeyCache } from '@chat-app/shared/chat';
|
|
|
|
import { devLocalSecretStore } from './secretStore';
|
|
|
|
// Aggressively scrub local crypto + chat caches on sign-out (and on
|
|
// app-close if the user opted in). Preserves things that aren't sensitive
|
|
// and would be annoying to lose (theme, locale, install-id).
|
|
//
|
|
// We can't enumerate IndexedDB names without async + the indexedDB API,
|
|
// so we list the ones we know about explicitly. Adding a new local store
|
|
// later? Append to LOCAL_DBS.
|
|
|
|
const LOCAL_DBS = ['soundboard', 'message-cache', 'chatapp-attachments'];
|
|
const PRESERVE_LOCAL_STORAGE = new Set([
|
|
'chatapp.theme',
|
|
'chatapp.locale',
|
|
'chatapp.installId',
|
|
'chatapp.wipeOnClose.v1',
|
|
'chatapp.autoLockMinutes.v1',
|
|
'i18nextLng',
|
|
]);
|
|
|
|
export async function wipeLocalState(userId: string | null): Promise<void> {
|
|
// 1. Per-conversation key cache (in-memory).
|
|
try { clearConvKeyCache(); } catch { /* never throws but be defensive */ }
|
|
|
|
// 2. Stronghold / secret-store: drop the user-priv blob for this user.
|
|
if (userId) {
|
|
try { await devLocalSecretStore.removeSecret('chatapp.userpriv.' + userId); }
|
|
catch (err) { console.warn('[wipe] userpriv remove failed', err); }
|
|
}
|
|
|
|
// 3. localStorage — preserve only the explicit whitelist.
|
|
try {
|
|
const keysToDrop: string[] = [];
|
|
for (let i = 0; i < window.localStorage.length; i++) {
|
|
const k = window.localStorage.key(i);
|
|
if (k && !PRESERVE_LOCAL_STORAGE.has(k)) keysToDrop.push(k);
|
|
}
|
|
for (const k of keysToDrop) window.localStorage.removeItem(k);
|
|
} catch (err) { console.warn('[wipe] localStorage clear failed', err); }
|
|
|
|
// 4. sessionStorage — always full.
|
|
try { window.sessionStorage.clear(); } catch { /* ignored */ }
|
|
|
|
// 5. IndexedDB — delete known databases. Resolves even when blocked so
|
|
// we don't hang sign-out forever.
|
|
await Promise.allSettled(LOCAL_DBS.map((name) => new Promise<void>((resolve) => {
|
|
try {
|
|
const req = window.indexedDB.deleteDatabase(name);
|
|
req.onsuccess = () => resolve();
|
|
req.onerror = () => resolve();
|
|
req.onblocked = () => resolve();
|
|
} catch { resolve(); }
|
|
})));
|
|
}
|