import { base64FromBytes, bytesFromBase64, type SecretStore } from '@chat-app/shared/auth'; import { isTauriRuntime } from './globalShortcut'; import { makeSecureFileStore, migrateLocalStorageToVault } from './secureFileStore'; // Two-tier SecretStore: // - Tauri runtime: encrypted single-file vault in `appLocalDataDir` // (`secureFileStore` — XSalsa20-Poly1305 + Argon2id KDF). 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 fileStore = makeSecureFileStore(userId); try { // Probe write/read to confirm the vault is usable on this machine. // If anything throws (perm denied, disk full, KDF error), fall back to // localStorage so the rest of the app keeps working. await fileStore.getSecret('__probe'); activeBackend = fileStore; try { await migrateLocalStorageToVault(userId, PREFIX); } catch (err: unknown) { console.warn('vault migration failed', err); } } catch (err: unknown) { console.warn('secure file vault 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; }