@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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +756 -0
  3. package/dist/ai/checker.js +222 -0
  4. package/dist/ai/checker.js.map +1 -0
  5. package/dist/ai/checklist.js +190 -0
  6. package/dist/ai/checklist.js.map +1 -0
  7. package/dist/ai/cli-presets.js +27 -0
  8. package/dist/ai/cli-presets.js.map +1 -0
  9. package/dist/ai/evidence-view.js +143 -0
  10. package/dist/ai/evidence-view.js.map +1 -0
  11. package/dist/ai/local-cli.js +58 -0
  12. package/dist/ai/local-cli.js.map +1 -0
  13. package/dist/ai/openai-compatible.js +78 -0
  14. package/dist/ai/openai-compatible.js.map +1 -0
  15. package/dist/ai/response-parser.js +105 -0
  16. package/dist/ai/response-parser.js.map +1 -0
  17. package/dist/ai/types.js +2 -0
  18. package/dist/ai/types.js.map +1 -0
  19. package/dist/analysis/consent-mode.js +225 -0
  20. package/dist/analysis/consent-mode.js.map +1 -0
  21. package/dist/analysis/deployment.js +2658 -0
  22. package/dist/analysis/deployment.js.map +1 -0
  23. package/dist/analysis/diagnosis/copy.en.js +303 -0
  24. package/dist/analysis/diagnosis/copy.en.js.map +1 -0
  25. package/dist/analysis/diagnosis/diagnose.js +172 -0
  26. package/dist/analysis/diagnosis/diagnose.js.map +1 -0
  27. package/dist/analysis/diagnosis/docs.js +118 -0
  28. package/dist/analysis/diagnosis/docs.js.map +1 -0
  29. package/dist/analysis/diagnosis/index.js +5 -0
  30. package/dist/analysis/diagnosis/index.js.map +1 -0
  31. package/dist/analysis/diagnosis/rules.js +656 -0
  32. package/dist/analysis/diagnosis/rules.js.map +1 -0
  33. package/dist/analysis/diagnosis/types.js +2 -0
  34. package/dist/analysis/diagnosis/types.js.map +1 -0
  35. package/dist/cli.js +344 -0
  36. package/dist/cli.js.map +1 -0
  37. package/dist/config/env.js +100 -0
  38. package/dist/config/env.js.map +1 -0
  39. package/dist/config/preflight.js +167 -0
  40. package/dist/config/preflight.js.map +1 -0
  41. package/dist/config/user-config.js +79 -0
  42. package/dist/config/user-config.js.map +1 -0
  43. package/dist/evidence/collect.js +527 -0
  44. package/dist/evidence/collect.js.map +1 -0
  45. package/dist/evidence/consent-accept.js +377 -0
  46. package/dist/evidence/consent-accept.js.map +1 -0
  47. package/dist/evidence/consent.js +340 -0
  48. package/dist/evidence/consent.js.map +1 -0
  49. package/dist/evidence/constants.js +615 -0
  50. package/dist/evidence/constants.js.map +1 -0
  51. package/dist/evidence/globals.js +132 -0
  52. package/dist/evidence/globals.js.map +1 -0
  53. package/dist/evidence/gtm-container.js +432 -0
  54. package/dist/evidence/gtm-container.js.map +1 -0
  55. package/dist/evidence/heuristic-banner.js +107 -0
  56. package/dist/evidence/heuristic-banner.js.map +1 -0
  57. package/dist/evidence/http-probe.js +139 -0
  58. package/dist/evidence/http-probe.js.map +1 -0
  59. package/dist/evidence/init-script.js +112 -0
  60. package/dist/evidence/init-script.js.map +1 -0
  61. package/dist/evidence/network.js +365 -0
  62. package/dist/evidence/network.js.map +1 -0
  63. package/dist/evidence/preference-center.js +368 -0
  64. package/dist/evidence/preference-center.js.map +1 -0
  65. package/dist/evidence/script.js +425 -0
  66. package/dist/evidence/script.js.map +1 -0
  67. package/dist/evidence/second-page.js +332 -0
  68. package/dist/evidence/second-page.js.map +1 -0
  69. package/dist/evidence/selector-probe.js +53 -0
  70. package/dist/evidence/selector-probe.js.map +1 -0
  71. package/dist/evidence/stealth.js +21 -0
  72. package/dist/evidence/stealth.js.map +1 -0
  73. package/dist/evidence/types.js +14 -0
  74. package/dist/evidence/types.js.map +1 -0
  75. package/dist/interactive/prompt.js +31 -0
  76. package/dist/interactive/prompt.js.map +1 -0
  77. package/dist/mcp/server.js +308 -0
  78. package/dist/mcp/server.js.map +1 -0
  79. package/dist/mcp/summary.js +41 -0
  80. package/dist/mcp/summary.js.map +1 -0
  81. package/dist/report/context.js +184 -0
  82. package/dist/report/context.js.map +1 -0
  83. package/dist/report/projection.js +125 -0
  84. package/dist/report/projection.js.map +1 -0
  85. package/dist/report/quote-check.js +90 -0
  86. package/dist/report/quote-check.js.map +1 -0
  87. package/dist/report/render.js +178 -0
  88. package/dist/report/render.js.map +1 -0
  89. package/dist/report/serializers.js +124 -0
  90. package/dist/report/serializers.js.map +1 -0
  91. package/dist/report/store.js +139 -0
  92. package/dist/report/store.js.map +1 -0
  93. package/dist/run/verify.js +77 -0
  94. package/dist/run/verify.js.map +1 -0
  95. package/dist/utils/duration.js +10 -0
  96. package/dist/utils/duration.js.map +1 -0
  97. package/dist/utils/errors.js +17 -0
  98. package/dist/utils/errors.js.map +1 -0
  99. package/dist/utils/logger.js +59 -0
  100. package/dist/utils/logger.js.map +1 -0
  101. package/dist/utils/runTimingsLog.js +27 -0
  102. package/dist/utils/runTimingsLog.js.map +1 -0
  103. package/dist/utils/slug.js +27 -0
  104. package/dist/utils/slug.js.map +1 -0
  105. package/dist/version.js +10 -0
  106. package/dist/version.js.map +1 -0
  107. package/package.json +57 -0
  108. 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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"}