/** * Vault — the bridge between app state, encryption, and storage adapters. */ import type { AppData, StorageMode } from '@/types'; import { encrypt, decrypt } from '@/lib/crypto/encryption'; import { CookieStorage, FileStorage, CloudStorage, type StorageAdapter, } from './adapters'; export interface VaultConfig { mode: StorageMode; username: string; password: string; /** Required iff mode === 'cloud' */ apiUrl?: string; getCloudToken?: () => string | null; } export class Vault { private adapter: StorageAdapter; private password: string; /** File adapter is held separately so the UI can call setFile() */ public readonly fileAdapter: FileStorage; constructor(cfg: VaultConfig) { this.password = cfg.password; this.fileAdapter = new FileStorage(); switch (cfg.mode) { case 'file': this.adapter = this.fileAdapter; break; case 'cloud': if (!cfg.apiUrl || !cfg.getCloudToken) { throw new Error('Cloud storage requires apiUrl and getCloudToken'); } this.adapter = new CloudStorage(cfg.apiUrl, cfg.getCloudToken); break; case 'cookie': default: this.adapter = new CookieStorage(cfg.username); break; } } async save(data: AppData): Promise { const json = JSON.stringify(data); const blob = await encrypt(json, this.password); await this.adapter.save(blob); } async load(): Promise { const blob = await this.adapter.load(); if (!blob) return null; const json = await decrypt(blob, this.password); return JSON.parse(json); } async clear(): Promise { await this.adapter.clear(); } /** Export current data as an encrypted file regardless of active storage mode */ async exportToFile(data: AppData): Promise { const json = JSON.stringify(data); const blob = await encrypt(json, this.password); await this.fileAdapter.save(blob); } } /** Check if a user already has encrypted data under their username (cookie mode) */ export async function cookieDataExists(username: string): Promise { const cs = new CookieStorage(username); const blob = await cs.load(); return blob != null; }