@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,527 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { EvidenceCollectionError } from "../utils/errors.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { acceptConsent as runConsentAcceptance, rejectConsent as runConsentRejection, snapshotStorage, waitForConsentPersisted, waitForConsentSignal, } from "./consent-accept.js";
|
|
5
|
+
import { nudgeVisitorInteraction, readConsentState } from "./consent.js";
|
|
6
|
+
import { DEFAULT_SCRIPT_HOSTS, DEFAULT_TRACKING_HOSTS, NETWORK_SETTLE_MS } from "./constants.js";
|
|
7
|
+
import { attachConsoleCollector, readCookiesAndStorage, readDreamdataGlobals, } from "./globals.js";
|
|
8
|
+
import { probeServedMarkup } from "./http-probe.js";
|
|
9
|
+
import { installEvidenceHooks } from "./init-script.js";
|
|
10
|
+
import { attachNetworkCollector, attachThirdPartySegmentCollector, determineCookielessSeenFirst, partitionCarriedOverRequests, partitionSecondPageRequests, summarizeDreamdataRequestCounts, summarizeModeCounts, summarizeObservedLibraries, summarizeObservedWriteKeys, } from "./network.js";
|
|
11
|
+
import { deriveNormalBundleSignals, detectThirdPartySegmentScript, readScriptPresence, scanTagManagerContainers, } from "./script.js";
|
|
12
|
+
import { navigateToSecondPage } from "./second-page.js";
|
|
13
|
+
import { installStealthPatches } from "./stealth.js";
|
|
14
|
+
import { ALL_PHASES } from "./types.js";
|
|
15
|
+
// Real Chrome (when installed) renders and fingerprints like an actual
|
|
16
|
+
// user's browser; the bundled Chromium build is a reasonable fallback for
|
|
17
|
+
// machines without Chrome (e.g. CI). --disable-blink-features and dropping
|
|
18
|
+
// --enable-automation remove the most visible automation signals on top of
|
|
19
|
+
// whichever binary we end up running.
|
|
20
|
+
async function launchRealisticBrowser(headless) {
|
|
21
|
+
const launchOptions = {
|
|
22
|
+
headless,
|
|
23
|
+
args: ["--disable-blink-features=AutomationControlled"],
|
|
24
|
+
ignoreDefaultArgs: ["--enable-automation"],
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
const browser = await chromium.launch({ ...launchOptions, channel: "chrome" });
|
|
28
|
+
return { browser, channel: "chrome" };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
logger.debug("Real Chrome channel unavailable, falling back to bundled Chromium", err);
|
|
32
|
+
const browser = await chromium.launch(launchOptions);
|
|
33
|
+
return { browser, channel: "chromium-bundled" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A bounded wait for the page to go quiet, on top of a navigation that only
|
|
38
|
+
* waited for the document to parse.
|
|
39
|
+
*
|
|
40
|
+
* The two used to be one condition: every navigation waited on `networkidle`
|
|
41
|
+
* for the whole navigation timeout, which always ran to the full timeout and
|
|
42
|
+
* then THREW on any page that keeps a socket open. Each phase logged a scary
|
|
43
|
+
* "did not reach networkidle" warning and then measured whatever state the
|
|
44
|
+
* page happened to be in — on zoominfo.com all three phases spent their
|
|
45
|
+
* entire budget waiting on a bot-wall interstitial that was never going to
|
|
46
|
+
* settle. Never reaching idle is not an error and is not reported as one;
|
|
47
|
+
* batchFlushWaitMs is what actually gives requests time to leave.
|
|
48
|
+
*/
|
|
49
|
+
async function settleNetwork(page, timeoutMs, label) {
|
|
50
|
+
const quiet = await page
|
|
51
|
+
.waitForLoadState("networkidle", { timeout: Math.min(NETWORK_SETTLE_MS, timeoutMs) })
|
|
52
|
+
.then(() => true)
|
|
53
|
+
.catch(() => false);
|
|
54
|
+
if (!quiet) {
|
|
55
|
+
logger.debug(`${label}: the page was still making requests after ${NETWORK_SETTLE_MS}ms; carrying on`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Waits until `deadline`, rather than sleeping for a fixed span on top of
|
|
60
|
+
* whatever waiting already happened since the page loaded.
|
|
61
|
+
*
|
|
62
|
+
* `settleNetwork` and friends already spend time watching this same page
|
|
63
|
+
* before a flush wait runs; stacking a full `batchFlushWaitMs` on top of that
|
|
64
|
+
* makes the observation window longer than intended without giving requests
|
|
65
|
+
* any more time to arrive than they already had. This keeps the window at
|
|
66
|
+
* exactly `batchFlushWaitMs` measured from page-load start, never less.
|
|
67
|
+
*/
|
|
68
|
+
async function waitUntilDeadline(page, deadline) {
|
|
69
|
+
const remaining = deadline - Date.now();
|
|
70
|
+
if (remaining > 0)
|
|
71
|
+
await page.waitForTimeout(remaining);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The single most consequential automation tell there is: headless Chrome
|
|
75
|
+
* writes `HeadlessChrome/<version>` into its User-Agent, in the header and in
|
|
76
|
+
* `navigator.userAgent` alike, and `isbot`-style checks match that substring.
|
|
77
|
+
*
|
|
78
|
+
* Sites do not merely log the visitor as a bot when they see it — they change
|
|
79
|
+
* what they render. meteomatics.com never mounts its consent banner at all
|
|
80
|
+
* for a matching UA, so every phase found no CMP, no accept control and
|
|
81
|
+
* nothing to drive, and all three consent states measured the same
|
|
82
|
+
* banner-free page. Nothing downstream can recover from that: the run is
|
|
83
|
+
* describing a page no visitor is ever served.
|
|
84
|
+
*
|
|
85
|
+
* Renaming it back to `Chrome` is not a disguise — it is what this same
|
|
86
|
+
* binary reports when it has a window — and it is applied at the CONTEXT
|
|
87
|
+
* level so the request header changes too, not just the JS-visible copy that
|
|
88
|
+
* a server-side check never reads.
|
|
89
|
+
*
|
|
90
|
+
* Returns undefined when the UA needs no correction (any headed run), so a
|
|
91
|
+
* context is only ever pinned to a UA when there is a reason to.
|
|
92
|
+
*/
|
|
93
|
+
async function headlessSafeUserAgent(browser) {
|
|
94
|
+
const context = await browser.newContext();
|
|
95
|
+
try {
|
|
96
|
+
const page = await context.newPage();
|
|
97
|
+
const ua = await page.evaluate(() => navigator.userAgent);
|
|
98
|
+
return ua.includes("HeadlessChrome") ? ua.replace("HeadlessChrome", "Chrome") : undefined;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
logger.debug("Could not read the browser's default user agent", err);
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await context.close().catch(() => { });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const NO_ACCEPTANCE = {
|
|
109
|
+
attempted: false,
|
|
110
|
+
vendor: null,
|
|
111
|
+
succeeded: false,
|
|
112
|
+
detail: null,
|
|
113
|
+
direction: null,
|
|
114
|
+
consentSignalConfirmed: null,
|
|
115
|
+
undrivenReason: null,
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Walks to a second page and summarises what tracked there.
|
|
119
|
+
*
|
|
120
|
+
* `requests` is the phase's live request log; everything appended from here on
|
|
121
|
+
* belongs to the second page, so the boundary is taken before the navigation
|
|
122
|
+
* rather than by clearing the log — the first page's evidence has to survive
|
|
123
|
+
* intact for the two to be compared at all.
|
|
124
|
+
*
|
|
125
|
+
* The index boundary alone is not enough. A request the FIRST page created can
|
|
126
|
+
* still be queued when the click lands and only reach the network afterwards,
|
|
127
|
+
* which would credit the second page with a page view it never sent — the
|
|
128
|
+
* exact shape of a "tracking survived the navigation" pass on a site where it
|
|
129
|
+
* did not. Those are separated out by creation time and handed back to the
|
|
130
|
+
* caller, which owns the first page's counts.
|
|
131
|
+
*/
|
|
132
|
+
async function collectSecondPage(page, requests, timeoutMs, batchFlushWaitMs, options) {
|
|
133
|
+
const boundary = requests.length;
|
|
134
|
+
const walkStartedAt = Date.now();
|
|
135
|
+
const navigation = await navigateToSecondPage(page, timeoutMs, options);
|
|
136
|
+
// Measured from the ACCEPTED navigation, so a walk that had to step past two
|
|
137
|
+
// uninstrumented sub-sites still gives the page it settled on its full flush
|
|
138
|
+
// window rather than one already spent on the retries.
|
|
139
|
+
await waitUntilDeadline(page, navigation.startedAt + batchFlushWaitMs);
|
|
140
|
+
const { fired: secondPageRequests, carriedOver } = partitionSecondPageRequests(requests.slice(boundary), walkStartedAt, navigation.startedAt);
|
|
141
|
+
const evidence = {
|
|
142
|
+
...navigation,
|
|
143
|
+
network: {
|
|
144
|
+
requests: secondPageRequests,
|
|
145
|
+
dreamdataRequestCounts: summarizeDreamdataRequestCounts(secondPageRequests),
|
|
146
|
+
...summarizeModeCounts(secondPageRequests),
|
|
147
|
+
},
|
|
148
|
+
hardLoad: navigation.kind === "hard" ? null : await collectHardLoad(page, requests, timeoutMs, batchFlushWaitMs),
|
|
149
|
+
cookies: await readCookiesAndStorage(page),
|
|
150
|
+
};
|
|
151
|
+
return { evidence, carriedOverToFirstPage: carriedOver };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Loads the current URL as a document, so the phase has ONE page view whose
|
|
155
|
+
* requests cannot be explained by anything but that load.
|
|
156
|
+
*
|
|
157
|
+
* Only called when the walk did not already produce a hard load. See
|
|
158
|
+
* SecondPageEvidence.hardLoad for why a phase that never gets one cannot be
|
|
159
|
+
* judged on double-counting at all: its entry page was loaded twice around
|
|
160
|
+
* the consent answer, and its second page — reached by the site's own router
|
|
161
|
+
* — never re-ran a script.
|
|
162
|
+
*
|
|
163
|
+
* The requests are attributed by their own creation time, so anything the
|
|
164
|
+
* pre-reload page had queued stays with the page that made it rather than
|
|
165
|
+
* being credited to this load as a second event it never sent.
|
|
166
|
+
*/
|
|
167
|
+
async function collectHardLoad(page, requests, timeoutMs, batchFlushWaitMs) {
|
|
168
|
+
const url = page.url();
|
|
169
|
+
const boundary = requests.length;
|
|
170
|
+
const startedAt = Date.now();
|
|
171
|
+
const failedReason = await page
|
|
172
|
+
.reload({ waitUntil: "domcontentloaded", timeout: timeoutMs })
|
|
173
|
+
.then(() => null)
|
|
174
|
+
.catch((err) => `the document did not load within ${timeoutMs}ms: ${err.message}`);
|
|
175
|
+
if (failedReason === null)
|
|
176
|
+
await settleNetwork(page, timeoutMs, "hard load");
|
|
177
|
+
await waitUntilDeadline(page, startedAt + batchFlushWaitMs);
|
|
178
|
+
const { fired } = partitionCarriedOverRequests(requests.slice(boundary), startedAt);
|
|
179
|
+
return {
|
|
180
|
+
url,
|
|
181
|
+
network: {
|
|
182
|
+
requests: fired,
|
|
183
|
+
dreamdataRequestCounts: summarizeDreamdataRequestCounts(fired),
|
|
184
|
+
...summarizeModeCounts(fired),
|
|
185
|
+
},
|
|
186
|
+
failedReason,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Observes one consent state end to end in a FRESH browser context.
|
|
191
|
+
*
|
|
192
|
+
* The isolation is load-bearing: a grant leaves the CMP's decision in a
|
|
193
|
+
* cookie, so a reject phase sharing that jar would replay the granted page
|
|
194
|
+
* and report the site as ignoring the refusal.
|
|
195
|
+
*/
|
|
196
|
+
async function collectPhase(phase, ctx, options) {
|
|
197
|
+
const { url, headless, timeoutMs, batchFlushWaitMs, trackingHosts = DEFAULT_TRACKING_HOSTS, scanTagManager = true, probeSecondPage = true, } = options;
|
|
198
|
+
const collectionErrors = [];
|
|
199
|
+
// The bundle can be served from a CDN host while requests go to a
|
|
200
|
+
// first-party one, so script detection watches the union of both.
|
|
201
|
+
const scriptHosts = [...new Set([...DEFAULT_SCRIPT_HOSTS, ...trackingHosts])];
|
|
202
|
+
let context = null;
|
|
203
|
+
try {
|
|
204
|
+
context = await ctx.browser.newContext({ userAgent: ctx.userAgent });
|
|
205
|
+
const page = await context.newPage();
|
|
206
|
+
page.setDefaultTimeout(timeoutMs);
|
|
207
|
+
await page.addInitScript(installStealthPatches);
|
|
208
|
+
await page.addInitScript(installEvidenceHooks);
|
|
209
|
+
const network = attachNetworkCollector(page, trackingHosts);
|
|
210
|
+
const { requests } = network;
|
|
211
|
+
const thirdPartySegmentRequests = attachThirdPartySegmentCollector(page);
|
|
212
|
+
const consoleCollector = attachConsoleCollector(page);
|
|
213
|
+
const navigate = async (action, label) => {
|
|
214
|
+
try {
|
|
215
|
+
await action();
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
collectionErrors.push(`${label} did not complete within ${timeoutMs}ms: ${err.message}`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
await settleNetwork(page, timeoutMs, label);
|
|
222
|
+
};
|
|
223
|
+
const initialNavigationStartedAt = Date.now();
|
|
224
|
+
await navigate(() => page.goto(url, { waitUntil: "domcontentloaded", timeout: timeoutMs }), "Navigation");
|
|
225
|
+
// Before ANY consent read, including the clean phase's — a banner that
|
|
226
|
+
// only builds itself on the visitor's first mouse move is absent from
|
|
227
|
+
// every phase otherwise, and "no consent platform was detected" then
|
|
228
|
+
// voids the whole consent half of the report. See
|
|
229
|
+
// nudgeVisitorInteraction.
|
|
230
|
+
const bannerReveal = await nudgeVisitorInteraction(page, async () => (await readConsentState(page)).cmpDetected);
|
|
231
|
+
let consentAcceptance = NO_ACCEPTANCE;
|
|
232
|
+
/**
|
|
233
|
+
* ics as it read BEFORE the banner was answered, for the driven phases.
|
|
234
|
+
*
|
|
235
|
+
* Kept because wasSetLate cannot be trusted from the settled read of a
|
|
236
|
+
* driven phase: that read happens after a reload that carries the CMP's
|
|
237
|
+
* own cookie, and a CMP restoring a stored answer can declare its
|
|
238
|
+
* defaults earlier than it does on a first visit. The late-setting a real
|
|
239
|
+
* first-time visitor meets only shows here.
|
|
240
|
+
*/
|
|
241
|
+
let icsBeforeDecision = null;
|
|
242
|
+
// Set once the post-consent reload is under way, so requests the answered
|
|
243
|
+
// page had already created can be told apart from the reloaded page's own.
|
|
244
|
+
let consentReloadStartedAt = null;
|
|
245
|
+
if (phase !== "clean") {
|
|
246
|
+
const direction = phase === "grant" ? "grant" : "deny";
|
|
247
|
+
const preAcceptance = await readConsentState(page);
|
|
248
|
+
icsBeforeDecision = preAcceptance.ics;
|
|
249
|
+
const expectedSignal = phase === "grant" ? "granted" : "denied";
|
|
250
|
+
// waitForConsentPersisted/waitForConsentSignal below are read on the
|
|
251
|
+
// PAGE WE ARE ABOUT TO DISCARD. A CMP can flip its in-memory
|
|
252
|
+
// "answered" flag synchronously while the response cookie it depends
|
|
253
|
+
// on is still in flight to its own consent host — that flag and the
|
|
254
|
+
// reload race each other, and reloading the instant the flag flips can
|
|
255
|
+
// beat the cookie, discarding it. The reloaded page then reports the
|
|
256
|
+
// banner as unanswered again, and this pass silently measures a
|
|
257
|
+
// pre-consent visit under a "grant"/"reject" label. The only page that
|
|
258
|
+
// can actually answer "did the decision survive?" is the one this
|
|
259
|
+
// function is about to describe, so it's checked again after the
|
|
260
|
+
// reload and retried once if the decision didn't make it across.
|
|
261
|
+
const MAX_ATTEMPTS = 2;
|
|
262
|
+
let attempt = 0;
|
|
263
|
+
let outcome = null;
|
|
264
|
+
let persisted = false;
|
|
265
|
+
let consentSignalConfirmed = null;
|
|
266
|
+
let survivedReload = false;
|
|
267
|
+
while (attempt < MAX_ATTEMPTS && !survivedReload) {
|
|
268
|
+
attempt += 1;
|
|
269
|
+
// Taken before the click so a generic (non-vendor) persistence check
|
|
270
|
+
// can tell "the site just wrote this" apart from "this was already
|
|
271
|
+
// here" — see waitForConsentPersisted.
|
|
272
|
+
const preClickStorage = await snapshotStorage(page);
|
|
273
|
+
outcome = phase === "grant" ? await runConsentAcceptance(page, timeoutMs) : await runConsentRejection(page, timeoutMs);
|
|
274
|
+
if (!outcome.succeeded) {
|
|
275
|
+
collectionErrors.push(`The ${phase} phase could not drive the banner${attempt > 1 ? ` (attempt ${attempt})` : ""}: ${outcome.detail}`);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
// The grant/denial persists asynchronously; reloading before it lands
|
|
279
|
+
// would replay the pre-consent page and misreport a healthy install.
|
|
280
|
+
// Run alongside the Consent Mode check below rather than after it —
|
|
281
|
+
// the two questions are independent (one reads the CMP's own storage,
|
|
282
|
+
// the other reads window.google_tag_data), so waiting for them one at
|
|
283
|
+
// a time only doubles the worst-case 8s poll for no reason.
|
|
284
|
+
[persisted, consentSignalConfirmed] = await Promise.all([
|
|
285
|
+
waitForConsentPersisted(page, timeoutMs, preClickStorage),
|
|
286
|
+
waitForConsentSignal(page, expectedSignal, timeoutMs),
|
|
287
|
+
]);
|
|
288
|
+
if (!persisted) {
|
|
289
|
+
collectionErrors.push(`Consent was submitted (${phase} phase${attempt > 1 ? `, attempt ${attempt}` : ""}) but the CMP never recorded a response; the evidence below may still be pre-consent.`);
|
|
290
|
+
}
|
|
291
|
+
// Separate question from the one above: did Consent Mode itself move?
|
|
292
|
+
// A CMP that stores a decision without emitting the gtag update leaves
|
|
293
|
+
// every consent-gated tag untouched, which reads exactly like a broken
|
|
294
|
+
// bundle unless the two are reported apart.
|
|
295
|
+
if (consentSignalConfirmed === false) {
|
|
296
|
+
collectionErrors.push(`The CMP recorded the ${direction}${attempt > 1 ? ` (attempt ${attempt})` : ""} but analytics_storage never moved to ${expectedSignal}; anything gated on Consent Mode could not have reacted.`);
|
|
297
|
+
}
|
|
298
|
+
// CMPs with automatic blocking neutralise tracking tags during parse,
|
|
299
|
+
// so the choice only takes effect on the next load. Evidence from the
|
|
300
|
+
// pre-consent load is dropped to keep the two states from mixing.
|
|
301
|
+
requests.length = 0;
|
|
302
|
+
consentReloadStartedAt = Date.now();
|
|
303
|
+
await navigate(() => page.reload({ waitUntil: "domcontentloaded", timeout: timeoutMs }), phase === "grant" ? "Post-consent reload" : "Post-rejection reload");
|
|
304
|
+
// The reloaded document is a fresh page that has seen no interaction,
|
|
305
|
+
// so anything interaction-gated has to be woken again — catonetworks'
|
|
306
|
+
// GTM tags fire on scroll, and without this the grant and reject
|
|
307
|
+
// phases measured an entry page on which no tag had ever loaded while
|
|
308
|
+
// the clean phase (nudged once, never reloaded) tracked fine.
|
|
309
|
+
await nudgeVisitorInteraction(page, async () => (await readConsentState(page)).cmpDetected);
|
|
310
|
+
// cmpHasResponse is read fresh on the reloaded page: for a known
|
|
311
|
+
// vendor/DOM banner it reflects what that page's own script found in
|
|
312
|
+
// storage, not what the discarded page remembered in memory. null
|
|
313
|
+
// (no vendor we can read this way — e.g. an unknown-custom heuristic
|
|
314
|
+
// match) is treated as "can't tell, proceed" rather than a failure,
|
|
315
|
+
// since there is no reliable general signal for a bespoke banner.
|
|
316
|
+
const reloadedConsent = await readConsentState(page);
|
|
317
|
+
survivedReload = reloadedConsent.cmpHasResponse !== false;
|
|
318
|
+
if (!survivedReload) {
|
|
319
|
+
collectionErrors.push(`The ${phase} phase's decision did not survive the reload${attempt > 1 ? ` (attempt ${attempt})` : ""}: the reloaded page reports the banner as unanswered again, so this pass would have measured a pre-consent state under a driven label.${attempt < MAX_ATTEMPTS ? " Retrying." : " Giving up after the retry — treating this phase as not successfully driven."}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Kept apart from the composite below: only a submitted decision gets
|
|
323
|
+
// as far as the post-decision reload, so which condition failed decides
|
|
324
|
+
// whether this phase's numbers describe the answered page at all.
|
|
325
|
+
const submitted = outcome?.succeeded ?? false;
|
|
326
|
+
const drove = submitted && persisted && survivedReload;
|
|
327
|
+
consentAcceptance = {
|
|
328
|
+
...(outcome ?? { attempted: true, succeeded: false, detail: null }),
|
|
329
|
+
vendor: preAcceptance.cmpVendor,
|
|
330
|
+
direction,
|
|
331
|
+
consentSignalConfirmed,
|
|
332
|
+
succeeded: drove,
|
|
333
|
+
undrivenReason: drove
|
|
334
|
+
? null
|
|
335
|
+
: !submitted
|
|
336
|
+
? "not-submitted"
|
|
337
|
+
: !survivedReload
|
|
338
|
+
? "did-not-survive-reload"
|
|
339
|
+
: "not-persisted",
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
// Wait for requests to flush BEFORE snapshotting globals: a bundle that
|
|
343
|
+
// takes slightly longer than readDreamdataGlobals' own ready-callback
|
|
344
|
+
// timeout to attach its global was previously read as "never
|
|
345
|
+
// initialized" even though its request had already reached the server
|
|
346
|
+
// by the time this function returned — a false global-object failure
|
|
347
|
+
// racing against a true core-tracking pass. Measured from when the
|
|
348
|
+
// CURRENT page's load started (the post-consent reload when there was
|
|
349
|
+
// one, the initial navigation otherwise) rather than added on top of
|
|
350
|
+
// whatever settling already happened — the window stays exactly
|
|
351
|
+
// batchFlushWaitMs long, never longer.
|
|
352
|
+
const flushDeadlineFrom = consentReloadStartedAt ?? initialNavigationStartedAt;
|
|
353
|
+
await waitUntilDeadline(page, flushDeadlineFrom + batchFlushWaitMs);
|
|
354
|
+
// Clearing the log above only drops requests that were already on the
|
|
355
|
+
// wire. Answering the banner is itself a tracking event for a bundle the
|
|
356
|
+
// answer just un-gated, and that request can still be queued when the
|
|
357
|
+
// reload starts — reaching the network afterwards, where it is
|
|
358
|
+
// indistinguishable from one the reloaded page sent. Counted there it
|
|
359
|
+
// reads as a single page view tracked twice, with no second loader
|
|
360
|
+
// anywhere in the DOM to explain it, which is a false exclusivity failure
|
|
361
|
+
// this harness manufactured for itself. The event's own creation
|
|
362
|
+
// timestamp is what separates them.
|
|
363
|
+
if (consentReloadStartedAt !== null) {
|
|
364
|
+
const { fired, carriedOver } = partitionCarriedOverRequests(requests, consentReloadStartedAt);
|
|
365
|
+
if (carriedOver.length > 0) {
|
|
366
|
+
requests.length = 0;
|
|
367
|
+
requests.push(...fired);
|
|
368
|
+
logger.debug(`Dropped ${carriedOver.length} request(s) in the ${phase} phase that the pre-reload page created and delivered after the reload`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// All six are independent reads of the same settled page, so they run
|
|
372
|
+
// together rather than one after another.
|
|
373
|
+
const [globals, presence, cookiesAndStorage, consent, thirdPartySegmentScript, tagManager] = await Promise.all([
|
|
374
|
+
readDreamdataGlobals(page, Math.min(2000, timeoutMs)),
|
|
375
|
+
readScriptPresence(page, scriptHosts),
|
|
376
|
+
readCookiesAndStorage(page),
|
|
377
|
+
readConsentState(page).then((snapshot) => ({ ...snapshot, bannerReveal, icsBeforeDecision })),
|
|
378
|
+
page.content().then((html) => detectThirdPartySegmentScript(html, page.url())),
|
|
379
|
+
scanTagManager
|
|
380
|
+
? scanTagManagerContainers(page, timeoutMs, ctx.containerCache, ctx.httpProbe?.tagManagerContainerIds ?? [])
|
|
381
|
+
: Promise.resolve({
|
|
382
|
+
scanned: false,
|
|
383
|
+
containerIds: [],
|
|
384
|
+
normalBundleTagFound: false,
|
|
385
|
+
cookielessBundleTagFound: false,
|
|
386
|
+
thirdPartySegmentTagFound: false,
|
|
387
|
+
normalBundleConsentGate: null,
|
|
388
|
+
normalBundleBlockingTriggers: null,
|
|
389
|
+
errors: [],
|
|
390
|
+
}),
|
|
391
|
+
]);
|
|
392
|
+
// The page has now been read, so the key this install carries is known and
|
|
393
|
+
// requests on a first-party delivery domain can be claimed — before the
|
|
394
|
+
// first-page log is frozen and before the second-page probe starts
|
|
395
|
+
// slicing it, so both pages see the same set.
|
|
396
|
+
network.attributeTo(presence.writeKey);
|
|
397
|
+
// Everything above describes the first page, so its requests are frozen
|
|
398
|
+
// here: the collector keeps appending to `requests`, and mixing the second
|
|
399
|
+
// page's events into these counts would hide the exact failure the second
|
|
400
|
+
// page exists to find — a bundle that fires once and then stops.
|
|
401
|
+
const firstPageRequests = [...requests];
|
|
402
|
+
const secondPageResult = probeSecondPage
|
|
403
|
+
? await collectSecondPage(page, requests, timeoutMs, batchFlushWaitMs, {
|
|
404
|
+
// Read from the page we are leaving. A site with no install anywhere
|
|
405
|
+
// has nothing to walk towards, so the retry does not run at all.
|
|
406
|
+
firstPageInstalled: presence.present || globals.dreamdataExists || globals.cookielessGlobalExists || tagManager.containerIds.length > 0,
|
|
407
|
+
trackingHosts,
|
|
408
|
+
// Shared across phases: what one consent state learned about a
|
|
409
|
+
// section holds for the others. See SecondPageOptions.
|
|
410
|
+
barredSections: ctx.barredSections,
|
|
411
|
+
})
|
|
412
|
+
: null;
|
|
413
|
+
const secondPage = secondPageResult?.evidence ?? null;
|
|
414
|
+
// A request the first page created but only delivered after the click
|
|
415
|
+
// belongs to the page that made it, wherever it landed.
|
|
416
|
+
firstPageRequests.push(...(secondPageResult?.carriedOverToFirstPage ?? []));
|
|
417
|
+
if (secondPage?.skippedReason) {
|
|
418
|
+
collectionErrors.push(`Tracking across a page change could not be observed in the ${phase} phase: ${secondPage.skippedReason}`);
|
|
419
|
+
}
|
|
420
|
+
const modeCounts = summarizeModeCounts(firstPageRequests);
|
|
421
|
+
const normalBundleSignals = deriveNormalBundleSignals({
|
|
422
|
+
presence,
|
|
423
|
+
tagManager,
|
|
424
|
+
dreamdataIsStubQueue: globals.dreamdataIsStubQueue,
|
|
425
|
+
// Existence, not behaviour: a normal request on either page proves the
|
|
426
|
+
// bundle belongs to this deployment, whichever page it fired on.
|
|
427
|
+
normalRequestCount: modeCounts.normalRequestCount + (secondPage?.network.normalRequestCount ?? 0),
|
|
428
|
+
httpMarkup: ctx.httpProbe?.script ?? null,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
phase,
|
|
432
|
+
url,
|
|
433
|
+
timestamp: new Date().toISOString(),
|
|
434
|
+
browser: { browserType: "chromium", channel: ctx.channel, headless },
|
|
435
|
+
script: {
|
|
436
|
+
...presence,
|
|
437
|
+
tagManager,
|
|
438
|
+
normalBundleSignals,
|
|
439
|
+
normalBundleConfigured: normalBundleSignals.length > 0,
|
|
440
|
+
},
|
|
441
|
+
network: {
|
|
442
|
+
requests: firstPageRequests,
|
|
443
|
+
dreamdataRequestCounts: summarizeDreamdataRequestCounts(firstPageRequests),
|
|
444
|
+
...modeCounts,
|
|
445
|
+
cookielessRequestSeenFirst: determineCookielessSeenFirst(firstPageRequests),
|
|
446
|
+
trackingHosts: [...trackingHosts],
|
|
447
|
+
observedWriteKeys: summarizeObservedWriteKeys(firstPageRequests),
|
|
448
|
+
observedLibraries: summarizeObservedLibraries(firstPageRequests),
|
|
449
|
+
// The entry URL was loaded twice in this phase — once before the
|
|
450
|
+
// banner was answered, once after — so two page events for it are
|
|
451
|
+
// the consent upgrade rather than two loaders.
|
|
452
|
+
spansConsentAnswer: consentReloadStartedAt !== null,
|
|
453
|
+
unattributedRequests: network.unattributedRequests,
|
|
454
|
+
},
|
|
455
|
+
globals,
|
|
456
|
+
cookies: cookiesAndStorage,
|
|
457
|
+
consent,
|
|
458
|
+
consentAcceptance,
|
|
459
|
+
secondPage,
|
|
460
|
+
thirdPartySegment: {
|
|
461
|
+
...thirdPartySegmentScript,
|
|
462
|
+
requestCount: thirdPartySegmentRequests.count(),
|
|
463
|
+
},
|
|
464
|
+
console: consoleCollector,
|
|
465
|
+
collectionErrors,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
finally {
|
|
469
|
+
await context
|
|
470
|
+
?.close()
|
|
471
|
+
.catch((err) => logger.warn(`Failed to close the ${phase} context cleanly: ${err.message}`));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Walks every requested consent state in one browser, each in its own
|
|
476
|
+
* context, and returns them together.
|
|
477
|
+
*
|
|
478
|
+
* The three states are what make a consent verdict possible at all: a single
|
|
479
|
+
* pass cannot tell a bundle that is correctly gated from one that is broken,
|
|
480
|
+
* because both look like "nothing fired".
|
|
481
|
+
*/
|
|
482
|
+
export async function collectEvidence(options, onPhase) {
|
|
483
|
+
const phases = options.phases ?? ALL_PHASES;
|
|
484
|
+
if (phases.length === 0)
|
|
485
|
+
throw new EvidenceCollectionError("No phases were requested.");
|
|
486
|
+
// Never allowed to fail the run: it is the one observation that survives a
|
|
487
|
+
// page the browser cannot read, so it must not depend on the browser
|
|
488
|
+
// working. It also has no dependency ON the browser — it's a bare fetch —
|
|
489
|
+
// so it runs alongside the launch rather than blocking it.
|
|
490
|
+
const [httpProbe, { browser, channel }] = await Promise.all([
|
|
491
|
+
options.probeOverHttp === false
|
|
492
|
+
? Promise.resolve(null)
|
|
493
|
+
: probeServedMarkup(options.url, options.timeoutMs, options.trackingHosts ?? []),
|
|
494
|
+
launchRealisticBrowser(options.headless).catch((err) => {
|
|
495
|
+
throw new EvidenceCollectionError(`Failed to launch a browser: ${err.message}`, err);
|
|
496
|
+
}),
|
|
497
|
+
]);
|
|
498
|
+
if (httpProbe !== null && httpProbe.failedReason !== null) {
|
|
499
|
+
logger.debug(`Plain-HTTP probe did not corroborate this run: ${httpProbe.failedReason}`);
|
|
500
|
+
}
|
|
501
|
+
const ctx = {
|
|
502
|
+
browser,
|
|
503
|
+
channel,
|
|
504
|
+
userAgent: await headlessSafeUserAgent(browser),
|
|
505
|
+
containerCache: new Map(),
|
|
506
|
+
barredSections: new Set(),
|
|
507
|
+
httpProbe,
|
|
508
|
+
};
|
|
509
|
+
try {
|
|
510
|
+
const collected = [];
|
|
511
|
+
for (const [index, phase] of phases.entries()) {
|
|
512
|
+
onPhase?.(phase, index, phases.length);
|
|
513
|
+
collected.push(await collectPhase(phase, ctx, options));
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
url: options.url,
|
|
517
|
+
timestamp: new Date().toISOString(),
|
|
518
|
+
browser: { browserType: "chromium", channel, headless: options.headless },
|
|
519
|
+
httpProbe,
|
|
520
|
+
phases: collected,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
finally {
|
|
524
|
+
await browser.close().catch((err) => logger.warn(`Failed to close browser cleanly: ${err.message}`));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/evidence/collect.ts"],"names":[],"mappings":"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"}
|