@slashgear/gdpr-cookie-scanner 3.5.1 → 3.7.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.
- package/CHANGELOG.md +106 -0
- package/CLAUDE.md +12 -1
- package/NEXT_STEPS.md +37 -3
- package/README.md +23 -0
- package/dist/analyzers/colour.d.ts +36 -0
- package/dist/analyzers/colour.d.ts.map +1 -0
- package/dist/analyzers/colour.js +75 -0
- package/dist/analyzers/colour.js.map +1 -0
- package/dist/analyzers/compliance.d.ts.map +1 -1
- package/dist/analyzers/compliance.js +24 -6
- package/dist/analyzers/compliance.js.map +1 -1
- package/dist/analyzers/tcf-decoder.d.ts +9 -0
- package/dist/analyzers/tcf-decoder.d.ts.map +1 -0
- package/dist/analyzers/tcf-decoder.js +123 -0
- package/dist/analyzers/tcf-decoder.js.map +1 -0
- package/dist/analyzers/wording.d.ts +1 -0
- package/dist/analyzers/wording.d.ts.map +1 -1
- package/dist/analyzers/wording.js +39 -0
- package/dist/analyzers/wording.js.map +1 -1
- package/dist/report/generator.d.ts +1 -2
- package/dist/report/generator.d.ts.map +1 -1
- package/dist/report/generator.js +80 -108
- package/dist/report/generator.js.map +1 -1
- package/dist/report/html.d.ts.map +1 -1
- package/dist/report/html.js +173 -4
- package/dist/report/html.js.map +1 -1
- package/dist/scanner/consent-modal.d.ts.map +1 -1
- package/dist/scanner/consent-modal.js +57 -39
- package/dist/scanner/consent-modal.js.map +1 -1
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +4 -0
- package/dist/scanner/index.js.map +1 -1
- package/dist/scanner/tcf.d.ts +9 -0
- package/dist/scanner/tcf.d.ts.map +1 -0
- package/dist/scanner/tcf.js +72 -0
- package/dist/scanner/tcf.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/index.html +37 -49
- package/docs/reports/www.arte.tv/after-accept.png +0 -0
- package/docs/reports/www.arte.tv/after-reject.png +0 -0
- package/docs/reports/www.arte.tv/gdpr-report-arte.tv-2026-02-24.html +997 -0
- package/docs/reports/www.deezer.com/after-accept.png +0 -0
- package/docs/reports/www.deezer.com/after-reject.png +0 -0
- package/docs/reports/www.deezer.com/gdpr-report-deezer.com-2026-02-22.html +1667 -0
- package/docs/reports/www.impots.gouv.fr/after-accept.png +0 -0
- package/docs/reports/www.impots.gouv.fr/after-reject.png +0 -0
- package/docs/reports/www.impots.gouv.fr/gdpr-report-impots.gouv.fr-2026-02-22.html +751 -0
- package/docs/reports/www.leboncoin.fr/after-accept.png +0 -0
- package/docs/reports/www.leboncoin.fr/after-reject.png +0 -0
- package/docs/reports/www.leboncoin.fr/gdpr-report-leboncoin.fr-2026-02-22.html +764 -0
- package/docs/reports/www.netflix.com/after-accept.png +0 -0
- package/docs/reports/www.netflix.com/after-reject.png +0 -0
- package/docs/reports/www.netflix.com/gdpr-report-netflix.com-2026-02-23.html +1050 -0
- package/docs/reports/www.radiofrance.fr/after-accept.png +0 -0
- package/docs/reports/www.radiofrance.fr/after-reject.png +0 -0
- package/docs/reports/www.radiofrance.fr/gdpr-report-radiofrance.fr-2026-02-24.html +1145 -0
- package/package.json +1 -2
- package/src/analyzers/colour.ts +89 -0
- package/src/analyzers/compliance.ts +35 -10
- package/src/analyzers/tcf-decoder.ts +130 -0
- package/src/analyzers/wording.ts +44 -0
- package/src/report/generator.ts +92 -119
- package/src/report/html.ts +197 -4
- package/src/scanner/consent-modal.ts +64 -38
- package/src/scanner/index.ts +5 -0
- package/src/scanner/tcf.ts +80 -0
- package/src/types.ts +29 -0
- package/tests/analyzers/colour.test.ts +187 -0
- package/tests/analyzers/compliance.test.ts +102 -0
- package/tests/analyzers/tcf-decoder.test.ts +292 -0
- package/tests/analyzers/wording.test.ts +38 -0
- package/tests/scanner/button-classification.test.ts +32 -0
- package/docs/reports/github.com/after-accept.png +0 -0
- package/docs/reports/github.com/after-reject.png +0 -0
- package/docs/reports/github.com/gdpr-checklist-github.com-2026-02-22.md +0 -44
- package/docs/reports/github.com/gdpr-cookies-github.com-2026-02-22.md +0 -29
- package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.md +0 -102
- package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.pdf +0 -0
- package/docs/reports/gitlab.com/after-accept.png +0 -0
- package/docs/reports/gitlab.com/after-reject.png +0 -0
- package/docs/reports/gitlab.com/gdpr-checklist-gitlab.com-2026-02-22.md +0 -44
- package/docs/reports/gitlab.com/gdpr-cookies-gitlab.com-2026-02-22.md +0 -55
- package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.md +0 -200
- package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.pdf +0 -0
- package/docs/reports/gitlab.com/modal-initial.png +0 -0
- package/docs/reports/npmjs.com/after-accept.png +0 -0
- package/docs/reports/npmjs.com/after-reject.png +0 -0
- package/docs/reports/npmjs.com/gdpr-checklist-npmjs.com-2026-02-22.md +0 -44
- package/docs/reports/npmjs.com/gdpr-cookies-npmjs.com-2026-02-22.md +0 -25
- package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.md +0 -88
- package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.pdf +0 -0
- package/docs/reports/reddit.com/after-accept.png +0 -0
- package/docs/reports/reddit.com/after-reject.png +0 -0
- package/docs/reports/reddit.com/gdpr-checklist-reddit.com-2026-02-22.md +0 -44
- package/docs/reports/reddit.com/gdpr-cookies-reddit.com-2026-02-22.md +0 -33
- package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.md +0 -148
- package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.pdf +0 -0
- package/docs/reports/reddit.com/modal-initial.png +0 -0
- package/docs/reports/stackoverflow.com/after-accept.png +0 -0
- package/docs/reports/stackoverflow.com/after-reject.png +0 -0
- package/docs/reports/stackoverflow.com/gdpr-checklist-stackoverflow.com-2026-02-22.md +0 -44
- package/docs/reports/stackoverflow.com/gdpr-cookies-stackoverflow.com-2026-02-22.md +0 -67
- package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.md +0 -206
- package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.pdf +0 -0
- package/docs/reports/stackoverflow.com/modal-initial.png +0 -0
- package/docs/reports/www.afp.com/after-accept.png +0 -0
- package/docs/reports/www.afp.com/after-reject.png +0 -0
- package/docs/reports/www.afp.com/gdpr-checklist-afp.com-2026-02-22.md +0 -44
- package/docs/reports/www.afp.com/gdpr-cookies-afp.com-2026-02-22.md +0 -42
- package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.md +0 -202
- package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.pdf +0 -0
- package/docs/reports/www.afp.com/modal-initial.png +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,111 @@
|
|
|
1
1
|
# @slashgear/gdpr-cookie-scanner
|
|
2
2
|
|
|
3
|
+
## 3.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 439a24d: feat: detect colour nudging dark pattern (green accept + grey/red reject)
|
|
8
|
+
|
|
9
|
+
Using a "positive" colour (green) on the accept button while the reject button
|
|
10
|
+
is visually de-emphasised (grey or red) is a documented dark pattern per
|
|
11
|
+
EDPB Guidelines 03/2022 § 3.3.3. The easyRefusal score now deducts 5 points
|
|
12
|
+
and surfaces a `nudging` warning when this pattern is detected.
|
|
13
|
+
|
|
14
|
+
A new `src/analyzers/colour.ts` module implements RGB→HSL conversion, perceptual
|
|
15
|
+
hue classification (green / red / grey / blue / neutral), and the nudging check.
|
|
16
|
+
32 unit tests cover the colour math and the compliance integration.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- ee162de: fix: classify reject before accept to handle "continuer sans accepter" dark pattern
|
|
21
|
+
|
|
22
|
+
Buttons like "Continuer sans accepter" or "Continue without accepting" contain the
|
|
23
|
+
word "accept/accepter" and were being incorrectly classified as accept buttons because
|
|
24
|
+
accept patterns were tested first. Swapping the check order (reject → accept) ensures
|
|
25
|
+
these rejection-phrased labels are correctly identified, preventing false negatives on
|
|
26
|
+
a very common dark pattern used by French and other European sites.
|
|
27
|
+
|
|
28
|
+
- 00c926b: fix: deduct points for indirect reject button labels in easyRefusal score
|
|
29
|
+
|
|
30
|
+
Buttons like "Continuer sans accepter" or "Continue without accepting" are technically
|
|
31
|
+
reject buttons but use indirect wording that makes the refusal non-obvious to users.
|
|
32
|
+
This is a dark pattern covered by EDPB Guidelines 03/2022 (§ 3.3.3 — hiding choices).
|
|
33
|
+
|
|
34
|
+
The easyRefusal score now deducts 5 points when such indirect wording is detected,
|
|
35
|
+
and a warning issue is surfaced in the report alongside the existing detection logic.
|
|
36
|
+
|
|
37
|
+
- 9f69e31: feat: add IAB TCF detection and consent string decoding
|
|
38
|
+
|
|
39
|
+
The scanner now detects TCF (Transparency & Consent Framework) implementations on scanned pages.
|
|
40
|
+
It checks for the `__tcfapi` JavaScript API, the `__tcfapiLocator` iframe, and `euconsent-v2`/`euconsent` cookies.
|
|
41
|
+
When a consent string is found, it is decoded (TCF v1 and v2 core segments) to extract CMP identity,
|
|
42
|
+
declared purposes, legitimate interests, and special features opt-ins.
|
|
43
|
+
|
|
44
|
+
This data is purely informational and does not affect the compliance score.
|
|
45
|
+
It appears as a new section in both the HTML and Markdown reports.
|
|
46
|
+
|
|
47
|
+
## 3.6.0
|
|
48
|
+
|
|
49
|
+
### Minor Changes
|
|
50
|
+
|
|
51
|
+
- 0f5ad85: Improve consent button detection and classification
|
|
52
|
+
|
|
53
|
+
**Wider element selector**: `extractButtons` now captures `input[type="button"]`, `input[type="submit"]`, and `<a>` elements with `href=""`, `href^="javascript:"`, or no `href` attribute — covering CMP implementations that do not use `<button>` or `[role="button"]`.
|
|
54
|
+
|
|
55
|
+
**Text fallback for icon-only buttons**: when `textContent` is empty, the button label is now resolved from `aria-label`, `value` (for `<input>`), then `title` in order.
|
|
56
|
+
|
|
57
|
+
**Icon component name stripping**: `normalizeText` now inserts spaces at camelCase boundaries, preventing React icon component names (e.g. `ArrowRightIcon`) leaked into `textContent` from breaking word-boundary anchors in classification patterns.
|
|
58
|
+
|
|
59
|
+
**All-locale classification**: button classification now always tests all locale pattern sets regardless of the page's declared `lang` attribute. This fixes misses when a page declares `lang="en"` but its CMP renders buttons in another language.
|
|
60
|
+
|
|
61
|
+
**Fixed close button detection**: replaced the broken `\bferm\b` / `\b×\b` pattern with explicit word forms (`fermer`, `close`, …) and a standalone-symbol regex (`^[×✕✗✖✘]$`).
|
|
62
|
+
|
|
63
|
+
**Removed ambiguous "continue"/"continuer" from accept patterns**: these words appear in both accept ("Continuer") and reject ("Continuer sans accepter") button labels; removing them from accept patterns prevents false positives while the reject patterns still cover the full phrases.
|
|
64
|
+
|
|
65
|
+
**Extended language patterns**: added common variants for EN, FR, DE, ES, IT, NL — including "allow all", "decline all", "necessary only", "nécessaires uniquement", "nur notwendige", etc.
|
|
66
|
+
|
|
67
|
+
- 19cea5a: Merge Markdown output into a single file
|
|
68
|
+
|
|
69
|
+
`-f md` now produces a single `gdpr-report-<host>-<date>.md` file containing
|
|
70
|
+
the compliance report, the checklist, and the cookie inventory separated by
|
|
71
|
+
`---` horizontal rules, instead of three separate files.
|
|
72
|
+
|
|
73
|
+
The separate `gdpr-checklist-*.md` and `gdpr-cookies-*.md` files are no longer
|
|
74
|
+
generated.
|
|
75
|
+
|
|
76
|
+
- 0625db9: Generate PDF directly from the HTML report instead of Markdown
|
|
77
|
+
|
|
78
|
+
The PDF output now uses the same styled HTML report as the browser view,
|
|
79
|
+
giving a visually consistent result (score grid, coloured badges, issue cards,
|
|
80
|
+
modal screenshot when available) instead of the previous plain Markdown → HTML
|
|
81
|
+
conversion.
|
|
82
|
+
|
|
83
|
+
`@media print` rules added to the HTML report: colour-accurate printing
|
|
84
|
+
(`print-color-adjust: exact`), page-break hints on sections and table rows,
|
|
85
|
+
screenshot height capped at 280px, and link colours reset to inherit.
|
|
86
|
+
|
|
87
|
+
`buildHtmlBody` and `wrapHtml` methods removed from `ReportGenerator` as they
|
|
88
|
+
are no longer needed. The `marked` dependency is no longer imported by the
|
|
89
|
+
generator.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- d491e93: Show consent modal screenshot in HTML report
|
|
94
|
+
|
|
95
|
+
When a scan is run with `--screenshots`, the captured `modal-initial.png` is now
|
|
96
|
+
displayed at the top of the "Consent modal" section in the HTML report, giving
|
|
97
|
+
auditors an immediate visual of the banner as it appeared on the page.
|
|
98
|
+
|
|
99
|
+
- 00be876: Improve PDF table density and readability
|
|
100
|
+
|
|
101
|
+
In `@media print`, data tables now use a smaller font (10px) and tighter
|
|
102
|
+
cell padding (5px 8px) to reduce cramping. Cookie description and tracker
|
|
103
|
+
URL cells get dedicated classes (`cell-desc`, `cell-url`) and are further
|
|
104
|
+
reduced to 9px and 7.5px respectively, keeping narrow columns (name,
|
|
105
|
+
domain, category, expiry) readable without being squeezed by long values.
|
|
106
|
+
Long code values wrap instead of overflowing, and sections containing
|
|
107
|
+
tables are allowed to break across pages.
|
|
108
|
+
|
|
3
109
|
## 3.5.1
|
|
4
110
|
|
|
5
111
|
### Patch Changes
|
package/CLAUDE.md
CHANGED
|
@@ -19,7 +19,18 @@ node dist/cli.js scan <url> -o ./reports --locale fr-FR --verbose
|
|
|
19
19
|
node dist/cli.js list-trackers
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
## Tests
|
|
23
|
+
|
|
24
|
+
Use [Vitest](https://vitest.dev/) for unit tests. Test files live in `tests/` and mirror the source structure (`tests/analyzers/`, `tests/classifiers/`, `tests/scanner/`, `tests/unit/`).
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm test # run all tests
|
|
28
|
+
pnpm test:watch # watch mode
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**When to write tests:** every new feature or bug fix must include tests. Pure functions (decoders, classifiers, analyzers) are the primary targets. Browser-dependent code (scanner phases) is not unit-tested.
|
|
32
|
+
|
|
33
|
+
**When to update the README:** every new user-facing feature must be documented in `README.md` — CLI flags, output formats, new report sections, etc.
|
|
23
34
|
|
|
24
35
|
## Commit checklist
|
|
25
36
|
|
package/NEXT_STEPS.md
CHANGED
|
@@ -20,13 +20,15 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
|
|
|
20
20
|
|
|
21
21
|
- **Cookie wall** — detect when the page content is blurred, hidden, or inaccessible before consent is given. Requires comparing DOM structure / visible content area before and after interaction.
|
|
22
22
|
|
|
23
|
-
- **Colour nudging** — accept button in green / reject button in grey or red is a documented dark pattern. Detect hue of each button's background colour and flag when accept is visually "positive" and reject is "negative".
|
|
23
|
+
- **Colour nudging** — accept button in green / reject button in grey or red is a documented dark pattern. Detect hue of each button's background colour and flag when accept is visually "positive" (green hue) and reject is "negative" (grey or red hue). Deduct from `easyRefusal`.
|
|
24
24
|
|
|
25
25
|
- **Scroll- or navigation-as-consent** — some sites display a banner and treat scrolling as acceptance. Would require simulating a scroll event and checking whether the banner disappears without an explicit click.
|
|
26
26
|
|
|
27
27
|
- **Bundled opt-outs** — some CMPs only provide "reject analytics + marketing" as a single checkbox rather than granular controls. Could be inferred from `hasGranularControls` combined with a single reject-all path.
|
|
28
28
|
|
|
29
|
-
- **
|
|
29
|
+
- **Legitimate interest abuse** — sites often declare legitimate interest for purposes 2–10 (advertising, profiling), bypassing consent entirely. When TCF is detected, flag when `purposesLegitimateInterest` includes ad-related IAB purposes (2, 3, 4, 7, 8, 9, 10).
|
|
30
|
+
|
|
31
|
+
- **Consent fatigue / re-prompting** — detect if the consent banner reappears on page reload after rejection. Requires a 5th scan phase: reload after reject and check for modal re-appearance.
|
|
30
32
|
|
|
31
33
|
---
|
|
32
34
|
|
|
@@ -40,6 +42,8 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
|
|
|
40
42
|
|
|
41
43
|
- **Firefox / WebKit** — Playwright supports both. Firefox in particular is relevant because some CMPs behave differently across engines. A `--browser chromium|firefox|webkit` flag would be low-effort to wire up.
|
|
42
44
|
|
|
45
|
+
- **Screenshot capture** — capture a screenshot of the consent modal at detection time and embed it in the HTML report as visual evidence. Useful for audit trails and regulatory submissions.
|
|
46
|
+
|
|
43
47
|
---
|
|
44
48
|
|
|
45
49
|
## Tracker & cookie classification
|
|
@@ -50,12 +54,42 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
|
|
|
50
54
|
|
|
51
55
|
## Report improvements
|
|
52
56
|
|
|
53
|
-
- **
|
|
57
|
+
- **JSON output format** — export the full `ScanResult` as a `gdpr-report-*.json` file. Enables programmatic consumption in CI pipelines, dashboards, and historical comparisons without parsing Markdown or HTML.
|
|
58
|
+
|
|
59
|
+
- **CMP fingerprinting** — identify which CMP is in use (OneTrust, Didomi, Axeptio, Cookiebot, TrustArc, Usercentrics…) from script URLs, global objects (`window.Didomi`, `window.OneTrust`…), or CSS class names. Enrich the report and correlate compliance scores with specific vendors.
|
|
54
60
|
|
|
55
61
|
- **Report localisation** — recommendations and section headings are hardcoded in French even when `--locale en-US` is used. The report language should follow `--locale`.
|
|
56
62
|
|
|
57
63
|
- **Historical comparison** — if a previous JSON report for the same hostname exists in the output directory, surface a diff (score delta, issues resolved/introduced). Useful for tracking progress over time.
|
|
58
64
|
|
|
65
|
+
- **TCF consent string cross-check** — after rejection, re-read the TCF consent string and verify `purposesConsent` is empty. Currently we capture the string before interaction only; a mismatch between the declared rejection and a non-empty consent string would be a critical finding.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Website (`docs/`)
|
|
70
|
+
|
|
71
|
+
The site is served as GitHub Pages from the `docs/` directory. Reports and cards are maintained manually.
|
|
72
|
+
|
|
73
|
+
- **HTML reports on the site** — generate `.html` reports into `docs/reports/<host>/` and point the "Live Reports" cards directly at the GitHub Pages URL instead of GitHub's Markdown renderer. Visitors stay on the site and get the full styled report experience.
|
|
74
|
+
|
|
75
|
+
- **Per-report `<title>` and `<meta description>`** — each HTML report should carry a specific page title (`"github.com — Grade F (15/100) | gdpr-cookie-scanner"`) and a meta description summarising the scan result. Currently the generator uses a generic title.
|
|
76
|
+
|
|
77
|
+
- **Open Graph / Twitter Card tags** — add `og:title`, `og:description`, `og:url` (and ideally `og:image` with a grade badge) to each report page so social shares display a meaningful preview card.
|
|
78
|
+
|
|
79
|
+
- **`sitemap.xml`** — auto-generated file listing the home page + every report page. Critical for search engine discovery of individual report pages. Can be a simple script that reads `docs/reports/` and writes the XML.
|
|
80
|
+
|
|
81
|
+
- **JSON-LD structured data** — `SoftwareApplication` schema on the home page; a `Dataset` or `Article` schema on each report page with `name`, `url`, `datePublished`, and a custom `score` property. Enables Google rich snippets.
|
|
82
|
+
|
|
83
|
+
- **`robots.txt`** — confirm (or add) a `robots.txt` that allows full crawling of `docs/` including report sub-directories.
|
|
84
|
+
|
|
85
|
+
- **Screenshots in the HTML report** — `after-reject.png` and `after-accept.png` are already captured alongside each report. Embedding them in the HTML report as visual evidence would make reports significantly more compelling.
|
|
86
|
+
|
|
87
|
+
- **Fix outdated copy** — the "What it checks" feature card still says "3 Markdown reports". Update to reflect the current single-file output and the HTML format.
|
|
88
|
+
|
|
89
|
+
- **"Submit a site" workflow** — a GitHub Issues template that lets users request a scan. A workflow picks it up, runs the scanner, commits the HTML report to `docs/reports/`, and posts a link back in the issue.
|
|
90
|
+
|
|
91
|
+
- **Dark mode** — `style.css` has no `@media (prefers-color-scheme: dark)` support. The design is simple enough that adding a dark palette would be straightforward.
|
|
92
|
+
|
|
59
93
|
---
|
|
60
94
|
|
|
61
95
|
## Testing
|
package/README.md
CHANGED
|
@@ -317,9 +317,32 @@ All formats contain:
|
|
|
317
317
|
- Detected dark patterns (missing reject button, visual asymmetry, pre-ticked boxes, misleading wording…)
|
|
318
318
|
- Cookie table before interaction, after reject, after accept
|
|
319
319
|
- Network tracker requests by phase
|
|
320
|
+
- **IAB TCF detection** — see below
|
|
320
321
|
- Targeted recommendations
|
|
321
322
|
- Legal references (RGPD, ePrivacy directive, CEPD guidelines, CNIL 2022)
|
|
322
323
|
|
|
324
|
+
### IAB TCF detection
|
|
325
|
+
|
|
326
|
+
When a site uses the [IAB Transparency & Consent Framework](https://iabeurope.eu/tcf-2-0/), the scanner automatically detects and decodes it. This is **informational only** — it does not affect the compliance score.
|
|
327
|
+
|
|
328
|
+
Detected signals:
|
|
329
|
+
|
|
330
|
+
- `window.__tcfapi` (TCF v2) or `window.__cmp` (TCF v1) JavaScript API
|
|
331
|
+
- `__tcfapiLocator` iframe
|
|
332
|
+
- `euconsent-v2` / `euconsent` cookie
|
|
333
|
+
|
|
334
|
+
When a consent string is found (via API or cookie), it is decoded to expose:
|
|
335
|
+
|
|
336
|
+
| Field | Description |
|
|
337
|
+
| --------------------------------- | ----------------------------------------------- |
|
|
338
|
+
| CMP ID & version | Which Consent Management Platform is in use |
|
|
339
|
+
| Consent language | Language the consent was collected in |
|
|
340
|
+
| Vendor list version | IAB GVL version at time of consent |
|
|
341
|
+
| Purposes with consent | IAB purposes explicitly consented to (IDs 1–11) |
|
|
342
|
+
| Purposes with legitimate interest | IAB purposes claimed under legitimate interest |
|
|
343
|
+
| Special features opted in | Geolocation, device scanning (IDs 1–2) |
|
|
344
|
+
| Publisher country | Country code of the publisher |
|
|
345
|
+
|
|
323
346
|
## Scoring
|
|
324
347
|
|
|
325
348
|
The score is made up of 4 criteria (25 points each):
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour nudging detection — EDPB Guidelines 03/2022 § 3.3.3.
|
|
3
|
+
*
|
|
4
|
+
* A "positive" colour (green = go, approve) on the accept button combined with
|
|
5
|
+
* a "negative" or neutral colour (grey, red) on the reject button steers users
|
|
6
|
+
* toward consent without technically hiding the refusal option.
|
|
7
|
+
*/
|
|
8
|
+
/** Returns [hue 0–360, saturation 0–100, lightness 0–100]. */
|
|
9
|
+
export declare function rgbToHsl(r: number, g: number, b: number): [number, number, number];
|
|
10
|
+
export type ButtonHue = "green" | "red" | "grey" | "blue" | "neutral";
|
|
11
|
+
/**
|
|
12
|
+
* Classify the perceptual "valence" of a colour:
|
|
13
|
+
* - green → positive, approval (h 80–165, s ≥ 25)
|
|
14
|
+
* - red → negative, danger (h ≤ 20 or ≥ 340, s ≥ 25)
|
|
15
|
+
* - grey → neutral / muted (s < 20)
|
|
16
|
+
* - blue → informational (h 195–265, s ≥ 25)
|
|
17
|
+
* - neutral → anything else
|
|
18
|
+
*
|
|
19
|
+
* Very dark (<10 L) or very light (>93 L) colours are treated as neutral
|
|
20
|
+
* because their hue carries little visual weight in a button context.
|
|
21
|
+
*/
|
|
22
|
+
export declare function classifyHue(r: number, g: number, b: number): ButtonHue;
|
|
23
|
+
export interface ColourNudgingResult {
|
|
24
|
+
acceptHue: ButtonHue | null;
|
|
25
|
+
rejectHue: ButtonHue | null;
|
|
26
|
+
/** True when accept is green and reject is grey or red. */
|
|
27
|
+
isNudging: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Detect colour nudging between the accept and reject buttons.
|
|
31
|
+
*
|
|
32
|
+
* Returns `isNudging: true` when the accept button has a "positive" hue (green)
|
|
33
|
+
* while the reject button has a "negative" or neutral hue (grey or red).
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectColourNudging(acceptBg: string | null | undefined, rejectBg: string | null | undefined): ColourNudgingResult;
|
|
36
|
+
//# sourceMappingURL=colour.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colour.d.ts","sourceRoot":"","sources":["../../src/analyzers/colour.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAsBlF;AAED,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAQtE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,mBAAmB,CAUrB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour nudging detection — EDPB Guidelines 03/2022 § 3.3.3.
|
|
3
|
+
*
|
|
4
|
+
* A "positive" colour (green = go, approve) on the accept button combined with
|
|
5
|
+
* a "negative" or neutral colour (grey, red) on the reject button steers users
|
|
6
|
+
* toward consent without technically hiding the refusal option.
|
|
7
|
+
*/
|
|
8
|
+
function parseRgb(css) {
|
|
9
|
+
const m = css.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);
|
|
10
|
+
if (!m)
|
|
11
|
+
return null;
|
|
12
|
+
return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
|
|
13
|
+
}
|
|
14
|
+
/** Returns [hue 0–360, saturation 0–100, lightness 0–100]. */
|
|
15
|
+
export function rgbToHsl(r, g, b) {
|
|
16
|
+
const rr = r / 255, gg = g / 255, bb = b / 255;
|
|
17
|
+
const max = Math.max(rr, gg, bb), min = Math.min(rr, gg, bb);
|
|
18
|
+
const l = (max + min) / 2;
|
|
19
|
+
if (max === min)
|
|
20
|
+
return [0, 0, Math.round(l * 100)];
|
|
21
|
+
const d = max - min;
|
|
22
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
23
|
+
let h;
|
|
24
|
+
switch (max) {
|
|
25
|
+
case rr:
|
|
26
|
+
h = (gg - bb) / d + (gg < bb ? 6 : 0);
|
|
27
|
+
break;
|
|
28
|
+
case gg:
|
|
29
|
+
h = (bb - rr) / d + 2;
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
h = (rr - gg) / d + 4;
|
|
33
|
+
}
|
|
34
|
+
return [Math.round(h * 60), Math.round(s * 100), Math.round(l * 100)];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Classify the perceptual "valence" of a colour:
|
|
38
|
+
* - green → positive, approval (h 80–165, s ≥ 25)
|
|
39
|
+
* - red → negative, danger (h ≤ 20 or ≥ 340, s ≥ 25)
|
|
40
|
+
* - grey → neutral / muted (s < 20)
|
|
41
|
+
* - blue → informational (h 195–265, s ≥ 25)
|
|
42
|
+
* - neutral → anything else
|
|
43
|
+
*
|
|
44
|
+
* Very dark (<10 L) or very light (>93 L) colours are treated as neutral
|
|
45
|
+
* because their hue carries little visual weight in a button context.
|
|
46
|
+
*/
|
|
47
|
+
export function classifyHue(r, g, b) {
|
|
48
|
+
const [h, s, l] = rgbToHsl(r, g, b);
|
|
49
|
+
if (l < 10 || l > 93)
|
|
50
|
+
return "neutral";
|
|
51
|
+
if (s < 20)
|
|
52
|
+
return "grey";
|
|
53
|
+
if (h >= 80 && h <= 165)
|
|
54
|
+
return "green";
|
|
55
|
+
if (h <= 20 || h >= 340)
|
|
56
|
+
return "red";
|
|
57
|
+
if (h >= 195 && h <= 265)
|
|
58
|
+
return "blue";
|
|
59
|
+
return "neutral";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Detect colour nudging between the accept and reject buttons.
|
|
63
|
+
*
|
|
64
|
+
* Returns `isNudging: true` when the accept button has a "positive" hue (green)
|
|
65
|
+
* while the reject button has a "negative" or neutral hue (grey or red).
|
|
66
|
+
*/
|
|
67
|
+
export function detectColourNudging(acceptBg, rejectBg) {
|
|
68
|
+
const acceptRgb = acceptBg ? parseRgb(acceptBg) : null;
|
|
69
|
+
const rejectRgb = rejectBg ? parseRgb(rejectBg) : null;
|
|
70
|
+
const acceptHue = acceptRgb ? classifyHue(...acceptRgb) : null;
|
|
71
|
+
const rejectHue = rejectRgb ? classifyHue(...rejectRgb) : null;
|
|
72
|
+
const isNudging = acceptHue === "green" && (rejectHue === "grey" || rejectHue === "red");
|
|
73
|
+
return { acceptHue, rejectHue, isNudging };
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=colour.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colour.js","sourceRoot":"","sources":["../../src/analyzers/colour.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,UAAU,eAAe;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wBAAwB,EAAE,aAAa,EAAE,CAAC;IAC1C,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,wBAAwB,EAAE,cAAc,EAAE,CAAC;IAC3C,kBAAkB,EAAE,cAAc,EAAE,CAAC;IACrC,kBAAkB,EAAE,cAAc,EAAE,CAAC;CACtC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAwQzE"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { analyzeButtonWording, analyzeModalText } from "./wording.js";
|
|
2
|
+
import { detectColourNudging } from "./colour.js";
|
|
2
3
|
export function analyzeCompliance(input) {
|
|
3
4
|
const issues = [];
|
|
4
5
|
// Determine whether a consent mechanism is actually required
|
|
@@ -11,6 +12,9 @@ export function analyzeCompliance(input) {
|
|
|
11
12
|
...input.networkAfterAccept,
|
|
12
13
|
].some((r) => r.requiresConsent);
|
|
13
14
|
const consentRequired = hasNonEssentialCookies || hasNonEssentialTrackers;
|
|
15
|
+
// Run wording analysis once — modal may not be detected, so these can be null
|
|
16
|
+
const wordingResult = input.modal.detected ? analyzeButtonWording(input.modal.buttons) : null;
|
|
17
|
+
const textResult = input.modal.detected ? analyzeModalText(input.modal.text) : null;
|
|
14
18
|
// ── A. Consent validity (0-25) ────────────────────────────────
|
|
15
19
|
let consentValidity = 25;
|
|
16
20
|
if (!input.modal.detected && consentRequired) {
|
|
@@ -23,9 +27,7 @@ export function analyzeCompliance(input) {
|
|
|
23
27
|
consentValidity = 0;
|
|
24
28
|
}
|
|
25
29
|
else if (input.modal.detected) {
|
|
26
|
-
// Wording analysis
|
|
27
|
-
const wordingResult = analyzeButtonWording(input.modal.buttons);
|
|
28
|
-
const textResult = analyzeModalText(input.modal.text);
|
|
30
|
+
// Wording analysis (wordingResult / textResult hoisted above)
|
|
29
31
|
issues.push(...wordingResult.issues, ...textResult.issues);
|
|
30
32
|
// Pre-ticked checkboxes
|
|
31
33
|
const preTicked = input.modal.checkboxes.filter((c) => c.isCheckedByDefault);
|
|
@@ -86,6 +88,23 @@ export function analyzeCompliance(input) {
|
|
|
86
88
|
easyRefusal -= 5;
|
|
87
89
|
}
|
|
88
90
|
}
|
|
91
|
+
// Indirect reject label ("continuer sans accepter", "continue without accepting"…)
|
|
92
|
+
if (wordingResult?.hasIndirectRejectLabel) {
|
|
93
|
+
easyRefusal -= 5;
|
|
94
|
+
}
|
|
95
|
+
// Colour nudging: green accept + grey/red reject
|
|
96
|
+
if (acceptButton && rejectButton) {
|
|
97
|
+
const { isNudging, acceptHue, rejectHue } = detectColourNudging(acceptButton.backgroundColor, rejectButton.backgroundColor);
|
|
98
|
+
if (isNudging) {
|
|
99
|
+
issues.push({
|
|
100
|
+
type: "nudging",
|
|
101
|
+
severity: "warning",
|
|
102
|
+
description: 'Accept button uses a "positive" colour (green) while reject is visually de-emphasised',
|
|
103
|
+
evidence: `Accept: ${acceptButton.backgroundColor} (${acceptHue}), Reject: ${rejectButton.backgroundColor} (${rejectHue}) — EDPB Guidelines 03/2022 § 3.3.3`,
|
|
104
|
+
});
|
|
105
|
+
easyRefusal -= 5;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
89
108
|
// Font size asymmetry
|
|
90
109
|
if (acceptButton?.fontSize && rejectButton?.fontSize) {
|
|
91
110
|
if (acceptButton.fontSize > rejectButton.fontSize * 1.3) {
|
|
@@ -142,9 +161,8 @@ export function analyzeCompliance(input) {
|
|
|
142
161
|
transparency -= 10;
|
|
143
162
|
}
|
|
144
163
|
// Already deducted in consentValidity for missing info
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
transparency -= wordingResult.missingInfo.length * 3;
|
|
164
|
+
if (textResult.missingInfo.length > 0) {
|
|
165
|
+
transparency -= textResult.missingInfo.length * 3;
|
|
148
166
|
}
|
|
149
167
|
// No privacy policy link in the modal
|
|
150
168
|
if (!input.modal.privacyPolicyUrl) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TcfConsentString } from "../types.js";
|
|
2
|
+
export declare const IAB_PURPOSES: Record<number, string>;
|
|
3
|
+
export declare const IAB_SPECIAL_FEATURES: Record<number, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Decode a TCF v1 or v2 consent string (core segment only).
|
|
6
|
+
* Returns null if decoding fails or if the version is not 1 or 2.
|
|
7
|
+
*/
|
|
8
|
+
export declare function decodeTcfConsentString(raw: string): TcfConsentString | null;
|
|
9
|
+
//# sourceMappingURL=tcf-decoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tcf-decoder.d.ts","sourceRoot":"","sources":["../../src/analyzers/tcf-decoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAY/C,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGvD,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAkE3E"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export const IAB_PURPOSES = {
|
|
2
|
+
1: "Store and/or access information on a device",
|
|
3
|
+
2: "Select basic ads",
|
|
4
|
+
3: "Create a personalised ads profile",
|
|
5
|
+
4: "Select personalised ads",
|
|
6
|
+
5: "Create a personalised content profile",
|
|
7
|
+
6: "Select personalised content",
|
|
8
|
+
7: "Measure ad performance",
|
|
9
|
+
8: "Measure content performance",
|
|
10
|
+
9: "Apply market research to generate audience insights",
|
|
11
|
+
10: "Develop and improve products",
|
|
12
|
+
11: "Use limited data to select content",
|
|
13
|
+
};
|
|
14
|
+
export const IAB_SPECIAL_FEATURES = {
|
|
15
|
+
1: "Use precise geolocation data",
|
|
16
|
+
2: "Actively scan device characteristics for identification",
|
|
17
|
+
};
|
|
18
|
+
class BitReader {
|
|
19
|
+
buf;
|
|
20
|
+
pos = 0;
|
|
21
|
+
constructor(buf) {
|
|
22
|
+
this.buf = buf;
|
|
23
|
+
}
|
|
24
|
+
readBits(n) {
|
|
25
|
+
let value = 0;
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
const byteIndex = Math.floor(this.pos / 8);
|
|
28
|
+
if (byteIndex >= this.buf.length)
|
|
29
|
+
throw new Error("BitReader: out of bounds");
|
|
30
|
+
const bitIndex = 7 - (this.pos % 8);
|
|
31
|
+
const bit = (this.buf[byteIndex] >> bitIndex) & 1;
|
|
32
|
+
// Use multiplication instead of bit shift to avoid 32-bit overflow on 36-bit timestamps
|
|
33
|
+
value = value * 2 + bit;
|
|
34
|
+
this.pos++;
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function deciSecondsToDate(ds) {
|
|
40
|
+
return new Date(ds * 100);
|
|
41
|
+
}
|
|
42
|
+
function readLanguage(reader) {
|
|
43
|
+
const c1 = reader.readBits(6) + 65; // 'A' = 0
|
|
44
|
+
const c2 = reader.readBits(6) + 65;
|
|
45
|
+
return String.fromCharCode(c1, c2);
|
|
46
|
+
}
|
|
47
|
+
function readBitField(reader, count) {
|
|
48
|
+
const active = [];
|
|
49
|
+
for (let i = 1; i <= count; i++) {
|
|
50
|
+
if (reader.readBits(1) === 1)
|
|
51
|
+
active.push(i);
|
|
52
|
+
}
|
|
53
|
+
return active;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decode a TCF v1 or v2 consent string (core segment only).
|
|
57
|
+
* Returns null if decoding fails or if the version is not 1 or 2.
|
|
58
|
+
*/
|
|
59
|
+
export function decodeTcfConsentString(raw) {
|
|
60
|
+
try {
|
|
61
|
+
// Take only the core segment (before '~')
|
|
62
|
+
const coreSegment = raw.split("~")[0];
|
|
63
|
+
// Convert base64url to standard base64 and decode
|
|
64
|
+
const base64 = coreSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
65
|
+
const buf = Buffer.from(base64, "base64");
|
|
66
|
+
const reader = new BitReader(buf);
|
|
67
|
+
const version = reader.readBits(6);
|
|
68
|
+
if (version !== 1 && version !== 2)
|
|
69
|
+
return null;
|
|
70
|
+
const created = deciSecondsToDate(reader.readBits(36));
|
|
71
|
+
const lastUpdated = deciSecondsToDate(reader.readBits(36));
|
|
72
|
+
const cmpId = reader.readBits(12);
|
|
73
|
+
const cmpVersion = reader.readBits(12);
|
|
74
|
+
reader.readBits(6); // consentScreen (unused)
|
|
75
|
+
const consentLanguage = readLanguage(reader);
|
|
76
|
+
const vendorListVersion = reader.readBits(12);
|
|
77
|
+
if (version === 1) {
|
|
78
|
+
const purposesAllowed = readBitField(reader, 24);
|
|
79
|
+
return {
|
|
80
|
+
raw,
|
|
81
|
+
version: 1,
|
|
82
|
+
created,
|
|
83
|
+
lastUpdated,
|
|
84
|
+
cmpId,
|
|
85
|
+
cmpVersion,
|
|
86
|
+
consentLanguage,
|
|
87
|
+
vendorListVersion,
|
|
88
|
+
specialFeatureOptins: [],
|
|
89
|
+
purposesConsent: purposesAllowed,
|
|
90
|
+
purposesLegitimateInterest: [],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// TCF v2
|
|
94
|
+
const tcfPolicyVersion = reader.readBits(6);
|
|
95
|
+
const isServiceSpecific = reader.readBits(1) === 1;
|
|
96
|
+
reader.readBits(1); // useNonStandardStacks
|
|
97
|
+
const specialFeatureOptins = readBitField(reader, 12);
|
|
98
|
+
const purposesConsent = readBitField(reader, 24);
|
|
99
|
+
const purposesLegitimateInterest = readBitField(reader, 24);
|
|
100
|
+
reader.readBits(1); // purposeOneTreatment
|
|
101
|
+
const publisherCC = readLanguage(reader);
|
|
102
|
+
return {
|
|
103
|
+
raw,
|
|
104
|
+
version: 2,
|
|
105
|
+
created,
|
|
106
|
+
lastUpdated,
|
|
107
|
+
cmpId,
|
|
108
|
+
cmpVersion,
|
|
109
|
+
consentLanguage,
|
|
110
|
+
vendorListVersion,
|
|
111
|
+
tcfPolicyVersion,
|
|
112
|
+
isServiceSpecific,
|
|
113
|
+
specialFeatureOptins,
|
|
114
|
+
purposesConsent,
|
|
115
|
+
purposesLegitimateInterest,
|
|
116
|
+
publisherCC,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=tcf-decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tcf-decoder.js","sourceRoot":"","sources":["../../src/analyzers/tcf-decoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ export interface WordingAnalysis {
|
|
|
4
4
|
missingInfo: string[];
|
|
5
5
|
hasPositiveActionForAccept: boolean;
|
|
6
6
|
hasExplicitRejectOption: boolean;
|
|
7
|
+
hasIndirectRejectLabel: boolean;
|
|
7
8
|
}
|
|
8
9
|
export declare function analyzeButtonWording(buttons: ConsentButton[]): WordingAnalysis;
|
|
9
10
|
export declare function analyzeModalText(text: string): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wording.d.ts","sourceRoot":"","sources":["../../src/analyzers/wording.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"wording.d.ts","sourceRoot":"","sources":["../../src/analyzers/wording.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA0DnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0BAA0B,EAAE,OAAO,CAAC;IACpC,uBAAuB,EAAE,OAAO,CAAC;IACjC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,eAAe,CAkE9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B,CAkBA"}
|