const DEFAULT_SETTINGS = { apiUrl: "http://127.0.0.1:8000", minimumScore: 55, maxItems: 250, }; chrome.runtime.onInstalled.addListener(async () => { const stored = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS)); await chrome.storage.local.set({ ...DEFAULT_SETTINGS, ...stored }); await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }); }); chrome.runtime.onStartup.addListener(async () => { await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }); }); async function getSettings() { const stored = await chrome.storage.local.get(Object.keys(DEFAULT_SETTINGS)); return { ...DEFAULT_SETTINGS, ...stored }; } async function getActiveWebTab() { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab?.id || !tab.url || !/^https?:\/\//i.test(tab.url)) { throw new Error("Open a normal HTTP or HTTPS page before running the audit."); } return tab; } async function ensureContentScript(tabId) { await chrome.scripting.executeScript({ target: { tabId }, files: ["content-script.js"], }); } async function sendToTab(tabId, message) { return chrome.tabs.sendMessage(tabId, message); } async function callApi(path, options = {}) { const settings = await getSettings(); const apiUrl = String(settings.apiUrl).replace(/\/+$/, ""); const response = await fetch(`${apiUrl}${path}`, options); const payload = await response.json().catch(() => ({})); if (!response.ok) { throw new Error(payload.error || `API request failed with status ${response.status}`); } return payload; } async function runScan() { const tab = await getActiveWebTab(); const settings = await getSettings(); await ensureContentScript(tab.id); const pageData = await sendToTab(tab.id, { type: "COLLECT_VISIBLE_TEXT", maxItems: settings.maxItems, }); if (!pageData?.items?.length) { return { status: "success", page: { title: tab.title || "", url: tab.url }, scanned: 0, flagged: 0, results: [], }; } const analysis = await callApi("/api/analyze-texts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items: pageData.items }), }); const flaggedResults = analysis.results.filter( (result) => result.isDarkPattern && result.confidence >= settings.minimumScore, ); await sendToTab(tab.id, { type: "APPLY_AUDIT_RESULTS", results: flaggedResults, }); return { status: "success", page: { title: tab.title || "", url: tab.url }, scanned: analysis.analyzed, flagged: flaggedResults.length, minimumScore: settings.minimumScore, results: flaggedResults, }; } async function clearHighlights() { const tab = await getActiveWebTab(); await ensureContentScript(tab.id); await sendToTab(tab.id, { type: "CLEAR_AUDIT_RESULTS" }); return { status: "success" }; } async function focusResult(elementId) { const tab = await getActiveWebTab(); await ensureContentScript(tab.id); await sendToTab(tab.id, { type: "FOCUS_AUDIT_RESULT", elementId, }); return { status: "success" }; } async function testConnection() { const health = await callApi("/api/health"); return { status: "success", health }; } async function saveSettings(settings) { const apiUrl = String(settings.apiUrl || DEFAULT_SETTINGS.apiUrl).trim(); let parsedUrl; try { parsedUrl = new URL(apiUrl); } catch { throw new Error("Enter a valid local API URL."); } if ( parsedUrl.protocol !== "http:" || !["127.0.0.1", "localhost"].includes(parsedUrl.hostname) ) { throw new Error("For privacy, the extension only connects to a localhost API."); } const minimumScore = Math.min( 95, Math.max(20, Number(settings.minimumScore) || DEFAULT_SETTINGS.minimumScore), ); const maxItems = Math.min( 300, Math.max(25, Number(settings.maxItems) || DEFAULT_SETTINGS.maxItems), ); const normalized = { apiUrl: parsedUrl.origin, minimumScore, maxItems, }; await chrome.storage.local.set(normalized); return { status: "success", settings: normalized }; } chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { const actions = { RUN_SCAN: runScan, CLEAR_HIGHLIGHTS: clearHighlights, FOCUS_RESULT: () => focusResult(message.elementId), TEST_CONNECTION: testConnection, GET_SETTINGS: getSettings, SAVE_SETTINGS: () => saveSettings(message.settings || {}), }; const action = actions[message?.type]; if (!action) { return false; } Promise.resolve(action()) .then((result) => sendResponse({ ok: true, ...result })) .catch((error) => sendResponse({ ok: false, error: error.message })); return true; });