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

187 lines (160 loc) 4.93 kB
/* eslint-disable no-restricted-syntax,no-await-in-loop,no-underscore-dangle */ import { AutoCMP, TabActor } from "../types"; import { AutoConsentCMPRule, AutoConsentRuleStep } from "../rules"; export async function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean> { let result = false; try { result = await predicate(); } catch (e) { console.warn('error in waitFor predicate', e); } if (!result && maxTimes > 0) { return new Promise((resolve) => { setTimeout(async () => { resolve(waitFor(predicate, maxTimes - 1, interval)); }, interval); }); } return Promise.resolve(result); } export default class AutoConsentBase implements AutoCMP { name: string hasSelfTest = true constructor(name: string) { this.name = name; } detectCmp(tab: TabActor): Promise<boolean> { throw new Error('Not Implemented'); } async detectPopup(tab: TabActor) { return false; } detectFrame(tab: TabActor, frame: { url: string }) { return false; } optOut(tab: TabActor): Promise<boolean> { throw new Error('Not Implemented'); } optIn(tab: TabActor): Promise<boolean> { throw new Error('Not Implemented'); } openCmp(tab: TabActor): Promise<boolean> { throw new Error('Not Implemented'); } async test(tab: TabActor): Promise<boolean> { // try IAB by default await tab.eval('__cmp(\'getVendorConsents\', undefined, r => window.__rcsResult = r)'); return tab.eval('Object.values(window.__rcsResult.purposeConsents).every(c => !c)'); } } async function evaluateRule(rule: AutoConsentRuleStep, tab: TabActor) { if (rule.frame && !tab.frame) { await waitFor(() => Promise.resolve(!!tab.frame), 10, 500); } const frameId = rule.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.url) { results.push(Promise.resolve(tab.url.startsWith(rule.url))); } if (rule.goto) { results.push(tab.goto(rule.goto)); } if (rule.hide) { results.push(tab.hideElements(rule.hide, frameId)); } // boolean and of results return (await Promise.all(results)).reduce((a, b) => a && b, true); } export class AutoConsent extends AutoConsentBase { constructor(public config: AutoConsentCMPRule) { super(config.name); } async _runRulesParallel(tab: TabActor, rules: AutoConsentRuleStep[]): Promise<boolean> { const detections = await Promise.all(rules.map(rule => evaluateRule(rule, tab))); return detections.some(r => !!r); } async _runRulesSequentially(tab: TabActor, rules: AutoConsentRuleStep[]): Promise<boolean> { for (const rule of rules) { const result = await evaluateRule(rule, tab); if (!result && !rule.optional) { return false; } } return true; } async detectCmp(tab: TabActor) { if (this.config.detectCmp) { return this._runRulesParallel(tab, this.config.detectCmp); } return false; } async detectPopup(tab: TabActor) { if (this.config.detectPopup) { return this._runRulesParallel(tab, this.config.detectPopup); } return false; } detectFrame(tab: TabActor, frame: { url: string }) { if (this.config.frame) { return frame.url.startsWith(this.config.frame); } return false; } async optOut(tab: TabActor) { if (this.config.optOut) { return this._runRulesSequentially(tab, this.config.optOut); } return false; } async optIn(tab: TabActor) { if (this.config.optIn) { return this._runRulesSequentially(tab, this.config.optIn); } return false; } async openCmp(tab: TabActor) { if (this.config.openCmp) { return this._runRulesSequentially(tab, this.config.openCmp); } return false; } async test(tab: TabActor) { if (this.config.test) { return this._runRulesSequentially(tab, this.config.test); } return super.test(tab); } }