/** * Settings service (background): storage access with validation. * * Load flow (Design.md ยง17.2): * 1. generate default settings * 2. read storage * 3. merge * 4. validate * 5. invalid values fall back to defaults * * The validated cache is refreshed on storage.onChanged. */ import { sanitizeSettings, defaultSettings } from '../shared/settings-schema.js'; const STORAGE_KEY = 'settings'; /** @type {object|null} cached validated settings */ let cache = null; /** * @param {object} [browserApi] injectable browser API (defaults to globalThis.browser) * @returns {object} */ function api(browserApi) { return browserApi || globalThis.browser; } /** * Load settings from storage.local, sanitize and cache them. * @param {object} [browserApi] * @returns {Promise} validated settings */ export async function loadSettings(browserApi) { const b = api(browserApi); let stored = null; try { const result = await b.storage.local.get({ settings: null }); stored = result.settings; } catch { stored = null; } cache = sanitizeSettings(stored); return cache; } /** * Save settings. Values are sanitized before writing. * @param {object} settings raw settings object from the UI * @param {object} [browserApi] * @returns {Promise} the validated settings actually stored */ export async function saveSettings(settings, browserApi) { const b = api(browserApi); const validated = sanitizeSettings(settings); await b.storage.local.set({ settings: validated }); cache = validated; return validated; } /** * @param {object} [browserApi] * @returns {Promise} current validated settings (cached when possible) */ export async function getSettings(browserApi) { if (cache) return cache; return loadSettings(browserApi); } /** * Register the storage.onChanged listener that keeps the cache in sync. * Must be called once from the background entry point. * @param {object} [browserApi] */ export function watchSettings(browserApi) { const b = api(browserApi); b.storage.onChanged.addListener((changes, areaName) => { if (areaName === 'local' && changes.settings) { cache = sanitizeSettings(changes.settings.newValue); } }); } /** * Test-only: clear the module-level settings cache so each test starts from * storage state instead of inheriting the previous test's cache. * @visibleForTesting */ export function __resetCacheForTests() { cache = null; }