UNPKG

@cliqz/autoconsent

Version:

This is a library of rules for navigating through common consent popups on the web. These rules can be run in a Firefox webextension, or in a puppeteer orchestrated headless browser. Using these rules, opt-in and opt-out options can be selected automatica

1,187 lines (1,170 loc) 41.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); /* eslint-disable no-restricted-syntax,no-await-in-loop,no-underscore-dangle */ async function waitFor(predicate, maxTimes, interval) { let result = await predicate(); if (!result && maxTimes > 0) { return new Promise((resolve) => { setTimeout(async () => { resolve(waitFor(predicate, maxTimes - 1, interval)); }, interval); }); } return Promise.resolve(result); } async function success(action) { const result = await action; if (!result) { throw new Error(`Action failed: ${action}`); } return result; } class AutoConsentBase { constructor(name) { this.hasSelfTest = true; this.name = name; } detectCmp(tab) { throw new Error('Not Implemented'); } async detectPopup(tab) { return false; } detectFrame(tab, frame) { return false; } optOut(tab) { throw new Error('Not Implemented'); } optIn(tab) { throw new Error('Not Implemented'); } openCmp(tab) { throw new Error('Not Implemented'); } async test(tab) { // try IAB by default return Promise.resolve(true); } } async function evaluateRule(rule, tab) { if (rule.frame && !tab.frame) { await waitFor(() => Promise.resolve(!!tab.frame), 10, 500); } const frameId = rule.frame && tab.frame ? tab.frame.id : undefined; const results = []; if (rule.exists) { results.push(tab.elementExists(rule.exists, frameId)); } if (rule.visible) { results.push(tab.elementsAreVisible(rule.visible, rule.check, frameId)); } if (rule.eval) { results.push(new Promise(async (resolve) => { // catch eval error silently try { resolve(await tab.eval(rule.eval, frameId)); } catch (e) { resolve(false); } })); } if (rule.waitFor) { results.push(tab.waitForElement(rule.waitFor, rule.timeout || 10000, frameId)); } if (rule.click) { if (rule.all === true) { results.push(tab.clickElements(rule.click, frameId)); } else { results.push(tab.clickElement(rule.click, frameId)); } } if (rule.waitForThenClick) { results.push(tab.waitForElement(rule.waitForThenClick, rule.timeout || 10000, frameId) .then(() => tab.clickElement(rule.waitForThenClick, frameId))); } if (rule.wait) { results.push(tab.wait(rule.wait)); } if (rule.goto) { results.push(tab.goto(rule.goto)); } if (rule.hide) { results.push(tab.hideElements(rule.hide, frameId)); } if (rule.waitForFrame) { results.push(waitFor(() => !!tab.frame, 40, 500)); } // boolean and of results return (await Promise.all(results)).reduce((a, b) => a && b, true); } class AutoConsent extends AutoConsentBase { constructor(config) { super(config.name); this.config = config; } async _runRulesParallel(tab, rules) { const detections = await Promise.all(rules.map(rule => evaluateRule(rule, tab))); return detections.every(r => !!r); } async _runRulesSequentially(tab, rules) { for (const rule of rules) { const result = await evaluateRule(rule, tab); if (!result && !rule.optional) { return false; } } return true; } async detectCmp(tab) { if (this.config.detectCmp) { return this._runRulesParallel(tab, this.config.detectCmp); } return false; } async detectPopup(tab) { if (this.config.detectPopup) { return this._runRulesParallel(tab, this.config.detectPopup); } return false; } detectFrame(tab, frame) { if (this.config.frame) { return frame.url.startsWith(this.config.frame); } return false; } async optOut(tab) { if (this.config.optOut) { return this._runRulesSequentially(tab, this.config.optOut); } return false; } async optIn(tab) { if (this.config.optIn) { return this._runRulesSequentially(tab, this.config.optIn); } return false; } async openCmp(tab) { if (this.config.openCmp) { return this._runRulesSequentially(tab, this.config.openCmp); } return false; } async test(tab) { if (this.config.test) { return this._runRulesSequentially(tab, this.config.test); } return super.test(tab); } } class TabActions { constructor(tabId, frame, sendContentMessage, browser) { this.frame = frame; this.sendContentMessage = sendContentMessage; this.browser = browser; this.id = tabId; } async elementExists(selector, frameId = 0) { console.log(`check for ${selector} in tab ${this.id}, frame ${frameId}`); return this.sendContentMessage(this.id, { type: "elemExists", selector }, { frameId }); } async clickElement(selector, frameId = 0) { console.log(`click element ${selector} in tab ${this.id}`); return this.sendContentMessage(this.id, { type: "click", selector }, { frameId }); } async clickElements(selector, frameId = 0) { console.log(`click elements ${selector} in tab ${this.id}`); return this.sendContentMessage(this.id, { type: "click", all: true, selector }, { frameId }); } async elementsAreVisible(selector, check, frameId = 0) { return this.sendContentMessage(this.id, { type: "elemVisible", selector, check }, { frameId }); } async getAttribute(selector, attribute, frameId = 0) { return this.sendContentMessage(this.id, { type: "getAttribute", selector, attribute }, { frameId }); } async eval(script, frameId = 0) { // console.log(`run ${script} in tab ${this.id}`); return await this.sendContentMessage(this.id, { type: "eval", script }, { frameId }); } async waitForElement(selector, timeout, frameId = 0) { const interval = 200; const times = Math.ceil(timeout / interval); return waitFor(() => this.elementExists(selector, frameId), times, interval); } async waitForThenClick(selector, timeout, frameId = 0) { await this.waitForElement(selector, timeout, frameId); await this.clickElement(selector, frameId); return true; } async hideElements(selectors, frameId = 0) { return this.sendContentMessage(this.id, { type: "hide", selectors }, { frameId }); } async getBrowserTab() { return this.browser.tabs.get(this.id); } async goto(url) { return this.browser.tabs.update(this.id, { url }); } wait(ms) { return new Promise(resolve => { setTimeout(() => resolve(true), ms); }); } matches(matcherConfig) { return this.sendContentMessage(this.id, { type: "matches", config: matcherConfig }, { frameId: 0 }); } executeAction(config, param) { return this.sendContentMessage(this.id, { type: "executeAction", config, param }, { frameId: 0 }); } } /** * This code is in most parts copied from https://github.com/cavi-au/Consent-O-Matic/blob/master/Extension/Tools.js * which is licened under the MIT. */ class Tools { static setBase(base) { Tools.base = base; } static findElement(options, parent = null, multiple = false) { let possibleTargets = null; if (parent != null) { possibleTargets = Array.from(parent.querySelectorAll(options.selector)); } else { if (Tools.base != null) { possibleTargets = Array.from(Tools.base.querySelectorAll(options.selector)); } else { possibleTargets = Array.from(document.querySelectorAll(options.selector)); } } if (options.textFilter != null) { possibleTargets = possibleTargets.filter(possibleTarget => { let textContent = possibleTarget.textContent.toLowerCase(); if (Array.isArray(options.textFilter)) { let foundText = false; for (let text of options.textFilter) { if (textContent.indexOf(text.toLowerCase()) !== -1) { foundText = true; break; } } return foundText; } else if (options.textFilter != null) { return textContent.indexOf(options.textFilter.toLowerCase()) !== -1; } }); } if (options.styleFilters != null) { possibleTargets = possibleTargets.filter(possibleTarget => { let styles = window.getComputedStyle(possibleTarget); let keep = true; for (let styleFilter of options.styleFilters) { let option = styles[styleFilter.option]; if (styleFilter.negated) { keep = keep && option !== styleFilter.value; } else { keep = keep && option === styleFilter.value; } } return keep; }); } if (options.displayFilter != null) { possibleTargets = possibleTargets.filter(possibleTarget => { if (options.displayFilter) { //We should be displayed return possibleTarget.offsetHeight !== 0; } else { //We should not be displayed return possibleTarget.offsetHeight === 0; } }); } if (options.iframeFilter != null) { possibleTargets = possibleTargets.filter(possibleTarget => { if (options.iframeFilter) { //We should be inside an iframe return window.location !== window.parent.location; } else { //We should not be inside an iframe return window.location === window.parent.location; } }); } if (options.childFilter != null) { possibleTargets = possibleTargets.filter(possibleTarget => { let oldBase = Tools.base; Tools.setBase(possibleTarget); let childResults = Tools.find(options.childFilter); Tools.setBase(oldBase); return childResults.target != null; }); } if (multiple) { return possibleTargets; } else { if (possibleTargets.length > 1) { console.warn("Multiple possible targets: ", possibleTargets, options, parent); } return possibleTargets[0]; } } static find(options, multiple = false) { let results = []; if (options.parent != null) { let parent = Tools.findElement(options.parent, null, multiple); if (parent != null) { if (parent instanceof Array) { parent.forEach(p => { let targets = Tools.findElement(options.target, p, multiple); if (targets instanceof Array) { targets.forEach(target => { results.push({ parent: p, target: target }); }); } else { results.push({ parent: p, target: targets }); } }); return results; } else { let targets = Tools.findElement(options.target, parent, multiple); if (targets instanceof Array) { targets.forEach(target => { results.push({ parent: parent, target: target }); }); } else { results.push({ parent: parent, target: targets }); } } } } else { let targets = Tools.findElement(options.target, null, multiple); if (targets instanceof Array) { targets.forEach(target => { results.push({ parent: null, target: target }); }); } else { results.push({ parent: null, target: targets }); } } if (results.length === 0) { results.push({ parent: null, target: null }); } if (multiple) { return results; } else { if (results.length !== 1) { console.warn("Multiple results found, even though multiple false", results); } return results[0]; } } } Tools.base = null; function matches(config) { const result = Tools.find(config); if (config.type === "css") { return !!result.target; } else if (config.type === "checkbox") { return !!result.target && result.target.checked; } } async function executeAction(config, param) { switch (config.type) { case "click": return clickAction(config); case "list": return listAction(config, param); case "consent": return consentAction(config, param); case "ifcss": return ifCssAction(config, param); case "waitcss": return waitCssAction(config); case "foreach": return forEachAction(config, param); case "hide": return hideAction(config); case "slide": return slideAction(config); case "close": return closeAction(); case "wait": return waitAction(config); case "eval": return evalAction(config); default: throw "Unknown action type: " + config.type; } } const STEP_TIMEOUT = 0; function waitTimeout(timeout) { return new Promise(resolve => { setTimeout(() => { resolve(); }, timeout); }); } async function clickAction(config) { const result = Tools.find(config); if (result.target != null) { result.target.click(); } return waitTimeout(STEP_TIMEOUT); } async function listAction(config, param) { for (let action of config.actions) { await executeAction(action, param); } } async function consentAction(config, consentTypes) { for (const consentConfig of config.consents) { const shouldEnable = consentTypes.indexOf(consentConfig.type) !== -1; if (consentConfig.matcher && consentConfig.toggleAction) { const isEnabled = matches(consentConfig.matcher); if (isEnabled !== shouldEnable) { await executeAction(consentConfig.toggleAction); } } else { if (shouldEnable) { await executeAction(consentConfig.trueAction); } else { await executeAction(consentConfig.falseAction); } } } } async function ifCssAction(config, param) { const result = Tools.find(config); if (!result.target) { if (config.trueAction) { await executeAction(config.trueAction, param); } } else { if (config.falseAction) { await executeAction(config.falseAction, param); } } } async function waitCssAction(config) { await new Promise(resolve => { let numRetries = config.retries || 10; const waitTime = config.waitTime || 250; const checkCss = () => { const result = Tools.find(config); if ((config.negated && result.target) || (!config.negated && !result.target)) { if (numRetries > 0) { numRetries -= 1; setTimeout(checkCss, waitTime); } else { resolve(); } } else { resolve(); } }; checkCss(); }); } async function forEachAction(config, param) { const results = Tools.find(config, true); const oldBase = Tools.base; for (const result of results) { if (result.target) { Tools.setBase(result.target); await executeAction(config.action, param); } } Tools.setBase(oldBase); } async function hideAction(config) { const result = Tools.find(config); if (result.target) { result.target.classList.add("Autoconsent-Hidden"); // result.target.setAttribute("style", "display: none;"); } } async function slideAction(config) { const result = Tools.find(config); const dragResult = Tools.find(config.dragTarget); if (result.target) { let targetBounds = result.target.getBoundingClientRect(); let dragTargetBounds = dragResult.target.getBoundingClientRect(); let yDiff = dragTargetBounds.top - targetBounds.top; let xDiff = dragTargetBounds.left - targetBounds.left; if (this.config.axis.toLowerCase() === "y") { xDiff = 0; } if (this.config.axis.toLowerCase() === "x") { yDiff = 0; } let screenX = window.screenX + targetBounds.left + targetBounds.width / 2.0; let screenY = window.screenY + targetBounds.top + targetBounds.height / 2.0; let clientX = targetBounds.left + targetBounds.width / 2.0; let clientY = targetBounds.top + targetBounds.height / 2.0; let mouseDown = document.createEvent("MouseEvents"); mouseDown.initMouseEvent("mousedown", true, true, window, 0, screenX, screenY, clientX, clientY, false, false, false, false, 0, result.target); let mouseMove = document.createEvent("MouseEvents"); mouseMove.initMouseEvent("mousemove", true, true, window, 0, screenX + xDiff, screenY + yDiff, clientX + xDiff, clientY + yDiff, false, false, false, false, 0, result.target); let mouseUp = document.createEvent("MouseEvents"); mouseUp.initMouseEvent("mouseup", true, true, window, 0, screenX + xDiff, screenY + yDiff, clientX + xDiff, clientY + yDiff, false, false, false, false, 0, result.target); result.target.dispatchEvent(mouseDown); await this.waitTimeout(10); result.target.dispatchEvent(mouseMove); await this.waitTimeout(10); result.target.dispatchEvent(mouseUp); } } async function waitAction(config) { await waitTimeout(config.waitTime); } async function closeAction(config) { window.close(); } async function evalAction(config) { console.log("eval!", config.code); return new Promise(resolve => { try { if (config.async) { window.eval(config.code); setTimeout(() => { resolve(window.eval("window.__consentCheckResult")); }, config.timeout || 250); } else { resolve(window.eval(config.code)); } } catch (e) { console.warn("eval error", e, config.code); resolve(false); } }); } let actionQueue = Promise.resolve(null); function handleMessage(message, debug = false) { if (message.type === "click") { const elem = document.querySelectorAll(message.selector); debug && console.log("[click]", message.selector, elem); if (elem.length > 0) { if (message.all === true) { elem.forEach(e => e.click()); } else { elem[0].click(); } } return elem.length > 0; } else if (message.type === "elemExists") { const exists = document.querySelector(message.selector) !== null; debug && console.log("[exists?]", message.selector, exists); return exists; } else if (message.type === "elemVisible") { const elem = document.querySelectorAll(message.selector); const results = new Array(elem.length); elem.forEach((e, i) => { results[i] = e.offsetParent !== null || window.getComputedStyle(e).display !== "none"; }); if (results.length === 0) { return false; } else if (message.check === "any") { return results.some(r => r); } else if (message.check === "none") { return results.every(r => !r); } // all return results.every(r => r); } else if (message.type === "getAttribute") { const elem = document.querySelector(message.selector); if (!elem) { return false; } return elem.getAttribute(message.attribute); } else if (message.type === "eval") { // TODO: chrome support const result = window.eval(message.script); // eslint-disable-line no-eval debug && console.log("[eval]", message.script, result); return result; } else if (message.type === "hide") { const parent = document.head || document.getElementsByTagName("head")[0] || document.documentElement; const rule = `${message.selectors.join(",")} { display: none !important; }`; const css = document.createElement("style"); css.type = "text/css"; css.id = "re-consent-css-rules"; css.appendChild(document.createTextNode(rule)); parent.appendChild(css); return message.selectors.length > 0; } else if (message.type === "matches") { const matched = matches(message.config); return matched; } else if (message.type === "executeAction") { actionQueue = actionQueue.then(() => executeAction(message.config, message.param)); return true; } return null; } class TrustArc extends AutoConsentBase { constructor() { super("TrustArc"); } detectFrame(_, frame) { return frame.url.startsWith("https://consent-pref.trustarc.com/?"); } async detectCmp(tab) { if (tab.frame && tab.frame.url.startsWith("https://consent-pref.trustarc.com/?")) { return true; } return tab.elementExists("#truste-show-consent"); } async detectPopup(tab) { return ((await tab.elementsAreVisible("#truste-consent-content")) || (tab.frame && (await tab.waitForElement("#defaultpreferencemanager", 5000, tab.frame.id)))); } async openFrame(tab) { if (await tab.elementExists("#truste-show-consent")) { await tab.clickElement("#truste-show-consent"); } } async navigateToSettings(tab, frameId) { // wait for it to load await waitFor(async () => { return ((await tab.elementExists(".shp", frameId)) || (await tab.elementsAreVisible(".advance", "any", frameId)) || tab.elementExists(".switch span:first-child", frameId)); }, 10, 500); // splash screen -> hit more information if (await tab.elementExists(".shp", frameId)) { await tab.clickElement(".shp", frameId); } await tab.waitForElement(".prefPanel", 5000, frameId); // go to advanced settings if not yet shown if (await tab.elementsAreVisible(".advance", "any", frameId)) { await tab.clickElement(".advance", frameId); } // takes a while to load the opt-in/opt-out buttons return await waitFor(() => tab.elementsAreVisible(".switch span:first-child", "any", frameId), 5, 1000); } async optOut(tab) { // await tab.hideElements(['.truste_overlay', '.truste_box_overlay', '.trustarc-banner', '.truste-banner']); if (await tab.elementExists("#truste-consent-required")) { return tab.clickElement("#truste-consent-required"); } if (!tab.frame) { await tab.clickElement("#truste-show-consent"); await waitFor(async () => !!tab.frame && (await tab.elementsAreVisible(".mainContent", "any", tab.frame.id)), 50, 100); } const frameId = tab.frame.id; tab.hideElements([".truste_popframe", ".truste_overlay", ".truste_box_overlay", "#truste-consent-track"]); if (await tab.elementExists(".required", frameId)) { await tab.clickElement(".required", frameId); } else { await this.navigateToSettings(tab, frameId); await tab.clickElements(".switch span:nth-child(1):not(.active)", frameId); await tab.clickElement(".submit", frameId); } await tab.waitForThenClick("#gwt-debug-close_id", 20000, tab.frame.id); return true; } async optIn(tab) { if (!tab.frame) { await this.openFrame(tab); await waitFor(() => !!tab.frame, 10, 200); } const frameId = tab.frame.id; await this.navigateToSettings(tab, frameId); await tab.clickElements(".switch span:nth-child(2)", frameId); await tab.clickElement(".submit", frameId); await waitFor(() => tab.elementExists("#gwt-debug-close_id", frameId), 300, 1000); await tab.clickElement("#gwt-debug-close_id", frameId); return true; } async openCmp(tab) { await tab.eval("truste.eu.clickListener()"); return true; } async test() { // TODO: find out how to test TrustArc return true; } } class Cookiebot extends AutoConsentBase { constructor() { super('Cybotcookiebot'); } async detectCmp(tab) { try { return await tab.eval('typeof window.CookieConsent === "object" && typeof window.CookieConsent.name === "string"'); } catch (e) { return false; } } detectPopup(tab) { return tab.elementExists('#CybotCookiebotDialog,#dtcookie-container,#cookiebanner'); } async optOut(tab) { if (await tab.elementExists('#dtcookie-container')) { return tab.clickElement('.h-dtcookie-decline'); } if (await tab.elementExists('.cookiebot__button--settings')) { await tab.clickElement('.cookiebot__button--settings'); } if (await tab.elementsAreVisible('#CybotCookiebotDialogBodyButtonDecline', 'all')) { return await tab.clickElement('#CybotCookiebotDialogBodyButtonDecline'); } if (await tab.elementExists('.cookiebanner__link--details')) { await tab.clickElement('.cookiebanner__link--details'); } await tab.clickElements('.CybotCookiebotDialogBodyLevelButton:checked:enabled,input[id*="CybotCookiebotDialogBodyLevelButton"]:checked:enabled'); if (await tab.elementExists('#CybotCookiebotDialogBodyButtonDecline')) { await tab.clickElement('#CybotCookiebotDialogBodyButtonDecline'); } if (await tab.elementExists('#CybotCookiebotDialogBodyButtonAcceptSelected')) { await tab.clickElement('#CybotCookiebotDialogBodyButtonAcceptSelected'); } else { await tab.clickElements('#CybotCookiebotDialogBodyLevelButtonAccept,#CybotCookiebotDialogBodyButtonAccept,#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowallSelection'); } return true; } async optIn(tab) { if (await tab.elementExists('#dtcookie-container')) { return tab.clickElement('.h-dtcookie-accept'); } await tab.clickElements('.CybotCookiebotDialogBodyLevelButton:not(:checked):enabled'); await tab.clickElement('#CybotCookiebotDialogBodyLevelButtonAccept'); await tab.clickElement('#CybotCookiebotDialogBodyButtonAccept'); return true; } async openCmp(tab) { await tab.eval('CookieConsent.renew() || true'); return tab.waitForElement('#CybotCookiebotDialog', 10000); } async test(tab) { return tab.eval('CookieConsent.declined === true'); } } class SourcePoint extends AutoConsentBase { constructor() { super("Sourcepoint"); } detectFrame(_, frame) { const url = new URL(frame.url); return (url.pathname === '/index.html' || url.pathname === '/privacy-manager/index.html') && url.searchParams.has('message_id') && url.searchParams.has('requestUUID'); } async detectCmp(tab) { return await tab.elementExists("div[id^='sp_message_container_']") || !!tab.frame; } async detectPopup(tab) { return await tab.elementsAreVisible("div[id^='sp_message_container_']") || !!tab.frame; } async optIn(tab) { return tab.clickElement(".sp_choice_type_11", tab.frame.id); } isManagerOpen(tab) { return tab.frame && new URL(tab.frame.url).pathname === "/privacy-manager/index.html"; } async optOut(tab) { tab.hideElements(["div[id^='sp_message_container_']"]); if (!this.isManagerOpen(tab)) { await waitFor(() => !!tab.frame, 30, 100); if (!await tab.elementExists("button.sp_choice_type_12", tab.frame.id)) { // do not sell button return tab.clickElement('button.sp_choice_type_13', tab.frame.id); } await success(tab.clickElement("button.sp_choice_type_12", tab.frame.id)); await waitFor(() => new URL(tab.frame.url).pathname === "/privacy-manager/index.html", 200, 100); } await tab.waitForElement('.type-modal', 20000, tab.frame.id); // reject all button is offered by some sites try { const path = await Promise.race([ tab.waitForElement('.sp_choice_type_REJECT_ALL', 2000, tab.frame.id).then(r => 0), tab.waitForElement('.reject-toggle', 2000, tab.frame.id).then(() => 1), tab.waitForElement('.pm-features', 2000, tab.frame.id).then(r => 2), ]); if (path === 0) { await tab.wait(1000); return await success(tab.clickElement('.sp_choice_type_REJECT_ALL', tab.frame.id)); } else if (path === 1) { await tab.clickElement('.reject-toggle', tab.frame.id); } else { await tab.waitForElement('.pm-features', 10000, tab.frame.id); await tab.clickElements('.checked > span', tab.frame.id); if (await tab.elementExists('.chevron', tab.frame.id)) { await tab.clickElement('.chevron', tab.frame.id); } } } catch (e) { } return await tab.clickElement('.sp_choice_type_SAVE_AND_EXIT', tab.frame.id); } async test(tab) { await tab.eval("__tcfapi('getTCData', 2, r => window.__rcsResult = r)"); return tab.eval("Object.values(window.__rcsResult.purpose.consents).every(c => !c)"); } } // Note: JS API is also available: // https://help.consentmanager.net/books/cmp/page/javascript-api class ConsentManager extends AutoConsentBase { constructor() { super("consentmanager.net"); } detectCmp(tab) { return tab.elementExists("#cmpbox"); } detectPopup(tab) { return tab.elementsAreVisible("#cmpbox .cmpmore", "any"); } async optOut(tab) { if (await tab.elementExists(".cmpboxbtnno")) { return tab.clickElement(".cmpboxbtnno"); } if (await tab.elementExists(".cmpwelcomeprpsbtn")) { await tab.clickElements(".cmpwelcomeprpsbtn > a[aria-checked=true]"); return await tab.clickElement(".cmpboxbtnsave"); } await tab.clickElement(".cmpboxbtncustom"); await tab.waitForElement(".cmptblbox", 2000); await tab.clickElements(".cmptdchoice > a[aria-checked=true]"); return tab.clickElement(".cmpboxbtnyescustomchoices"); } async optIn(tab) { return tab.clickElement(".cmpboxbtnyes"); } } // Note: JS API is also available: // https://help.consentmanager.net/books/cmp/page/javascript-api class Evidon extends AutoConsentBase { constructor() { super("Evidon"); } detectCmp(tab) { return tab.elementExists("#_evidon_banner"); } detectPopup(tab) { return tab.elementsAreVisible("#_evidon_banner"); } async optOut(tab) { if (await tab.elementExists("#_evidon-decline-button")) { return tab.clickElement("#_evidon-decline-button"); } tab.hideElements(["#evidon-prefdiag-overlay", "#evidon-prefdiag-background"]); await tab.clickElement("#_evidon-option-button"); await tab.waitForElement("#evidon-prefdiag-overlay", 5000); return tab.clickElement("#evidon-prefdiag-decline"); } async optIn(tab) { return tab.clickElement("#_evidon-accept-button"); } } const rules = [ new TrustArc(), new Cookiebot(), new SourcePoint(), new ConsentManager(), new Evidon(), ]; function createAutoCMP(config) { return new AutoConsent(config); } const rules$1 = rules; class ConsentOMaticCMP { constructor(name, config) { this.name = name; this.config = config; this.methods = new Map(); config.methods.forEach(methodConfig => { if (methodConfig.action) { this.methods.set(methodConfig.name, methodConfig.action); } }); this.hasSelfTest = this.methods.has("TEST_CONSENT"); } async detectCmp(tab) { return (await Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.presentMatcher)))).some(matched => matched); } async detectPopup(tab) { return (await Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.showingMatcher)))).some(matched => matched); } async executeAction(tab, method, param) { if (this.methods.has(method)) { return tab.executeAction(this.methods.get(method), param); } return true; } async optOut(tab) { await this.executeAction(tab, "HIDE_CMP"); await this.executeAction(tab, "OPEN_OPTIONS"); await this.executeAction(tab, "HIDE_CMP"); await this.executeAction(tab, "DO_CONSENT", []); await this.executeAction(tab, "SAVE_CONSENT"); return true; } async optIn(tab) { await this.executeAction(tab, "HIDE_CMP"); await this.executeAction(tab, "OPEN_OPTIONS"); await this.executeAction(tab, "HIDE_CMP"); await this.executeAction(tab, "DO_CONSENT", ['D', 'A', 'B', 'E', 'F', 'X']); await this.executeAction(tab, "SAVE_CONSENT"); return true; } async openCmp(tab) { await this.executeAction(tab, "HIDE_CMP"); await this.executeAction(tab, "OPEN_OPTIONS"); return true; } test(tab) { return this.executeAction(tab, "TEST_CONSENT"); } detectFrame(tab, frame) { return false; } } class TabConsent { constructor(tab, ruleCheckPromise) { this.tab = tab; this.optOutStatus = null; this.checked = ruleCheckPromise; ruleCheckPromise.then(rule => this.rule = rule); } getCMPName() { if (this.rule) { return this.rule.name; } return null; } async isPopupOpen(retries = 1, interval = 1000) { const isOpen = await this.rule.detectPopup(this.tab); if (!isOpen && retries > 0) { return new Promise((resolve) => setTimeout(() => resolve(this.isPopupOpen(retries - 1, interval)), interval)); } return isOpen; } async doOptOut() { try { this.optOutStatus = await this.rule.optOut(this.tab); return this.optOutStatus; } catch (e) { this.optOutStatus = e; throw e; } } async doOptIn() { return this.rule.optIn(this.tab); } hasTest() { return !!this.rule.hasSelfTest; } async testOptOutWorked() { return this.rule.test(this.tab); } async applyCosmetics(selectors) { const hidden = await this.tab.hideElements(selectors); return hidden; } } class AutoConsent$1 { constructor(browser, sendContentMessage) { this.browser = browser; this.sendContentMessage = sendContentMessage; this.consentFrames = new Map(); this.tabCmps = new Map(); this.sendContentMessage = sendContentMessage; this.rules = [...rules$1]; } addCMP(config) { this.rules.push(createAutoCMP(config)); } disableCMPs(cmpNames) { this.rules = this.rules.filter((cmp) => !cmpNames.includes(cmp.name)); } addConsentomaticCMP(name, config) { this.rules.push(new ConsentOMaticCMP(`com_${name}`, config)); } createTab(tabId) { return new TabActions(tabId, this.consentFrames.get(tabId), this.sendContentMessage, this.browser); } async checkTab(tabId) { const tab = this.createTab(tabId); const consent = new TabConsent(tab, this.detectDialog(tab, 20)); this.tabCmps.set(tabId, consent); // check tabs consent.checked.then((rule) => { if (this.consentFrames.has(tabId) && rule) { const frame = this.consentFrames.get(tabId); if (frame.type === rule.name) { consent.tab.frame = frame; } } }); return this.tabCmps.get(tabId); } removeTab(tabId) { this.tabCmps.delete(tabId); this.consentFrames.delete(tabId); } onFrame({ tabId, url, frameId }) { // ignore main frames if (frameId === 0) { return; } try { const frame = { id: frameId, url: url, }; const tab = this.createTab(tabId); const frameMatch = this.rules.findIndex(r => r.detectFrame(tab, frame)); if (frameMatch > -1) { this.consentFrames.set(tabId, { type: this.rules[frameMatch].name, url, id: frameId, }); if (this.tabCmps.has(tabId)) { this.tabCmps.get(tabId).tab.frame = this.consentFrames.get(tabId); } } } catch (e) { console.error(e); } } async detectDialog(tab, retries) { const found = await new Promise(async (resolve) => { let earlyReturn = false; await Promise.all(this.rules.map(async (r, index) => { try { if (await r.detectCmp(tab)) { earlyReturn = true; resolve(index); } } catch (e) { console.warn('detectCMP error', r.name, e); } })); if (!earlyReturn) { resolve(-1); } }); if (found === -1 && retries > 0) { return new Promise((resolve) => { setTimeout(async () => { const result = this.detectDialog(tab, retries - 1); resolve(result); }, 500); }); } return found > -1 ? this.rules[found] : null; } } exports.Tab = TabActions; exports.createAutoCMP = createAutoCMP; exports.default = AutoConsent$1; exports.handleContentMessage = handleMessage; exports.rules = rules$1; exports.waitFor = waitFor;