@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
250 lines (230 loc) • 6.22 kB
text/typescript
import Tools from "./tools";
export function matches(config: any) {
const result = Tools.find(config);
if (config.type === "css") {
return !!result.target;
} else if (config.type === "checkbox") {
return !!result.target && result.target.checked;
}
}
export async function executeAction(config: any, param?: any): Promise<boolean | void> {
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(config);
case "wait":
return waitAction(config);
case "eval":
return evalAction(config);
default:
throw "Unknown action type: " + config.type;
}
}
const STEP_TIMEOUT = 0;
function waitTimeout(timeout: number): Promise<void> {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, timeout);
});
}
async function clickAction(config: any) {
const result = Tools.find(config);
if (result.target != null) {
result.target.click();
}
return waitTimeout(STEP_TIMEOUT);
}
async function listAction(config: any, param: any) {
for (let action of config.actions) {
await executeAction(action, param);
}
}
async function consentAction(config: any, consentTypes: any) {
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: any, param: any) {
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: any) {
await new Promise<void>(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: any, param: any) {
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: any) {
const result = Tools.find(config);
if (result.target) {
result.target.classList.add("Autoconsent-Hidden");
// result.target.setAttribute("style", "display: none;");
}
}
async function slideAction(config: any) {
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: any) {
await waitTimeout(config.waitTime);
}
async function closeAction(config: any) {
window.close();
}
async function evalAction(config: any): Promise<boolean> {
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);
}
});
}