@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,360 lines (1,341 loc) • 51.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
/* eslint-disable no-restricted-syntax,no-await-in-loop,no-underscore-dangle */
function waitFor(predicate, maxTimes, interval) {
return __awaiter(this, void 0, void 0, function* () {
let result = false;
try {
result = yield predicate();
}
catch (e) {
console.warn('error in waitFor predicate', e);
}
if (!result && maxTimes > 0) {
return new Promise((resolve) => {
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
resolve(waitFor(predicate, maxTimes - 1, interval));
}), interval);
});
}
return Promise.resolve(result);
});
}
class AutoConsentBase {
constructor(name) {
this.hasSelfTest = true;
this.name = name;
}
detectCmp(tab) {
throw new Error('Not Implemented');
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
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');
}
test(tab) {
return __awaiter(this, void 0, void 0, function* () {
// try IAB by default
yield tab.eval('__cmp(\'getVendorConsents\', undefined, r => window.__rcsResult = r)');
return tab.eval('Object.values(window.__rcsResult.purposeConsents).every(c => !c)');
});
}
}
function evaluateRule(rule, tab) {
return __awaiter(this, void 0, void 0, function* () {
if (rule.frame && !tab.frame) {
yield 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((resolve) => __awaiter(this, void 0, void 0, function* () {
// catch eval error silently
try {
resolve(yield 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 (yield Promise.all(results)).reduce((a, b) => a && b, true);
});
}
class AutoConsent extends AutoConsentBase {
constructor(config) {
super(config.name);
this.config = config;
}
_runRulesParallel(tab, rules) {
return __awaiter(this, void 0, void 0, function* () {
const detections = yield Promise.all(rules.map(rule => evaluateRule(rule, tab)));
return detections.some(r => !!r);
});
}
_runRulesSequentially(tab, rules) {
return __awaiter(this, void 0, void 0, function* () {
for (const rule of rules) {
const result = yield evaluateRule(rule, tab);
if (!result && !rule.optional) {
return false;
}
}
return true;
});
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (this.config.detectCmp) {
return this._runRulesParallel(tab, this.config.detectCmp);
}
return false;
});
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
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;
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (this.config.optOut) {
return this._runRulesSequentially(tab, this.config.optOut);
}
return false;
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (this.config.optIn) {
return this._runRulesSequentially(tab, this.config.optIn);
}
return false;
});
}
openCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (this.config.openCmp) {
return this._runRulesSequentially(tab, this.config.openCmp);
}
return false;
});
}
test(tab) {
const _super = Object.create(null, {
test: { get: () => super.test }
});
return __awaiter(this, void 0, void 0, function* () {
if (this.config.test) {
return this._runRulesSequentially(tab, this.config.test);
}
return _super.test.call(this, tab);
});
}
}
class TabActions {
constructor(tabId, url, frame, sendContentMessage, browser) {
this.url = url;
this.frame = frame;
this.sendContentMessage = sendContentMessage;
this.browser = browser;
this.id = tabId;
}
elementExists(selector, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
// console.log(`check for ${selector} in tab ${this.id}, frame ${frameId}`);
return this.sendContentMessage(this.id, {
type: "elemExists",
selector
}, {
frameId
});
});
}
clickElement(selector, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
// console.log(`click element ${selector} in tab ${this.id}`);
return this.sendContentMessage(this.id, {
type: "click",
selector
}, {
frameId
});
});
}
clickElements(selector, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
// console.log(`click elements ${selector} in tab ${this.id}`);
return this.sendContentMessage(this.id, {
type: "click",
all: true,
selector
}, {
frameId
});
});
}
elementsAreVisible(selector, check, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
return this.sendContentMessage(this.id, {
type: "elemVisible",
selector,
check
}, {
frameId
});
});
}
getAttribute(selector, attribute, frameId) {
return __awaiter(this, void 0, void 0, function* () {
return this.sendContentMessage(this.id, {
type: "getAttribute",
selector,
attribute
}, { frameId });
});
}
eval(script, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
// console.log(`run ${script} in tab ${this.id}`);
return yield this.sendContentMessage(this.id, {
type: "eval",
script
}, { frameId });
});
}
waitForElement(selector, timeout, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
const interval = 200;
const times = Math.ceil(timeout / interval);
return waitFor(() => this.elementExists(selector, frameId), times, interval);
});
}
waitForThenClick(selector, timeout, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
yield this.waitForElement(selector, timeout, frameId);
yield this.clickElement(selector, frameId);
return true;
});
}
hideElements(selectors, frameId = 0) {
return __awaiter(this, void 0, void 0, function* () {
return this.sendContentMessage(this.id, {
type: "hide",
selectors
}, { frameId });
});
}
getBrowserTab() {
return __awaiter(this, void 0, void 0, function* () {
return this.browser.tabs.get(this.id);
});
}
goto(url) {
return __awaiter(this, void 0, void 0, function* () {
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;
}
}
function executeAction(config, param) {
return __awaiter(this, void 0, void 0, function* () {
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);
});
}
function clickAction(config) {
return __awaiter(this, void 0, void 0, function* () {
const result = Tools.find(config);
if (result.target != null) {
result.target.click();
}
return waitTimeout(STEP_TIMEOUT);
});
}
function listAction(config, param) {
return __awaiter(this, void 0, void 0, function* () {
for (let action of config.actions) {
yield executeAction(action, param);
}
});
}
function consentAction(config, consentTypes) {
return __awaiter(this, void 0, void 0, function* () {
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) {
yield executeAction(consentConfig.toggleAction);
}
}
else {
if (shouldEnable) {
yield executeAction(consentConfig.trueAction);
}
else {
yield executeAction(consentConfig.falseAction);
}
}
}
});
}
function ifCssAction(config, param) {
return __awaiter(this, void 0, void 0, function* () {
const result = Tools.find(config);
if (!result.target) {
if (config.trueAction) {
yield executeAction(config.trueAction, param);
}
}
else {
if (config.falseAction) {
yield executeAction(config.falseAction, param);
}
}
});
}
function waitCssAction(config) {
return __awaiter(this, void 0, void 0, function* () {
yield 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();
});
});
}
function forEachAction(config, param) {
return __awaiter(this, void 0, void 0, function* () {
const results = Tools.find(config, true);
const oldBase = Tools.base;
for (const result of results) {
if (result.target) {
Tools.setBase(result.target);
yield executeAction(config.action, param);
}
}
Tools.setBase(oldBase);
});
}
function hideAction(config) {
return __awaiter(this, void 0, void 0, function* () {
const result = Tools.find(config);
if (result.target) {
result.target.classList.add("Autoconsent-Hidden");
// result.target.setAttribute("style", "display: none;");
}
});
}
function slideAction(config) {
return __awaiter(this, void 0, void 0, function* () {
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);
yield this.waitTimeout(10);
result.target.dispatchEvent(mouseMove);
yield this.waitTimeout(10);
result.target.dispatchEvent(mouseUp);
}
});
}
function waitAction(config) {
return __awaiter(this, void 0, void 0, function* () {
yield waitTimeout(config.waitTime);
});
}
function closeAction(config) {
return __awaiter(this, void 0, void 0, function* () {
window.close();
});
}
function evalAction(config) {
return __awaiter(this, void 0, void 0, function* () {
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);
}
});
});
}
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 Promise.resolve(elem.length > 0);
}
else if (message.type === "elemExists") {
const exists = document.querySelector(message.selector) !== null;
debug && console.log("[exists?]", message.selector, exists);
return Promise.resolve(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;
});
if (results.length === 0) {
return Promise.resolve(false);
}
else if (message.check === "any") {
return Promise.resolve(results.some(r => r));
}
else if (message.check === "none") {
return Promise.resolve(results.every(r => !r));
}
// all
return Promise.resolve(results.every(r => r));
}
else if (message.type === "getAttribute") {
const elem = document.querySelector(message.selector);
if (!elem) {
return Promise.resolve(false);
}
return Promise.resolve(elem.getAttribute(message.attribute));
}
else if (message.type === "eval") {
// TODO: chrome support
try {
const result = window.eval(message.script); // eslint-disable-line no-eval
debug && console.log("[eval]", message.script, result);
return Promise.resolve(result);
}
catch (e) {
return Promise.reject(e);
}
}
else if (message.type === "hide") {
const parent = document.head ||
document.getElementsByTagName("head")[0] ||
document.documentElement;
const hidden = message.selectors.filter(selector => {
const matching = document.querySelectorAll(selector);
return matching.length > 0;
}, []);
const rule = `${hidden.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 Promise.resolve(hidden);
}
else if (message.type === "matches") {
const matched = matches(message.config);
return Promise.resolve(matched);
}
else if (message.type === "executeAction") {
return executeAction(message.config, message.param).then(result => {
return result !== undefined ? result : true;
});
}
return Promise.resolve(null);
}
class TagCommander extends AutoConsentBase {
constructor() {
super('tagcommander');
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield tab.eval('window.tC && window.tC.privacyCenter !== undefined');
}
catch (e) {
return false;
}
});
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
return (yield tab.elementExists('#dnt-banner')) ||
(yield tab.elementExists('#privacy-iframe')) ||
(yield tab.elementExists('#footer_tc_privacy')) ||
(yield tab.elementExists('#header_tc_privacy'));
});
}
detectFrame(tab, frame) {
return frame.url.startsWith('http://cdn.tagcommander.com/privacy') ||
frame.url.startsWith('https://cdn.tagcommander.com/privacy');
}
openFrame(tab) {
return __awaiter(this, void 0, void 0, function* () {
if ((yield tab.elementExists('#footer_tc_privacy')) ||
(yield tab.elementExists('#footer_tc_privacy_privacy_center')) ||
(yield tab.elementExists('#header_tc_privacy'))) {
yield this.openCmp(tab);
}
});
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield tab.elementExists('#privacy-iframe'))) {
yield this.openFrame(tab);
yield waitFor(() => !!tab.frame, 10, 200);
}
if (!tab.frame) {
return false;
}
yield tab.wait(500);
yield tab.waitForElement('#privacy-cat-modal', 20000, tab.frame.id);
yield tab.wait(500);
yield tab.clickElements('.btn-yes', tab.frame.id);
yield tab.wait(200);
yield tab.clickElement('.modal-footer > button', tab.frame.id);
return true;
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield tab.elementExists('#privacy-iframe'))) {
yield this.openCmp(tab);
yield waitFor(() => !!tab.frame, 10, 200);
}
if (!tab.frame) {
return false;
}
yield new Promise(resolve => setTimeout(resolve, 500));
yield tab.waitForElement('#privacy-cat-modal', 20000, tab.frame.id);
yield tab.clickElements('.btn-no', tab.frame.id);
yield tab.clickElement('.modal-footer > button', tab.frame.id);
return true;
});
}
openCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield tab.eval('tC.privacyCenter.showPrivacyCenter();');
if (yield tab.waitForElement('#privacy-iframe', 10000)) {
yield new Promise(resolve => setTimeout(resolve, 500));
return true;
}
return false;
});
}
test(tab) {
return __awaiter(this, void 0, void 0, function* () {
return tab.eval('tC.privacy.categories[0] === "-1"');
});
}
}
class TrustArc extends AutoConsentBase {
constructor() {
super('TrustArc');
}
detectFrame(_, frame) {
return frame.url.startsWith('https://consent-pref.trustarc.com/?');
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (tab.frame && tab.frame.url.startsWith('https://consent-pref.trustarc.com/?')) {
return true;
}
return tab.elementExists('#truste-show-consent');
});
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (tab.frame) {
return tab.waitForElement('#defaultpreferencemanager', 5000, tab.frame.id);
}
return tab.elementExists('#truste-show-consent');
});
}
openFrame(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (yield tab.elementExists('#truste-show-consent')) {
yield tab.clickElement('#truste-show-consent');
}
});
}
navigateToSettings(tab, frameId) {
return __awaiter(this, void 0, void 0, function* () {
// wait for it to load
yield waitFor(() => __awaiter(this, void 0, void 0, function* () {
return (yield tab.elementExists('.shp', frameId)) ||
(yield tab.elementsAreVisible('.advance', 'any', frameId)) ||
tab.elementExists('.switch span:first-child', frameId);
}), 10, 500);
// splash screen -> hit more information
if (yield tab.elementExists('.shp', frameId)) {
yield tab.clickElement('.shp', frameId);
}
// wait for consent options
if (!(yield tab.waitForElement('.switch span:first-child', 2000, frameId))) {
return false;
}
// go to advanced settings if not yet shown
if (yield tab.elementsAreVisible('.advance', 'any', frameId)) {
yield tab.clickElement('.advance', frameId);
}
// takes a while to load the opt-in/opt-out buttons
return yield waitFor(() => tab.elementsAreVisible('.switch span:first-child', 'any', frameId), 5, 1000);
});
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield tab.hideElements(['.truste_overlay', '.truste_box_overlay', '.trustarc-banner', '.truste-banner']);
return true;
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (!tab.frame) {
yield this.openFrame(tab);
yield waitFor(() => !!tab.frame, 10, 200);
}
const frameId = tab.frame.id;
yield this.navigateToSettings(tab, frameId);
yield tab.clickElements('.switch span:nth-child(2)', frameId);
yield tab.clickElement('.submit', frameId);
yield waitFor(() => tab.elementExists('#gwt-debug-close_id', frameId), 300, 1000);
yield tab.clickElement('#gwt-debug-close_id', frameId);
return true;
});
}
openCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield tab.eval('truste.eu.clickListener()');
return true;
});
}
test() {
return __awaiter(this, void 0, void 0, function* () {
// TODO: find out how to test TrustArc
return true;
});
}
}
class Cookiebot extends AutoConsentBase {
constructor() {
super('Cybotcookiebot');
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield tab.eval('typeof window.CookieConsent === "object"');
}
catch (e) {
return false;
}
});
}
detectPopup(tab) {
return tab.elementExists('#CybotCookiebotDialog,#dtcookie-container,#cookiebanner');
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (yield tab.elementExists('#dtcookie-container')) {
return tab.clickElement('.h-dtcookie-decline');
}
if (yield tab.elementExists('.cookiebot__button--settings')) {
yield tab.clickElement('.cookiebot__button--settings');
}
if (yield tab.elementsAreVisible('#CybotCookiebotDialogBodyButtonDecline', 'all')) {
return yield tab.clickElement('#CybotCookiebotDialogBodyButtonDecline');
}
yield tab.clickElements('.CybotCookiebotDialogBodyLevelButton:checked:enabled,input[id*="CybotCookiebotDialogBodyLevelButton"]:checked:enabled');
if (yield tab.elementExists('#CybotCookiebotDialogBodyButtonDecline')) {
yield tab.clickElement('#CybotCookiebotDialogBodyButtonDecline');
}
yield tab.clickElements('#CybotCookiebotDialogBodyLevelButtonAccept,#CybotCookiebotDialogBodyButtonAccept');
// await tab.clickElement('#CybotCookiebotDialogBodyButtonAccept');
return true;
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (yield tab.elementExists('#dtcookie-container')) {
return tab.clickElement('.h-dtcookie-accept');
}
yield tab.clickElements('.CybotCookiebotDialogBodyLevelButton:not(:checked):enabled');
yield tab.clickElement('#CybotCookiebotDialogBodyLevelButtonAccept');
yield tab.clickElement('#CybotCookiebotDialogBodyButtonAccept');
return true;
});
}
openCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield tab.eval('CookieConsent.renew() || true');
return tab.waitForElement('#CybotCookiebotDialog', 10000);
});
}
test(tab) {
return __awaiter(this, void 0, void 0, function* () {
return tab.eval('CookieConsent.declined === true');
});
}
}
function clickFirstMatching(tab, elements) {
return __awaiter(this, void 0, void 0, function* () {
const exists = yield Promise.all(elements.map(e => tab.elementExists(e)));
const first = exists.findIndex(e => !!e);
if (first >= 0) {
yield tab.clickElement(elements[first]);
return;
}
throw new Error('cannot open CMP');
});
}
const popupContainers = [
'div[class*="popup_popup-"]',
'div[class*="banner_banner-"]',
'div[class*="main_banner-"]',
'.prism-modal-banner',
'div[class*="popupFooter_content-"]'
];
const moreInfoElements = [
'a[class^="banner_learnMore-"]',
'button[class*="intro_rejectAll-"],span[class*="intro_rejectAll-"]',
'.prism-modal-banner .ad-settings',
// link to open options has no class or id, so this might be flaky
'div[class^="banner_message-"] > span:first-child > a',
'div[class^="banner_copy-"] > a',
'div[class^="main_buttons-"] > div > button:first-child',
'div[class^="banner_option-"]:nth-child(3) > a,a[class^="banner_option-"]:nth-child(3)',
];
class AppGdpr extends AutoConsentBase {
constructor() {
super('app_gdpr');
}
detectCmp(tab) {
return tab.elementExists('div[class^="app_gdpr-"]');
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
return !(yield tab.elementExists('div[class*="banner_hidden-"]'))
&& (yield tab.elementsAreVisible(popupContainers.join(','), 'any'))
&& (yield tab.elementExists(moreInfoElements.join(',')))
&& this.detectCmp(tab);
});
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield clickFirstMatching(tab, moreInfoElements);
if (yield tab.elementExists('button[class*="details_btnDeactivate-"],button[class*="introV2_rejectAll-"')) {
yield tab.waitForThenClick('button[class*="details_btnDeactivate-"],button[class*="introV2_rejectAll-"');
return tab.clickElement('button[class*="details_btnSave-"],button[class*="details_save-"]');
}
else if (yield tab.elementExists('div[class*="purposes_purposeItem-"]')) {
let i = 1;
while (yield tab.elementExists(`div[class*="purposes_purposeItem-"]:nth-child(${i})`)) {
yield tab.clickElement(`div[class*="purposes_purposeItem-"]:nth-child(${i})`);
yield tab.clickElements('span[class*="switch_isSelected-"]');
i += 1;
}
}
else if (yield tab.elementExists('span[class*="switch_isSelected-"]')) {
// streamlined UI with categories
yield tab.clickElements('span[class*="switch_isSelected-"]');
}
else {
// we have to turn off vendors for all categories...
let i = 1;
while (yield tab.elementExists(`div[class*="summary_purposeItem-"]:nth-child(${i}) > a`)) {
yield tab.clickElement(`div[class*="summary_purposeItem-"]:nth-child(${i}) > a`);
yield tab.clickElements('span[class*="switch_isSelected-"]');
yield tab.clickElement('button[class*="details_back"]');
i += 1;
}
}
return yield tab.clickElement('button[class*="details_save"]');
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (yield tab.elementExists('a[class^="banner_continue-"]')) {
yield tab.clickElement('a[class^="banner_continue-"]');
}
if (yield tab.elementExists('span[class^="banner_consent-"')) {
yield tab.clickElement('span[class^="banner_consent-"');
}
return true;
});
}
}
const domains = [
'techradar.com',
'cyclingnews.com',
'livescience.com',
'gamesradar.com',
'itproportal.com',
];
class Future extends AutoConsentBase {
constructor() {
super('future');
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
return !!((yield tab.elementExists('#cmp-container-id')) && tab.frame);
});
}
detectFrame(tab, frame) {
return domains.some(d => frame.url.startsWith(`https://consent.cmp.${d}/`));
}
detectPopup(tab) {
return tab.elementExists('#cmp-container-id');
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield waitFor(() => !!tab.frame, 10, 500))) {
throw new Error('no frame');
}
yield waitFor(() => !!tab.frame, 10, 500);
yield tab.waitForElement('#mainMoreInfo', 10000, tab.frame.id);
yield tab.clickElement('#mainMoreInfo', tab.frame.id);
yield tab.clickElement('.cmp-btn-acceptall', tab.frame.id);
yield tab.clickElement('.cmp-btn-acceptall', tab.frame.id);
return true;
});
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield waitFor(() => !!tab.frame, 10, 500))) {
throw new Error('no frame');
}
yield tab.waitForElement('#mainMoreInfo', 10000, tab.frame.id);
yield tab.clickElement('#mainMoreInfo', tab.frame.id);
yield tab.clickElement('.cmp-btn-rejectall', tab.frame.id);
yield tab.clickElement('#confirmLeave', tab.frame.id);
return true;
});
}
}
const rules = [
new TagCommander(),
new TrustArc(),
new Cookiebot(),
new AppGdpr(),
new Future(),
];
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");
}
detectCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
return (yield Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.presentMatcher)))).some(matched => matched);
});
}
detectPopup(tab) {
return __awaiter(this, void 0, void 0, function* () {
return (yield Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.showingMatcher)))).some(matched => matched);
});
}
executeAction(tab, method, param) {
return __awaiter(this, void 0, void 0, function* () {
if (this.methods.has(method)) {
return tab.executeAction(this.methods.get(method), param);
}
return true;
});
}
optOut(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield this.executeAction(tab, "HIDE_CMP");
yield this.executeAction(tab, "OPEN_OPTIONS");
yield this.executeAction(tab, "HIDE_CMP");
yield this.executeAction(tab, "DO_CONSENT", []);
yield this.executeAction(tab, "SAVE_CONSENT");
return true;
});
}
optIn(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield this.executeAction(tab, "HIDE_CMP");
yield this.executeAction(tab, "OPEN_OPTIONS");
yield this.executeAction(tab, "HIDE_CMP");
yield this.executeAction(tab, "DO_CONSENT", ['D', 'A', 'B', 'E', 'F', 'X']);
yield this.executeAction(tab, "SAVE_CONSENT");
return true;
});
}
openCmp(tab) {
return __awaiter(this, void 0, void 0, function* () {
yield this.executeAction(tab, "HIDE_CMP");
yield this.executeAction(tab, "OPEN_OPTIONS");
return true;
});
}
test(tab) {
return this.executeAction(tab, "TEST_CONSENT");
}
detectFrame(tab, frame) {
return false;
}
}
class TabConsent {
constructor(tab, url, ruleCheckPromise) {
this.tab = tab;
this.url = url;
this.checked = ruleCheckPromise;
ruleCheckPromise.then(rule => this.rule = rule);
}
getCMPName() {
if (this.rule) {
return this.rule.name;
}
return null;
}
isPopupOpen() {
return __awaiter(this, void 0, void 0, function* () {
return (yield this.rule.detectPopup(this.tab)) || (yield new Promise((resolve) => {
setTimeout(() => __awaiter(this, void 0, void 0, function* () { return resolve(yield this.rule.detectPopup(this.tab)); }), 1000);
}));
});
}
doOptOut() {
return __awaiter(this, void 0, void 0, function* () {
return this.rule.optOut(this.tab);
});
}
doOptIn() {
return __awaiter(this, void 0, void 0, function* () {
return this.rule.optIn(this.tab);
});
}
hasTest() {
return !!this.rule.hasSelfTest;
}
testOptOutWorked() {
return __awaiter(this, void 0, void 0, function* () {
return this.rule.test(this.tab);
});
}
applyCosmetics(selectors) {
return __awaiter(this, void 0, void 0, function* () {
const hidden = yield 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));
}
addConsentomaticCMP(name, config) {
this.rules.push(new ConsentOMaticCMP(`com_${name}`, config));
}
createTab(tabId, url) {
return new TabActions(tabId, url, this.consentFrames.get(tabId), this.sendContentMessage, this.browser);
}
checkTab(tabId) {
return __awaiter(this, void 0, void 0, function* () {
const tabInfo = yield this.browser.tabs.get(tabId);
const pageUrl = new URL(tabInfo.url);
if (!this.tabCmps.has(tabId) || this.tabCmps.get(tabId).url.href !== pageUrl.href) {
const tab = this.createTab(tabId, pageUrl.href);
const consent = new TabConsent(tab, pageUrl, this.detectDialog(tab, 5));
this.tabCmps.set(tabId, consent);
// check tabs
consent.checked.then((rule) => {
if (this.consentFrames.has(tabId)) {
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);
}
onFrame({ tabId, url, frameId }) {
// ignore main frames
if (frameId === 0) {
return;
}
try {
const frame = {
id: frameId,
url: