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:
parent
5e1fd62ed9
commit
f0850f0785
22 changed files with 1389 additions and 25 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue