@duckduckgo/autoconsent 1.0.8 → 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 (154) 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 -1387
  6. package/dist/autoconsent.esm.js +1 -1379
  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 +58 -55
  14. package/lib/cmps/evidon.ts +29 -18
  15. package/lib/cmps/onetrust.ts +32 -20
  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/baden-wuerttemberg-de.json +7 -3
  38. package/rules/autoconsent/bundesregierung-de.json +5 -1
  39. package/rules/autoconsent/cc-banner.json +0 -1
  40. package/rules/autoconsent/cookie-notice.json +0 -1
  41. package/rules/autoconsent/cookieconsent.json +5 -6
  42. package/rules/autoconsent/destatis-de.json +0 -1
  43. package/rules/autoconsent/etsy.json +3 -2
  44. package/rules/autoconsent/eu-cookie-compliance.json +0 -1
  45. package/rules/autoconsent/hl-co-uk.json +8 -9
  46. package/rules/autoconsent/johnlewis.json +5 -2
  47. package/rules/autoconsent/notice-cookie.json +0 -1
  48. package/rules/autoconsent/osano.json +0 -1
  49. package/rules/autoconsent/tealium.json +4 -5
  50. package/rules/rules.json +44 -37
  51. package/tests/192.spec.ts +1 -1
  52. package/tests/arzt-auskunft.spec.ts +1 -1
  53. package/tests/asus.spec.ts +1 -1
  54. package/tests/ausopen.spec.ts +1 -1
  55. package/tests/aws.amazon.spec.ts +1 -1
  56. package/tests/baden-wuerttemberg.spec.ts +1 -1
  57. package/tests/borlabs.spec.ts +1 -1
  58. package/tests/bundesregierung.spec.ts +5 -2
  59. package/tests/ccbanner.spec.ts +1 -1
  60. package/tests/consentmanager.spec.ts +3 -3
  61. package/tests/cookiebot.spec.ts +8 -2
  62. package/tests/cookieconsent.spec.ts +1 -1
  63. package/tests/cookielawinfo.spec.ts +1 -1
  64. package/tests/cookienotice.spec.ts +1 -1
  65. package/tests/corona-in-zahlen.spec.ts +1 -1
  66. package/tests/deepl.spec.ts +1 -1
  67. package/tests/destatis.spec.ts +1 -1
  68. package/tests/didomi.spec.ts +6 -2
  69. package/tests/drupal.spec.ts +8 -0
  70. package/tests/dunelm.spec.ts +1 -1
  71. package/tests/etsy.spec.ts +1 -1
  72. package/tests/eu-cookie-compliance-banner.spec.ts +1 -1
  73. package/tests/evidon.spec.ts +1 -1
  74. package/tests/fundingchoices.spec.ts +2 -1
  75. package/tests/gov-uk.spec.ts +1 -1
  76. package/tests/hl-co-uk.spec.ts +1 -1
  77. package/tests/hubspot.spec.ts +1 -1
  78. package/tests/ionos.spec.ts +1 -1
  79. package/tests/johnlewis.spec.ts +2 -2
  80. package/tests/klaro.spec.ts +1 -1
  81. package/tests/marksandspencer.spec.ts +1 -1
  82. package/tests/mediamarkt.spec.ts +1 -1
  83. package/tests/metoffice-gov-uk.spec.ts +1 -1
  84. package/tests/microsoft.spec.ts +1 -1
  85. package/tests/moneysavingexpert.spec.ts +1 -1
  86. package/tests/motor-talk.spec.ts +1 -1
  87. package/tests/national-lottery.spec.ts +1 -1
  88. package/tests/netflix.spec.ts +1 -1
  89. package/tests/nhs.spec.ts +1 -1
  90. package/tests/notice-cookie.spec.ts +1 -1
  91. package/tests/obi.spec.ts +1 -1
  92. package/tests/oil.spec.ts +1 -1
  93. package/tests/onetrust.spec.ts +10 -1
  94. package/tests/osano.spec.ts +1 -1
  95. package/tests/otto.spec.ts +1 -1
  96. package/tests/paypal.spec.ts +1 -1
  97. package/tests/quantcast.spec.ts +4 -1
  98. package/tests/snigel.spec.ts +1 -1
  99. package/tests/sourcepoint.spec.ts +8 -8
  100. package/tests/springer.spec.ts +1 -1
  101. package/tests/steampowered.spec.ts +1 -1
  102. package/tests/tealium.spec.ts +1 -1
  103. package/tests/testcmp.spec.ts +1 -1
  104. package/tests/thalia.spec.ts +1 -1
  105. package/tests/thefreedictionary.spec.ts +1 -1
  106. package/tests/trustarc.spec.ts +25 -4
  107. package/tests/usercentrics-1.spec.ts +1 -1
  108. package/tests/uswitch.spec.ts +1 -1
  109. package/tests/vodafone.spec.ts +1 -1
  110. package/tests/waitrose.spec.ts +1 -1
  111. package/tests/wetransfer.spec.ts +1 -1
  112. package/tests/wordpressgdpr.spec.ts +1 -1
  113. package/tests/xing.spec.ts +1 -1
  114. package/tsconfig.json +2 -2
  115. package/.eslintrc +0 -12
  116. package/cosmetics/rules.json +0 -110
  117. package/dist/autoconsent.puppet.js +0 -1078
  118. package/lib/cmps/all.js +0 -19
  119. package/lib/cmps/base.js +0 -174
  120. package/lib/cmps/consentmanager.js +0 -31
  121. package/lib/cmps/cookiebot.js +0 -77
  122. package/lib/cmps/evidon.js +0 -26
  123. package/lib/cmps/onetrust.js +0 -34
  124. package/lib/cmps/sourcepoint.js +0 -82
  125. package/lib/cmps/sourcepoint.ts +0 -95
  126. package/lib/cmps/trustarc.js +0 -106
  127. package/lib/cmps/trustarc.ts +0 -147
  128. package/lib/config.js +0 -1
  129. package/lib/consentomatic/index.js +0 -52
  130. package/lib/detector.js +0 -33
  131. package/lib/detector.ts +0 -34
  132. package/lib/hider.js +0 -13
  133. package/lib/hider.ts +0 -16
  134. package/lib/index.js +0 -4
  135. package/lib/messages.d.ts +0 -61
  136. package/lib/node.js +0 -35
  137. package/lib/node.ts +0 -43
  138. package/lib/puppet/tab.js +0 -121
  139. package/lib/puppet/tab.ts +0 -146
  140. package/lib/rules.d.ts +0 -80
  141. package/lib/tabwrapper.js +0 -67
  142. package/lib/tabwrapper.ts +0 -74
  143. package/lib/types.d.ts +0 -61
  144. package/lib/web/consentomatic/index.js +0 -188
  145. package/lib/web/consentomatic/index.ts +0 -249
  146. package/lib/web/consentomatic/tools.js +0 -177
  147. package/lib/web/content-utils.js +0 -29
  148. package/lib/web/content-utils.ts +0 -31
  149. package/lib/web/content.js +0 -89
  150. package/lib/web/content.ts +0 -80
  151. package/lib/web/tab.js +0 -112
  152. package/lib/web/tab.ts +0 -178
  153. package/lib/web.js +0 -95
  154. package/tests/runner.ts +0 -61
@@ -0,0 +1,131 @@
1
+ import fs from "fs";
2
+ import path from 'path';
3
+ import { test, expect, Page, Frame } from "@playwright/test";
4
+ import { waitFor } from "../lib/utils";
5
+ import { ContentScriptMessage } from "../lib/messages";
6
+ import { enableLogs } from "../lib/config";
7
+
8
+ const testRegion = (process.env.REGION || "NA").trim();
9
+
10
+ type TestOptions = {
11
+ testOptOut: boolean;
12
+ testSelfTest: boolean;
13
+ skipRegions?: string[];
14
+ onlyRegions?: string[];
15
+ };
16
+ const defaultOptions: TestOptions = {
17
+ testOptOut: true,
18
+ testSelfTest: true,
19
+ skipRegions: [],
20
+ onlyRegions: [],
21
+ };
22
+
23
+ const contentScript = fs.readFileSync(
24
+ path.join(__dirname, "../dist/autoconsent.playwright.js"),
25
+ "utf8"
26
+ );
27
+
28
+ export async function injectContentScript(page: Page | Frame) {
29
+ try {
30
+ await page.evaluate(contentScript);
31
+ } catch (e) {
32
+ // frame was detached
33
+ // console.log(e);
34
+ }
35
+ }
36
+
37
+ export function generateTest(
38
+ url: string,
39
+ expectedCmp: string,
40
+ options: TestOptions = { testOptOut: true, testSelfTest: true }
41
+ ) {
42
+ test(`${url.split("://")[1]} .${testRegion}`, async ({ page }) => {
43
+ if (options.onlyRegions && options.onlyRegions.length > 0 && !options.onlyRegions.includes(testRegion)) {
44
+ test.skip();
45
+ }
46
+ if (options.skipRegions && options.skipRegions.includes(testRegion)) {
47
+ test.skip();
48
+ }
49
+ enableLogs && page.on('console', async msg => {
50
+ console.log(` page log:`, msg.text());
51
+ });
52
+ await page.exposeBinding("autoconsentSendMessage", messageCallback);
53
+ await page.goto(url, { waitUntil: "commit" });
54
+
55
+ // set up a messaging function
56
+ const received: ContentScriptMessage[] = [];
57
+
58
+ function isMessageReceived(msg: Partial<ContentScriptMessage>, partial = true) {
59
+ return received.some((m) => {
60
+ const keysMatch = partial || Object.keys(m).length === Object.keys(msg).length;
61
+ return keysMatch && Object.keys(msg).every(
62
+ (k) => (<any>m)[k] === (<any>msg)[k]
63
+ );
64
+ });
65
+ }
66
+
67
+ let selfTestFrame: Frame = null;
68
+ async function messageCallback({ frame }: { frame: Frame }, msg: ContentScriptMessage) {
69
+ enableLogs && msg.type !== 'eval' && console.log(msg);
70
+ received.push(msg);
71
+ switch (msg.type) {
72
+ case 'optInResult':
73
+ case 'optOutResult': {
74
+ if (msg.scheduleSelfTest) {
75
+ selfTestFrame = frame;
76
+ }
77
+ break;
78
+ }
79
+ case 'autoconsentDone': {
80
+ if (selfTestFrame && options.testSelfTest) {
81
+ await selfTestFrame.evaluate(`autoconsentReceiveMessage({ type: "selfTest" })`);
82
+ }
83
+ break;
84
+ }
85
+ case 'eval': {
86
+ const result = await frame.evaluate(msg.code);
87
+ await frame.evaluate(`autoconsentReceiveMessage({ id: "${msg.id}", type: "evalResp", result: ${JSON.stringify(result)} })`);
88
+ break;
89
+ }
90
+ case 'autoconsentError': {
91
+ console.error(url, msg.details);
92
+ break;
93
+ }
94
+ }
95
+ }
96
+
97
+ // inject content scripts into every frame
98
+ await injectContentScript(page);
99
+ page.frames().forEach(injectContentScript);
100
+ page.on("framenavigated", injectContentScript);
101
+
102
+ // wait for all messages and assertions
103
+ await waitFor(() => isMessageReceived({ type: "popupFound", cmp: expectedCmp }), 50, 500);
104
+ expect(isMessageReceived({ type: "popupFound", cmp: expectedCmp })).toBe(true);
105
+
106
+ if (options.testOptOut) {
107
+ await waitFor(() => isMessageReceived({ type: "optOutResult", result: true }), 50, 300);
108
+ expect(isMessageReceived({ type: "optOutResult", result: true })).toBe(true);
109
+ }
110
+ if (options.testSelfTest && selfTestFrame) {
111
+ await waitFor(() => isMessageReceived({ type: "selfTestResult", result: true }), 50, 300);
112
+ expect(isMessageReceived({ type: "selfTestResult", result: true })).toBe(true);
113
+ }
114
+ await waitFor(() => isMessageReceived({ type: "autoconsentDone" }), 10, 500);
115
+ expect(isMessageReceived({ type: "autoconsentDone" })).toBe(true);
116
+
117
+ expect(isMessageReceived({ type: "autoconsentError" })).toBe(false);
118
+ });
119
+ }
120
+
121
+ export default function generateCMPTests(
122
+ cmp: string,
123
+ sites: string[],
124
+ options: Partial<TestOptions> = {}
125
+ ) {
126
+ test.describe(cmp, () => {
127
+ sites.forEach((url) => {
128
+ generateTest(url, cmp, Object.assign({}, defaultOptions, options));
129
+ });
130
+ });
131
+ }
@@ -0,0 +1,36 @@
1
+ import AutoConsent from "../lib/web";
2
+ import { BackgroundMessage } from "../lib/messages";
3
+ import { Config, RuleBundle } from "../lib/types";
4
+ import * as rules from '../rules/rules.json';
5
+
6
+ declare global {
7
+ interface Window {
8
+ initAutoconsentStandalone: () => void;
9
+ autoconsentStandaloneSendMessage: (msg: string) => void;
10
+ autoconsentStandaloneReceiveMessage: (message: BackgroundMessage) => void;
11
+ }
12
+ }
13
+
14
+
15
+ window.initAutoconsentStandalone = (config: Config = {
16
+ enabled: true,
17
+ autoAction: 'optOut',
18
+ disabledCmps: [],
19
+ enablePrehide: true,
20
+ detectRetries: 20,
21
+ }) => {
22
+ if (!window.autoconsentStandaloneReceiveMessage) {
23
+ const autoconsent = new AutoConsent(
24
+ async message => {
25
+ window.autoconsentStandaloneSendMessage(JSON.stringify(message));
26
+ },
27
+ config,
28
+ <RuleBundle>rules
29
+ );
30
+ window.autoconsentStandaloneReceiveMessage = (msg) => {
31
+ autoconsent.receiveMessageCallback(msg);
32
+ }
33
+ } else {
34
+ console.warn('autoconsent already initialized', window.autoconsentStandaloneReceiveMessage);
35
+ }
36
+ }
@@ -25,6 +25,13 @@ const config: PlaywrightTestConfig = {
25
25
  ...devices['Desktop Firefox'],
26
26
  proxy,
27
27
  },
28
+ },
29
+ {
30
+ name: 'chrome',
31
+ use: {
32
+ ...devices['Desktop Chrome'],
33
+ proxy,
34
+ },
28
35
  }
29
36
  ],
30
37
  };
package/readme.md CHANGED
@@ -1,28 +1,26 @@
1
- ## Autoconsent
1
+ # Autoconsent
2
2
 
3
3
  This is a library of rules for navigating through common consent popups on the web. These rules
4
- can be run in a Firefox webextension, or in a puppeteer orchestrated headless browser. Using
4
+ can be run in a Chrome extension, or in a Playwright-orchestrated headless browser. Using
5
5
  these rules, opt-in and opt-out options can be selected automatically, without requiring
6
6
  user-input.
7
7
 
8
- ### Install Standalone
8
+ ## Browser extension
9
9
 
10
- The standalone addon can be built with the following steps:
10
+ The web extension can be built with the following steps:
11
11
 
12
12
  ```bash
13
13
  # Download dependencies
14
14
  npm ci
15
- # Build JS bundles
16
- npm run bundle
17
15
  # Build consent ruleset
18
16
  npm run build-rules
17
+ # Build JS bundles (rules must be built first)
18
+ npm run bundle
19
19
  ```
20
20
 
21
- The standalone addon can be found in the `addon` directory and can be run with `npm start`.
22
- Alternatively, you can use `web-ext build -s addon/` to generate a packaged addon that can
23
- be installed in an existing Firefox profile.
21
+ The extension-specific code can be found in the `addon` directory and can be [loaded directly from there](https://developer.chrome.com/docs/extensions/mv3/getstarted/#unpacked) in developer mode.
24
22
 
25
- ### Rules
23
+ ## Rules
26
24
 
27
25
  The library's functionality is implemented as a set of rules that define how to manage consent on
28
26
  a subset of sites. These generally correspond to specific Consent Management Providers (CMPs)
@@ -41,33 +39,50 @@ There are currently three ways of implementing a CMP:
41
39
  3. As a [Consent-O-Matic](https://github.com/cavi-au/Consent-O-Matic) rule. The `ConsentOMaticCMP` class implements
42
40
  compability with rules written for the Consent-O-Matic extension.
43
41
 
44
- ### Rule Syntax
42
+ ## Intermediate rules
43
+
44
+ Sometimes the opt-out process requires actions that span across multiple pages or iframes. In this case it is necessary to define stages (each corresponding to a separate page context) as separate rulesets. Each one, except the very last stage, must be marked as intermediate using the `intermediate: true` flag. If the `intermediate` flag is not set correctly, autoconsent may report a successful opt-out even if it is not yet finished.
45
+
46
+ ## Context filters
47
+
48
+ By default, rules will be executed in all top-level documents. Some rules are designed for specific contexts (e.g. only nested iframes, or only specific URLs). This can be configured in `runContext` field (see the syntax reference below).
49
+
50
+ ## Rule Syntax Reference
45
51
 
46
52
  An autoconsent CMP rule can be written as either:
47
- * a class implementing the `AutoCMP` interface, or
48
53
  * a JSON file adhering to the `AutoConsentCMPRule` type.
54
+ * a class implementing the `AutoCMP` interface, or
55
+ * common JSON rules are available as reusable functions in [rule-executors.ts](/lib/rule-executors.ts). You can also use existing class-based rules as reference.
49
56
 
50
- In most cases the JSON syntax should be sufficient, unless non-linear logic is required, in which case a class is required.
57
+ In most cases the JSON syntax should be sufficient, unless some complex non-linear logic is required, in which case a class is required.
51
58
 
52
- Both JSON and class implementations require 5 main components:
59
+ Both JSON and class implementations have the following components:
53
60
  * `name` - to identify this CMP.
54
61
  * `detectCMP` - which determines if this CMP is included on the page.
55
62
  * `detectPopup` - which determines if a popup is being shown by the CMP.
56
- * `optOut` - executes actions to do an 'opt-out' from the popup screen. i.e. denying all consents possible.
57
- * `optIn` - execut actions for an 'opt-in' from the popup screen.
63
+ * `optOut` - a list of actions to do an 'opt-out' from the popup screen. i.e. denying all consents possible.
64
+ * `optIn` - a list of actions for an 'opt-in' from the popup screen.
65
+ * (optional) `prehideSelectors` - a list of CSS selectors to "pre-hide" early before detecting a CMP. This helps against flickering. Pre-hiding is done using CSS `opacity` and `z-index`, so be it should be used with care to prevent conflicts with the opt-out process.
66
+ * (optional) `intermediate` - a boolean flag indicating that the ruleset is part of a multi-stage process, see the [Intermediate rules](#intermediate-rules) section. This is `false` by default.
67
+ * (optional) `runContext` - an object describing when this rule should be tried:
68
+ * `main` - boolean, set to `true` if the rule should be executed in top-level documents (default: `true`)
69
+ * `frame` - boolean, set to `true` if the rule should be executed in nested frames (default: `false`)
70
+ * `url` - string, specifies a string prefix that should match the page URL (default: empty)
71
+ * (optional) `test` - a list of actions to verify a successful opt-out. This is currently only used in Playwright tests.
58
72
 
59
- Except for `name` this are defined as a set of checks or actions on the page. In the JSON syntax this is a list of `AutoConsentRuleStep` objects. For `detect` checks, we return true for the check if all steps return true. For opt in and out, we execute actions in order, exiting if one fails. The following checks/actions are supported:
60
73
 
61
- #### Element exists
74
+ `detectCMP`, `detectPopup`, `optOut`, `optIn`, and `test` are defined as a set of checks or actions on the page. In the JSON syntax this is a list of `AutoConsentRuleStep` objects. For `detect` checks, we return true for the check if all steps return true. For opt in and out, we execute actions in order, exiting if one fails. The following checks/actions are supported:
75
+
76
+ ### Element exists
62
77
 
63
78
  ```json
64
79
  {
65
80
  "exists": "selector"
66
81
  }
67
82
  ```
68
- Returns true if `document.querySelect(selector)` returns elements.
83
+ Returns true if `document.querySelector(selector)` returns elements.
69
84
 
70
- #### Element visible
85
+ ### Element visible
71
86
 
72
87
  ```json
73
88
  {
@@ -75,18 +90,9 @@ Returns true if `document.querySelect(selector)` returns elements.
75
90
  "check": "any" | "all" | "none"
76
91
  }
77
92
  ```
78
- Returns true if an element returned from `document.querySelect(selector)` is current visible on the page. If `check` is `all`, every element must be visible. If `check` is `none`, no element should be visible.
93
+ Returns true if elements returned from `document.querySelectorAll(selector)` are currently visible on the page. If `check` is `all`, every element must be visible. If `check` is `none`, no element should be visible. Visibility check is a CSS-based heuristic.
79
94
 
80
- #### Eval
81
-
82
- ```json
83
- {
84
- "eval": "code"
85
- }
86
- ```
87
- Evaluates `code` in the context of the page. NB: the result of this action depends on the truthiness of the evaluated expression, make sure it returns `true` in case of success.
88
-
89
- #### Wait for element
95
+ ### Wait for element
90
96
 
91
97
  ```json
92
98
  {
@@ -96,25 +102,26 @@ Evaluates `code` in the context of the page. NB: the result of this action depen
96
102
  ```
97
103
  Waits until `selector` exists in the page. After `timeout` ms the step fails.
98
104
 
99
- #### Click and element
105
+ ### Click an element
100
106
  ```json
101
107
  {
102
108
  "click": "selector",
103
109
  "all": true | false,
104
110
  }
105
111
  ```
106
- Click on an element returned by `selector`. If `all` is `true`, all matching elements are clicked.
112
+ Click on an element returned by `selector`. If `all` is `true`, all matching elements are clicked. If `all` is `false`, only the first returned value is clicked.
107
113
 
108
- #### Wait for then click
114
+ ### Wait for then click
109
115
  ```json
110
116
  {
111
117
  "waitForThenClick": "selector",
112
- "timeout": 1000
118
+ "timeout": 1000,
119
+ "all": true | false
113
120
  }
114
121
  ```
115
122
  Combines `waitFor` and `click`.
116
123
 
117
- #### Wait
124
+ ### Unconditional wait
118
125
  ```json
119
126
  {
120
127
  "wait": 1000,
@@ -122,30 +129,33 @@ Combines `waitFor` and `click`.
122
129
  ```
123
130
  Wait for the specified number of milliseconds.
124
131
 
125
- #### Go to URL
132
+ ### Hide
126
133
  ```json
127
134
  {
128
- "goto": "url"
135
+ "hide": ["selector", ...],
136
+ "method": "display" | "opacity"
129
137
  }
130
138
  ```
131
- Navigate the page to the given URL.
139
+ Hide the elements matched by the selectors. `method` defines how elements are hidden: "display" sets `display: none`, "opacity" sets `opacity: 0`. Method is "display" by default.
140
+
141
+ ### Eval
132
142
 
133
- #### Hide rule
134
143
  ```json
135
144
  {
136
- "hide": ["selector", ...]
145
+ "eval": "code"
137
146
  }
138
147
  ```
139
- Set the elements matched by the selectors to `display: none`.
148
+ Evaluates `code` in the context of the page. The rule is considered successful if it *evaluates to a truthy value*.
149
+ 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.
140
150
 
141
- #### Frames
151
+ ### Optional actions
142
152
 
143
- In some cases, rules have to interact with `iframes` in the page. The CMP rule defintion can optionally include a `frame` component that should be the _prefix_ of the expected frame URL. Checks and actions can then add `"frame": true` to indicate that the check or action should be done on the iframe's document (rather than main frame).
153
+ Any rule can include the `"optional": true` to ignore failure.
144
154
 
145
- #### Optional actions
155
+ ## API
146
156
 
147
- Any rule can include the `"optional": true` to ignore failure.
157
+ See [this document](/api.md) for more details on internal APIs.
148
158
 
149
- ### License
159
+ ## License
150
160
 
151
161
  MPLv2.
package/rollup.config.js CHANGED
@@ -1,14 +1,29 @@
1
+ import json from '@rollup/plugin-json';
1
2
  import typescript from '@rollup/plugin-typescript';
3
+ import { terser } from "rollup-plugin-terser";
2
4
  import pkg from './package.json';
3
5
 
4
6
  export default [{
5
- input: './lib/node.ts',
7
+ input: './playwright/content.ts',
6
8
  output: [{
7
- file: 'dist/autoconsent.puppet.js',
8
- format: 'cjs'
9
+ file: 'dist/autoconsent.playwright.js',
10
+ format: 'iife'
9
11
  }],
10
12
  plugins: [
13
+ json(),
11
14
  typescript(),
15
+ terser(),
16
+ ]
17
+ }, {
18
+ input: './playwright/standalone.ts',
19
+ output: [{
20
+ file: 'dist/autoconsent.standalone.js',
21
+ format: 'iife'
22
+ }],
23
+ plugins: [
24
+ json(),
25
+ typescript(),
26
+ terser(),
12
27
  ]
13
28
  }, {
14
29
  input: './lib/web.ts',
@@ -22,7 +37,8 @@ export default [{
22
37
  }],
23
38
  plugins: [
24
39
  typescript(),
25
- ]
40
+ terser(),
41
+ ],
26
42
  }, {
27
43
  input: './addon/background.ts',
28
44
  output: [{
@@ -31,6 +47,7 @@ export default [{
31
47
  }],
32
48
  plugins: [
33
49
  typescript(),
50
+ terser(),
34
51
  ]
35
52
  }, {
36
53
  input: './addon/content.ts',
@@ -40,15 +57,6 @@ export default [{
40
57
  }],
41
58
  plugins: [
42
59
  typescript(),
43
- ]
44
- }, {
45
- input: './addon/test.ts',
46
- output: [{
47
- file: './addon/test.bundle.js',
48
- format: 'iife',
49
- external: ['chai', 'mocha']
50
- }],
51
- plugins: [
52
- typescript(),
53
- ]
60
+ terser(),
61
+ ],
54
62
  }];
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "baden-wuerttemberg.de",
3
- "isHidingRule": true,
4
3
  "prehideSelectors": [".cookie-alert.t-dark"],
5
4
  "detectCmp": [{ "exists": ".cookie-alert.t-dark" }],
6
5
  "detectPopup": [{ "visible": ".cookie-alert.t-dark" }],
7
- "optIn": [{ "click": ".cookie-alert__button" }],
8
- "optOut": []
6
+ "optIn": [
7
+ { "click": ".cookie-alert__form input:not([disabled]):not([checked])" },
8
+ { "click": ".cookie-alert__button button" }
9
+ ],
10
+ "optOut": [
11
+ { "hide": [".cookie-alert.t-dark"] }
12
+ ]
9
13
  }
@@ -6,7 +6,11 @@
6
6
  "optIn": [{ "click": ".bpa-accept-all-button" }],
7
7
  "optOut": [
8
8
  {
9
- "click": ".bpa-close-button"
9
+ "wait": 500,
10
+ "comment": "click is not immediately recognized"
11
+ },
12
+ {
13
+ "waitForThenClick": ".bpa-close-button"
10
14
  }
11
15
  ],
12
16
  "test": [
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "cc_banner",
3
3
  "prehideSelectors": [".cc_banner-wrapper"],
4
- "isHidingRule": true,
5
4
  "detectCmp": [{ "exists": ".cc_banner-wrapper" }],
6
5
  "detectPopup": [{ "visible": ".cc_banner" }],
7
6
  "optIn": [{ "click": ".cc_btn_accept_all" }],
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "cookie-notice",
3
3
  "prehideSelectors": ["#cookie-notice"],
4
- "isHidingRule": true,
5
4
  "detectCmp": [{ "exists": "#cookie-notice" }],
6
5
  "detectPopup": [{ "visible": "#cookie-notice" }],
7
6
  "optIn": [{ "hide": ["#cn-accept-cookie"] }],
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "cookieconsent",
3
- "prehideSelectors": ["[aria-label=\"cookieconsent\"]"],
4
- "isHidingRule": true,
5
- "detectCmp": [{ "exists": "[aria-label=\"cookieconsent\"]" }],
6
- "detectPopup": [{ "visible": "[aria-label=\"cookieconsent\"]" }],
3
+ "prehideSelectors": ["[aria-label=\"cookieconsent\"][aria-describedby=\"cookieconsent:desc\"]"],
4
+ "detectCmp": [{ "exists": "[aria-label=\"cookieconsent\"][aria-describedby=\"cookieconsent:desc\"]" }],
5
+ "detectPopup": [{ "visible": "[aria-label=\"cookieconsent\"][aria-describedby=\"cookieconsent:desc\"]" }],
7
6
  "optIn": [{ "click": ".cc-dismiss" }],
8
- "optOut": [{ "hide": ["[aria-label=\"cookieconsent\"]"] }]
9
- }
7
+ "optOut": [{ "hide": ["[aria-label=\"cookieconsent\"][aria-describedby=\"cookieconsent:desc\"]"] }]
8
+ }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "destatis.de",
3
3
  "prehideSelectors": ["div[aria-labelledby=cookiebannerhead]"],
4
- "isHidingRule": true,
5
4
  "detectCmp": [{ "exists": ".cookiebannerbox" }],
6
5
  "detectPopup": [{ "visible": ".cookiebannerbox" }],
7
6
  "optOut": [{ "hide": [".cookiebannerbox"] }]
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "etsy",
3
+ "prehideSelectors": ["#gdpr-single-choice-overlay", "#gdpr-privacy-settings"],
3
4
  "detectCmp": [{ "exists": "#gdpr-single-choice-overlay" }],
4
5
  "detectPopup": [{ "visible": "#gdpr-single-choice-overlay" }],
5
6
  "optOut": [
6
- {"hide": ["#gdpr-single-choice-overlay", "#gdpr-privacy-settings"]},
7
7
  {"click": "button[data-gdpr-open-full-settings]"},
8
- {"wait": 500},
8
+ {"waitForVisible": ".gdpr-overlay-body input", "timeout": 3000},
9
+ {"wait": 1000},
9
10
  {"eval": "document.querySelectorAll('.gdpr-overlay-body input').forEach(toggle => { toggle.checked = false; }) || true"},
10
11
  {"eval": "document.querySelector('.gdpr-overlay-view button[data-wt-overlay-close]').click() || true"}
11
12
  ],
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "name": "eu-cookie-compliance-banner",
3
- "isHidingRule": true,
4
3
  "detectCmp": [{ "exists": ".eu-cookie-compliance-banner-info" }],
5
4
  "detectPopup": [{ "visible": ".eu-cookie-compliance-banner-info" }],
6
5
  "optIn": [{ "click": ".agree-button" }],
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "hl.co.uk",
3
- "prehideSelectors": [".cookieModalContent", "#cookie-banner-overlay"],
2
+ "name": "hl.co.uk",
3
+ "prehideSelectors": [".cookieModalContent", "#cookie-banner-overlay"],
4
4
  "detectCmp": [{ "exists": "#cookie-banner-overlay" }],
5
5
  "detectPopup": [{ "exists": "#cookie-banner-overlay" }],
6
6
  "optIn": [{ "click": "#acceptCookieButton" }],
@@ -12,19 +12,18 @@
12
12
  "hide": [".cookieSettingsModal"]
13
13
  },
14
14
  {
15
- "wait": 500
15
+ "waitFor": "#AOCookieToggle"
16
16
  },
17
17
  {
18
- "click": "#AOCookieToggle"
18
+ "click": "#AOCookieToggle[aria-pressed=true]",
19
+ "optional": true
19
20
  },
20
21
  {
21
- "eval": "document.querySelector('#AOCookieToggle').getAttribute('aria-pressed') === 'false'"
22
+ "waitFor": "#TPCookieToggle"
22
23
  },
23
24
  {
24
- "click": "#TPCookieToggle"
25
- },
26
- {
27
- "eval": "document.querySelector('#TPCookieToggle').getAttribute('aria-pressed') === 'false'"
25
+ "click": "#TPCookieToggle[aria-pressed=true]",
26
+ "optional": true
28
27
  },
29
28
  {
30
29
  "click": "#updateCookieButton"
@@ -6,8 +6,11 @@
6
6
  "optOut": [
7
7
  {"click": "button[data-test^=manage-cookies]"},
8
8
  {"wait": "500"},
9
- {"eval": "!!Array.from(document.querySelectorAll('label[data-test^=toggle]')).forEach(e => e.click())", "optional": true },
10
- {"eval": "Array.from(document.querySelectorAll('label[data-test^=toggle]')).filter(e => e.className.match('checked') && !e.className.match('disabled')).length === 0"},
9
+ {
10
+ "click": "label[data-test^=toggle][class*=checked]:not([class*=disabled])",
11
+ "all": true,
12
+ "optional": true
13
+ },
11
14
  {"click": "button[data-test=save-preferences]"}
12
15
  ],
13
16
  "optIn": [{ "click": "button[data-test=allow-all]"}]
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "notice-cookie",
3
3
  "prehideSelectors": [".button--notice"],
4
- "isHidingRule": true,
5
4
  "detectCmp": [{ "exists": ".notice--cookie" }],
6
5
  "detectPopup": [{ "visible": ".notice--cookie" }],
7
6
  "optIn": [{ "click": ".button--notice" }],
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "osano",
3
3
  "prehideSelectors": [".osano-cm-window"],
4
- "isHidingRule": true,
5
4
  "detectCmp": [{ "exists": ".osano-cm-window" }],
6
5
  "detectPopup": [{ "visible": ".osano-cm-dialog" }],
7
6
  "optIn": [{ "click": ".osano-cm-accept-all" }],
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "name": "Tealium",
3
3
  "prehideSelectors": ["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#consent-layer"],
4
- "isHidingRule": false,
5
- "detectCmp": [{ "exists": "#__tealiumGDPRecModal" }, { "eval": "window.utag && typeof utag.gdpr === 'object'" }],
4
+ "detectCmp": [{ "visible": "#__tealiumGDPRecModal" }, { "eval": "typeof window.utag !== 'undefined' && typeof utag.gdpr === 'object'" }],
6
5
  "detectPopup": [{ "visible": "#__tealiumGDPRecModal" }],
7
6
  "optOut": [
8
7
  { "hide": ["#__tealiumGDPRecModal", "#__tealiumGDPRcpPrefs", "#consent-layer"] },
9
- { "click": "#cm-acceptNone,.js-accept-essential-cookies"}
8
+ { "waitForThenClick": "#cm-acceptNone,.js-accept-essential-cookies", "timeout": 1000 },
9
+ { "eval": "utag.gdpr.setConsentValue(false) || true" }
10
10
  ],
11
11
  "optIn": [
12
12
  { "hide": ["#__tealiumGDPRecModal"] },
13
- { "eval": "utag.gdpr.setConsentValue(true)" }
13
+ { "eval": "utag.gdpr.setConsentValue(true) || true" }
14
14
  ],
15
15
  "test": [
16
16
  { "eval": "utag.gdpr.getConsentState() !== 1" }
17
17
  ]
18
18
  }
19
-