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

171 lines (170 loc) 4.97 kB
/* eslint-disable no-restricted-syntax,no-await-in-loop,no-underscore-dangle */ export 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); } export async function success(action) { const result = await action; if (!result) { throw new Error(`Action failed: ${action}`); } return result; } export default 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.undoHide) { results.push(tab.undoHideElements(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); } export class AutoConsent extends AutoConsentBase { constructor(config) { super(config.name); this.config = config; } get prehideSelectors() { return this.config.prehideSelectors; } get isHidingRule() { return this.config.isHidingRule; } 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); } }