@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
173 lines (151 loc) • 4.47 kB
text/typescript
import Tab from './web/tab';
import handleContentMessage from './web/content';
import { rules, createAutoCMP } from './index';
import { Browser, MessageSender, AutoCMP, TabActor } from './types';
import { ConsentOMaticCMP, ConsentOMaticConfig } from './consentomatic/index';
import { AutoConsentCMPRule } from './rules';
export * from './index';
export {
Tab,
handleContentMessage,
}
class TabConsent {
checked: Promise<AutoCMP>
rule: AutoCMP
optOutStatus: boolean | Error = null
constructor(public tab: TabActor, ruleCheckPromise: Promise<AutoCMP>) {
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: string[]) {
const hidden = await this.tab.hideElements(selectors);
return hidden;
}
}
export default class AutoConsent {
consentFrames: Map<number, any> = new Map()
tabCmps: Map<number, TabConsent> = new Map()
rules: AutoCMP[]
constructor(protected browser: Browser, protected sendContentMessage: MessageSender) {
this.sendContentMessage = sendContentMessage;
this.rules = [...rules];
}
addCMP(config: AutoConsentCMPRule) {
this.rules.push(createAutoCMP(config));
}
disableCMPs(cmpNames: String[]) {
this.rules = this.rules.filter((cmp) => !cmpNames.includes(cmp.name))
}
addConsentomaticCMP(name: string, config: ConsentOMaticConfig) {
this.rules.push(new ConsentOMaticCMP(`com_${name}`, config));
}
createTab(tabId: number) {
return new Tab(tabId,
this.consentFrames.get(tabId),
this.sendContentMessage,
this.browser);
}
async checkTab(tabId: number) {
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: number) {
this.tabCmps.delete(tabId);
this.consentFrames.delete(tabId);
}
onFrame({ tabId, url, frameId }: { tabId: number, url: string, frameId: number }) {
// 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: TabActor, retries: number): Promise<AutoCMP> {
const found: number = 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;
}
}