@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,332 @@
1
+ import { COOKIELESS_GLOBAL_KEY, DEFAULT_SCRIPT_HOSTS, NETWORK_SETTLE_MS, SECOND_PAGE_ASSET_EXTENSIONS, SECOND_PAGE_LINK_ATTRIBUTE, SECOND_PAGE_MAX_CANDIDATES, SECOND_PAGE_MAX_RETRIES, SECOND_PAGE_PATH_DENYLIST, SEGMENT_COMPAT_GLOBAL_KEY, } from "./constants.js";
2
+ import { nudgeVisitorInteraction } from "./consent.js";
3
+ import { logger } from "../utils/logger.js";
4
+ /**
5
+ * Runs in the page: picks one same-origin link to a DIFFERENT path and stamps
6
+ * it so the click can find it again after this call returns.
7
+ *
8
+ * Scored rather than first-match: a visitor clicks something visible in the
9
+ * primary navigation, and a probe that follows a hidden footer link to a
10
+ * deep, query-stringed URL tests a page the site itself may treat
11
+ * differently. The denylist is the important half — see
12
+ * SECOND_PAGE_PATH_DENYLIST for why legal and auth pages prove nothing.
13
+ */
14
+ function rankInternalLinks(args) {
15
+ const { denyPathFragments, assetExtensions } = args;
16
+ const here = new URL(window.location.href);
17
+ const scored = Array.from(document.querySelectorAll("a[href]"))
18
+ .map((element) => {
19
+ const anchor = element;
20
+ const raw = anchor.getAttribute("href") ?? "";
21
+ if (/^(?:mailto:|tel:|javascript:|#)/i.test(raw.trim()))
22
+ return null;
23
+ if (anchor.hasAttribute("download"))
24
+ return null;
25
+ // A new tab leaves this page as-is, so nothing about the second page
26
+ // would be observable on it.
27
+ if ((anchor.getAttribute("target") ?? "").toLowerCase() === "_blank")
28
+ return null;
29
+ let target;
30
+ try {
31
+ target = new URL(anchor.href, here);
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ if (target.origin !== here.origin)
37
+ return null;
38
+ if (target.protocol !== "http:" && target.protocol !== "https:")
39
+ return null;
40
+ // Same page: a fragment or a self-link navigates nowhere.
41
+ if (target.pathname === here.pathname)
42
+ return null;
43
+ const lowerPath = target.pathname.toLowerCase();
44
+ if (assetExtensions.some((ext) => lowerPath.endsWith(ext)))
45
+ return null;
46
+ if (denyPathFragments.some((fragment) => lowerPath.includes(fragment)))
47
+ return null;
48
+ const rects = anchor.getClientRects();
49
+ const visible = rects.length > 0 && anchor.offsetParent !== null;
50
+ const depth = lowerPath.split("/").filter(Boolean).length;
51
+ let score = 0;
52
+ if (visible)
53
+ score += 4;
54
+ if (depth === 1)
55
+ score += 2;
56
+ else if (depth === 2)
57
+ score += 1;
58
+ if (target.search === "")
59
+ score += 1;
60
+ return { score, href: target.href, text: (anchor.textContent ?? "").trim().slice(0, 80) };
61
+ })
62
+ .filter((candidate) => candidate !== null);
63
+ // A stable sort on a pre-indexed list, so first-seen wins ties and the
64
+ // ranking is identical across runs of the same page rather than depending on
65
+ // the engine's sort. The whole ranking is returned, not just the winner: the
66
+ // best link can turn out to lead somewhere that was never instrumented, and
67
+ // the caller needs somewhere to go next.
68
+ return scored
69
+ .map((candidate, index) => ({ candidate, index }))
70
+ .sort((a, b) => b.candidate.score - a.candidate.score || a.index - b.index)
71
+ .map(({ candidate }) => ({ href: candidate.href, text: candidate.text }))
72
+ .slice(0, args.maxCandidates);
73
+ }
74
+ /**
75
+ * Stamps one already-ranked link so the click can find it again.
76
+ *
77
+ * Separate from ranking because a rejected candidate is left behind by going
78
+ * BACK to the first page, which rebuilds the DOM — every element reference
79
+ * from the previous ranking is gone by then, and the href is the only handle
80
+ * that survives.
81
+ */
82
+ function stampLink(args) {
83
+ for (const element of Array.from(document.querySelectorAll("a[href]"))) {
84
+ const anchor = element;
85
+ if (anchor.href === args.href) {
86
+ anchor.setAttribute(args.markerAttribute, "1");
87
+ return true;
88
+ }
89
+ }
90
+ return false;
91
+ }
92
+ /**
93
+ * Reads whether the current page carries the install. Runs in the page.
94
+ */
95
+ function readInstallPresence(args) {
96
+ const sources = Array.from(document.querySelectorAll("script[src]")).map((script) => script.src);
97
+ const bag = window;
98
+ return {
99
+ dreamdataScript: sources.some((src) => args.scriptHosts.some((host) => src.includes(host))),
100
+ dreamdataGlobal: args.globalKeys.some((key) => bag[key] !== undefined && bag[key] !== null),
101
+ tagManager: Array.isArray(bag.dataLayer) || sources.some((src) => src.includes("googletagmanager.com")),
102
+ };
103
+ }
104
+ async function readEvidenceState(page) {
105
+ return page
106
+ .evaluate(() => {
107
+ const state = window.__ddEvidence;
108
+ return {
109
+ documentId: typeof state?.documentId === "string" ? state.documentId : null,
110
+ historyCount: Array.isArray(state?.historyEvents) ? state.historyEvents.length : 0,
111
+ };
112
+ })
113
+ .catch(() => ({ documentId: null, historyCount: 0 }));
114
+ }
115
+ async function readHistoryEventsSince(page, since) {
116
+ return page
117
+ .evaluate((from) => {
118
+ const events = window.__ddEvidence
119
+ ?.historyEvents;
120
+ return Array.isArray(events) ? events.slice(from) : [];
121
+ }, since)
122
+ .catch(() => []);
123
+ }
124
+ const NOT_ATTEMPTED = (skippedReason, fromUrl) => ({
125
+ attempted: false,
126
+ skippedReason,
127
+ fromUrl,
128
+ toUrl: null,
129
+ linkText: null,
130
+ kind: null,
131
+ urlChanged: false,
132
+ historyEvents: [],
133
+ install: null,
134
+ rejectedCandidates: [],
135
+ startedAt: Date.now(),
136
+ });
137
+ /** The first path segment, which is what a separately-built sub-site occupies. */
138
+ function sectionOf(href) {
139
+ try {
140
+ return new URL(href).pathname.toLowerCase().split("/").filter(Boolean)[0] ?? "";
141
+ }
142
+ catch {
143
+ return "";
144
+ }
145
+ }
146
+ async function readInstall(page, trackingHosts) {
147
+ const raw = await page
148
+ .evaluate(readInstallPresence, {
149
+ scriptHosts: [...DEFAULT_SCRIPT_HOSTS, ...trackingHosts],
150
+ globalKeys: [COOKIELESS_GLOBAL_KEY, "dreamdata", SEGMENT_COMPAT_GLOBAL_KEY],
151
+ })
152
+ .catch((err) => {
153
+ logger.debug("Second-page install probe failed", err);
154
+ return null;
155
+ });
156
+ if (raw === null)
157
+ return null;
158
+ return { ...raw, present: raw.dreamdataScript || raw.dreamdataGlobal || raw.tagManager };
159
+ }
160
+ /** Clicks one ranked link and reports where it landed. */
161
+ async function attemptCandidate(page, candidate, fromUrl, timeoutMs, trackingHosts) {
162
+ const stamped = await page
163
+ .evaluate(stampLink, { href: candidate.href, markerAttribute: SECOND_PAGE_LINK_ATTRIBUTE })
164
+ .catch(() => false);
165
+ const before = await readEvidenceState(page);
166
+ const startedAt = Date.now();
167
+ // dispatchEvent skips the actionability checks a real click needs — hit
168
+ // testing above all — so a consent overlay in front of the link cannot
169
+ // block the probe. A destroyed execution context here IS the navigation
170
+ // starting, not a failure.
171
+ const dispatched = stamped &&
172
+ (await page
173
+ .locator(`[${SECOND_PAGE_LINK_ATTRIBUTE}]`)
174
+ .first()
175
+ .dispatchEvent("click", {}, { timeout: timeoutMs })
176
+ .then(() => true)
177
+ .catch((err) => {
178
+ logger.debug("Second-page click dispatch reported an error", err);
179
+ return false;
180
+ }));
181
+ // Bounded rather than the full navigation timeout: a page whose router
182
+ // never changes the URL would otherwise spend the entire budget here,
183
+ // three times a run — the same reasoning the networkidle wait right below
184
+ // already applies to itself. toUrl/urlChanged are read from page.url()
185
+ // after this regardless, so a change that lands just past the bound is
186
+ // still detected; this only caps how long the wait can block.
187
+ await page.waitForURL((url) => url.toString() !== fromUrl, { timeout: Math.min(NETWORK_SETTLE_MS, timeoutMs) }).catch(() => { });
188
+ await page.waitForLoadState("networkidle", { timeout: Math.min(NETWORK_SETTLE_MS, timeoutMs) }).catch(() => { });
189
+ const toUrl = page.url();
190
+ const urlChanged = toUrl !== fromUrl;
191
+ // The second page is another document that has seen no interaction, so
192
+ // anything interaction-gated needs waking here too. catonetworks' GTM tags
193
+ // fire on scroll: without this the walk landed on a page where no bundle had
194
+ // ever loaded and the run read that as "tracking stopped after the first
195
+ // page view", about a site that tracks every page a visitor actually reads.
196
+ if (urlChanged)
197
+ await nudgeVisitorInteraction(page);
198
+ const after = await readEvidenceState(page);
199
+ // A hard load re-runs the init script, so the id changes and the history
200
+ // log belongs to the new document rather than to this navigation.
201
+ const kind = after.documentId === null || before.documentId === null
202
+ ? null
203
+ : after.documentId === before.documentId
204
+ ? "soft"
205
+ : "hard";
206
+ return {
207
+ candidate,
208
+ startedAt,
209
+ toUrl: urlChanged ? toUrl : null,
210
+ urlChanged,
211
+ dispatched,
212
+ kind,
213
+ historyEvents: kind === "soft" ? await readHistoryEventsSince(page, before.historyCount) : [],
214
+ install: urlChanged ? await readInstall(page, trackingHosts) : null,
215
+ };
216
+ }
217
+ /**
218
+ * Follows one internal link the way a visitor would, and reports whether the
219
+ * site handled it as a document load or as a same-document route change.
220
+ *
221
+ * The click is dispatched on the element rather than driven through the mouse
222
+ * on purpose, twice over: an unanswered consent banner covers the page in the
223
+ * clean phase and would swallow every real click, and a router that listens
224
+ * for the click event reacts to a dispatched one just as it would to a real
225
+ * one. `page.goto` was the other option and is the wrong one — it forces a
226
+ * document load, which is precisely the case a single-page app never
227
+ * exercises, so the SPA failure this probe exists to catch would be hidden by
228
+ * the probe itself.
229
+ *
230
+ * A landing page with no install on it is walked past rather than judged.
231
+ * whereby.com serves /blog/ from a separate Gatsby build carrying no tag
232
+ * manager at all, and "Blog" in the primary nav is exactly what the scoring
233
+ * picks — so the run reported "tracking stopped after the first page view"
234
+ * about a site whose every other page tracks. Each rejection also bars the
235
+ * rest of that first path segment, because a sub-site is a whole section and
236
+ * the next three candidates are usually more of it.
237
+ */
238
+ export async function navigateToSecondPage(page, timeoutMs, options) {
239
+ const fromUrl = page.url();
240
+ const rank = async () => page
241
+ .evaluate(rankInternalLinks, {
242
+ denyPathFragments: SECOND_PAGE_PATH_DENYLIST,
243
+ assetExtensions: SECOND_PAGE_ASSET_EXTENSIONS,
244
+ markerAttribute: SECOND_PAGE_LINK_ATTRIBUTE,
245
+ maxCandidates: SECOND_PAGE_MAX_CANDIDATES,
246
+ })
247
+ .catch((err) => {
248
+ logger.debug("Second-page link search failed", err);
249
+ return [];
250
+ });
251
+ let candidates = await rank();
252
+ if (candidates.length === 0) {
253
+ return NOT_ATTEMPTED("No same-origin link to another page was found, so no second page could be visited.", fromUrl);
254
+ }
255
+ const rejectedCandidates = [];
256
+ const barredSections = options.barredSections;
257
+ const tried = new Set();
258
+ let attempt = null;
259
+ for (let round = 0; round <= SECOND_PAGE_MAX_RETRIES; round += 1) {
260
+ const next = candidates.find((c) => !tried.has(c.href) && !barredSections.has(sectionOf(c.href)));
261
+ if (next === undefined)
262
+ break;
263
+ tried.add(next.href);
264
+ // A section barred by an earlier phase is skipped without being visited,
265
+ // so this costs nothing here — the navigation was paid for once.
266
+ attempt = await attemptCandidate(page, next, fromUrl, timeoutMs, options.trackingHosts);
267
+ // Only an uninstrumented landing page is worth walking past. A click that
268
+ // went nowhere is reported as-is: retrying it would replace a real finding
269
+ // ("this link does not navigate") with a different page's evidence.
270
+ const wrongSubSite = attempt.urlChanged && options.firstPageInstalled && attempt.install?.present === false;
271
+ if (!wrongSubSite)
272
+ break;
273
+ rejectedCandidates.push({
274
+ url: attempt.toUrl ?? next.href,
275
+ reason: "no Dreamdata script, global or tag manager is present, so this page is not part of the instrumented site",
276
+ });
277
+ barredSections.add(sectionOf(next.href));
278
+ attempt = null;
279
+ // Back to the first page, which rebuilds the DOM — so the ranking has to
280
+ // be taken again before anything can be stamped.
281
+ const returned = await page
282
+ .goBack({ waitUntil: "domcontentloaded", timeout: timeoutMs })
283
+ .then(() => page.url() === fromUrl)
284
+ .catch(() => false);
285
+ if (!returned)
286
+ break;
287
+ await page.waitForLoadState("networkidle", { timeout: Math.min(NETWORK_SETTLE_MS, timeoutMs) }).catch(() => { });
288
+ candidates = await rank();
289
+ }
290
+ if (attempt === null) {
291
+ return {
292
+ ...NOT_ATTEMPTED(`Every candidate link led to a page with no Dreamdata install on it (${rejectedCandidates
293
+ .map((c) => c.url)
294
+ .join(", ")}), so tracking across a page change could not be observed on a page this install covers.`, fromUrl),
295
+ attempted: true,
296
+ rejectedCandidates,
297
+ };
298
+ }
299
+ const { candidate, kind, historyEvents, install, startedAt } = attempt;
300
+ const linkText = candidate.text.length > 0 ? candidate.text : null;
301
+ if (!attempt.urlChanged) {
302
+ return {
303
+ attempted: true,
304
+ skippedReason: attempt.dispatched
305
+ ? `The link to ${candidate.href} was clicked but the URL never changed, so no second page was observed.`
306
+ : `The link to ${candidate.href} could not be clicked, so no second page was observed.`,
307
+ fromUrl,
308
+ toUrl: null,
309
+ linkText,
310
+ kind: null,
311
+ urlChanged: false,
312
+ historyEvents,
313
+ install,
314
+ rejectedCandidates,
315
+ startedAt,
316
+ };
317
+ }
318
+ return {
319
+ attempted: true,
320
+ skippedReason: kind === null ? "The navigation happened but could not be classified as a document load or a route change." : null,
321
+ fromUrl,
322
+ toUrl: attempt.toUrl,
323
+ linkText,
324
+ kind,
325
+ urlChanged: true,
326
+ historyEvents,
327
+ install,
328
+ rejectedCandidates,
329
+ startedAt,
330
+ };
331
+ }
332
+ //# sourceMappingURL=second-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"second-page.js","sourceRoot":"","sources":["../../src/evidence/second-page.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /** How long to keep re-checking the whole list for a control that has yet to render. */
2
+ const SELECTOR_POLL_INTERVAL_MS = 250;
3
+ /**
4
+ * Tries each selector in order, but only pays the click-actionability wait
5
+ * for one that actually exists on the page — `locator.count()` does not
6
+ * auto-wait, so a batched check over a whole vendor list costs milliseconds
7
+ * instead of `2000ms × misses` when almost none of them match.
8
+ *
9
+ * The batched check has to be POLLED, not taken once. A plain probe loop got
10
+ * its grace for a late-rendering control for free: the first `locator.click()`
11
+ * auto-waits, so a banner that builds itself a second after the page settles
12
+ * was still there by the time the click gave up. A single `count()` snapshot
13
+ * has no such wait, and dropping it would quietly turn "this CMP renders
14
+ * late" into "this page has no reject button" — a wrong verdict about a
15
+ * customer's install, which is worse than the seconds it saves. So the list is
16
+ * re-checked until `timeoutMs` (capped at 2000ms, the old per-click wait) has
17
+ * passed with nothing found. The saving is intact: that window is now paid
18
+ * once for the whole list instead of once per miss.
19
+ *
20
+ * Otherwise unchanged from a plain probe loop: still first-match-wins, and
21
+ * still falls through to the next existing selector if a click on an earlier
22
+ * one fails (e.g. present in the DOM but covered by another element). A
23
+ * selector whose click failed is not retried on a later pass — the retry is
24
+ * for controls that were not there yet, and re-clicking one that was there
25
+ * would restore the very `2000ms × misses` this exists to avoid.
26
+ */
27
+ export async function clickFirstMatching(page, selectors, timeoutMs) {
28
+ const clickTimeout = Math.min(2000, timeoutMs);
29
+ const deadline = Date.now() + clickTimeout;
30
+ const attempted = new Set();
31
+ for (;;) {
32
+ const counts = await Promise.all(selectors.map((selector) => attempted.has(selector) ? 0 : page.locator(selector).first().count().catch(() => 0)));
33
+ for (let i = 0; i < selectors.length; i++) {
34
+ if (counts[i] === 0)
35
+ continue;
36
+ const selector = selectors[i];
37
+ attempted.add(selector);
38
+ const clicked = await page
39
+ .locator(selector)
40
+ .first()
41
+ .click({ timeout: clickTimeout })
42
+ .then(() => true)
43
+ .catch(() => false);
44
+ if (clicked)
45
+ return selector;
46
+ }
47
+ const remaining = deadline - Date.now();
48
+ if (remaining <= 0)
49
+ return null;
50
+ await page.waitForTimeout(Math.min(SELECTOR_POLL_INTERVAL_MS, remaining));
51
+ }
52
+ }
53
+ //# sourceMappingURL=selector-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector-probe.js","sourceRoot":"","sources":["../../src/evidence/selector-probe.ts"],"names":[],"mappings":"AAEA,wFAAwF;AACxF,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAU,EACV,SAA4B,EAC5B,SAAiB;IAEjB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,SAAS,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CACzB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CACpF,CACF,CAAC;QACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;gBAAE,SAAS;YAC9B,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,IAAI;iBACvB,OAAO,CAAC,QAAQ,CAAC;iBACjB,KAAK,EAAE;iBACP,KAAK,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;iBAChC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;iBAChB,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YACtB,IAAI,OAAO;gBAAE,OAAO,QAAQ,CAAC;QAC/B,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxC,IAAI,SAAS,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Removes the most common automation tells so a page's own bot-detection or
3
+ * CMP logic sees something closer to a real visitor's browser. This is not
4
+ * a full stealth suite — just the handful of checks that actually change
5
+ * consent/tracking behavior in practice (navigator.webdriver, a missing
6
+ * window.chrome, an empty plugins list).
7
+ */
8
+ export function installStealthPatches() {
9
+ Object.defineProperty(navigator, "webdriver", { get: () => undefined });
10
+ const w = window;
11
+ if (!w.chrome) {
12
+ w.chrome = { runtime: {} };
13
+ }
14
+ Object.defineProperty(navigator, "plugins", {
15
+ get: () => [1, 2, 3, 4, 5].map(() => ({ name: "Chrome PDF Plugin" })),
16
+ });
17
+ Object.defineProperty(navigator, "languages", {
18
+ get: () => ["en-US", "en"],
19
+ });
20
+ }
21
+ //# sourceMappingURL=stealth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stealth.js","sourceRoot":"","sources":["../../src/evidence/stealth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;IAExE,MAAM,CAAC,GAAG,MAAyC,CAAC;IACpD,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACd,CAAC,CAAC,MAAM,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE;QAC1C,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;KACtE,CAAC,CAAC;IAEH,MAAM,CAAC,cAAc,CAAC,SAAS,EAAE,WAAW,EAAE;QAC5C,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC;KAC3B,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,14 @@
1
+ export const CONSENT_SIGNAL_NAMES = [
2
+ "ad_storage",
3
+ "ad_user_data",
4
+ "ad_personalization",
5
+ "analytics_storage",
6
+ "functionality_storage",
7
+ "personalization_storage",
8
+ "security_storage",
9
+ ];
10
+ /** The two signals v2 adds. Both must be present for a page to be running v2. */
11
+ export const CONSENT_V2_ONLY_SIGNALS = ["ad_user_data", "ad_personalization"];
12
+ /** Canonical order: what a visitor meets first, then each explicit answer. */
13
+ export const ALL_PHASES = ["clean", "grant", "reject"];
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/evidence/types.ts"],"names":[],"mappings":"AA0CA,MAAM,CAAC,MAAM,oBAAoB,GAAiC;IAChE,YAAY;IACZ,cAAc;IACd,oBAAoB;IACpB,mBAAmB;IACnB,uBAAuB;IACvB,yBAAyB;IACzB,kBAAkB;CACnB,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,uBAAuB,GAAiC,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;AAsG5G,8EAA8E;AAC9E,MAAM,CAAC,MAAM,UAAU,GAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC"}
@@ -0,0 +1,31 @@
1
+ import * as clack from "@clack/prompts";
2
+ /** The only URL shape the harness drives: anything else is a typo, not a site. */
3
+ export function isValidHttpUrl(value) {
4
+ try {
5
+ const parsed = new URL(value);
6
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export async function promptForUrl() {
13
+ clack.intro("Dreamdata tracking verification");
14
+ const url = await clack.text({
15
+ message: "Which URL should be checked?",
16
+ placeholder: "https://example.com",
17
+ validate: (value) => {
18
+ if (!value)
19
+ return "A URL is required.";
20
+ if (!isValidHttpUrl(value))
21
+ return "Enter a valid http(s) URL.";
22
+ return undefined;
23
+ },
24
+ });
25
+ if (clack.isCancel(url)) {
26
+ clack.cancel("Cancelled.");
27
+ process.exit(1);
28
+ }
29
+ return url;
30
+ }
31
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/interactive/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAExC,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,OAAO,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,KAAK,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAE/C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC;QAC3B,OAAO,EAAE,8BAA8B;QACvC,WAAW,EAAE,qBAAqB;QAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;YAClB,IAAI,CAAC,KAAK;gBAAE,OAAO,oBAAoB,CAAC;YACxC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC;gBAAE,OAAO,4BAA4B,CAAC;YAChE,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;IAEH,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}