@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
JavaScript
/* 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);
}
}