Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New rules #26

Merged
merged 30 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5a34e0a
Unblock scrolling in sourcepoint
muodov Aug 1, 2022
5fe499b
Fix aws rule
muodov Aug 1, 2022
9a20fbc
Run popup detection steps sequentially
muodov Aug 5, 2022
080d1d8
Handle multiple CMPs on the same page (opt-out is still executed for …
muodov Aug 5, 2022
51d511f
Expand the klaro rule
muodov Aug 5, 2022
5f5ee86
Fix cookieconsent rule
muodov Aug 5, 2022
4bb4dea
Add a bunch of detection rules (no opt-out yet)
muodov Aug 5, 2022
578dca4
some test fixes
muodov Aug 5, 2022
8c4c982
Indicate which CMP was found in the extension
muodov Aug 16, 2022
57fc609
Add opt-out rules for complianz, cookie information, primebox, tartea…
muodov Aug 16, 2022
1da27fc
Conditional JSON rules
muodov Aug 16, 2022
f3f7bbd
Add opt-out rules for Moove
muodov Aug 16, 2022
5b55488
Add opt-out rules for Termly
muodov Aug 16, 2022
4825e98
Fix termly and tests for Moove
muodov Aug 17, 2022
8e34076
Add opt-out rules for jquery.cookiebar plugin
muodov Aug 17, 2022
ed94027
Opt-out rules for UK Cookie Consent
muodov Aug 17, 2022
296152f
Add rules for eu cookie law and Mediavine
muodov Aug 17, 2022
5fbf3cd
Add rules for EZoic
muodov Aug 17, 2022
86b5dc8
Test fixes
muodov Aug 17, 2022
2dca986
remove outdated rule
muodov Aug 17, 2022
57ab56a
Add rules for DSGVO Tools
muodov Aug 17, 2022
3532a96
Sort out different complianz flavors
muodov Aug 18, 2022
4a40228
Stabilize mediavine opt-in
muodov Aug 18, 2022
4979bac
Fix rule for vodafone
muodov Aug 18, 2022
a8ad838
Fix complianz test
muodov Aug 18, 2022
dc3e48b
Add iubenda
muodov Aug 19, 2022
e2a4af4
Limit conditionals to visible and exists rules
muodov Aug 23, 2022
e15bfa6
Fix sourcepoint rule
muodov Aug 23, 2022
4f2f394
Add rules for usercentrics-banner
muodov Aug 24, 2022
97c21ba
clarify the "if" docs
muodov Aug 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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