/** * @file prefsBackup.ts * @description Export / import of THIS browser's preferences (theme, language, * notification prefs, pet, sidebar, workspace layout …) as one JSON file, for * Settings → Data. Only allowlisted `localStorage` keys travel — never the * dashboard access token or a cache — and an import writes only those keys, * as strings, so a crafted file cannot plant anything else. * @author Nguyễn Ngọc Trí Vĩ */ export const PREFS_FORMAT = "agenthub-browser-prefs"; const EXACT = new Set([ "theme", "i18nextLng", "agent-monitor-notifications", "dashboard_tab", "sidebar-collapsed", "agent-dashboard-sagi-enabled", "agent-dashboard-sagi-muted", "agent-dashboard-sagi-pos", "agenthub-data-scope", ]); /** Whether a `localStorage` key is a preference that may be exported/imported. */ export function isPrefKey(key: string): boolean { return EXACT.has(key) || key.startsWith("agenthub."); } export interface PrefsFile { format: typeof PREFS_FORMAT; version: 1; exported_at: string; prefs: Record; } /** Every preference key this browser holds. */ export function exportPrefs(): PrefsFile { const prefs: Record = {}; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && isPrefKey(k)) prefs[k] = localStorage.getItem(k) ?? ""; } return { format: PREFS_FORMAT, version: 1, exported_at: new Date().toISOString(), prefs }; } /** Write a prefs file's allowlisted string values; returns how many. Throws on a foreign file. */ export function importPrefs(text: string): number { const data = JSON.parse(text) as Partial; if (!data || data.format !== PREFS_FORMAT || typeof data.prefs !== "object" || !data.prefs) throw new Error("not an AgentHub preferences file"); let n = 0; for (const [k, v] of Object.entries(data.prefs)) { if (!isPrefKey(k) || typeof v !== "string") continue; localStorage.setItem(k, v); n++; } return n; }