feat: @agor/stores package (3 stores) + 58 BackendAdapter tests

@agor/stores:
- theme.svelte.ts, notifications.svelte.ts, health.svelte.ts extracted
- Original files replaced with re-exports (zero consumer changes needed)
- pnpm workspace + Vite/tsconfig aliases configured

BackendAdapter tests (58 new):
- backend-adapter.test.ts: 9 tests (lifecycle, singleton, testing seam)
- tauri-adapter.test.ts: 28 tests (invoke mapping, command names, params)
- electrobun-adapter.test.ts: 21 tests (RPC names, capabilities, stubs)

Total: 523 tests passing (was 465, +58)
This commit is contained in:
Hibryda 2026-03-22 04:45:56 +01:00
parent 5e1fd62ed9
commit f0850f0785
22 changed files with 1389 additions and 25 deletions

View file

@ -265,6 +265,70 @@ export class SettingsDb {
this.db.query("DELETE FROM keybindings WHERE id = ?").run(id);
}
// ── Remote credential vault (Feature 3) ──────────────────────────────────
private getMachineKey(): string {
try {
const h = require("os").hostname();
return h || "agor-default-key";
} catch {
return "agor-default-key";
}
}
private xorObfuscate(text: string, key: string): string {
const result: number[] = [];
for (let i = 0; i < text.length; i++) {
result.push(text.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return Buffer.from(result).toString("base64");
}
private xorDeobfuscate(encoded: string, key: string): string {
const buf = Buffer.from(encoded, "base64");
const result: string[] = [];
for (let i = 0; i < buf.length; i++) {
result.push(String.fromCharCode(buf[i] ^ key.charCodeAt(i % key.length)));
}
return result.join("");
}
storeRelayCredential(url: string, token: string, label?: string): void {
const key = this.getMachineKey();
const obfuscated = this.xorObfuscate(token, key);
const data = JSON.stringify({ url, token: obfuscated, label: label ?? url });
this.setSetting(`relay_cred_${url}`, data);
}
getRelayCredential(url: string): { url: string; token: string; label: string } | null {
const raw = this.getSetting(`relay_cred_${url}`);
if (!raw) return null;
try {
const data = JSON.parse(raw) as { url: string; token: string; label: string };
const key = this.getMachineKey();
return { url: data.url, token: this.xorDeobfuscate(data.token, key), label: data.label };
} catch {
return null;
}
}
listRelayCredentials(): Array<{ url: string; label: string }> {
const all = this.getAll();
const results: Array<{ url: string; label: string }> = [];
for (const [k, v] of Object.entries(all)) {
if (!k.startsWith("relay_cred_")) continue;
try {
const data = JSON.parse(v) as { url: string; label: string };
results.push({ url: data.url, label: data.label });
} catch { /* skip malformed */ }
}
return results;
}
deleteRelayCredential(url: string): void {
this.db.query("DELETE FROM settings WHERE key = ?").run(`relay_cred_${url}`);
}
// ── Lifecycle ─────────────────────────────────────────────────────────────
close(): void {