// Cookie Pop-ups — answers cookie consent dialogs automatically. // // The work happens in inject.js (DuckDuckGo's autoconsent library, MPL-2.0, // running in every http(s) frame's isolated world). This side holds the // user's settings, hands the rule bundle to each frame that asks, and keeps // count of what was handled so the panel can show it per site. const fs = require("node:fs"); const path = require("node:path"); let api = null; let rules = null; // { autoconsent: [], compact: } let rulesVersion = ""; let enabled = true; let mode = "optOut"; // "optOut" = reject all but essentials; "optIn" = accept (just make it go away) let skip = new Set(); // hosts where the add-on stays out let handledTotal = 0; let perTab = new Map(); // tabId -> { host, handled, cmp, at, failed } let unsubTabs = null, emitTimer = null, saveTimer = null; const hostOf = (u) => { try { return new URL(u).host; } catch { return ""; } }; function loadRules() { const dir = path.join(api.folder, "rules"); const compact = JSON.parse(fs.readFileSync(path.join(dir, "compact-rules.json"), "utf8")); try { rulesVersion = fs.readFileSync(path.join(dir, "VERSION"), "utf8").trim(); } catch { rulesVersion = ""; } rules = { autoconsent: [], compact }; api.log(`rules loaded: ${Array.isArray(compact.r) ? compact.r.length : "?"} CMP rules (autoconsent ${rulesVersion || "?"})`); } // The configuration every frame runs with. Mirrors DuckDuckGo's own // defaults; heuristics catch banners no rule knows yet. function frameConfig() { return { enabled: true, autoAction: mode, disabledCmps: [], enablePrehide: true, enableCosmeticRules: true, enableGeneratedRules: true, detectRetries: 20, isMainWorld: false, prehideTimeout: 2000, enableHeuristicDetection: true, enablePopupMutationObserver: false, visualTest: false, performanceLoggingEnabled: false, // Unknown banners: in reject mode only press buttons that read as a // rejection; in accept mode the library's first tier of accept buttons. heuristicPopupSearchTimeout: 1000, heuristicMode: mode === "optOut" ? "reject" : "tier1", logs: { lifecycle: false, rulesteps: false, detectionsteps: false, evals: false, errors: false, messages: false, waits: false }, }; } function snapshot() { let tab = null; try { tab = api.tabs.active(); } catch {} const host = tab ? tab.host : ""; const httpPage = !!tab && /^https?:/i.test(tab.url || ""); const t = tab ? perTab.get(tab.id) : null; const here = t && t.host === host ? t : null; return { enabled, mode, handledTotal, rulesVersion, site: httpPage && host ? { host, skipped: skip.has(host), handled: here ? here.handled : 0, cmp: here ? here.cmp : "", failed: here ? here.failed : 0 } : null, }; } function pushState() { try { api.emit("state", snapshot()); } catch {} } function pushStateSoon() { if (emitTimer) return; emitTimer = setTimeout(() => { emitTimer = null; pushState(); }, 200); } function scheduleSave() { if (saveTimer) return; saveTimer = setTimeout(() => { saveTimer = null; api.storage.set("handledTotal", handledTotal); }, 2000); } async function activate(a) { api = a; enabled = api.storage.get("enabled", true) !== false; mode = api.storage.get("mode", "optOut") === "optIn" ? "optIn" : "optOut"; skip = new Set((api.storage.get("skip", []) || []).map(String)); handledTotal = Number(api.storage.get("handledTotal", 0)) || 0; try { loadRules(); } catch (err) { api.log("rules unavailable:", err?.message); rules = null; } api.registerSidebarPanel({ id: "main", title: "Cookie Pop-ups", page: "panel.html" }); // From frames: the library asking whether (and how) to run here. api.onMessage("consent.init", (p, ctx) => { if (!ctx || ctx.from !== "page" || !enabled || !rules) return { enabled: false }; const host = hostOf(ctx.origin); if (host && skip.has(host)) return { enabled: false }; return { enabled: true, config: frameConfig(), rules }; }); // From frames: what happened. Count a handled pop-up per tab once the // library reports it is done; a failed opt-out is remembered for the panel. api.onMessage("consent.event", (p, ctx) => { if (!ctx || ctx.from !== "page" || !p) return false; const host = hostOf(ctx.origin); let t = perTab.get(ctx.tabId); if (!t || t.host !== host) { t = { host, handled: 0, cmp: "", at: 0, failed: 0 }; perTab.set(ctx.tabId, t); } if (p.type === "autoconsentDone") { t.handled++; t.cmp = String(p.cmp || ""); t.at = Date.now(); handledTotal++; scheduleSave(); pushStateSoon(); } else if ((p.type === "optOutResult" || p.type === "optInResult") && p.result === false) { t.failed++; t.cmp = String(p.cmp || ""); pushStateSoon(); } else if (p.type === "autoconsentError") { t.failed++; pushStateSoon(); } return true; }); // From the panel. api.onMessage("state", () => snapshot()); api.onMessage("set-enabled", (p) => { enabled = !!(p && p.enabled); api.storage.set("enabled", enabled); pushState(); return snapshot(); }); api.onMessage("set-mode", (p) => { mode = p && p.mode === "optIn" ? "optIn" : "optOut"; api.storage.set("mode", mode); pushState(); return snapshot(); }); api.onMessage("skip-site", (p) => { const host = String((p && p.host) || "").toLowerCase(); if (!host) return snapshot(); if (p.skipped) skip.add(host); else skip.delete(host); api.storage.set("skip", [...skip]); pushState(); return snapshot(); }); unsubTabs = api.tabs.onChange(() => pushState()); api.log(`active — ${enabled ? "on" : "off"}, mode ${mode}, ${skip.size} site(s) excluded`); } function deactivate() { try { unsubTabs && unsubTabs(); } catch {} clearTimeout(emitTimer); if (saveTimer) { clearTimeout(saveTimer); api.storage.set("handledTotal", handledTotal); } } module.exports = { activate, deactivate };