Spaces:
Runtime error
Runtime error
| 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; | |
| }); | |