import { base64FromBytes, bytesFromBase64, type SecretStore } from '@chat-app/shared/auth'; import { isTauriRuntime } from './globalShortcut'; import { makeStrongholdStore, migrateLocalStorageToStronghold, } from './strongholdStore'; // Two-tier SecretStore: // - Electron runtime: safeStorage-backed store in `` // (`strongholdStore`). DPAPI on Windows / Keychain on macOS / // libsecret on Linux seals the per-user blob. Survives app // reinstalls when the OS preserves the data dir. // - Web / pre-auth: plain localStorage (legacy fallback). // // Callers import `devLocalSecretStore` and call `setSecretStoreUser(userId)` // once the session is known. The singleton object's identity is stable so // existing imports keep working. const PREFIX = 'chatapp.secret:'; const localStore: SecretStore = { async getSecret(key: string): Promise { const raw = window.localStorage.getItem(PREFIX + key); if (!raw) return null; return bytesFromBase64(raw); }, async setSecret(key: string, value: Uint8Array): Promise { const encoded = await base64FromBytes(value); window.localStorage.setItem(PREFIX + key, encoded); }, async removeSecret(key: string): Promise { window.localStorage.removeItem(PREFIX + key); }, }; let activeBackend: SecretStore = localStore; let activeUserId: string | null = null; export async function setSecretStoreUser(userId: string | null): Promise { if (userId === activeUserId) return; activeUserId = userId; if (userId && isTauriRuntime()) { const store = makeStrongholdStore(userId); try { // Probe read to confirm the store is usable on this machine. If // anything throws, fall back to localStorage so the rest of the // app keeps working. await store.getSecret('__probe'); activeBackend = store; try { await migrateLocalStorageToStronghold(userId, PREFIX); } catch (err: unknown) { console.warn('vault migration failed', err); } } catch (err: unknown) { console.warn('secure store init failed — falling back to localStorage', err); activeBackend = localStore; } } else { activeBackend = localStore; } } export const devLocalSecretStore: SecretStore = { async getSecret(key) { return activeBackend.getSecret(key); }, async setSecret(key, value) { return activeBackend.setSecret(key, value); }, async removeSecret(key) { return activeBackend.removeSecret(key); }, }; export function isEncryptedVaultActive(): boolean { return activeBackend !== localStore; }