/// Vault operations module. /// /// Bridges the WASM crypto functions with the git host API to provide /// high-level vault operations: fetch/decrypt manifest, fetch/decrypt entries, /// encrypt/write entries, search, and URL matching. import type { GitHost } from './git-host'; import type { Entry, Manifest, ManifestEntry } from '../shared/types'; // WASM module reference — set once during init. // eslint-disable-next-line @typescript-eslint/no-explicit-any let wasm: any = null; /// Store the WASM module reference after initialization. // eslint-disable-next-line @typescript-eslint/no-explicit-any export function setWasm(w: any): void { wasm = w; } function requireWasm(): any { if (!wasm) throw new Error('WASM module not initialized'); return wasm; } /// Vault metadata: salt and KDF params stored unencrypted in the repo. export interface VaultMeta { salt: Uint8Array; paramsJson: string; } /// Read the vault salt and KDF params from the git repo. export async function fetchVaultMeta(git: GitHost): Promise { const saltBytes = await git.readFile('.idfoto/salt'); const paramsRaw = await git.readFile('.idfoto/params.json'); const paramsJson = new TextDecoder().decode(paramsRaw); return { salt: saltBytes, paramsJson }; } /// Fetch and decrypt the manifest from the git repo. export async function fetchAndDecryptManifest( git: GitHost, masterKey: Uint8Array, ): Promise { const w = requireWasm(); const ciphertext = await git.readFile('manifest.enc'); const json = w.decrypt_manifest(ciphertext, masterKey); return JSON.parse(json) as Manifest; } /// Fetch and decrypt a single entry from the git repo. export async function fetchAndDecryptEntry( git: GitHost, masterKey: Uint8Array, id: string, ): Promise { const w = requireWasm(); const ciphertext = await git.readFile(`entries/${id}.enc`); const json = w.decrypt_entry(ciphertext, masterKey); return JSON.parse(json) as Entry; } /// Encrypt an entry and write it to the git repo. export async function encryptAndWriteEntry( git: GitHost, masterKey: Uint8Array, id: string, entry: Entry, message: string, ): Promise { const w = requireWasm(); const entryJson = JSON.stringify(entry); const ciphertext = w.encrypt_entry(entryJson, masterKey); await git.writeFile(`entries/${id}.enc`, ciphertext, message); } /// Encrypt the manifest and write it to the git repo. export async function encryptAndWriteManifest( git: GitHost, masterKey: Uint8Array, manifest: Manifest, message: string, ): Promise { const w = requireWasm(); const manifestJson = JSON.stringify(manifest); const ciphertext = w.encrypt_manifest(manifestJson, masterKey); await git.writeFile('manifest.enc', ciphertext, message); } /// Filter manifest entries by group (case-insensitive). If no group given, returns all. export function listEntries( manifest: Manifest, group?: string, ): Array<[string, ManifestEntry]> { const entries = Object.entries(manifest.entries); if (!group) return entries; const g = group.toLowerCase(); return entries.filter(([, e]) => e.group?.toLowerCase() === g ); } /// Case-insensitive substring search on name, url, and username. export function searchEntries( manifest: Manifest, query: string, ): Array<[string, ManifestEntry]> { const q = query.toLowerCase(); return Object.entries(manifest.entries).filter(([, e]) => { if (e.name.toLowerCase().includes(q)) return true; if (e.url?.toLowerCase().includes(q)) return true; if (e.username?.toLowerCase().includes(q)) return true; return false; }); } /// Find entries whose URL matches the given page URL by hostname. export function findByUrl( manifest: Manifest, url: string, ): Array<[string, ManifestEntry]> { let hostname: string; try { hostname = new URL(url).hostname; } catch { return []; } return Object.entries(manifest.entries).filter(([, e]) => { if (!e.url) return false; try { const entryHost = new URL(e.url).hostname; return entryHost === hostname; } catch { return false; } }); }