feat(desktop): implement workspace foundation + desktop-client skeleton
Plan 0 (workspaces): add workspaces + workspace_memberships schema, extend JWT/Actor/claims with primary_workspace_id, seed default workspace per tenant, thread workspace_id through tenant monitoring quota. Plan A (desktop skeleton): new Electron app (apps/desktop-client) with main/ preload/renderer, shared Vue component package (packages/ui-shared), and server surface — desktop client registration + token rotation + heartbeat, SSE task event stream, desktop accounts/tasks/content handlers, publish job endpoint, and supporting repositories, services, sqlc queries, and migrations. Hard cutover per plan: remove browser-extension monitoring callback endpoints, stub legacy media API in admin-web, and delete monitoring_callback_handler.go.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
import { app, safeStorage } from "electron/main";
|
||||
|
||||
type VaultStore = Record<string, string>;
|
||||
|
||||
function vaultPath(): string {
|
||||
return join(app.getPath("userData"), "desktop-vault.json");
|
||||
}
|
||||
|
||||
function loadStore(): VaultStore {
|
||||
try {
|
||||
return JSON.parse(readFileSync(vaultPath(), "utf8")) as VaultStore;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function persistStore(store: VaultStore): void {
|
||||
const target = vaultPath();
|
||||
mkdirSync(dirname(target), { recursive: true });
|
||||
writeFileSync(target, JSON.stringify(store, null, 2), "utf8");
|
||||
}
|
||||
|
||||
export function describeVaultBackend() {
|
||||
return {
|
||||
encryptionAvailable: safeStorage.isEncryptionAvailable(),
|
||||
backend:
|
||||
typeof safeStorage.getSelectedStorageBackend === "function"
|
||||
? safeStorage.getSelectedStorageBackend()
|
||||
: "unknown",
|
||||
path: vaultPath(),
|
||||
};
|
||||
}
|
||||
|
||||
export function writeEncrypted(key: string, value: string): void {
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
throw new Error("safeStorage encryption is unavailable");
|
||||
}
|
||||
|
||||
const store = loadStore();
|
||||
store[key] = safeStorage.encryptString(value).toString("base64");
|
||||
persistStore(store);
|
||||
}
|
||||
|
||||
export function readEncrypted(key: string): string | null {
|
||||
const encoded = loadStore()[key];
|
||||
if (!encoded) {
|
||||
return null;
|
||||
}
|
||||
return safeStorage.decryptString(Buffer.from(encoded, "base64"));
|
||||
}
|
||||
Reference in New Issue
Block a user