Skip to content

Commit

Permalink
New rules (#26)
Browse files Browse the repository at this point in the history
* Unblock scrolling in sourcepoint

* Fix aws rule

* Run popup detection steps sequentially

* Handle multiple CMPs on the same page (opt-out is still executed for only one of them)

* Expand the klaro rule

* Fix cookieconsent rule

* Add a bunch of detection rules (no opt-out yet)

* some test fixes

* Indicate which CMP was found in the extension

* Add opt-out rules for complianz, cookie information, primebox, tarteaucitron and wp-cookie-notice

* Conditional JSON rules

* Add opt-out rules for Moove

* Add opt-out rules for Termly

* Fix termly and tests for Moove

* Add opt-out rules for jquery.cookiebar plugin

* Opt-out rules for UK Cookie Consent

* Add rules for eu cookie law and Mediavine

* Add rules for EZoic

* Test fixes

* remove outdated rule

* Add rules for DSGVO Tools

* Sort out different complianz flavors

* Stabilize mediavine opt-in

* Fix rule for vodafone

* Fix complianz test

* Add iubenda

* Limit conditionals to visible and exists rules

* Fix sourcepoint rule

* Add rules for usercentrics-banner

* clarify the "if" docs
  • Loading branch information
muodov authored Aug 25, 2022
1 parent 476a487 commit 4f858c9
Show file tree
Hide file tree
Showing 62 changed files with 731 additions and 144 deletions.
22 changes: 12 additions & 10 deletions addon/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ async function evalInTab(tabId: number, frameId: number, code: string): Promise<
return window.eval(code);
} catch (e) {
// ignore CSP errors
console.warn('eval error', code, e);
return;
}
},
Expand All @@ -85,27 +86,28 @@ if (manifestVersion === 2) {

function showOptOutStatus(
tabId: number,
status: "success" | "complete" | "working" | "available" | "verified" | "idle"
status: "success" | "complete" | "working" | "available" | "verified" | "idle",
cmp = '',
) {
let title = "";
let icon = "icons/cookie-idle.png";
if (status === "success") {
title = "Opt out successful!";
title = `Opt out successful! (${cmp})`;
icon = "icons/party.png";
} else if (status === "complete") {
title = "Opt out complete!";
title = `Opt out complete! (${cmp})`;
icon = "icons/tick.png";
} else if (status === "working") {
title = "Processing...";
title = `Processing... (${cmp})`;
icon = "icons/cog.png";
} else if (status === "verified") {
title = "Verified";
title = `Verified (${cmp})`;
icon = "icons/verified.png";
} else if (status === "idle") {
title = "Idle";
icon = "icons/cookie-idle.png";
} else if (status === "available") {
title = "Click to opt out";
title = `Click to opt out (${cmp})`;
icon = "icons/cookie.png";
}
enableLogs && console.log('Setting action state to', status);
Expand Down Expand Up @@ -164,15 +166,15 @@ chrome.runtime.onMessage.addListener(
});
break;
case "popupFound":
showOptOutStatus(tabId, "available");
showOptOutStatus(tabId, "available", msg.cmp);
storageSet({
[`detected${tabId}`]: frameId,
});
break;
case "optOutResult":
case "optInResult":
if (msg.result) {
showOptOutStatus(tabId, "working");
showOptOutStatus(tabId, "working", msg.cmp);
if (msg.scheduleSelfTest) {
await storageSet({
[`selfTest${tabId}`]: frameId,
Expand All @@ -182,11 +184,11 @@ chrome.runtime.onMessage.addListener(
break;
case "selfTestResult":
if (msg.result) {
showOptOutStatus(tabId, "verified");
showOptOutStatus(tabId, "verified", msg.cmp);
}
break;
case "autoconsentDone": {
showOptOutStatus(tabId, "success");
showOptOutStatus(tabId, "success", msg.cmp);
// sometimes self-test needs to be done in another frame
const selfTestKey = `selfTest${tabId}`;
const selfTestFrameId = (await chrome.storage.local.get(selfTestKey))?.[selfTestKey];
Expand Down
2 changes: 2 additions & 0 deletions lib/cmps/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Evidon from './evidon';
import { AutoConsentCMPRule } from '../rules';
import Onetrust from './onetrust';
import { AutoCMP } from '../types';
import Klaro from './klaro';

const rules: AutoCMP[] = [
new TrustArcTop(),
Expand All @@ -19,6 +20,7 @@ const rules: AutoCMP[] = [
new ConsentManager(),
new Evidon(),
new Onetrust(),
new Klaro(),
];

export function createAutoCMP(config: AutoConsentCMPRule): AutoConsentCMP {
Expand Down
61 changes: 37 additions & 24 deletions lib/cmps/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ async function evaluateRuleStep(rule: AutoConsentRuleStep) {
if (rule.hide) {
results.push(hide(rule.hide, rule.method));
}
if (rule.if) {
if (!rule.if.exists && !rule.if.visible) {
console.error('invalid conditional rule', rule.if);
return false;
}
const condition = await evaluateRuleStep(rule.if);
enableLogs && console.log('Condition is', condition);
if (condition) {
results.push(_runRulesSequentially(rule.then));
} else if (rule.else) {
results.push(_runRulesSequentially(rule.else));
}
}

if (results.length === 0) {
enableLogs && console.warn('Unrecognized rule', rule);
Expand All @@ -125,6 +138,24 @@ async function evaluateRuleStep(rule: AutoConsentRuleStep) {
return all.reduce((a, b) => a && b, true);
}

async function _runRulesParallel(rules: AutoConsentRuleStep[]): Promise<boolean> {
const results = rules.map(rule => evaluateRuleStep(rule));
const detections = await Promise.all(results);
return detections.every(r => !!r);
}

async function _runRulesSequentially(rules: AutoConsentRuleStep[]): Promise<boolean> {
for (const rule of rules) {
enableLogs && console.log('Running rule...', rule);
const result = await evaluateRuleStep(rule);
enableLogs && console.log('...rule result', result);
if (!result && !rule.optional) {
return false;
}
}
return true;
}

export class AutoConsentCMP extends AutoConsentCMPBase {

constructor(public config: AutoConsentCMPRule) {
Expand All @@ -144,64 +175,46 @@ export class AutoConsentCMP extends AutoConsentCMPBase {
return this.config.prehideSelectors;
}

async _runRulesParallel(rules: AutoConsentRuleStep[]): Promise<boolean> {
const results = rules.map(rule => evaluateRuleStep(rule));
const detections = await Promise.all(results);
return detections.every(r => !!r);
}

async _runRulesSequentially(rules: AutoConsentRuleStep[]): Promise<boolean> {
for (const rule of rules) {
enableLogs && console.log('Running rule...', rule);
const result = await evaluateRuleStep(rule);
enableLogs && console.log('...rule result', result);
if (!result && !rule.optional) {
return false;
}
}
return true;
}

async detectCmp() {
if (this.config.detectCmp) {
return this._runRulesParallel(this.config.detectCmp);
return _runRulesParallel(this.config.detectCmp);
}
return false;
}

async detectPopup() {
if (this.config.detectPopup) {
return this._runRulesParallel(this.config.detectPopup);
return _runRulesSequentially(this.config.detectPopup);
}
return false;
}

async optOut() {
if (this.config.optOut) {
enableLogs && console.log('Initiated optOut()', this.config.optOut);
return this._runRulesSequentially(this.config.optOut);
return _runRulesSequentially(this.config.optOut);
}
return false;
}

async optIn() {
if (this.config.optIn) {
enableLogs && console.log('Initiated optIn()', this.config.optIn);
return this._runRulesSequentially(this.config.optIn);
return _runRulesSequentially(this.config.optIn);
}
return false;
}

async openCmp() {
if (this.config.openCmp) {
return this._runRulesSequentially(this.config.openCmp);
return _runRulesSequentially(this.config.openCmp);
}
return false;
}

async test() {
if (this.hasSelfTest) {
return this._runRulesSequentially(this.config.test);
return _runRulesSequentially(this.config.test);
}
return super.test();
}
Expand Down
66 changes: 66 additions & 0 deletions lib/cmps/klaro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { click, doEval, elementExists, elementVisible, waitForElement } from "../rule-executors";
import AutoConsentCMPBase from "./base";

export default class Klaro extends AutoConsentCMPBase {

prehideSelectors = [".klaro"]
settingsOpen = false;

constructor() {
super("Klaro");
}

get hasSelfTest(): boolean {
return true;
}

get isIntermediate(): boolean {
return false;
}

async detectCmp() {
if (elementExists('.klaro > .cookie-modal')) {
this.settingsOpen = true;
return true;
}
return elementExists(".klaro > .cookie-notice");
}

async detectPopup() {
return elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal", 'any');
}

async optOut() {
if (click('.klaro .cn-decline')) {
return true;
}

if (!this.settingsOpen) {
click('.klaro .cn-learn-more');
await waitForElement('.klaro > .cookie-modal', 2000);
this.settingsOpen = true;
}

if (click('.klaro .cn-decline')) {
return true;
}

click('.cm-purpose:not(.cm-toggle-all) > input:not(.half-checked)', true);
return click('.cm-btn-accept');
}

async optIn() {
if (click('.klaro .cm-btn-accept-all')) {
return true;
}
if (this.settingsOpen) {
click('.cm-purpose:not(.cm-toggle-all) > input.half-checked', true);
return click('.cm-btn-accept');
}
return click('.klaro .cookie-notice .cm-btn-success');
}

async test() {
return await doEval('klaro.getManager().config.services.every(c => c.required || !klaro.getManager().consents[c.name])');
}
}
17 changes: 9 additions & 8 deletions lib/cmps/sourcepoint-frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
return true;
}
return (url.pathname === '/index.html' || url.pathname === '/privacy-manager/index.html')
&& url.searchParams.has('message_id') && url.searchParams.has('requestUUID');
&& (url.searchParams.has('message_id') || url.searchParams.has('requestUUID') || url.searchParams.has('consentUUID'));
}

async detectPopup() {
Expand All @@ -57,16 +57,17 @@ export default class SourcePoint extends AutoConsentCMPBase {

async optOut() {
if (!this.isManagerOpen()) {
const actionable = await waitForElement('button.sp_choice_type_12,button.sp_choice_type_13');
const actionable = await waitForElement('.sp_choice_type_12,.sp_choice_type_13');
if (!actionable) {
return false;
}
if (!elementExists("button.sp_choice_type_12")) {
if (!elementExists(".sp_choice_type_12")) {
// do not sell button
return click("button.sp_choice_type_13");
return click(".sp_choice_type_13");
}

click("button.sp_choice_type_12");
click(".sp_choice_type_12");
// the page may navigate at this point but that's okay
await waitFor(
() => location.pathname === "/privacy-manager/index.html",
200,
Expand All @@ -88,9 +89,8 @@ export default class SourcePoint extends AutoConsentCMPBase {
await wait(1000);
return click(rejectSelector1);
} else if (path === 1) {
return click(rejectSelector2);
click(rejectSelector2);
} else if (path === 2) {
// TODO: check if this is still working
await waitForElement('.pm-features', 10000);
click('.checked > span', true);

Expand All @@ -99,6 +99,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
} catch (e) {
enableLogs && console.warn(e);
}
return click('.sp_choice_type_SAVE_AND_EXIT');
click('.sp_choice_type_SAVE_AND_EXIT');
return true;
}
}
5 changes: 5 additions & 0 deletions lib/cmps/sourcepoint-top.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export default class SourcePoint extends AutoConsentCMPBase {
}

async optOut() {
// unblock scrolling
const container = document.querySelector('.sp-message-open');
if (container) {
container.classList.remove('sp-message-open');
}
return true;
}

Expand Down
9 changes: 8 additions & 1 deletion lib/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export type AutoConsentRuleStep = { optional?: boolean } & Partial<
Partial<WaitForThenClickRule> &
Partial<WaitRule> &
Partial<UrlRule> &
Partial<HideRule>;
Partial<HideRule> &
Partial<IfRule>;

export type ElementExistsRule = {
exists: string;
Expand Down Expand Up @@ -80,3 +81,9 @@ export type HideRule = {
hide: string[];
method?: HideMethod;
};

export type IfRule = {
if: Partial<ElementExistsRule> & Partial<ElementVisibleRule>;
then: AutoConsentRuleStep[];
else?: AutoConsentRuleStep[];
};
Loading

0 comments on commit 4f858c9

Please sign in to comment.