@duckduckgo/autoconsent 1.0.6 → 2.0.0

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 (165) hide show
  1. package/.eslintrc.cjs +14 -0
  2. package/.vscode/settings.json +7 -0
  3. package/Jenkinsfile +68 -39
  4. package/api.md +104 -0
  5. package/dist/autoconsent.cjs.js +1 -1371
  6. package/dist/autoconsent.esm.js +1 -1363
  7. package/dist/autoconsent.playwright.js +1 -0
  8. package/dist/autoconsent.standalone.js +1 -0
  9. package/lib/cmps/all.ts +15 -10
  10. package/lib/cmps/base.ts +91 -91
  11. package/lib/cmps/consentmanager.ts +31 -19
  12. package/lib/cmps/consentomatic.ts +89 -0
  13. package/lib/cmps/cookiebot.ts +62 -53
  14. package/lib/cmps/evidon.ts +29 -18
  15. package/lib/cmps/onetrust.ts +37 -19
  16. package/lib/cmps/sourcepoint-frame.ts +102 -0
  17. package/lib/cmps/sourcepoint-top.ts +47 -0
  18. package/lib/cmps/trustarc-frame.ts +115 -0
  19. package/lib/cmps/trustarc-top.ts +91 -0
  20. package/lib/consentomatic/index.ts +233 -70
  21. package/lib/{web/consentomatic → consentomatic}/tools.ts +0 -0
  22. package/lib/eval-handler.ts +58 -0
  23. package/lib/index.ts +0 -2
  24. package/lib/messages.ts +100 -0
  25. package/lib/rule-executors.ts +108 -0
  26. package/lib/rules.ts +82 -0
  27. package/lib/types.ts +35 -0
  28. package/lib/utils.ts +64 -0
  29. package/lib/web.ts +283 -74
  30. package/package.json +17 -14
  31. package/playwright/content.ts +27 -0
  32. package/playwright/runner.ts +131 -0
  33. package/playwright/standalone.ts +36 -0
  34. package/playwright.config.ts +7 -0
  35. package/readme.md +57 -47
  36. package/rollup.config.js +23 -15
  37. package/rules/autoconsent/192.json +17 -0
  38. package/rules/autoconsent/ausopen.json +7 -0
  39. package/rules/autoconsent/aws-amazon.json +1 -1
  40. package/rules/autoconsent/baden-wuerttemberg-de.json +7 -3
  41. package/rules/autoconsent/bing.json +14 -0
  42. package/rules/autoconsent/bundesregierung-de.json +6 -2
  43. package/rules/autoconsent/cc-banner.json +0 -1
  44. package/rules/autoconsent/cookie-notice.json +0 -1
  45. package/rules/autoconsent/cookieconsent.json +5 -6
  46. package/rules/autoconsent/destatis-de.json +0 -1
  47. package/rules/autoconsent/dunelm.json +18 -0
  48. package/rules/autoconsent/etsy.json +3 -2
  49. package/rules/autoconsent/eu-cookie-compliance.json +0 -1
  50. package/rules/autoconsent/gov-uk.json +10 -0
  51. package/rules/autoconsent/hl-co-uk.json +8 -9
  52. package/rules/autoconsent/johnlewis.json +5 -2
  53. package/rules/autoconsent/marksandspencer.json +7 -0
  54. package/rules/autoconsent/notice-cookie.json +0 -1
  55. package/rules/autoconsent/osano.json +0 -1
  56. package/rules/autoconsent/{paypal-de.json → paypal.json} +6 -2
  57. package/rules/autoconsent/tealium.json +4 -5
  58. package/rules/autoconsent/uswitch.json +8 -0
  59. package/rules/autoconsent/waitrose.json +28 -0
  60. package/rules/autoconsent/wetransfer.json +7 -0
  61. package/rules/rules.json +314 -39
  62. package/tests/192.spec.ts +7 -0
  63. package/tests/arzt-auskunft.spec.ts +1 -1
  64. package/tests/asus.spec.ts +1 -1
  65. package/tests/ausopen.spec.ts +7 -0
  66. package/tests/aws.amazon.spec.ts +1 -1
  67. package/tests/baden-wuerttemberg.spec.ts +1 -1
  68. package/tests/borlabs.spec.ts +1 -1
  69. package/tests/bundesregierung.spec.ts +5 -2
  70. package/tests/ccbanner.spec.ts +1 -1
  71. package/tests/consentmanager.spec.ts +3 -3
  72. package/tests/cookiebot.spec.ts +8 -1
  73. package/tests/cookieconsent.spec.ts +1 -1
  74. package/tests/cookielawinfo.spec.ts +1 -1
  75. package/tests/cookienotice.spec.ts +1 -1
  76. package/tests/corona-in-zahlen.spec.ts +1 -1
  77. package/tests/deepl.spec.ts +1 -1
  78. package/tests/destatis.spec.ts +1 -1
  79. package/tests/didomi.spec.ts +6 -2
  80. package/tests/drupal.spec.ts +8 -0
  81. package/tests/dunelm.spec.ts +7 -0
  82. package/tests/etsy.spec.ts +1 -1
  83. package/tests/eu-cookie-compliance-banner.spec.ts +1 -1
  84. package/tests/evidon.spec.ts +1 -1
  85. package/tests/fundingchoices.spec.ts +2 -1
  86. package/tests/gov-uk.spec.ts +9 -0
  87. package/tests/hl-co-uk.spec.ts +1 -1
  88. package/tests/hubspot.spec.ts +1 -1
  89. package/tests/ionos.spec.ts +1 -1
  90. package/tests/johnlewis.spec.ts +2 -2
  91. package/tests/klaro.spec.ts +1 -1
  92. package/tests/marksandspencer.spec.ts +7 -0
  93. package/tests/mediamarkt.spec.ts +1 -1
  94. package/tests/metoffice-gov-uk.spec.ts +1 -1
  95. package/tests/microsoft.spec.ts +1 -1
  96. package/tests/moneysavingexpert.spec.ts +1 -1
  97. package/tests/motor-talk.spec.ts +1 -1
  98. package/tests/national-lottery.spec.ts +1 -1
  99. package/tests/netflix.spec.ts +1 -1
  100. package/tests/nhs.spec.ts +1 -1
  101. package/tests/notice-cookie.spec.ts +1 -1
  102. package/tests/obi.spec.ts +1 -1
  103. package/tests/oil.spec.ts +1 -1
  104. package/tests/onetrust.spec.ts +10 -1
  105. package/tests/osano.spec.ts +1 -1
  106. package/tests/otto.spec.ts +1 -1
  107. package/tests/paypal.spec.ts +8 -6
  108. package/tests/quantcast.spec.ts +4 -1
  109. package/tests/snigel.spec.ts +1 -1
  110. package/tests/sourcepoint.spec.ts +8 -8
  111. package/tests/springer.spec.ts +1 -1
  112. package/tests/steampowered.spec.ts +1 -1
  113. package/tests/tealium.spec.ts +1 -1
  114. package/tests/testcmp.spec.ts +1 -1
  115. package/tests/thalia.spec.ts +1 -1
  116. package/tests/thefreedictionary.spec.ts +1 -1
  117. package/tests/trustarc.spec.ts +25 -3
  118. package/tests/usercentrics-1.spec.ts +1 -1
  119. package/tests/uswitch.spec.ts +7 -0
  120. package/tests/vodafone.spec.ts +1 -1
  121. package/tests/waitrose.spec.ts +7 -0
  122. package/tests/wetransfer.spec.ts +7 -0
  123. package/tests/wordpressgdpr.spec.ts +1 -1
  124. package/tests/xing.spec.ts +1 -1
  125. package/tsconfig.json +2 -2
  126. package/.eslintrc +0 -12
  127. package/cosmetics/rules.json +0 -110
  128. package/dist/autoconsent.puppet.js +0 -1072
  129. package/lib/cmps/all.js +0 -19
  130. package/lib/cmps/base.js +0 -174
  131. package/lib/cmps/consentmanager.js +0 -31
  132. package/lib/cmps/cookiebot.js +0 -73
  133. package/lib/cmps/evidon.js +0 -26
  134. package/lib/cmps/onetrust.js +0 -32
  135. package/lib/cmps/sourcepoint.js +0 -82
  136. package/lib/cmps/sourcepoint.ts +0 -95
  137. package/lib/cmps/trustarc.js +0 -106
  138. package/lib/cmps/trustarc.ts +0 -147
  139. package/lib/config.js +0 -1
  140. package/lib/consentomatic/index.js +0 -52
  141. package/lib/detector.js +0 -33
  142. package/lib/detector.ts +0 -34
  143. package/lib/hider.js +0 -13
  144. package/lib/hider.ts +0 -16
  145. package/lib/index.js +0 -4
  146. package/lib/messages.d.ts +0 -61
  147. package/lib/node.js +0 -35
  148. package/lib/node.ts +0 -43
  149. package/lib/puppet/tab.js +0 -121
  150. package/lib/puppet/tab.ts +0 -146
  151. package/lib/rules.d.ts +0 -80
  152. package/lib/tabwrapper.js +0 -67
  153. package/lib/tabwrapper.ts +0 -74
  154. package/lib/types.d.ts +0 -61
  155. package/lib/web/consentomatic/index.js +0 -188
  156. package/lib/web/consentomatic/index.ts +0 -249
  157. package/lib/web/consentomatic/tools.js +0 -177
  158. package/lib/web/content-utils.js +0 -29
  159. package/lib/web/content-utils.ts +0 -31
  160. package/lib/web/content.js +0 -79
  161. package/lib/web/content.ts +0 -71
  162. package/lib/web/tab.js +0 -112
  163. package/lib/web/tab.ts +0 -178
  164. package/lib/web.js +0 -95
  165. package/tests/runner.ts +0 -61
package/lib/types.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { ContentScriptMessage } from "./messages";
2
+ import { ConsentOMaticConfig } from "./cmps/consentomatic";
3
+ import { AutoConsentCMPRule, RunContext } from "./rules";
4
+
5
+ export type MessageSender = (message: ContentScriptMessage) => Promise<void>;
6
+
7
+ export interface AutoCMP {
8
+ name: string
9
+ hasSelfTest: boolean
10
+ isIntermediate: boolean;
11
+ prehideSelectors?: string[];
12
+ runContext: RunContext;
13
+ checkRunContext(): boolean;
14
+ detectCmp(): Promise<boolean>
15
+ detectPopup(): Promise<boolean>
16
+ optOut(): Promise<boolean>
17
+ optIn(): Promise<boolean>
18
+ openCmp(): Promise<boolean>
19
+ test(): Promise<boolean>
20
+ }
21
+
22
+ export type RuleBundle = {
23
+ autoconsent: AutoConsentCMPRule[];
24
+ consentomatic: { [name: string]: ConsentOMaticConfig };
25
+ };
26
+
27
+ export type AutoAction = 'optOut' | 'optIn' | null;
28
+
29
+ export type Config = {
30
+ enabled: boolean;
31
+ autoAction: AutoAction;
32
+ disabledCmps: string[];
33
+ enablePrehide: boolean;
34
+ detectRetries: number;
35
+ }
package/lib/utils.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { HideMethod } from "./rules";
2
+
3
+ // get or create a style container for CSS overrides
4
+ export function getStyleElement(styleOverrideElementId = "autoconsent-css-rules"): HTMLStyleElement {
5
+ const styleSelector = `style#${styleOverrideElementId}`;
6
+ const existingElement = document.querySelector(styleSelector);
7
+ if (existingElement && existingElement instanceof HTMLStyleElement) {
8
+ return existingElement;
9
+ } else {
10
+ const parent =
11
+ document.head ||
12
+ document.getElementsByTagName("head")[0] ||
13
+ document.documentElement;
14
+ const css = document.createElement("style");
15
+ css.id = styleOverrideElementId;
16
+ parent.appendChild(css);
17
+ return css;
18
+ }
19
+ }
20
+
21
+ // hide elements with a CSS rule
22
+ export function hideElements(
23
+ styleEl: HTMLStyleElement,
24
+ selectors: string[],
25
+ method: HideMethod = 'display',
26
+ ): boolean {
27
+ const hidingSnippet = method === "opacity" ? `opacity: 0` : `display: none`; // use display by default
28
+ const rule = `${selectors.join(
29
+ ","
30
+ )} { ${hidingSnippet} !important; z-index: -1 !important; pointer-events: none !important; } `;
31
+
32
+ if (styleEl instanceof HTMLStyleElement) {
33
+ styleEl.innerText += rule;
34
+ return selectors.length > 0;
35
+ }
36
+ return false;
37
+ }
38
+
39
+ export async function waitFor(predicate: () => Promise<boolean> | boolean, maxTimes: number, interval: number): Promise<boolean> {
40
+ const result = await predicate();
41
+ if (!result && maxTimes > 0) {
42
+ return new Promise((resolve) => {
43
+ setTimeout(async () => {
44
+ resolve(waitFor(predicate, maxTimes - 1, interval));
45
+ }, interval);
46
+ });
47
+ }
48
+ return Promise.resolve(result);
49
+ }
50
+
51
+ export function isElementVisible(elem: HTMLElement): boolean {
52
+ if (!elem) {
53
+ return false;
54
+ }
55
+ if (elem.offsetParent !== null) {
56
+ return true;
57
+ } else {
58
+ const css = window.getComputedStyle(elem);
59
+ if (css.position === 'fixed' && css.display !== "none") { // fixed elements may be visible even if the parent is not
60
+ return true;
61
+ }
62
+ }
63
+ return false;
64
+ }
package/lib/web.ts CHANGED
@@ -1,35 +1,89 @@
1
- import Tab from './web/tab';
2
- import handleContentMessage from './web/content';
3
- import TabConsent from './tabwrapper';
4
- import detectDialog from './detector';
5
- import { rules, createAutoCMP } from './index';
6
- import { Browser, MessageSender, AutoCMP, TabActor } from './types';
7
- import { ConsentOMaticCMP, ConsentOMaticConfig } from './consentomatic/index';
1
+ import { rules as dynamicRules, createAutoCMP } from './index';
2
+ import { MessageSender, AutoCMP, RuleBundle, Config } from './types';
3
+ import { ConsentOMaticCMP, ConsentOMaticConfig } from './cmps/consentomatic';
8
4
  import { AutoConsentCMPRule } from './rules';
9
- import prehideElements from './hider';
10
5
  import { enableLogs } from './config';
6
+ import { BackgroundMessage, InitMessage } from './messages';
7
+ import { prehide, undoPrehide } from './rule-executors';
8
+ import { evalState, resolveEval } from './eval-handler';
11
9
 
12
10
  export * from './index';
13
- export {
14
- Tab,
15
- handleContentMessage,
16
- }
17
11
 
18
12
  export default class AutoConsent {
19
- consentFrames: Map<number, any> = new Map()
20
- tabCmps: Map<number, TabConsent> = new Map()
21
- rules: AutoCMP[]
13
+ rules: AutoCMP[] = [];
14
+ config: Config;
15
+ foundCmp: AutoCMP = null;
16
+ protected sendContentMessage: MessageSender;
22
17
 
23
- constructor(protected browser: Browser, protected sendContentMessage: MessageSender) {
18
+ constructor(sendContentMessage: MessageSender, config: Config = null, declarativeRules: RuleBundle = null) {
19
+ evalState.sendContentMessage = sendContentMessage;
24
20
  this.sendContentMessage = sendContentMessage;
25
- this.rules = [...rules];
21
+ this.rules = [...dynamicRules];
22
+
23
+ enableLogs && console.log('autoconsent init', window.location.href);
24
+ if (config) {
25
+ this.initialize(config, declarativeRules);
26
+ } else {
27
+ const initMsg: InitMessage = {
28
+ type: "init",
29
+ url: window.location.href,
30
+ };
31
+ sendContentMessage(initMsg);
32
+ }
33
+ }
34
+
35
+ initialize(config: Config, declarativeRules: RuleBundle) {
36
+ this.config = config;
37
+ if (!config.enabled) {
38
+ enableLogs && console.log("autoconsent is disabled");
39
+ return;
40
+ }
41
+
42
+ this.parseRules(declarativeRules);
43
+ if (config.disabledCmps?.length > 0) {
44
+ this.disableCMPs(config.disabledCmps);
45
+ }
46
+
47
+ if (config.enablePrehide) {
48
+ if (document.documentElement) {
49
+ this.prehideElements(); // prehide as early as possible to prevent flickering
50
+ } else {
51
+ // we're injected really early
52
+ const delayedPrehide = () => {
53
+ window.removeEventListener('DOMContentLoaded', delayedPrehide);
54
+ this.prehideElements();
55
+ }
56
+ window.addEventListener('DOMContentLoaded', delayedPrehide);
57
+ }
58
+ }
59
+
60
+ // start detection
61
+ if (document.readyState === 'loading') {
62
+ const onReady = () => {
63
+ window.removeEventListener('DOMContentLoaded', onReady);
64
+ this.start();
65
+ }
66
+ window.addEventListener('DOMContentLoaded', onReady);
67
+ } else {
68
+ this.start();
69
+ }
70
+ }
71
+
72
+ parseRules(declarativeRules: RuleBundle) {
73
+ Object.keys(declarativeRules.consentomatic).forEach((name) => {
74
+ this.addConsentomaticCMP(name, declarativeRules.consentomatic[name]);
75
+ });
76
+ declarativeRules.autoconsent.forEach((rule) => {
77
+ this.addCMP(rule);
78
+ });
79
+ enableLogs && console.log("added rules", this.rules);
26
80
  }
27
81
 
28
82
  addCMP(config: AutoConsentCMPRule) {
29
83
  this.rules.push(createAutoCMP(config));
30
84
  }
31
85
 
32
- disableCMPs(cmpNames: String[]) {
86
+ disableCMPs(cmpNames: string[]) {
33
87
  this.rules = this.rules.filter((cmp) => !cmpNames.includes(cmp.name))
34
88
  }
35
89
 
@@ -37,78 +91,233 @@ export default class AutoConsent {
37
91
  this.rules.push(new ConsentOMaticCMP(`com_${name}`, config));
38
92
  }
39
93
 
40
- createTab(tabId: number) {
41
- return new Tab(tabId,
42
- this.consentFrames.get(tabId),
43
- this.sendContentMessage,
44
- this.browser);
94
+ // start the detection process, possibly with a delay
95
+ start() {
96
+ if (window.requestIdleCallback) {
97
+ window.requestIdleCallback(() => this._start(), { timeout: 500 });
98
+ } else {
99
+ this._start();
100
+ }
45
101
  }
46
102
 
47
- async checkTab(tabId: number, prehide = true) {
48
- enableLogs && console.log('checking tab', tabId, this.consentFrames, this.tabCmps);
49
- const tab = this.createTab(tabId);
50
- if (prehide) {
51
- this.prehideElements(tab);
52
- }
53
- const consent = new TabConsent(tab, this.detectDialog(tab, 20));
54
- this.tabCmps.set(tabId, consent);
55
- // check tabs
56
- consent.checked.then((rule) => {
57
- if (this.consentFrames.has(tabId) && rule) {
58
- const frame = this.consentFrames.get(tabId);
59
- enableLogs && console.log(`Found ${rule.name} in a nested iframe ${frame.id} inside tab ${tabId}`);
60
- if (frame.type === rule.name) {
61
- consent.tab.frame = frame;
103
+ async _start() {
104
+ enableLogs && console.log(`Detecting CMPs on ${window.location.href}`)
105
+ const cmp = await this.findCmp(this.config.detectRetries);
106
+ if (cmp) {
107
+ enableLogs && console.log("detected CMP:", cmp.name, window.location.href);
108
+ this.sendContentMessage({
109
+ type: 'cmpDetected',
110
+ url: location.href,
111
+ cmp: cmp.name,
112
+ }); // notify the browser
113
+ const isOpen = await this.waitForPopup(cmp);
114
+ if (!isOpen) {
115
+ enableLogs && console.log('no popup found');
116
+ if (this.config.enablePrehide) {
117
+ undoPrehide();
62
118
  }
119
+ return false;
63
120
  }
64
- enableLogs && console.log('finished checking tab', tabId, this.consentFrames, this.tabCmps);
65
- // no CMP detected, undo hiding
66
- if (!rule && prehide) {
67
- enableLogs && console.log('no CMP detected, undo hiding');
68
- tab.undoHideElements();
121
+
122
+ this.foundCmp = cmp;
123
+ this.sendContentMessage({
124
+ type: 'popupFound',
125
+ cmp: cmp.name,
126
+ url: location.href,
127
+ }); // notify the browser
128
+
129
+ if (this.config.autoAction === 'optOut') {
130
+ return await this.doOptOut();
131
+ } else if (this.config.autoAction === 'optIn') {
132
+ return await this.doOptIn();
133
+ }
134
+
135
+ enableLogs && console.log("waiting for opt-out signal...", location.href);
136
+ return true;
137
+ } else {
138
+ enableLogs && console.log("no CMP found", location.href);
139
+ if (this.config.enablePrehide) {
140
+ undoPrehide();
69
141
  }
142
+ return false;
143
+ }
144
+ }
145
+
146
+ async findCmp(retries: number): Promise<AutoCMP> {
147
+ let foundCmp: AutoCMP = null;
148
+ const allFoundCmps: AutoCMP[] = [];
149
+
150
+ for (const cmp of this.rules) {
151
+ try {
152
+ if (!cmp.checkRunContext()) {
153
+ continue;
154
+ }
155
+ const result = await cmp.detectCmp();
156
+ if (result) {
157
+ enableLogs && console.log(`Found CMP: ${cmp.name}`);
158
+ allFoundCmps.push(cmp);
159
+ if (!foundCmp) {
160
+ foundCmp = cmp;
161
+ }
162
+ }
163
+ } catch (e) {
164
+ enableLogs && console.warn(`error detecting ${cmp.name}`, e);
165
+ }
166
+ }
167
+
168
+ if (allFoundCmps.length > 1) {
169
+ const errorDetails = {
170
+ msg: `Found multiple CMPs, check the detection rules.`,
171
+ cmps: allFoundCmps.map((cmp) => cmp.name),
172
+ };
173
+ enableLogs && console.warn(errorDetails.msg, errorDetails.cmps);
174
+ this.sendContentMessage({
175
+ type: 'autoconsentError',
176
+ details: errorDetails,
177
+ });
178
+ }
179
+
180
+ if (!foundCmp && retries > 0) {
181
+ return new Promise((resolve) => {
182
+ setTimeout(async () => {
183
+ const result = this.findCmp(retries - 1);
184
+ resolve(result);
185
+ }, 500);
186
+ });
187
+ }
188
+
189
+ return foundCmp;
190
+ }
191
+
192
+ async doOptOut(): Promise<boolean> {
193
+ let optOutResult;
194
+ if (!this.foundCmp) {
195
+ enableLogs && console.log('no CMP to opt out');
196
+ optOutResult = false;
197
+ } else {
198
+ enableLogs && console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`);
199
+ optOutResult = await this.foundCmp.optOut();
200
+ enableLogs && console.log(`${this.foundCmp.name}: opt out result ${optOutResult}`);
201
+ }
202
+
203
+ if (this.config.enablePrehide) {
204
+ undoPrehide();
205
+ }
206
+
207
+ this.sendContentMessage({
208
+ type: 'optOutResult',
209
+ cmp: this.foundCmp ? this.foundCmp.name : 'none',
210
+ result: optOutResult,
211
+ scheduleSelfTest: this.foundCmp && this.foundCmp.hasSelfTest,
212
+ url: location.href,
70
213
  });
71
214
 
72
- return this.tabCmps.get(tabId);
215
+ if (optOutResult && !this.foundCmp.isIntermediate) {
216
+ this.sendContentMessage({
217
+ type: 'autoconsentDone',
218
+ cmp: this.foundCmp.name,
219
+ url: location.href,
220
+ });
221
+ }
222
+
223
+ return optOutResult;
73
224
  }
74
225
 
75
- removeTab(tabId: number) {
76
- this.tabCmps.delete(tabId);
77
- this.consentFrames.delete(tabId);
226
+ async doOptIn(): Promise<boolean> {
227
+ let optInResult;
228
+ if (!this.foundCmp) {
229
+ enableLogs && console.log('no CMP to opt in');
230
+ optInResult = false;
231
+ } else {
232
+ enableLogs && console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`);
233
+ optInResult = await this.foundCmp.optIn();
234
+ }
235
+
236
+ if (this.config.enablePrehide) {
237
+ undoPrehide();
238
+ }
239
+
240
+ this.sendContentMessage({
241
+ type: 'optInResult',
242
+ cmp: this.foundCmp ? this.foundCmp.name : 'none',
243
+ result: optInResult,
244
+ scheduleSelfTest: this.foundCmp && this.foundCmp.hasSelfTest,
245
+ url: location.href,
246
+ });
247
+
248
+ if (optInResult && !this.foundCmp.isIntermediate) {
249
+ this.sendContentMessage({
250
+ type: 'autoconsentDone',
251
+ cmp: this.foundCmp.name,
252
+ url: location.href,
253
+ });
254
+ }
255
+
256
+ return optInResult;
78
257
  }
79
258
 
80
- onFrame({ tabId, url, frameId }: { tabId: number, url: string, frameId: number }) {
81
- // ignore main frames
82
- if (frameId === 0) {
83
- return;
259
+ async doSelfTest(): Promise<boolean> {
260
+ let selfTestResult;
261
+ if (!this.foundCmp) {
262
+ enableLogs && console.log('no CMP to self test');
263
+ selfTestResult = false;
264
+ } else {
265
+ enableLogs && console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`);
266
+ selfTestResult = await this.foundCmp.test();
84
267
  }
85
- try {
86
- const frame = {
87
- id: frameId,
88
- url: url,
89
- };
90
- const tab = this.createTab(tabId);
91
- const frameMatch = this.rules.findIndex(r => r.detectFrame(tab, frame));
92
- if (frameMatch > -1) {
93
- this.consentFrames.set(tabId, {
94
- type: this.rules[frameMatch].name,
95
- url,
96
- id: frameId,
97
- });
98
- if (this.tabCmps.has(tabId)) {
99
- this.tabCmps.get(tabId).tab.frame = this.consentFrames.get(tabId);
100
- }
101
- }
102
- } catch (e) {
103
- console.error(e);
268
+
269
+ this.sendContentMessage({
270
+ type: 'selfTestResult',
271
+ cmp: this.foundCmp ? this.foundCmp.name : 'none',
272
+ result: selfTestResult,
273
+ url: location.href,
274
+ });
275
+ return selfTestResult;
276
+ }
277
+
278
+ async waitForPopup(cmp: AutoCMP, retries = 5, interval = 500): Promise<boolean> {
279
+ enableLogs && console.log('checking if popup is open...', cmp.name);
280
+ const isOpen = await cmp.detectPopup();
281
+ if (!isOpen && retries > 0) {
282
+ return new Promise((resolve) => setTimeout(() => resolve(this.waitForPopup(cmp, retries - 1, interval)), interval));
104
283
  }
284
+ enableLogs && console.log(`popup is ${isOpen ? 'open' : 'not open'}`);
285
+ return isOpen;
105
286
  }
106
287
 
107
- async detectDialog(tab: TabActor, retries: number): Promise<AutoCMP> {
108
- return detectDialog(tab, retries, this.rules);
288
+ prehideElements(): boolean {
289
+ // hide rules not specific to a single CMP rule
290
+ const globalHidden = [
291
+ "#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium",
292
+ ]
293
+
294
+ const selectors = this.rules.reduce((selectorList, rule) => {
295
+ if (rule.prehideSelectors) {
296
+ return [...selectorList, ...rule.prehideSelectors];
297
+ }
298
+ return selectorList;
299
+ }, globalHidden);
300
+
301
+ return prehide(selectors);
109
302
  }
110
303
 
111
- async prehideElements(tab: TabActor): Promise<void> {
112
- return prehideElements(tab, this.rules);
304
+ async receiveMessageCallback(message: BackgroundMessage) {
305
+ if (enableLogs && message.type !== 'evalResp' /* evals are noisy */) {
306
+ console.log('received from background', message, window.location.href);
307
+ }
308
+ switch (message.type) {
309
+ case 'initResp':
310
+ this.initialize(message.config, message.rules);
311
+ break;
312
+ case 'optOut':
313
+ await this.doOptOut();
314
+ break;
315
+ case 'selfTest':
316
+ await this.doSelfTest();
317
+ break;
318
+ case 'evalResp':
319
+ resolveEval(message.id, message.result);
320
+ break;
321
+ }
113
322
  }
114
323
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckduckgo/autoconsent",
3
- "version": "1.0.6",
3
+ "version": "2.0.0",
4
4
  "description": "",
5
5
  "main": "dist/autoconsent.cjs.js",
6
6
  "module": "dist/autoconsent.esm.js",
@@ -8,34 +8,37 @@
8
8
  "lib": "lib"
9
9
  },
10
10
  "scripts": {
11
- "clean": "rm -r dist lib/**/*.js lib/*.js",
12
- "build": "tsc",
11
+ "clean": "rm -r dist lib/**/*.js lib/*.js addon/*.js",
13
12
  "bundle": "rollup -c",
14
13
  "watch": "rollup -c -w",
15
- "start": "web-ext run -s ./addon --firefox firefoxdeveloperedition",
16
14
  "test": "playwright test",
17
15
  "test:webkit": "playwright test --project webkit",
18
16
  "test:firefox": "playwright test --project firefox",
17
+ "test:chrome": "playwright test --project chrome",
19
18
  "fetch-fanboy-list": "wget https://www.fanboy.co.nz/fanboy-cookiemonster.txt",
20
19
  "fetch-site-list": "curl https://s3.amazonaws.com/cdn.cliqz.com/re-consent/supported_sites.txt > sites.txt",
21
20
  "build-rules": "node rules/build.js && cp rules/rules.json addon/",
22
21
  "version": "node update_version.js && git add addon/manifest.json",
23
22
  "vendor-copy": "mkdir -p addon/vendor && cp node_modules/mocha/mocha.* addon/vendor/ && cp node_modules/chai/chai.js addon/vendor/",
24
- "prepublish": "npm run build-rules && npm run build && npm run bundle"
23
+ "prepublish": "npm run build-rules && npm run bundle"
25
24
  },
26
25
  "author": "Sam Macbeth",
27
26
  "license": "MPL-2.0",
28
- "dependencies": {},
29
27
  "devDependencies": {
30
28
  "@playwright/test": "^1.17.1",
31
- "@rollup/plugin-typescript": "^4.0.0",
32
- "@types/chai": "^4.2.11",
33
- "@types/mocha": "^8.0.0",
29
+ "@rollup/plugin-json": "^4.1.0",
30
+ "@rollup/plugin-typescript": "^8.3.2",
31
+ "@types/chai": "^4.3.1",
32
+ "@types/chrome": "^0.0.188",
33
+ "@types/mocha": "^9.1.1",
34
+ "@typescript-eslint/eslint-plugin": "^5.25.0",
35
+ "@typescript-eslint/parser": "^5.25.0",
34
36
  "chai": "^4.2.0",
35
- "mocha": "^8.0.1",
36
- "rollup": "^1.32.1",
37
- "typescript": "^3.8.3",
38
- "web-ext": "^4.2.0",
39
- "web-ext-types": "^3.2.1"
37
+ "eslint-config-airbnb": "^19.0.4",
38
+ "mocha": "^10.0.0",
39
+ "rollup": "^2.73.0",
40
+ "rollup-plugin-terser": "^7.0.2",
41
+ "tslib": "^2.4.0",
42
+ "typescript": "^4.6.4"
40
43
  }
41
44
  }
@@ -0,0 +1,27 @@
1
+ import AutoConsent from "../lib/web";
2
+ import { BackgroundMessage } from "../lib/messages";
3
+ import { MessageSender, RuleBundle } from "../lib/types";
4
+ import * as rules from '../rules/rules.json';
5
+
6
+ declare global {
7
+ interface Window {
8
+ autoconsentSendMessage: MessageSender;
9
+ autoconsentReceiveMessage: (message: BackgroundMessage) => Promise<void>;
10
+ }
11
+ }
12
+
13
+ if (!window.autoconsentReceiveMessage) {
14
+ const consent = new AutoConsent(window.autoconsentSendMessage, {
15
+ enabled: true,
16
+ autoAction: 'optOut',
17
+ disabledCmps: [],
18
+ enablePrehide: true,
19
+ detectRetries: 20,
20
+ }, <RuleBundle>rules);
21
+
22
+ window.autoconsentReceiveMessage = (message: BackgroundMessage) => {
23
+ return Promise.resolve(consent.receiveMessageCallback(message));
24
+ };
25
+ } else {
26
+ console.warn('autoconsent already initialized', window.autoconsentReceiveMessage);
27
+ }