79 lines
2.2 KiB
TypeScript
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;
|
|
}
|