fintechdarkpatterns / extension /service-worker.js
yujisium's picture
Fintech Dark Patterns NLP Detector - full project upload
5752a28 verified
Raw
History Blame Contribute Delete
4.79 kB
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;
});