@duckduckgo/autoconsent 2.1.2 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.vscode/.idea/.vscode.iml +9 -0
  2. package/.vscode/.idea/modules.xml +8 -0
  3. package/.vscode/.idea/workspace.xml +28 -0
  4. package/.vscode/settings.json +7 -0
  5. package/dist/addon-firefox/background.bundle.js +1 -1
  6. package/dist/addon-firefox/content.bundle.js +1 -1
  7. package/dist/addon-firefox/manifest.json +1 -1
  8. package/dist/addon-firefox/rules.json +985 -51
  9. package/dist/addon-mv3/background.bundle.js +1 -1
  10. package/dist/addon-mv3/content.bundle.js +1 -1
  11. package/dist/addon-mv3/manifest.json +1 -1
  12. package/dist/addon-mv3/rules.json +985 -51
  13. package/dist/autoconsent.cjs.js +1 -1
  14. package/dist/autoconsent.esm.js +1 -1
  15. package/dist/autoconsent.playwright.js +1 -1
  16. package/lib/cmps/all.ts +7 -0
  17. package/lib/cmps/base.ts +37 -24
  18. package/lib/cmps/consentmanager.ts +27 -4
  19. package/lib/cmps/conversant.ts +60 -0
  20. package/lib/cmps/klaro.ts +66 -0
  21. package/lib/cmps/onetrust.ts +3 -3
  22. package/lib/cmps/sourcepoint-frame.ts +9 -8
  23. package/lib/cmps/sourcepoint-top.ts +5 -0
  24. package/lib/cmps/trustarc-frame.ts +3 -0
  25. package/lib/cmps/uniconsent.ts +71 -0
  26. package/lib/rules.ts +8 -1
  27. package/lib/web.ts +48 -25
  28. package/package.json +1 -1
  29. package/readme.md +29 -0
  30. package/rules/autoconsent/adroll.json +11 -0
  31. package/rules/autoconsent/aws-amazon.json +6 -1
  32. package/rules/autoconsent/axeptio.json +41 -0
  33. package/rules/autoconsent/clickio.json +18 -0
  34. package/rules/autoconsent/complianz-banner.json +13 -0
  35. package/rules/autoconsent/complianz-categories.json +17 -0
  36. package/rules/autoconsent/complianz-notice.json +14 -0
  37. package/rules/autoconsent/complianz-optin.json +20 -0
  38. package/rules/autoconsent/cookie-notice.json +3 -1
  39. package/rules/autoconsent/cookieinformation.json +14 -0
  40. package/rules/autoconsent/dsgvo.json +16 -0
  41. package/rules/autoconsent/eu-cookie-law.json +20 -0
  42. package/rules/autoconsent/ezoic.json +19 -0
  43. package/rules/autoconsent/iubenda.json +23 -0
  44. package/rules/autoconsent/jquery-cookiebar.json +25 -0
  45. package/rules/autoconsent/mediavine.json +20 -0
  46. package/rules/autoconsent/moove.json +28 -0
  47. package/rules/autoconsent/paypal.json +4 -1
  48. package/rules/autoconsent/primebox.json +19 -0
  49. package/rules/autoconsent/privacymanager.json +30 -0
  50. package/rules/autoconsent/pubtech.json +42 -0
  51. package/rules/autoconsent/sibbo.json +43 -0
  52. package/rules/autoconsent/sirdata.json +11 -0
  53. package/rules/autoconsent/tarteaucitron.json +18 -0
  54. package/rules/autoconsent/tealium.json +0 -1
  55. package/rules/autoconsent/termly.json +31 -0
  56. package/rules/autoconsent/testcmp.json +4 -1
  57. package/rules/autoconsent/uk-cookie-consent.json +15 -0
  58. package/rules/autoconsent/{usercentrics-1.json → usercentrics-api.json} +2 -2
  59. package/rules/autoconsent/usercentrics-button.json +14 -0
  60. package/rules/autoconsent/vodafone-de.json +5 -5
  61. package/rules/autoconsent/wp-cookie-notice.json +12 -0
  62. package/rules/rules.json +985 -51
  63. package/tests/adroll.spec.ts +15 -0
  64. package/tests/axeptio.spec.ts +9 -0
  65. package/tests/clickio.spec.ts +10 -0
  66. package/tests/complianz-banner.spec.ts +7 -0
  67. package/tests/complianz-categories.spec.ts +14 -0
  68. package/tests/{cookieconsent.spec.ts → complianz-notice.spec.ts} +1 -2
  69. package/tests/complianz-optin.spec.ts +6 -0
  70. package/tests/consentmanager.spec.ts +2 -1
  71. package/tests/conversant.spec.ts +10 -0
  72. package/tests/{cookienotice.spec.ts → cookie-notice.spec.ts} +0 -0
  73. package/tests/cookieinformation.spec.ts +10 -0
  74. package/tests/dsgvo.spec.ts +6 -0
  75. package/tests/eu-cookie-law.spec.ts +6 -0
  76. package/tests/ezoic.spec.ts +8 -0
  77. package/tests/iubenda.spec.ts +8 -0
  78. package/tests/jquery-cookiebar.spec.ts +6 -0
  79. package/tests/klaro.spec.ts +6 -2
  80. package/tests/mediavine.spec.ts +8 -0
  81. package/tests/moove.spec.ts +14 -0
  82. package/tests/oil.spec.ts +3 -2
  83. package/tests/primebox.spec.ts +7 -0
  84. package/tests/privacymanager.spec.ts +8 -0
  85. package/tests/pubtech.spec.ts +13 -0
  86. package/tests/sibbo.spec.ts +16 -0
  87. package/tests/sirdata.spec.ts +8 -0
  88. package/tests/tarteaucitron.spec.ts +9 -0
  89. package/tests/tealium.spec.ts +1 -1
  90. package/tests/termly.spec.ts +12 -0
  91. package/tests/trustarc.spec.ts +1 -9
  92. package/tests/uk-cookie-consent.spec.ts +7 -0
  93. package/tests/uniconsent.spec.ts +12 -0
  94. package/tests/{usercentrics-1.spec.ts → usercentrics-api.spec.ts} +3 -2
  95. package/tests/usercentrics-button.spec.ts +8 -0
  96. package/tests/wp-cookie-notice.spec.ts +8 -0
  97. package/dist/autoconsent.standalone.js +0 -1
  98. package/rules/autoconsent/cookieconsent.json +0 -8
  99. package/rules/autoconsent/destatis-de.json +0 -8
  100. package/rules/autoconsent/klaro.json +0 -10
  101. package/tests/destatis.spec.ts +0 -7
@@ -1,14 +1,15 @@
1
- import { click, elementExists, elementVisible, waitForElement } from "../rule-executors";
1
+ import { click, doEval, elementExists, elementVisible, wait, waitForElement } from "../rule-executors";
2
2
  import AutoConsentCMPBase from "./base";
3
3
 
4
4
  // Note: JS API is also available:
5
5
  // https://help.consentmanager.net/books/cmp/page/javascript-api
6
6
  export default class ConsentManager extends AutoConsentCMPBase {
7
7
 
8
- prehideSelectors = ["#cmpbox,#cmpbox2"]
8
+ prehideSelectors = ["#cmpbox,#cmpbox2"];
9
+ apiAvailable = false;
9
10
 
10
11
  get hasSelfTest(): boolean {
11
- return false;
12
+ return this.apiAvailable;
12
13
  }
13
14
 
14
15
  get isIntermediate(): boolean {
@@ -20,14 +21,27 @@ export default class ConsentManager extends AutoConsentCMPBase {
20
21
  }
21
22
 
22
23
  async detectCmp() {
23
- return elementExists("#cmpbox");
24
+ this.apiAvailable = await doEval('window.__cmp && typeof __cmp("getCMPData") === "object"');
25
+ if (!this.apiAvailable) {
26
+ return elementExists("#cmpbox");
27
+ } else {
28
+ return true;
29
+ }
24
30
  }
25
31
 
26
32
  async detectPopup() {
33
+ if (this.apiAvailable) {
34
+ return await doEval("!__cmp('consentStatus').userChoiceExists");
35
+ }
27
36
  return elementVisible("#cmpbox .cmpmore", 'any');
28
37
  }
29
38
 
30
39
  async optOut() {
40
+ await wait(500);
41
+ if (this.apiAvailable) {
42
+ return await doEval("__cmp('setConsent', 0)");
43
+ }
44
+
31
45
  if (click(".cmpboxbtnno")) {
32
46
  return true;
33
47
  }
@@ -46,6 +60,15 @@ export default class ConsentManager extends AutoConsentCMPBase {
46
60
  }
47
61
 
48
62
  async optIn() {
63
+ if (this.apiAvailable) {
64
+ return await doEval("__cmp('setConsent', 1)");
65
+ }
49
66
  return click(".cmpboxbtnyes");
50
67
  }
68
+
69
+ async test() {
70
+ if (this.apiAvailable) {
71
+ return await doEval("__cmp('consentStatus').userChoiceExists");
72
+ }
73
+ }
51
74
  }
@@ -0,0 +1,60 @@
1
+ import { click, elementExists, elementVisible, waitForElement, waitForThenClick } from "../rule-executors";
2
+ import { waitFor } from "../utils";
3
+ import AutoConsentCMPBase from "./base";
4
+
5
+ export default class Conversant extends AutoConsentCMPBase {
6
+
7
+ prehideSelectors = [".cmp-root"]
8
+
9
+ constructor() {
10
+ super("Conversant");
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ return elementExists(".cmp-root .cmp-receptacle");
23
+ }
24
+
25
+ async detectPopup() {
26
+ return elementVisible(".cmp-root .cmp-receptacle", 'any');
27
+ }
28
+
29
+ async optOut() {
30
+ if (!(await waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))) {
31
+ return false;
32
+ }
33
+
34
+ if (!(await waitForElement(".cmp-view-tab-tabs"))) {
35
+ return false;
36
+ }
37
+
38
+ await waitForThenClick(".cmp-view-tab-tabs > :first-child");
39
+ await waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");
40
+
41
+ for (const item of Array.from(document.querySelectorAll('.cmp-accordion-item'))) {
42
+ (<HTMLElement>item.querySelector('.cmp-accordion-item-title')).click();
43
+ await waitFor(() => !!item.querySelector('.cmp-accordion-item-content.cmp-active'), 10, 50);
44
+ const content = item.querySelector('.cmp-accordion-item-content.cmp-active');
45
+ content.querySelectorAll('.cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)').forEach((e: HTMLElement) => e.click());
46
+ content.querySelectorAll('.cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)').forEach((e: HTMLElement) => e.click());
47
+ // await waitFor(() => !item.querySelector('.cmp-toggle-deny--active,.cmp-toggle-checkbox--active'), 5, 50); // this may take a long time
48
+ }
49
+ await click(".cmp-main-button:not(.cmp-main-button--primary)");
50
+ return true;
51
+ }
52
+
53
+ async optIn() {
54
+ return waitForThenClick(".cmp-main-button.cmp-main-button--primary");
55
+ }
56
+
57
+ async test() {
58
+ return document.cookie.includes('cmp-data=0');
59
+ }
60
+ }
@@ -0,0 +1,66 @@
1
+ import { click, doEval, elementExists, elementVisible, waitForElement } from "../rule-executors";
2
+ import AutoConsentCMPBase from "./base";
3
+
4
+ export default class Klaro extends AutoConsentCMPBase {
5
+
6
+ prehideSelectors = [".klaro"]
7
+ settingsOpen = false;
8
+
9
+ constructor() {
10
+ super("Klaro");
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ if (elementExists('.klaro > .cookie-modal')) {
23
+ this.settingsOpen = true;
24
+ return true;
25
+ }
26
+ return elementExists(".klaro > .cookie-notice");
27
+ }
28
+
29
+ async detectPopup() {
30
+ return elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal", 'any');
31
+ }
32
+
33
+ async optOut() {
34
+ if (click('.klaro .cn-decline')) {
35
+ return true;
36
+ }
37
+
38
+ if (!this.settingsOpen) {
39
+ click('.klaro .cn-learn-more');
40
+ await waitForElement('.klaro > .cookie-modal', 2000);
41
+ this.settingsOpen = true;
42
+ }
43
+
44
+ if (click('.klaro .cn-decline')) {
45
+ return true;
46
+ }
47
+
48
+ click('.cm-purpose:not(.cm-toggle-all) > input:not(.half-checked)', true);
49
+ return click('.cm-btn-accept');
50
+ }
51
+
52
+ async optIn() {
53
+ if (click('.klaro .cm-btn-accept-all')) {
54
+ return true;
55
+ }
56
+ if (this.settingsOpen) {
57
+ click('.cm-purpose:not(.cm-toggle-all) > input.half-checked', true);
58
+ return click('.cm-btn-accept');
59
+ }
60
+ return click('.klaro .cookie-notice .cm-btn-success');
61
+ }
62
+
63
+ async test() {
64
+ return await doEval('klaro.getManager().config.services.every(c => c.required || !klaro.getManager().consents[c.name])');
65
+ }
66
+ }
@@ -4,7 +4,7 @@ import AutoConsentCMPBase from "./base";
4
4
 
5
5
  export default class Onetrust extends AutoConsentCMPBase {
6
6
 
7
- prehideSelectors = ["#onetrust-banner-sdk,#onetrust-consent-sdk,.optanon-alert-box-wrapper,.onetrust-pc-dark-filter,.js-consent-banner"]
7
+ prehideSelectors = ["#onetrust-banner-sdk,#onetrust-consent-sdk,.onetrust-pc-dark-filter,.js-consent-banner"]
8
8
 
9
9
  constructor() {
10
10
  super("Onetrust");
@@ -19,11 +19,11 @@ export default class Onetrust extends AutoConsentCMPBase {
19
19
  }
20
20
 
21
21
  async detectCmp() {
22
- return elementExists("#onetrust-banner-sdk,.optanon-alert-box-wrapper");
22
+ return elementExists("#onetrust-banner-sdk");
23
23
  }
24
24
 
25
25
  async detectPopup() {
26
- return elementVisible("#onetrust-banner-sdk,.optanon-alert-box-wrapper", 'all');
26
+ return elementVisible("#onetrust-banner-sdk", 'all');
27
27
  }
28
28
 
29
29
  async optOut() {
@@ -32,7 +32,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
32
32
  return true;
33
33
  }
34
34
  return (url.pathname === '/index.html' || url.pathname === '/privacy-manager/index.html')
35
- && url.searchParams.has('message_id') && url.searchParams.has('requestUUID');
35
+ && (url.searchParams.has('message_id') || url.searchParams.has('requestUUID') || url.searchParams.has('consentUUID'));
36
36
  }
37
37
 
38
38
  async detectPopup() {
@@ -57,16 +57,17 @@ export default class SourcePoint extends AutoConsentCMPBase {
57
57
 
58
58
  async optOut() {
59
59
  if (!this.isManagerOpen()) {
60
- const actionable = await waitForElement('button.sp_choice_type_12,button.sp_choice_type_13');
60
+ const actionable = await waitForElement('.sp_choice_type_12,.sp_choice_type_13');
61
61
  if (!actionable) {
62
62
  return false;
63
63
  }
64
- if (!elementExists("button.sp_choice_type_12")) {
64
+ if (!elementExists(".sp_choice_type_12")) {
65
65
  // do not sell button
66
- return click("button.sp_choice_type_13");
66
+ return click(".sp_choice_type_13");
67
67
  }
68
68
 
69
- click("button.sp_choice_type_12");
69
+ click(".sp_choice_type_12");
70
+ // the page may navigate at this point but that's okay
70
71
  await waitFor(
71
72
  () => location.pathname === "/privacy-manager/index.html",
72
73
  200,
@@ -88,9 +89,8 @@ export default class SourcePoint extends AutoConsentCMPBase {
88
89
  await wait(1000);
89
90
  return click(rejectSelector1);
90
91
  } else if (path === 1) {
91
- return click(rejectSelector2);
92
+ click(rejectSelector2);
92
93
  } else if (path === 2) {
93
- // TODO: check if this is still working
94
94
  await waitForElement('.pm-features', 10000);
95
95
  click('.checked > span', true);
96
96
 
@@ -99,6 +99,7 @@ export default class SourcePoint extends AutoConsentCMPBase {
99
99
  } catch (e) {
100
100
  enableLogs && console.warn(e);
101
101
  }
102
- return click('.sp_choice_type_SAVE_AND_EXIT');
102
+ click('.sp_choice_type_SAVE_AND_EXIT');
103
+ return true;
103
104
  }
104
105
  }
@@ -35,6 +35,11 @@ export default class SourcePoint extends AutoConsentCMPBase {
35
35
  }
36
36
 
37
37
  async optOut() {
38
+ // unblock scrolling
39
+ const container = document.querySelector('.sp-message-open');
40
+ if (container) {
41
+ container.classList.remove('sp-message-open');
42
+ }
38
43
  return true;
39
44
  }
40
45
 
@@ -100,6 +100,9 @@ export default class TrustArcFrame extends AutoConsentCMPBase {
100
100
  }
101
101
 
102
102
  async optIn() {
103
+ if (click('.call')) {
104
+ return true;
105
+ }
103
106
  await this.navigateToSettings();
104
107
  click(".switch span:nth-child(2)", true);
105
108
 
@@ -0,0 +1,71 @@
1
+ import { elementExists, elementVisible, wait, waitForElement, waitForThenClick } from "../rule-executors";
2
+ import AutoConsentCMPBase from "./base";
3
+
4
+ export default class Uniconsent extends AutoConsentCMPBase {
5
+ constructor() {
6
+ super("Uniconsent");
7
+ }
8
+
9
+ get prehideSelectors(): string[] {
10
+ return ['.unic', '.modal:has(.unic)'];
11
+ }
12
+
13
+ get hasSelfTest(): boolean {
14
+ return true;
15
+ }
16
+
17
+ get isIntermediate(): boolean {
18
+ return false;
19
+ }
20
+
21
+ async detectCmp() {
22
+ return elementExists(".unic .unic-box,.unic .unic-bar");
23
+ }
24
+
25
+ async detectPopup() {
26
+ return elementVisible(".unic .unic-box,.unic .unic-bar", 'any');
27
+ }
28
+
29
+ async optOut() {
30
+ await waitForElement(".unic button", 1000);
31
+ document.querySelectorAll(".unic button").forEach((button: HTMLButtonElement) => {
32
+ const text = button.textContent;
33
+ if (text.includes('Manage Options') || text.includes('Optionen verwalten')) {
34
+ button.click();
35
+ }
36
+ });
37
+
38
+ if (await waitForElement('.unic input[type=checkbox]', 1000)) {
39
+ await waitForElement('.unic button', 1000);
40
+
41
+ document.querySelectorAll('.unic input[type=checkbox]').forEach((c: HTMLInputElement) => {
42
+ if (c.checked) {
43
+ c.click();
44
+ }
45
+ });
46
+
47
+ for (const b of <NodeListOf<HTMLButtonElement>>document.querySelectorAll('.unic button')) {
48
+ const text = b.textContent;
49
+ for (const pattern of ['Confirm Choices', 'Save Choices', 'Auswahl speichern']) {
50
+ if (text.includes(pattern)) {
51
+ b.click();
52
+ await wait(500); // give it some time to close the popup
53
+ return true;
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ return false;
60
+ }
61
+
62
+ async optIn() {
63
+ return waitForThenClick(".unic #unic-agree");
64
+ }
65
+
66
+ async test() {
67
+ await wait(1000);
68
+ const res = elementExists(".unic .unic-box,.unic .unic-bar");
69
+ return !res;
70
+ }
71
+ }
package/lib/rules.ts CHANGED
@@ -28,7 +28,8 @@ export type AutoConsentRuleStep = { optional?: boolean } & Partial<
28
28
  Partial<WaitForThenClickRule> &
29
29
  Partial<WaitRule> &
30
30
  Partial<UrlRule> &
31
- Partial<HideRule>;
31
+ Partial<HideRule> &
32
+ Partial<IfRule>;
32
33
 
33
34
  export type ElementExistsRule = {
34
35
  exists: string;
@@ -80,3 +81,9 @@ export type HideRule = {
80
81
  hide: string[];
81
82
  method?: HideMethod;
82
83
  };
84
+
85
+ export type IfRule = {
86
+ if: Partial<ElementExistsRule> & Partial<ElementVisibleRule>;
87
+ then: AutoConsentRuleStep[];
88
+ else?: AutoConsentRuleStep[];
89
+ };
package/lib/web.ts CHANGED
@@ -107,16 +107,46 @@ export default class AutoConsent {
107
107
 
108
108
  async _start() {
109
109
  enableLogs && console.log(`Detecting CMPs on ${window.location.href}`)
110
- const cmp = await this.findCmp(this.config.detectRetries);
111
- if (cmp) {
112
- enableLogs && console.log("detected CMP:", cmp.name, window.location.href);
113
- this.sendContentMessage({
114
- type: 'cmpDetected',
115
- url: location.href,
116
- cmp: cmp.name,
117
- }); // notify the browser
118
- const isOpen = await this.waitForPopup(cmp);
119
- if (!isOpen) {
110
+ const cmps = await this.findCmp(this.config.detectRetries);
111
+ if (cmps.length > 0) {
112
+ const popupLookups: Promise<boolean>[] = [];
113
+ for (const cmp of cmps) {
114
+ enableLogs && console.log("detected CMP:", cmp.name, window.location.href);
115
+ this.sendContentMessage({
116
+ type: 'cmpDetected',
117
+ url: location.href,
118
+ cmp: cmp.name,
119
+ }); // notify the browser
120
+ popupLookups.push(this.waitForPopup(cmp).then((isOpen) => {
121
+ if (isOpen) {
122
+ if (!this.foundCmp) {
123
+ this.foundCmp = cmp;
124
+ }
125
+ this.sendContentMessage({
126
+ type: 'popupFound',
127
+ cmp: cmp.name,
128
+ url: location.href,
129
+ }); // notify the browser
130
+ return true;
131
+ } else {
132
+ return Promise.reject(`${cmp.name} popup not found`);
133
+ }
134
+ }));
135
+ }
136
+
137
+ // could use `somethingOpen = await Promise.any(popupLookups).catch(() => false)`, but Promise.any is often unavailable in polyfilled environments
138
+ let somethingOpen = false;
139
+ for (const popupLookup of popupLookups) {
140
+ try {
141
+ await popupLookup;
142
+ somethingOpen = true;
143
+ break;
144
+ } catch (e) {
145
+ continue;
146
+ }
147
+ }
148
+
149
+ if (!somethingOpen) {
120
150
  enableLogs && console.log('no popup found');
121
151
  if (this.config.enablePrehide) {
122
152
  undoPrehide();
@@ -124,13 +154,6 @@ export default class AutoConsent {
124
154
  return false;
125
155
  }
126
156
 
127
- this.foundCmp = cmp;
128
- this.sendContentMessage({
129
- type: 'popupFound',
130
- cmp: cmp.name,
131
- url: location.href,
132
- }); // notify the browser
133
-
134
157
  if (this.config.autoAction === 'optOut') {
135
158
  return await this.doOptOut();
136
159
  } else if (this.config.autoAction === 'optIn') {
@@ -148,8 +171,7 @@ export default class AutoConsent {
148
171
  }
149
172
  }
150
173
 
151
- async findCmp(retries: number): Promise<AutoCMP> {
152
- let foundCmp: AutoCMP = null;
174
+ async findCmp(retries: number): Promise<AutoCMP[]> {
153
175
  const allFoundCmps: AutoCMP[] = [];
154
176
 
155
177
  for (const cmp of this.rules) {
@@ -161,9 +183,6 @@ export default class AutoConsent {
161
183
  if (result) {
162
184
  enableLogs && console.log(`Found CMP: ${cmp.name}`);
163
185
  allFoundCmps.push(cmp);
164
- if (!foundCmp) {
165
- foundCmp = cmp;
166
- }
167
186
  }
168
187
  } catch (e) {
169
188
  enableLogs && console.warn(`error detecting ${cmp.name}`, e);
@@ -182,7 +201,7 @@ export default class AutoConsent {
182
201
  });
183
202
  }
184
203
 
185
- if (!foundCmp && retries > 0) {
204
+ if (allFoundCmps.length === 0 && retries > 0) {
186
205
  return new Promise((resolve) => {
187
206
  setTimeout(async () => {
188
207
  const result = this.findCmp(retries - 1);
@@ -191,7 +210,7 @@ export default class AutoConsent {
191
210
  });
192
211
  }
193
212
 
194
- return foundCmp;
213
+ return allFoundCmps;
195
214
  }
196
215
 
197
216
  async doOptOut(): Promise<boolean> {
@@ -236,6 +255,7 @@ export default class AutoConsent {
236
255
  } else {
237
256
  enableLogs && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
238
257
  optInResult = await this.foundCmp.optIn();
258
+ enableLogs && console.log(`${this.foundCmp.name}: opt in result ${optInResult}`);
239
259
  }
240
260
 
241
261
  if (this.config.enablePrehide) {
@@ -286,7 +306,7 @@ export default class AutoConsent {
286
306
  if (!isOpen && retries > 0) {
287
307
  return new Promise((resolve) => setTimeout(() => resolve(this.waitForPopup(cmp, retries - 1, interval)), interval));
288
308
  }
289
- enableLogs && console.log(`popup is ${isOpen ? 'open' : 'not open'}`);
309
+ enableLogs && console.log(cmp.name, `popup is ${isOpen ? 'open' : 'not open'}`);
290
310
  return isOpen;
291
311
  }
292
312
 
@@ -314,6 +334,9 @@ export default class AutoConsent {
314
334
  case 'initResp':
315
335
  this.initialize(message.config, message.rules);
316
336
  break;
337
+ case 'optIn':
338
+ await this.doOptIn();
339
+ break;
317
340
  case 'optOut':
318
341
  await this.doOptOut();
319
342
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "",
5
5
  "main": "dist/autoconsent.cjs.js",
6
6
  "module": "dist/autoconsent.esm.js",
package/readme.md CHANGED
@@ -106,6 +106,17 @@ Returns true if elements returned from `document.querySelectorAll(selector)` are
106
106
  ```
107
107
  Waits until `selector` exists in the page. After `timeout` ms the step fails.
108
108
 
109
+ ### Wait for visibility
110
+
111
+ ```json
112
+ {
113
+ "waitForVisible": "selector",
114
+ "timeout": 1000,
115
+ "check": "any" | "all" | "none"
116
+ }
117
+ ```
118
+ Waits until element is visible in the page. After `timeout` ms the step fails.
119
+
109
120
  ### Click an element
110
121
  ```json
111
122
  {
@@ -152,6 +163,24 @@ Hide the elements matched by the selectors. `method` defines how elements are hi
152
163
  Evaluates `code` in the context of the page. The rule is considered successful if it *evaluates to a truthy value*.
153
164
  Eval rules are not 100% reliable because they can be blocked by a CSP policy on the page. Therefore, they should only be used as a last resort when none of the other rules are sufficient.
154
165
 
166
+ ### Conditionals
167
+
168
+ ```json
169
+ {
170
+ "if": { "exists": "selector" },
171
+ "then": [
172
+ { "click": ".button1" },
173
+ { "click": ".button3" }
174
+ ],
175
+ "else": [
176
+ { "click": ".button2" }
177
+ ]
178
+ }
179
+ ```
180
+
181
+ Allows to do conditional branching in JSON rules. The `if` section can contain either a "visible" or "exists" rule. Depending on the result of that rule, `then` or `else` sequences will be executed. `else` section is optional.
182
+ The "if" rule is considered successful as long as all rules inside the chosen branch are successful. The other branch, as well as the result of the condition itself, do not affect the result of the whole rule.
183
+
155
184
  ### Optional actions
156
185
 
157
186
  Any rule can include the `"optional": true` to ignore failure.
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "Adroll",
3
+ "prehideSelectors": ["#adroll_consent_container"],
4
+ "detectCmp": [{ "exists": "#adroll_consent_container" }],
5
+ "detectPopup": [{ "visible": "#adroll_consent_container" }],
6
+ "optIn": [ { "waitForThenClick": "#adroll_consent_accept" } ],
7
+ "optOut": [ { "waitForThenClick": "#adroll_consent_reject" } ],
8
+ "test": [
9
+ { "eval": "!document.cookie.includes('__adroll_fpc')" }
10
+ ]
11
+ }
@@ -9,7 +9,12 @@
9
9
  "click": "button[data-id=awsccc-cb-btn-customize]"
10
10
  },
11
11
  {
12
- "eval": "Array.from(document.querySelectorAll('input[aria-checked=true')).forEach(e => e.click()) || true"
12
+ "waitFor": "input[aria-checked]"
13
+ },
14
+ {
15
+ "click": "input[aria-checked=true]",
16
+ "all": true,
17
+ "optional": true
13
18
  },
14
19
  {
15
20
  "click": "button[data-id=awsccc-cs-btn-save]"
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "axeptio",
3
+ "prehideSelectors": [".axeptio_widget"],
4
+ "detectCmp":
5
+ [
6
+ {
7
+ "exists": ".axeptio_widget"
8
+ }
9
+ ],
10
+ "detectPopup":
11
+ [
12
+ {
13
+ "visible": ".axeptio_widget"
14
+ }
15
+ ],
16
+ "optIn":
17
+ [
18
+ {
19
+ "waitFor": ".axeptio-widget--open"
20
+ },
21
+ {
22
+ "click": "button#axeptio_btn_acceptAll"
23
+ }
24
+ ],
25
+ "optOut":
26
+ [
27
+
28
+ {
29
+ "waitFor": ".axeptio-widget--open"
30
+ },
31
+ {
32
+ "click": "button#axeptio_btn_dismiss"
33
+ }
34
+ ],
35
+ "test":
36
+ [
37
+ {
38
+ "eval": "document.cookie.includes('axeptio_authorized_vendors=%2C%2C')"
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "click.io",
3
+ "prehideSelectors": ["#cl-consent"],
4
+ "detectCmp": [{ "exists": "#cl-consent" }],
5
+ "detectPopup": [{ "visible": "#cl-consent" }],
6
+ "optIn": [ { "waitForThenClick": "#cl-consent [data-role=\"b_agree\"]" } ],
7
+ "optOut": [
8
+ { "waitFor": "#cl-consent [data-role=\"b_options\"]" },
9
+ { "wait": 500 },
10
+ { "click": "#cl-consent [data-role=\"b_options\"]" },
11
+ { "waitFor": ".cl-consent-popup.cl-consent-visible [data-role=\"alloff\"]" },
12
+ { "click": ".cl-consent-popup.cl-consent-visible [data-role=\"alloff\"]", "all": true },
13
+ { "click": "[data-role=\"b_save\"]" }
14
+ ],
15
+ "test": [
16
+ { "eval": "document.cookie.includes('__lxG__consent__v2_daisybit=')", "comment": "TODO: this only checks if we interacted at all" }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "Complianz banner",
3
+ "prehideSelectors": ["#cmplz-cookiebanner-container"],
4
+ "detectCmp": [{ "exists": "#cmplz-cookiebanner-container .cmplz-cookiebanner" }],
5
+ "detectPopup": [{ "visible": "#cmplz-cookiebanner-container .cmplz-cookiebanner", "check": "any" }],
6
+ "optIn": [
7
+ { "waitForThenClick": ".cmplz-cookiebanner .cmplz-accept" }
8
+ ],
9
+ "optOut": [
10
+ { "waitForThenClick": ".cmplz-cookiebanner .cmplz-deny" }
11
+ ],
12
+ "test": [ { "eval": "document.cookie.includes('cmplz_banner-status=dismissed')" } ]
13
+ }