/* global chrome */ const STORAGE_KEYS = { host: 'csai_host', port: 'csai_port', https: 'csai_https', lastProjectId: 'csai_last_project', lastRole: 'csai_last_role', lastAgentMode: 'csai_last_agent_mode', lastInstruction: 'csai_last_instruction', filterApiOnly: 'csai_filter_api_only', captureEnabled: 'csai_capture_enabled', renderMarkdown: 'csai_render_markdown', showDebugEvents: 'csai_show_debug', }; const SESSION_TOKEN_KEY = 'csai_token'; const DEFAULTS = { host: '127.0.0.1', port: '8080', https: true, lastInstruction: CSAI_DEFAULT_INSTRUCTION, filterApiOnly: true, captureEnabled: true, renderMarkdown: true, showDebugEvents: false, }; function extensionContextAlive() { try { return !!(typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id); } catch (_) { return false; } } function extensionContextError() { const err = new Error('Extension context invalidated'); err.name = 'ExtensionContextError'; err.contextInvalidated = true; return err; } function isExtensionContextError(err) { if (!err) return false; if (err.contextInvalidated || err.name === 'ExtensionContextError') return true; return /extension context invalidated/i.test(String(err.message || err)); } function normalizeStorageError(err) { if (isExtensionContextError(err)) return extensionContextError(); return err instanceof Error ? err : new Error(String(err)); } function rejectLastError(reject) { const msg = chrome.runtime.lastError && chrome.runtime.lastError.message; if (!msg) return false; reject(/invalidated/i.test(msg) ? extensionContextError() : new Error(msg)); return true; } function localGet(keys) { return new Promise((resolve, reject) => { try { if (!extensionContextAlive()) { reject(extensionContextError()); return; } chrome.storage.local.get(keys, (data) => { if (rejectLastError(reject)) return; resolve(data); }); } catch (err) { reject(normalizeStorageError(err)); } }); } function localSet(obj) { return new Promise((resolve, reject) => { try { if (!extensionContextAlive()) { reject(extensionContextError()); return; } chrome.storage.local.set(obj, () => { if (rejectLastError(reject)) return; resolve(); }); } catch (err) { reject(normalizeStorageError(err)); } }); } function sessionGet(keys) { return new Promise((resolve, reject) => { try { if (!extensionContextAlive()) { reject(extensionContextError()); return; } const store = chrome.storage.session || chrome.storage.local; store.get(keys, (data) => { if (rejectLastError(reject)) return; resolve(data); }); } catch (err) { reject(normalizeStorageError(err)); } }); } function sessionSet(obj) { return new Promise((resolve, reject) => { try { if (!extensionContextAlive()) { reject(extensionContextError()); return; } const store = chrome.storage.session || chrome.storage.local; store.set(obj, () => { if (rejectLastError(reject)) return; resolve(); }); } catch (err) { reject(normalizeStorageError(err)); } }); } async function loadConfig() { const data = await localGet(Object.values(STORAGE_KEYS)); const sess = await sessionGet([SESSION_TOKEN_KEY, SESSION_TOKEN_EXPIRY_KEY]); return { host: data[STORAGE_KEYS.host] || DEFAULTS.host, port: data[STORAGE_KEYS.port] || DEFAULTS.port, https: data[STORAGE_KEYS.https] !== false, token: sess[SESSION_TOKEN_KEY] || '', tokenExpiresAt: sess[SESSION_TOKEN_EXPIRY_KEY] || '', lastProjectId: data[STORAGE_KEYS.lastProjectId] || '', lastRole: data[STORAGE_KEYS.lastRole] || '', lastAgentMode: data[STORAGE_KEYS.lastAgentMode] || 'eino_single', lastInstruction: data[STORAGE_KEYS.lastInstruction] || DEFAULTS.lastInstruction, filterApiOnly: data[STORAGE_KEYS.filterApiOnly] !== false, captureEnabled: data[STORAGE_KEYS.captureEnabled] !== false, renderMarkdown: data[STORAGE_KEYS.renderMarkdown] !== false, showDebugEvents: data[STORAGE_KEYS.showDebugEvents] === true, }; } async function saveConfig(partial) { const localMap = {}; if (partial.host != null) localMap[STORAGE_KEYS.host] = partial.host; if (partial.port != null) localMap[STORAGE_KEYS.port] = partial.port; if (partial.https != null) localMap[STORAGE_KEYS.https] = partial.https; if (partial.lastProjectId != null) localMap[STORAGE_KEYS.lastProjectId] = partial.lastProjectId; if (partial.lastRole != null) localMap[STORAGE_KEYS.lastRole] = partial.lastRole; if (partial.lastAgentMode != null) localMap[STORAGE_KEYS.lastAgentMode] = partial.lastAgentMode; if (partial.lastInstruction != null) localMap[STORAGE_KEYS.lastInstruction] = partial.lastInstruction; if (partial.filterApiOnly != null) localMap[STORAGE_KEYS.filterApiOnly] = partial.filterApiOnly; if (partial.captureEnabled != null) localMap[STORAGE_KEYS.captureEnabled] = partial.captureEnabled; if (partial.renderMarkdown != null) localMap[STORAGE_KEYS.renderMarkdown] = partial.renderMarkdown; if (partial.showDebugEvents != null) localMap[STORAGE_KEYS.showDebugEvents] = partial.showDebugEvents; if (Object.keys(localMap).length) await localSet(localMap); const sessionMap = {}; if (partial.token != null) sessionMap[SESSION_TOKEN_KEY] = partial.token; if (partial.tokenExpiresAt != null) sessionMap[SESSION_TOKEN_EXPIRY_KEY] = partial.tokenExpiresAt; if (Object.keys(sessionMap).length) await sessionSet(sessionMap); } function baseUrlFrom(cfg) { const scheme = cfg.https ? 'https' : 'http'; return `${scheme}://${cfg.host}:${cfg.port}`; } /** Request optional host permission for the configured CyberStrikeAI origin. */ async function ensureHostPermission(baseUrl) { if (!extensionContextAlive()) throw extensionContextError(); if (!chrome.permissions || !chrome.permissions.request) return; let origin; try { origin = new URL(baseUrl).origin + '/*'; } catch (_) { throw new Error('Invalid Host/Port'); } const has = await chrome.permissions.contains({ origins: [origin] }); if (has) return; const granted = await chrome.permissions.request({ origins: [origin] }); if (!granted) { throw new Error('Permission required to access the CyberStrikeAI server'); } }