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

720 lines (704 loc) 26.8 kB
'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 Tab { constructor(page, url, frames) { // puppeteer doesn't have tab IDs this.id = 1; this.page = page; this.url = url; this.frames = frames; } elementExists(selector, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { try { const elements = yield this.frames[frameId].$$(selector); return elements.length > 0; } catch (e) { return false; } }); } clickElement(selector, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { if (yield this.elementExists(selector, frameId)) { try { return yield this.frames[frameId].click(selector); } catch (e) { return false; } } return false; }); } clickElements(selector, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { const elements = yield this.frames[frameId].$$(selector); try { yield Promise.all(elements.map((elem) => elem.click())); return true; } catch (e) { return false; } }); } elementsAreVisible(selector, check, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { if (!(yield this.elementExists(selector, frameId))) { return false; } const visible = yield this.frames[frameId].$$eval(selector, (nodes) => nodes.map((n) => n.offestParent !== null)); if (visible.length === 0) { return false; } else if (check === 'any') { return visible.some(r => r); } else if (check === 'none') { return visible.every(r => !r); } return visible.every(r => r); }); } getAttribute(selector, attribute, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { const elem = yield this.frames[frameId].$(selector); if (elem) { return (yield elem.getProperty(attribute)).jsonValue(); } }); } eval(script, frameId = 0) { return __awaiter(this, void 0, void 0, function* () { return yield this.frames[frameId].evaluate(script); }); } 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 Promise.resolve(false); }); } goto(url) { return __awaiter(this, void 0, void 0, function* () { return this.page.goto(url); }); } wait(ms) { return new Promise((resolve) => { setTimeout(() => resolve(true), ms); }); } matches(options) { throw new Error("Method not implemented."); } executeAction(config, param) { throw new Error("Method not implemented."); } } 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; exports.Tab = Tab; exports.createAutoCMP = createAutoCMP; exports.rules = rules$1; exports.waitFor = waitFor;