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

147 lines (146 loc) 4.71 kB
import Tab from './web/tab'; import handleContentMessage from './web/content'; import { rules, createAutoCMP } from './index'; import { ConsentOMaticCMP } from './consentomatic/index'; export * from './index'; export { Tab, handleContentMessage, }; class TabConsent { constructor(tab, ruleCheckPromise) { this.tab = tab; this.optOutStatus = null; this.checked = ruleCheckPromise; ruleCheckPromise.then(rule => this.rule = rule); } getCMPName() { if (this.rule) { return this.rule.name; } return null; } async isPopupOpen(retries = 1, interval = 1000) { const isOpen = await this.rule.detectPopup(this.tab); if (!isOpen && retries > 0) { return new Promise((resolve) => setTimeout(() => resolve(this.isPopupOpen(retries - 1, interval)), interval)); } return isOpen; } async doOptOut() { try { this.optOutStatus = await this.rule.optOut(this.tab); return this.optOutStatus; } catch (e) { this.optOutStatus = e; throw e; } } async doOptIn() { return this.rule.optIn(this.tab); } hasTest() { return !!this.rule.hasSelfTest; } async testOptOutWorked() { return this.rule.test(this.tab); } async applyCosmetics(selectors) { const hidden = await this.tab.hideElements(selectors); return hidden; } } export default class AutoConsent { constructor(browser, sendContentMessage) { this.browser = browser; this.sendContentMessage = sendContentMessage; this.consentFrames = new Map(); this.tabCmps = new Map(); this.sendContentMessage = sendContentMessage; this.rules = [...rules]; } addCMP(config) { this.rules.push(createAutoCMP(config)); } disableCMPs(cmpNames) { this.rules = this.rules.filter((cmp) => !cmpNames.includes(cmp.name)); } addConsentomaticCMP(name, config) { this.rules.push(new ConsentOMaticCMP(`com_${name}`, config)); } createTab(tabId) { return new Tab(tabId, this.consentFrames.get(tabId), this.sendContentMessage, this.browser); } async checkTab(tabId) { const tab = this.createTab(tabId); const consent = new TabConsent(tab, this.detectDialog(tab, 20)); this.tabCmps.set(tabId, consent); // check tabs consent.checked.then((rule) => { if (this.consentFrames.has(tabId) && rule) { 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); this.consentFrames.delete(tabId); } onFrame({ tabId, url, frameId }) { // ignore main frames if (frameId === 0) { return; } try { const frame = { id: frameId, url: url, }; const tab = this.createTab(tabId); const frameMatch = this.rules.findIndex(r => r.detectFrame(tab, frame)); if (frameMatch > -1) { this.consentFrames.set(tabId, { type: this.rules[frameMatch].name, url, id: frameId, }); if (this.tabCmps.has(tabId)) { this.tabCmps.get(tabId).tab.frame = this.consentFrames.get(tabId); } } } catch (e) { console.error(e); } } async detectDialog(tab, retries) { const found = await new Promise(async (resolve) => { let earlyReturn = false; await Promise.all(this.rules.map(async (r, index) => { try { if (await r.detectCmp(tab)) { earlyReturn = true; resolve(index); } } catch (e) { console.warn('detectCMP error', r.name, e); } })); if (!earlyReturn) { resolve(-1); } }); if (found === -1 && retries > 0) { return new Promise((resolve) => { setTimeout(async () => { const result = this.detectDialog(tab, retries - 1); resolve(result); }, 500); }); } return found > -1 ? this.rules[found] : null; } }