ten99timecard/client/src/lib/storage/vault.ts
2026-03-04 21:21:59 -05:00

79 lines
2.2 KiB
TypeScript

/**
* 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<void> {
const json = JSON.stringify(data);
const blob = await encrypt(json, this.password);
await this.adapter.save(blob);
}
async load(): Promise<AppData | null> {
const blob = await this.adapter.load();
if (!blob) return null;
const json = await decrypt(blob, this.password);
return JSON.parse(json);
}
async clear(): Promise<void> {
await this.adapter.clear();
}
/** Export current data as an encrypted file regardless of active storage mode */
async exportToFile(data: AppData): Promise<void> {
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<boolean> {
const cs = new CookieStorage(username);
const blob = await cs.load();
return blob != null;
}