@dreamdata_io/verify-tracking 0.1.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/LICENSE +21 -0
- package/README.md +756 -0
- package/dist/ai/checker.js +222 -0
- package/dist/ai/checker.js.map +1 -0
- package/dist/ai/checklist.js +190 -0
- package/dist/ai/checklist.js.map +1 -0
- package/dist/ai/cli-presets.js +27 -0
- package/dist/ai/cli-presets.js.map +1 -0
- package/dist/ai/evidence-view.js +143 -0
- package/dist/ai/evidence-view.js.map +1 -0
- package/dist/ai/local-cli.js +58 -0
- package/dist/ai/local-cli.js.map +1 -0
- package/dist/ai/openai-compatible.js +78 -0
- package/dist/ai/openai-compatible.js.map +1 -0
- package/dist/ai/response-parser.js +105 -0
- package/dist/ai/response-parser.js.map +1 -0
- package/dist/ai/types.js +2 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/analysis/consent-mode.js +225 -0
- package/dist/analysis/consent-mode.js.map +1 -0
- package/dist/analysis/deployment.js +2658 -0
- package/dist/analysis/deployment.js.map +1 -0
- package/dist/analysis/diagnosis/copy.en.js +303 -0
- package/dist/analysis/diagnosis/copy.en.js.map +1 -0
- package/dist/analysis/diagnosis/diagnose.js +172 -0
- package/dist/analysis/diagnosis/diagnose.js.map +1 -0
- package/dist/analysis/diagnosis/docs.js +118 -0
- package/dist/analysis/diagnosis/docs.js.map +1 -0
- package/dist/analysis/diagnosis/index.js +5 -0
- package/dist/analysis/diagnosis/index.js.map +1 -0
- package/dist/analysis/diagnosis/rules.js +656 -0
- package/dist/analysis/diagnosis/rules.js.map +1 -0
- package/dist/analysis/diagnosis/types.js +2 -0
- package/dist/analysis/diagnosis/types.js.map +1 -0
- package/dist/cli.js +344 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.js +100 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/preflight.js +167 -0
- package/dist/config/preflight.js.map +1 -0
- package/dist/config/user-config.js +79 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/evidence/collect.js +527 -0
- package/dist/evidence/collect.js.map +1 -0
- package/dist/evidence/consent-accept.js +377 -0
- package/dist/evidence/consent-accept.js.map +1 -0
- package/dist/evidence/consent.js +340 -0
- package/dist/evidence/consent.js.map +1 -0
- package/dist/evidence/constants.js +615 -0
- package/dist/evidence/constants.js.map +1 -0
- package/dist/evidence/globals.js +132 -0
- package/dist/evidence/globals.js.map +1 -0
- package/dist/evidence/gtm-container.js +432 -0
- package/dist/evidence/gtm-container.js.map +1 -0
- package/dist/evidence/heuristic-banner.js +107 -0
- package/dist/evidence/heuristic-banner.js.map +1 -0
- package/dist/evidence/http-probe.js +139 -0
- package/dist/evidence/http-probe.js.map +1 -0
- package/dist/evidence/init-script.js +112 -0
- package/dist/evidence/init-script.js.map +1 -0
- package/dist/evidence/network.js +365 -0
- package/dist/evidence/network.js.map +1 -0
- package/dist/evidence/preference-center.js +368 -0
- package/dist/evidence/preference-center.js.map +1 -0
- package/dist/evidence/script.js +425 -0
- package/dist/evidence/script.js.map +1 -0
- package/dist/evidence/second-page.js +332 -0
- package/dist/evidence/second-page.js.map +1 -0
- package/dist/evidence/selector-probe.js +53 -0
- package/dist/evidence/selector-probe.js.map +1 -0
- package/dist/evidence/stealth.js +21 -0
- package/dist/evidence/stealth.js.map +1 -0
- package/dist/evidence/types.js +14 -0
- package/dist/evidence/types.js.map +1 -0
- package/dist/interactive/prompt.js +31 -0
- package/dist/interactive/prompt.js.map +1 -0
- package/dist/mcp/server.js +308 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/summary.js +41 -0
- package/dist/mcp/summary.js.map +1 -0
- package/dist/report/context.js +184 -0
- package/dist/report/context.js.map +1 -0
- package/dist/report/projection.js +125 -0
- package/dist/report/projection.js.map +1 -0
- package/dist/report/quote-check.js +90 -0
- package/dist/report/quote-check.js.map +1 -0
- package/dist/report/render.js +178 -0
- package/dist/report/render.js.map +1 -0
- package/dist/report/serializers.js +124 -0
- package/dist/report/serializers.js.map +1 -0
- package/dist/report/store.js +139 -0
- package/dist/report/store.js.map +1 -0
- package/dist/run/verify.js +77 -0
- package/dist/run/verify.js.map +1 -0
- package/dist/utils/duration.js +10 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/errors.js +17 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.js +59 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/runTimingsLog.js +27 -0
- package/dist/utils/runTimingsLog.js.map +1 -0
- package/dist/utils/slug.js +27 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +57 -0
- package/scripts/install.sh +314 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { BANNER_CONTEXT_KEYWORDS, BANNER_REVEAL_SCROLL_PX, BANNER_REVEAL_SETTLE_MS, CMP_VENDORS, DOM_ONLY_CMP_VENDORS, GENERIC_ACCEPT_PHRASES, GENERIC_REJECT_PHRASES, PREFERENCE_CENTER_COUNTERPART_PHRASES, } from "./constants.js";
|
|
2
|
+
import { runHeuristicBanner } from "./heuristic-banner.js";
|
|
3
|
+
import { CONSENT_SIGNAL_NAMES, CONSENT_V2_ONLY_SIGNALS, } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Turns one recorded dataLayer consent push into typed fields.
|
|
6
|
+
*
|
|
7
|
+
* Parsed here rather than in the page for the same reason
|
|
8
|
+
* classifyAnalyticsStorage is: what a report says about a customer's install
|
|
9
|
+
* has to be decided somewhere a unit test can reach.
|
|
10
|
+
*
|
|
11
|
+
* Three fields matter beyond the value itself, and none of them was readable
|
|
12
|
+
* before without reaching into `raw` — which both compaction layers drop:
|
|
13
|
+
*
|
|
14
|
+
* - `verb` (raw[1]). A `default` is the site's configuration; an `update` is
|
|
15
|
+
* the visitor's decision. Logic that reads a push log without separating
|
|
16
|
+
* them treats a permissive default meant for some other visitor as consent.
|
|
17
|
+
* - `region` (raw[2].region). A region-scoped declaration overrides an
|
|
18
|
+
* earlier global one for the visitors it covers. It is the reason a push
|
|
19
|
+
* log can read "granted" on a page Google resolved to "denied".
|
|
20
|
+
* - `signals`. Which of the seven the call actually named — a v2-aware CMP
|
|
21
|
+
* call carries the two ad signals v2 added, a v1 one does not.
|
|
22
|
+
*/
|
|
23
|
+
export function parseConsentPush(raw, timestamp) {
|
|
24
|
+
const push = { raw, verb: null, region: null, waitForUpdate: null, timestamp };
|
|
25
|
+
if (!Array.isArray(raw) || raw[0] !== "consent") {
|
|
26
|
+
// The {event:"consent"} object form init-script.ts also records. Nothing
|
|
27
|
+
// more can be said about it than that it happened.
|
|
28
|
+
push.verb = "other";
|
|
29
|
+
return push;
|
|
30
|
+
}
|
|
31
|
+
const verb = raw[1];
|
|
32
|
+
push.verb = verb === "default" || verb === "update" || verb === "declare" ? verb : "other";
|
|
33
|
+
const payload = raw[2];
|
|
34
|
+
if (payload === undefined || payload === null || typeof payload !== "object")
|
|
35
|
+
return push;
|
|
36
|
+
const signals = {};
|
|
37
|
+
for (const name of CONSENT_SIGNAL_NAMES) {
|
|
38
|
+
const value = payload[name];
|
|
39
|
+
if (value === "granted" || value === "denied")
|
|
40
|
+
signals[name] = value;
|
|
41
|
+
}
|
|
42
|
+
if (Object.keys(signals).length > 0)
|
|
43
|
+
push.signals = signals;
|
|
44
|
+
if (signals.analytics_storage !== undefined)
|
|
45
|
+
push.analyticsStorage = signals.analytics_storage;
|
|
46
|
+
// A `region` present but empty is not a scope; only a list of regions is.
|
|
47
|
+
const region = payload["region"];
|
|
48
|
+
if (Array.isArray(region) && region.length > 0) {
|
|
49
|
+
push.region = region.filter((entry) => typeof entry === "string");
|
|
50
|
+
}
|
|
51
|
+
const wait = payload["wait_for_update"];
|
|
52
|
+
if (typeof wait === "number" && Number.isFinite(wait))
|
|
53
|
+
push.waitForUpdate = wait;
|
|
54
|
+
return push;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Turns one google_tag_data.ics analytics_storage entry into a state and a
|
|
58
|
+
* provenance.
|
|
59
|
+
*
|
|
60
|
+
* The provenance rests on which KEYS the entry carries, not on its
|
|
61
|
+
* `implicit` flag. gtag.js sets `implicit: true` on every entry it ever
|
|
62
|
+
* touches — verified against live gtag.js for "nothing declared", an
|
|
63
|
+
* unregioned default, a region-scoped default that matches, and an explicit
|
|
64
|
+
* update: all of them read `implicit: true`. It therefore cannot tell a real
|
|
65
|
+
* declared default from gtag.js's own fallback, and trusting it labelled
|
|
66
|
+
* every properly-configured default as "implicit-default".
|
|
67
|
+
*
|
|
68
|
+
* What gtag.js does do is only write `default` when a declared default
|
|
69
|
+
* actually applies to this visitor. A region-scoped default whose region
|
|
70
|
+
* list doesn't cover them leaves the entry as bare `{implicit: true}` —
|
|
71
|
+
* indistinguishable from a site that declared nothing, and correctly so:
|
|
72
|
+
* neither is a Consent Mode decision for this visitor.
|
|
73
|
+
*
|
|
74
|
+
* `declare` is ignored: gtag('consent','declare',…) is a no-op in current
|
|
75
|
+
* gtag.js (it leaves the entry untouched and suppresses no data collection).
|
|
76
|
+
*/
|
|
77
|
+
export function classifyAnalyticsStorage(entry) {
|
|
78
|
+
if (entry === null)
|
|
79
|
+
return { state: null, source: null };
|
|
80
|
+
// An explicit consent *update* always wins over a *default*; which one we
|
|
81
|
+
// read is recorded, because a declared default is a configuration choice,
|
|
82
|
+
// not a user decision.
|
|
83
|
+
const hasUpdate = entry.update !== undefined && entry.update !== null;
|
|
84
|
+
const hasDefault = entry.default !== undefined && entry.default !== null;
|
|
85
|
+
const raw = hasUpdate ? entry.update : entry.default;
|
|
86
|
+
const state = raw === true || raw === "granted" ? "granted" : raw === false || raw === "denied" ? "denied" : "unset";
|
|
87
|
+
const source = hasUpdate ? "update" : hasDefault ? "default" : "implicit-default";
|
|
88
|
+
return { state, source };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolves every signal ics.entries carries, through the one rule above.
|
|
92
|
+
*
|
|
93
|
+
* The point of reading all seven rather than analytics_storage alone is the
|
|
94
|
+
* v1/v2 question: v2 is defined by ad_user_data and ad_personalization
|
|
95
|
+
* existing, and "exists" is only half of it — a signal whose source is
|
|
96
|
+
* implicit-default has nothing declared for it, which is a half-configured v2
|
|
97
|
+
* rather than a working one. One rule covers all seven, so there is still
|
|
98
|
+
* only one definition of what a state and a provenance are.
|
|
99
|
+
*/
|
|
100
|
+
export function classifyConsentEntries(entries) {
|
|
101
|
+
const resolved = {};
|
|
102
|
+
for (const name of CONSENT_SIGNAL_NAMES) {
|
|
103
|
+
const entry = entries[name];
|
|
104
|
+
if (entry === undefined || entry === null)
|
|
105
|
+
continue;
|
|
106
|
+
const { state, source } = classifyAnalyticsStorage(entry);
|
|
107
|
+
// classifyAnalyticsStorage only returns nulls for a null entry, which the
|
|
108
|
+
// guard above already excluded.
|
|
109
|
+
if (state === null || source === null)
|
|
110
|
+
continue;
|
|
111
|
+
resolved[name] = { state, source, region: entry.region };
|
|
112
|
+
}
|
|
113
|
+
return resolved;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Whether the page is running Consent Mode v2 or v1, from the keys
|
|
117
|
+
* ics.entries carries.
|
|
118
|
+
*
|
|
119
|
+
* Deliberately NOT a judgement about whether Consent Mode is configured:
|
|
120
|
+
* gtag/GTM creates every key it knows about as soon as a container loads, so
|
|
121
|
+
* a page that never calls gtag('consent', …) still reports seven keys. Only
|
|
122
|
+
* ics.active answers that, and the two are asked separately.
|
|
123
|
+
*/
|
|
124
|
+
export function consentModeVersionFromKeys(entryKeys) {
|
|
125
|
+
return CONSENT_V2_ONLY_SIGNALS.every((name) => entryKeys.includes(name)) ? "v2" : "v1";
|
|
126
|
+
}
|
|
127
|
+
const EMPTY = {
|
|
128
|
+
cmpDetected: false,
|
|
129
|
+
cmpVendor: null,
|
|
130
|
+
cmpVendorConfirmed: false,
|
|
131
|
+
cmpHasResponse: null,
|
|
132
|
+
googleTagDataExists: false,
|
|
133
|
+
ics: null,
|
|
134
|
+
rawEntries: {},
|
|
135
|
+
rawConsentPushes: [],
|
|
136
|
+
// readConsentState is itself what nudgeVisitorInteraction calls to
|
|
137
|
+
// decide whether a nudge worked, so it cannot know the answer. The phase
|
|
138
|
+
// stamps the real value onto the snapshot it keeps.
|
|
139
|
+
bannerReveal: "not-needed",
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Does what a visitor does in their first seconds on a page: moves the mouse,
|
|
143
|
+
* and scrolls.
|
|
144
|
+
*
|
|
145
|
+
* Not a nicety. Two of the ten accounts in the corpus render or load NOTHING
|
|
146
|
+
* tracking-related until the page sees an interaction, and neither is
|
|
147
|
+
* detectable by waiting:
|
|
148
|
+
*
|
|
149
|
+
* - factorial.es builds its consent banner from the first `mousemove` or
|
|
150
|
+
* `scroll` (`I && (I = false, S())`), with no timer anywhere. A passive run
|
|
151
|
+
* waited fifteen seconds and saw no banner, then reported "no consent
|
|
152
|
+
* platform was detected" about a site that has one.
|
|
153
|
+
* - catonetworks.com fires its GTM tags on scroll. With no scroll not one
|
|
154
|
+
* Dreamdata script is ever injected, and the run said the visitor "agreed to
|
|
155
|
+
* identified tracking and did not get it" about a site that tracks
|
|
156
|
+
* correctly. A mouse move alone does NOT reach it; only the scroll does.
|
|
157
|
+
*
|
|
158
|
+
* So both steps always run, and they run for every phase before any consent
|
|
159
|
+
* or script evidence is read. The earlier version stopped as soon as a CMP was
|
|
160
|
+
* detected, which is exactly why catonetworks was missed: Cookiebot answered
|
|
161
|
+
* on load, the nudge was skipped as unnecessary, and the tags it would have
|
|
162
|
+
* triggered never loaded.
|
|
163
|
+
*
|
|
164
|
+
* A run that never interacts is not measuring a cautious visitor — it is
|
|
165
|
+
* measuring one who loads a page and then freezes, which is nobody. The scroll
|
|
166
|
+
* can start lazy-loading and scroll-depth tracking, and that is the correct
|
|
167
|
+
* trade: those are things a real visit also does. The position is restored so
|
|
168
|
+
* nothing read afterwards depends on where the page was left.
|
|
169
|
+
*/
|
|
170
|
+
export async function nudgeVisitorInteraction(page,
|
|
171
|
+
/**
|
|
172
|
+
* Optional. When given, it is sampled around each step so the caller can
|
|
173
|
+
* report WHICH interaction revealed a banner. Omit it on a page where there
|
|
174
|
+
* is no banner question — the second-page walk — and the interaction still
|
|
175
|
+
* happens, which is the part every caller needs.
|
|
176
|
+
*/
|
|
177
|
+
isDetected) {
|
|
178
|
+
const detectedBefore = isDetected ? await isDetected() : true;
|
|
179
|
+
const viewport = page.viewportSize() ?? { width: 1280, height: 800 };
|
|
180
|
+
await page.mouse.move(viewport.width / 2, viewport.height / 2).catch(() => { });
|
|
181
|
+
await page.waitForTimeout(BANNER_REVEAL_SETTLE_MS);
|
|
182
|
+
const detectedAfterMove = detectedBefore || (isDetected !== undefined && (await isDetected()));
|
|
183
|
+
await page.mouse.wheel(0, BANNER_REVEAL_SCROLL_PX).catch(() => { });
|
|
184
|
+
await page.waitForTimeout(BANNER_REVEAL_SETTLE_MS);
|
|
185
|
+
await page.evaluate(() => window.scrollTo(0, 0)).catch(() => { });
|
|
186
|
+
if (detectedBefore)
|
|
187
|
+
return "not-needed";
|
|
188
|
+
if (detectedAfterMove)
|
|
189
|
+
return "mousemove";
|
|
190
|
+
return isDetected !== undefined && (await isDetected()) ? "scroll" : "unrevealed";
|
|
191
|
+
}
|
|
192
|
+
export async function readConsentState(page) {
|
|
193
|
+
const result = await page
|
|
194
|
+
.evaluate(({ vendors, domVendors, signalNames }) => {
|
|
195
|
+
const w = window;
|
|
196
|
+
const googleTagDataExists = Boolean(w.google_tag_data);
|
|
197
|
+
// Read raw and classify in node (classifyAnalyticsStorage,
|
|
198
|
+
// classifyConsentEntries, judgeConsentModeV2), so the rules that decide
|
|
199
|
+
// a fail/pass are unit-testable rather than buried in a page closure.
|
|
200
|
+
// `?? null` keeps a real `false` intact while collapsing "key absent"
|
|
201
|
+
// to something that survives serialization.
|
|
202
|
+
//
|
|
203
|
+
// Every ics read is wrapped: these are undocumented gtag.js internals,
|
|
204
|
+
// and one renamed property must cost this phase its consent evidence at
|
|
205
|
+
// most, never the whole collection.
|
|
206
|
+
let ics = null;
|
|
207
|
+
const rawEntries = {};
|
|
208
|
+
try {
|
|
209
|
+
if (googleTagDataExists) {
|
|
210
|
+
const raw = w.google_tag_data?.ics;
|
|
211
|
+
const entries = raw?.entries ?? {};
|
|
212
|
+
const asBool = (value) => (typeof value === "boolean" ? value : null);
|
|
213
|
+
ics = {
|
|
214
|
+
present: Boolean(raw),
|
|
215
|
+
active: asBool(raw?.active),
|
|
216
|
+
usedDefault: asBool(raw?.usedDefault),
|
|
217
|
+
usedUpdate: asBool(raw?.usedUpdate),
|
|
218
|
+
wasSetLate: asBool(raw?.wasSetLate),
|
|
219
|
+
waitPeriodTimedOut: asBool(raw?.waitPeriodTimedOut),
|
|
220
|
+
entryKeys: Object.keys(entries).sort(),
|
|
221
|
+
};
|
|
222
|
+
for (const name of signalNames) {
|
|
223
|
+
const entry = entries[name];
|
|
224
|
+
if (!entry)
|
|
225
|
+
continue;
|
|
226
|
+
const region = Array.isArray(entry.region)
|
|
227
|
+
? entry.region.filter((value) => typeof value === "string")
|
|
228
|
+
: null;
|
|
229
|
+
rawEntries[name] = {
|
|
230
|
+
update: entry.update ?? null,
|
|
231
|
+
default: entry.default ?? null,
|
|
232
|
+
region: region !== null && region.length > 0 ? region : null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
ics = null;
|
|
239
|
+
}
|
|
240
|
+
const bag = w;
|
|
241
|
+
const globalVendor = vendors.find((v) => Boolean(bag[v.globalKey]));
|
|
242
|
+
// Only consulted when no vendor global matched: a rendered banner is
|
|
243
|
+
// real evidence of a gate, an always-present tracker global is not.
|
|
244
|
+
const domVendor = globalVendor
|
|
245
|
+
? undefined
|
|
246
|
+
: domVendors.find((v) => document.getElementById(v.bannerElementId) !== null);
|
|
247
|
+
const vendor = globalVendor ?? domVendor;
|
|
248
|
+
let cmpHasResponse = null;
|
|
249
|
+
if (typeof w.Cookiebot?.hasResponse === "boolean") {
|
|
250
|
+
cmpHasResponse = w.Cookiebot.hasResponse;
|
|
251
|
+
}
|
|
252
|
+
else if (typeof w.OneTrust?.IsAlertBoxClosed === "function") {
|
|
253
|
+
try {
|
|
254
|
+
cmpHasResponse = Boolean(w.OneTrust.IsAlertBoxClosed());
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
cmpHasResponse = null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else if (typeof w.getCkyConsent === "function") {
|
|
261
|
+
// CookieYes writes cookieyes-consent on load, before the visitor has
|
|
262
|
+
// answered anything, so the cookie's presence proves nothing; the
|
|
263
|
+
// flag inside it is the only thing that says an answer was given.
|
|
264
|
+
// This matters most for banners answered through the preference
|
|
265
|
+
// centre, where it is the reload check's only evidence.
|
|
266
|
+
try {
|
|
267
|
+
cmpHasResponse = Boolean(w.getCkyConsent()?.isUserActionCompleted);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
cmpHasResponse = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else if (domVendor) {
|
|
274
|
+
cmpHasResponse = document.cookie.includes(`${domVendor.responseCookiePrefix}=`);
|
|
275
|
+
}
|
|
276
|
+
else if (vendor?.id === "complianz") {
|
|
277
|
+
// Complianz stores its decision in a cookie rather than exposing a
|
|
278
|
+
// readable flag on window.complianz; the banner's own visibility
|
|
279
|
+
// class is the fallback when the cookie hasn't been set yet.
|
|
280
|
+
cmpHasResponse = document.cookie.includes("cmplz_banner-status=");
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
bannerReveal: "not-needed",
|
|
284
|
+
// google_tag_data alone means GTM is present, not that a CMP is —
|
|
285
|
+
// so a real vendor global is required to claim a CMP.
|
|
286
|
+
cmpDetected: vendor !== undefined,
|
|
287
|
+
cmpVendor: vendor?.id ?? null,
|
|
288
|
+
cmpVendorConfirmed: vendor !== undefined,
|
|
289
|
+
cmpHasResponse,
|
|
290
|
+
googleTagDataExists,
|
|
291
|
+
ics,
|
|
292
|
+
rawEntries,
|
|
293
|
+
rawConsentPushes: w.__ddEvidence?.consentPushes ?? [],
|
|
294
|
+
};
|
|
295
|
+
}, {
|
|
296
|
+
vendors: CMP_VENDORS.map((v) => ({ id: v.id, globalKey: v.globalKey })),
|
|
297
|
+
domVendors: DOM_ONLY_CMP_VENDORS.map((v) => ({
|
|
298
|
+
id: v.id,
|
|
299
|
+
bannerElementId: v.bannerElementId,
|
|
300
|
+
responseCookiePrefix: v.responseCookiePrefix,
|
|
301
|
+
})),
|
|
302
|
+
signalNames: CONSENT_SIGNAL_NAMES,
|
|
303
|
+
})
|
|
304
|
+
.catch(() => EMPTY);
|
|
305
|
+
// No named vendor and no known DOM banner matched — try the generic
|
|
306
|
+
// text-matched fallback before concluding there is no gate at all. A
|
|
307
|
+
// bespoke, hand-rolled banner exposes none of the above but still has an
|
|
308
|
+
// "Accept all"/"Necessary only"-style pair a visitor would actually see.
|
|
309
|
+
let heuristicOverride = null;
|
|
310
|
+
if (!result.cmpDetected) {
|
|
311
|
+
const heuristic = await page
|
|
312
|
+
.evaluate(runHeuristicBanner, {
|
|
313
|
+
mode: "detect",
|
|
314
|
+
acceptPhrases: GENERIC_ACCEPT_PHRASES,
|
|
315
|
+
rejectPhrases: GENERIC_REJECT_PHRASES,
|
|
316
|
+
preferenceCounterpartPhrases: PREFERENCE_CENTER_COUNTERPART_PHRASES,
|
|
317
|
+
contextKeywords: BANNER_CONTEXT_KEYWORDS,
|
|
318
|
+
})
|
|
319
|
+
.catch(() => null);
|
|
320
|
+
if (heuristic?.detected) {
|
|
321
|
+
heuristicOverride = { cmpDetected: true, cmpVendor: "unknown-custom", cmpVendorConfirmed: false };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const { rawConsentPushes, rawEntries, ...snapshot } = result;
|
|
325
|
+
const signals = classifyConsentEntries(rawEntries);
|
|
326
|
+
const { state, source } = classifyAnalyticsStorage(rawEntries["analytics_storage"] ?? null);
|
|
327
|
+
return {
|
|
328
|
+
...snapshot,
|
|
329
|
+
analyticsStorageState: state,
|
|
330
|
+
analyticsStorageSource: source,
|
|
331
|
+
signals,
|
|
332
|
+
// A CMP being present does not imply this: many CMPs never call
|
|
333
|
+
// gtag('consent', …) at all, and a declared default can be scoped to a
|
|
334
|
+
// region this visitor isn't in, which leaves no applicable value behind.
|
|
335
|
+
consentModeConfigured: source === "update" || source === "default",
|
|
336
|
+
...heuristicOverride,
|
|
337
|
+
dataLayerConsentPushes: rawConsentPushes.map((push) => parseConsentPush(push.raw, push.timestamp)),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/evidence/consent.ts"],"names":[],"mappings":"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"}
|