@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,107 @@
|
|
|
1
|
+
export function runHeuristicBanner(args) {
|
|
2
|
+
const { mode, direction, acceptPhrases, rejectPhrases, preferenceCounterpartPhrases, contextKeywords } = args;
|
|
3
|
+
const NONE = {
|
|
4
|
+
detected: false,
|
|
5
|
+
acceptLabel: null,
|
|
6
|
+
rejectLabel: null,
|
|
7
|
+
counterpart: null,
|
|
8
|
+
preferenceLabel: null,
|
|
9
|
+
clicked: null,
|
|
10
|
+
};
|
|
11
|
+
const normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
12
|
+
const isVisible = (el) => {
|
|
13
|
+
const style = window.getComputedStyle(el);
|
|
14
|
+
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity) === 0)
|
|
15
|
+
return false;
|
|
16
|
+
const rect = el.getBoundingClientRect();
|
|
17
|
+
return rect.width > 0 && rect.height > 0;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Every clickable control on the page, INCLUDING the ones inside open
|
|
21
|
+
* shadow roots.
|
|
22
|
+
*
|
|
23
|
+
* egnyte.com runs TrustArc, which renders its whole banner — "Accept All",
|
|
24
|
+
* "Decline All" and all — inside a shadow root on a
|
|
25
|
+
* `.trustarc_newcm_container` host. `document.querySelectorAll` does not
|
|
26
|
+
* cross that boundary, so the run reported "no consent platform was
|
|
27
|
+
* detected", never answered the banner, and concluded the site tracks
|
|
28
|
+
* nothing in any phase. A click dispatched on an element inside an open
|
|
29
|
+
* shadow root works exactly as it does in the light DOM; the only thing
|
|
30
|
+
* missing was finding it.
|
|
31
|
+
*/
|
|
32
|
+
const collectControls = () => {
|
|
33
|
+
const found = [];
|
|
34
|
+
const walk = (root, depth) => {
|
|
35
|
+
for (const el of Array.from(root.querySelectorAll("button, a, [role='button']")))
|
|
36
|
+
found.push(el);
|
|
37
|
+
if (depth >= 5)
|
|
38
|
+
return;
|
|
39
|
+
for (const el of Array.from(root.querySelectorAll("*"))) {
|
|
40
|
+
const shadow = el.shadowRoot;
|
|
41
|
+
if (shadow)
|
|
42
|
+
walk(shadow, depth + 1);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
walk(document, 0);
|
|
46
|
+
return found;
|
|
47
|
+
};
|
|
48
|
+
/** parentElement, but stepping out of a shadow root onto its host. */
|
|
49
|
+
const climb = (node) => node.parentElement ?? (node.getRootNode().host ?? null);
|
|
50
|
+
const candidates = collectControls().filter(isVisible);
|
|
51
|
+
const findMatch = (phrases) => candidates.find((el) => phrases.includes(normalize(el.textContent))) ?? null;
|
|
52
|
+
const acceptEl = findMatch(acceptPhrases);
|
|
53
|
+
const rejectEl = findMatch(rejectPhrases);
|
|
54
|
+
if (!acceptEl && !rejectEl)
|
|
55
|
+
return NONE;
|
|
56
|
+
// Climb from whichever control matched, looking for an ancestor that both
|
|
57
|
+
// reads like a cookie notice and contains a second consent control. Both
|
|
58
|
+
// conditions must be satisfied by the SAME ancestor level's subtree.
|
|
59
|
+
const primary = acceptEl ?? rejectEl;
|
|
60
|
+
const opposite = acceptEl === primary ? rejectEl : acceptEl;
|
|
61
|
+
const preferenceEls = candidates.filter((el) => el !== primary && preferenceCounterpartPhrases.includes(normalize(el.textContent)));
|
|
62
|
+
let container = primary;
|
|
63
|
+
let contextOk = false;
|
|
64
|
+
let counterpart = null;
|
|
65
|
+
let preferenceEl = null;
|
|
66
|
+
for (let depth = 0; depth < 8 && container && !(contextOk && counterpart !== null); depth++) {
|
|
67
|
+
const text = normalize(container.textContent);
|
|
68
|
+
if (!contextOk && contextKeywords.some((k) => text.includes(k)))
|
|
69
|
+
contextOk = true;
|
|
70
|
+
// The opposite control is the stronger proof, so it wins when the same
|
|
71
|
+
// container holds both.
|
|
72
|
+
if (counterpart === null && opposite && container.contains(opposite))
|
|
73
|
+
counterpart = "opposite-control";
|
|
74
|
+
if (counterpart === null) {
|
|
75
|
+
const found = preferenceEls.find((el) => container.contains(el));
|
|
76
|
+
if (found) {
|
|
77
|
+
counterpart = "preference-center";
|
|
78
|
+
preferenceEl = found;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
container = climb(container);
|
|
82
|
+
}
|
|
83
|
+
if (!contextOk || counterpart === null)
|
|
84
|
+
return NONE;
|
|
85
|
+
const acceptLabel = acceptEl ? normalize(acceptEl.textContent) : null;
|
|
86
|
+
const rejectLabel = rejectEl ? normalize(rejectEl.textContent) : null;
|
|
87
|
+
const preferenceLabel = preferenceEl ? normalize(preferenceEl.textContent) : null;
|
|
88
|
+
const found = {
|
|
89
|
+
detected: true,
|
|
90
|
+
acceptLabel,
|
|
91
|
+
rejectLabel,
|
|
92
|
+
counterpart,
|
|
93
|
+
preferenceLabel,
|
|
94
|
+
clicked: null,
|
|
95
|
+
};
|
|
96
|
+
if (mode === "detect")
|
|
97
|
+
return found;
|
|
98
|
+
// Nothing to click in this direction — the banner is real, it just has no
|
|
99
|
+
// one-click answer here. The caller's next fallback is the preference
|
|
100
|
+
// centre, which is the only way such a banner can be answered at all.
|
|
101
|
+
const target = direction === "accept" ? acceptEl : rejectEl;
|
|
102
|
+
if (!target)
|
|
103
|
+
return found;
|
|
104
|
+
target.click();
|
|
105
|
+
return { ...found, clicked: direction === "accept" ? acceptLabel : rejectLabel };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=heuristic-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristic-banner.js","sourceRoot":"","sources":["../../src/evidence/heuristic-banner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the site serves to something that is not a browser.
|
|
3
|
+
*
|
|
4
|
+
* A bot wall does not refuse the request — it answers it with a different
|
|
5
|
+
* document. The browser gets a challenge interstitial (a press-and-hold
|
|
6
|
+
* control, a spinner, a "verify you are human" page), and every field a phase
|
|
7
|
+
* collects from that page is empty: no script tags, no GTM container id, no
|
|
8
|
+
* consent banner, no requests, not even a same-origin link to walk to. That
|
|
9
|
+
* emptiness is indistinguishable from a site with nothing installed, and it
|
|
10
|
+
* is the shape a report is most dangerous in — zoominfo.com was reported as
|
|
11
|
+
* "nothing-installed" with "install the script" as the advice, while its GTM
|
|
12
|
+
* container shipped both bundles all along.
|
|
13
|
+
*
|
|
14
|
+
* A plain HTTP GET carries none of the fingerprints those walls key on, so it
|
|
15
|
+
* is usually served the real markup. It cannot answer a single behavioural
|
|
16
|
+
* question — nothing executes, no request is sent, no consent is answered — but
|
|
17
|
+
* it answers two the run otherwise loses entirely: what is installed, and
|
|
18
|
+
* whether the browser was shown the same page at all.
|
|
19
|
+
*
|
|
20
|
+
* Never fails the run. Every failure mode is recorded and handed on as
|
|
21
|
+
* "we could not corroborate", which is what it is.
|
|
22
|
+
*/
|
|
23
|
+
import { logger } from "../utils/logger.js";
|
|
24
|
+
import { CMP_MARKUP_MARKERS, CSR_FRAMEWORK_MARKERS } from "./constants.js";
|
|
25
|
+
import { gtmContainerIdsFromText, readScriptPresenceFromHtml } from "./script.js";
|
|
26
|
+
/**
|
|
27
|
+
* An honest client identity, deliberately not a browser's.
|
|
28
|
+
*
|
|
29
|
+
* Claiming to be Chrome was tried and is worse on both counts. It is a
|
|
30
|
+
* disguise, which is the line this tool does not cross — and on zoominfo.com
|
|
31
|
+
* it is also what gets the request refused: a Chrome UA is answered HTTP 403
|
|
32
|
+
* with a 5 KB challenge body, while this one is served the full 428 KB page,
|
|
33
|
+
* because what the wall is defending against is browsers it cannot fingerprint.
|
|
34
|
+
* Chasing the header combination that slips past a given WAF is an arms race
|
|
35
|
+
* with no end and a silent failure mode, so the probe does not play it: it says
|
|
36
|
+
* what it is, and a refusal is reported as a refusal (see
|
|
37
|
+
* assessPageReadability, which treats a non-2xx here as a finding in its own
|
|
38
|
+
* right rather than as an absence of evidence).
|
|
39
|
+
*/
|
|
40
|
+
const PROBE_USER_AGENT = "dreamdata-tracking-verification/0.1 (+https://dreamdata.io)";
|
|
41
|
+
const PROBE_ACCEPT = "text/html,*/*;q=0.8";
|
|
42
|
+
/** Enough markup to read the head and the nav; whole pages can be megabytes. */
|
|
43
|
+
const MAX_BYTES = 3_000_000;
|
|
44
|
+
/**
|
|
45
|
+
* Same-origin links, counted the way the second-page probe looks for one.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately crude — this is a corroboration signal, not a navigation
|
|
48
|
+
* target. What matters is the difference between "the document has a site's
|
|
49
|
+
* worth of links in it" and "the document has none", because the second is
|
|
50
|
+
* what an interstitial looks like and the first is what the same URL serves
|
|
51
|
+
* over plain HTTP when a wall turned the browser away.
|
|
52
|
+
*/
|
|
53
|
+
function countSameOriginLinks(html, baseUrl) {
|
|
54
|
+
let origin;
|
|
55
|
+
try {
|
|
56
|
+
origin = new URL(baseUrl).origin;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
const hrefs = [...html.matchAll(/<a[^>]*\shref=["']([^"']+)["']/gi)].map((m) => m[1]);
|
|
62
|
+
const sameOrigin = new Set();
|
|
63
|
+
for (const href of hrefs) {
|
|
64
|
+
if (href.startsWith("#") || /^(?:mailto|tel|javascript):/i.test(href))
|
|
65
|
+
continue;
|
|
66
|
+
try {
|
|
67
|
+
const resolved = new URL(href, baseUrl);
|
|
68
|
+
if (resolved.origin === origin)
|
|
69
|
+
sameOrigin.add(resolved.href.replace(/#.*$/, ""));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A malformed href is not a link.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return sameOrigin.size;
|
|
76
|
+
}
|
|
77
|
+
function cmpMarkersIn(html) {
|
|
78
|
+
return [...new Set(CMP_MARKUP_MARKERS.filter((m) => m.pattern.test(html)).map((m) => m.vendor))];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Corroboration only, and the weakest kind. A framework name cannot say how
|
|
82
|
+
* the site navigates — see CSR_FRAMEWORK_MARKERS — but it is the only routing
|
|
83
|
+
* hint a run has left when a wall turned the browser away.
|
|
84
|
+
*/
|
|
85
|
+
function frameworkMarkersIn(html) {
|
|
86
|
+
return [...new Set(CSR_FRAMEWORK_MARKERS.filter((m) => m.pattern.test(html)).map((m) => m.framework))];
|
|
87
|
+
}
|
|
88
|
+
export async function probeServedMarkup(url, timeoutMs, extraHosts = []) {
|
|
89
|
+
const controller = new AbortController();
|
|
90
|
+
const deadline = setTimeout(() => controller.abort(), Math.min(20_000, Math.max(5_000, timeoutMs)));
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
redirect: "follow",
|
|
94
|
+
signal: controller.signal,
|
|
95
|
+
headers: {
|
|
96
|
+
"user-agent": PROBE_USER_AGENT,
|
|
97
|
+
accept: PROBE_ACCEPT,
|
|
98
|
+
"accept-language": "en-US,en;q=0.9",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const body = await response.text();
|
|
102
|
+
const html = body.length > MAX_BYTES ? body.slice(0, MAX_BYTES) : body;
|
|
103
|
+
const finalUrl = response.url || url;
|
|
104
|
+
return {
|
|
105
|
+
attempted: true,
|
|
106
|
+
status: response.status,
|
|
107
|
+
finalUrl,
|
|
108
|
+
bytes: body.length,
|
|
109
|
+
truncated: body.length > MAX_BYTES,
|
|
110
|
+
failedReason: null,
|
|
111
|
+
sameOriginLinkCount: countSameOriginLinks(html, finalUrl),
|
|
112
|
+
tagManagerContainerIds: gtmContainerIdsFromText(html),
|
|
113
|
+
cmpMarkers: cmpMarkersIn(html),
|
|
114
|
+
frameworkMarkers: frameworkMarkersIn(html),
|
|
115
|
+
script: readScriptPresenceFromHtml(html, finalUrl, extraHosts),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const reason = err.name === "AbortError" ? `timed out after ${timeoutMs}ms` : err.message;
|
|
120
|
+
logger.debug(`Plain-HTTP probe of ${url} failed`, err);
|
|
121
|
+
return {
|
|
122
|
+
attempted: true,
|
|
123
|
+
status: null,
|
|
124
|
+
finalUrl: null,
|
|
125
|
+
bytes: null,
|
|
126
|
+
truncated: false,
|
|
127
|
+
failedReason: reason,
|
|
128
|
+
sameOriginLinkCount: 0,
|
|
129
|
+
tagManagerContainerIds: [],
|
|
130
|
+
cmpMarkers: [],
|
|
131
|
+
frameworkMarkers: [],
|
|
132
|
+
script: null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
clearTimeout(deadline);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=http-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-probe.js","sourceRoot":"","sources":["../../src/evidence/http-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAGlF;;;;;;;;;;;;;GAaG;AACH,MAAM,gBAAgB,GAAG,6DAA6D,CAAC;AAEvF,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAE3C,gFAAgF;AAChF,MAAM,SAAS,GAAG,SAAS,CAAC;AAE5B;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,IAAY,EAAE,OAAe;IACzD,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,kCAAkC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAChF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM;gBAAE,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC;AACzB,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACzG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAW,EACX,SAAiB,EACjB,aAAgC,EAAE;IAElC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAEpG,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,YAAY,EAAE,gBAAgB;gBAC9B,MAAM,EAAE,YAAY;gBACpB,iBAAiB,EAAE,gBAAgB;aACpC;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACvE,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAC;QAErC,OAAO;YACL,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,QAAQ;YACR,KAAK,EAAE,IAAI,CAAC,MAAM;YAClB,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,SAAS;YAClC,YAAY,EAAE,IAAI;YAClB,mBAAmB,EAAE,oBAAoB,CAAC,IAAI,EAAE,QAAQ,CAAC;YACzD,sBAAsB,EAAE,uBAAuB,CAAC,IAAI,CAAC;YACrD,UAAU,EAAE,YAAY,CAAC,IAAI,CAAC;YAC9B,gBAAgB,EAAE,kBAAkB,CAAC,IAAI,CAAC;YAC1C,MAAM,EAAE,0BAA0B,CAAC,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC;SAC/D,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAI,GAAa,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,mBAAmB,SAAS,IAAI,CAAC,CAAC,CAAE,GAAa,CAAC,OAAO,CAAC;QAChH,MAAM,CAAC,KAAK,CAAC,uBAAuB,GAAG,SAAS,EAAE,GAAG,CAAC,CAAC;QACvD,OAAO;YACL,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,IAAI;YACX,SAAS,EAAE,KAAK;YAChB,YAAY,EAAE,MAAM;YACpB,mBAAmB,EAAE,CAAC;YACtB,sBAAsB,EAAE,EAAE;YAC1B,UAAU,EAAE,EAAE;YACd,gBAAgB,EAAE,EAAE;YACpB,MAAM,EAAE,IAAI;SACb,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injected via page.addInitScript before any page script runs, so it can
|
|
3
|
+
* observe dataLayer.push(['consent', ...]) calls and window.gtag(...) calls
|
|
4
|
+
* even if the site never leaves those values inspectable afterwards.
|
|
5
|
+
*
|
|
6
|
+
* A site that reassigns window.dataLayer to a brand new array (some CMP
|
|
7
|
+
* snippets do) is covered: the accessor below re-wraps whatever is assigned.
|
|
8
|
+
* Still best-effort rather than a hard guarantee — a snippet that captures a
|
|
9
|
+
* direct reference to the array before this script runs can push through it
|
|
10
|
+
* without going past the wrapper.
|
|
11
|
+
*/
|
|
12
|
+
export function installEvidenceHooks() {
|
|
13
|
+
const w = window;
|
|
14
|
+
w.__ddEvidence = {
|
|
15
|
+
consentPushes: [],
|
|
16
|
+
gtagCalls: [],
|
|
17
|
+
historyEvents: [],
|
|
18
|
+
// This script re-runs on every document, so a changed id after following
|
|
19
|
+
// a link means a new document was loaded and an unchanged one means the
|
|
20
|
+
// site's own router handled it without one — the difference between "the
|
|
21
|
+
// whole install re-ran" and "nothing re-ran", which is invisible from the
|
|
22
|
+
// URL alone.
|
|
23
|
+
documentId: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
24
|
+
};
|
|
25
|
+
// A soft navigation leaves no trace in the network log or the DOM; the
|
|
26
|
+
// History API call is the only evidence that the site navigated at all.
|
|
27
|
+
const recordHistory = (kind, url) => {
|
|
28
|
+
w.__ddEvidence.historyEvents.push({
|
|
29
|
+
kind,
|
|
30
|
+
url: typeof url === "string" ? url : null,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
for (const method of ["pushState", "replaceState"]) {
|
|
35
|
+
const original = history[method].bind(history);
|
|
36
|
+
history[method] = function patched(...args) {
|
|
37
|
+
recordHistory(method, args[2]);
|
|
38
|
+
return original(...args);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
window.addEventListener("popstate", () => recordHistory("popstate", location.href));
|
|
42
|
+
/**
|
|
43
|
+
* Marks an array whose push is already wrapped. It has to live on the array
|
|
44
|
+
* itself, not in a flag in this closure: the accessor below re-wraps on
|
|
45
|
+
* every assignment to window.dataLayer, and a site that genuinely swaps in a
|
|
46
|
+
* new array still needs that one wrapped.
|
|
47
|
+
*/
|
|
48
|
+
const WRAPPED_MARKER = "__ddPushWrapped";
|
|
49
|
+
const wrapDataLayer = (arr) => {
|
|
50
|
+
// The near-universal `window.dataLayer = window.dataLayer || []` snippet
|
|
51
|
+
// assigns the SAME array back, so without this guard every such
|
|
52
|
+
// assignment stacked another wrapper on it and one real push was recorded
|
|
53
|
+
// once per layer — 13 identical entries, same millisecond, for a single
|
|
54
|
+
// gtag('consent', ...) call.
|
|
55
|
+
if (arr[WRAPPED_MARKER])
|
|
56
|
+
return;
|
|
57
|
+
Object.defineProperty(arr, WRAPPED_MARKER, { value: true, enumerable: false, configurable: true });
|
|
58
|
+
const originalPush = arr.push.bind(arr);
|
|
59
|
+
arr.push = ((...items) => {
|
|
60
|
+
for (const item of items) {
|
|
61
|
+
// gtag.js's own `gtag(){dataLayer.push(arguments)}` pushes an
|
|
62
|
+
// arguments object, not a real Array — Array.isArray on it is
|
|
63
|
+
// always false, so array-likes must be normalized before the
|
|
64
|
+
// consent-tuple check or every gtag('consent', ...) call is missed.
|
|
65
|
+
const normalized = item !== null &&
|
|
66
|
+
typeof item === "object" &&
|
|
67
|
+
!Array.isArray(item) &&
|
|
68
|
+
typeof item.length === "number"
|
|
69
|
+
? Array.from(item)
|
|
70
|
+
: item;
|
|
71
|
+
const isConsentTuple = Array.isArray(normalized) && normalized[0] === "consent";
|
|
72
|
+
const isConsentEvent = normalized !== null &&
|
|
73
|
+
typeof normalized === "object" &&
|
|
74
|
+
!Array.isArray(normalized) &&
|
|
75
|
+
normalized.event === "consent";
|
|
76
|
+
if (isConsentTuple || isConsentEvent) {
|
|
77
|
+
w.__ddEvidence.consentPushes.push({ raw: normalized, timestamp: Date.now() });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return originalPush(...items);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
if (Array.isArray(w.dataLayer)) {
|
|
84
|
+
wrapDataLayer(w.dataLayer);
|
|
85
|
+
}
|
|
86
|
+
let currentDataLayer = w.dataLayer;
|
|
87
|
+
Object.defineProperty(w, "dataLayer", {
|
|
88
|
+
configurable: true,
|
|
89
|
+
get() {
|
|
90
|
+
return currentDataLayer;
|
|
91
|
+
},
|
|
92
|
+
set(value) {
|
|
93
|
+
currentDataLayer = value;
|
|
94
|
+
if (Array.isArray(value))
|
|
95
|
+
wrapDataLayer(value);
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
let currentGtag = w.gtag;
|
|
99
|
+
Object.defineProperty(w, "gtag", {
|
|
100
|
+
configurable: true,
|
|
101
|
+
get() {
|
|
102
|
+
return currentGtag;
|
|
103
|
+
},
|
|
104
|
+
set(fn) {
|
|
105
|
+
currentGtag = (...args) => {
|
|
106
|
+
w.__ddEvidence.gtagCalls.push({ args, timestamp: Date.now() });
|
|
107
|
+
return fn(...args);
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=init-script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-script.js","sourceRoot":"","sources":["../../src/evidence/init-script.ts"],"names":[],"mappings":"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"}
|