@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,365 @@
|
|
|
1
|
+
import { COOKIELESS_LIBRARY_NAME_MARKER, DEFAULT_TRACKING_HOSTS, ENDPOINT_NAMES, ENDPOINT_REGEX, isDreamdataUrl, THIRD_PARTY_SEGMENT_TRACKING_HOST, } from "./constants.js";
|
|
2
|
+
function classifyEndpoint(url) {
|
|
3
|
+
const match = ENDPOINT_REGEX.exec(url);
|
|
4
|
+
if (!match)
|
|
5
|
+
return {};
|
|
6
|
+
const endpoint = match[1];
|
|
7
|
+
return { endpoint, endpointName: ENDPOINT_NAMES[endpoint] };
|
|
8
|
+
}
|
|
9
|
+
function safePostDataJSON(request) {
|
|
10
|
+
try {
|
|
11
|
+
return request.postDataJSON();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function asBag(value) {
|
|
18
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A `/api/v1/b` batch wraps N events under `batch`. Flatten so identity and
|
|
22
|
+
* library checks look at the events themselves, not just the envelope.
|
|
23
|
+
*/
|
|
24
|
+
function eventsOf(payload) {
|
|
25
|
+
const root = asBag(payload);
|
|
26
|
+
if (!root)
|
|
27
|
+
return [];
|
|
28
|
+
const batch = Array.isArray(root.batch) ? root.batch : [];
|
|
29
|
+
const nested = batch.map(asBag).filter((e) => e !== undefined);
|
|
30
|
+
return [root, ...nested];
|
|
31
|
+
}
|
|
32
|
+
function libraryNameOf(event) {
|
|
33
|
+
const name = asBag(asBag(event.context)?.library)?.name;
|
|
34
|
+
return typeof name === "string" ? name : undefined;
|
|
35
|
+
}
|
|
36
|
+
export function classifyMode(payload) {
|
|
37
|
+
const names = eventsOf(payload).map(libraryNameOf).filter((n) => n !== undefined);
|
|
38
|
+
if (names.length === 0)
|
|
39
|
+
return "unknown";
|
|
40
|
+
return names.some((n) => n.toLowerCase().includes(COOKIELESS_LIBRARY_NAME_MARKER)) ? "cookieless" : "normal";
|
|
41
|
+
}
|
|
42
|
+
function isPresent(value) {
|
|
43
|
+
if (value === undefined || value === null)
|
|
44
|
+
return false;
|
|
45
|
+
if (typeof value === "string")
|
|
46
|
+
return value.length > 0;
|
|
47
|
+
if (typeof value === "object")
|
|
48
|
+
return Object.keys(value).length > 0;
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Deterministic presence check for the identity fields the checklist asks
|
|
53
|
+
* about. Cookieless events legitimately carry no anonymousId, so recording
|
|
54
|
+
* this per-request lets the judge apply the right rule per mode instead of
|
|
55
|
+
* inferring absence from a raw payload dump.
|
|
56
|
+
*/
|
|
57
|
+
export function extractWriteKeys(payload) {
|
|
58
|
+
const keys = eventsOf(payload)
|
|
59
|
+
.map((event) => event.writeKey)
|
|
60
|
+
.filter((key) => typeof key === "string" && key.length > 0);
|
|
61
|
+
return [...new Set(keys)];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The event's own id, as the envelope carries it. Two requests sharing one means
|
|
65
|
+
* the SAME event was delivered twice, which no single healthy instance does —
|
|
66
|
+
* the cheapest sign of a second bundle replaying a buffer the first already
|
|
67
|
+
* sent. A batch envelope has no id of its own, hence the null.
|
|
68
|
+
*/
|
|
69
|
+
export function extractMessageId(payload) {
|
|
70
|
+
const id = asBag(payload)?.messageId;
|
|
71
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
72
|
+
}
|
|
73
|
+
export function extractIdentity(payload) {
|
|
74
|
+
const events = eventsOf(payload);
|
|
75
|
+
const anyOf = (read) => events.some((event) => isPresent(read(event)));
|
|
76
|
+
return {
|
|
77
|
+
hasWriteKey: anyOf((e) => e.writeKey),
|
|
78
|
+
hasAnonymousId: anyOf((e) => e.anonymousId ?? asBag(e.context)?.anonymousId),
|
|
79
|
+
hasUserId: anyOf((e) => e.userId ?? asBag(e.context)?.userId),
|
|
80
|
+
hasUserData: anyOf((e) => e.userData ?? asBag(e.context)?.userData ?? asBag(e.context)?.user_data),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Attaches a passive request listener (no interception) for event requests.
|
|
85
|
+
*
|
|
86
|
+
* Dreamdata-owned hosts are recognised automatically, so a first-party install
|
|
87
|
+
* on api.s.dreamdata.io needs no configuration. `trackingHosts` extends that
|
|
88
|
+
* for fully custom CNAMEs, which cannot be inferred; the watched set is
|
|
89
|
+
* recorded in the report because anything outside it is invisible to the run.
|
|
90
|
+
*/
|
|
91
|
+
export function attachNetworkCollector(page, trackingHosts = DEFAULT_TRACKING_HOSTS) {
|
|
92
|
+
const requests = [];
|
|
93
|
+
let unattributedRequests = [];
|
|
94
|
+
let installWriteKey = null;
|
|
95
|
+
const isTracked = (url) => isDreamdataUrl(url, trackingHosts);
|
|
96
|
+
const isOurs = (request) => installWriteKey !== null && extractWriteKeys(request.payload).includes(installWriteKey);
|
|
97
|
+
page.on("request", (request) => {
|
|
98
|
+
const url = request.url();
|
|
99
|
+
const { endpoint, endpointName } = classifyEndpoint(url);
|
|
100
|
+
// Dreamdata's hosts also serve the bundles themselves and helper scripts.
|
|
101
|
+
// Only event requests belong in the request log; a script download counted
|
|
102
|
+
// as an event would read as tracking that never happened.
|
|
103
|
+
if (endpoint === undefined)
|
|
104
|
+
return;
|
|
105
|
+
const payload = request.method() === "POST" ? safePostDataJSON(request) : undefined;
|
|
106
|
+
const watched = isTracked(url);
|
|
107
|
+
if (!watched && !isRequestCandidate(url, payload))
|
|
108
|
+
return;
|
|
109
|
+
const entry = {
|
|
110
|
+
url,
|
|
111
|
+
method: request.method(),
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
endpoint,
|
|
114
|
+
endpointName,
|
|
115
|
+
payload,
|
|
116
|
+
mode: classifyMode(payload),
|
|
117
|
+
identity: extractIdentity(payload),
|
|
118
|
+
};
|
|
119
|
+
if (watched || isOurs(entry))
|
|
120
|
+
requests.push(entry);
|
|
121
|
+
else
|
|
122
|
+
unattributedRequests.push(entry);
|
|
123
|
+
});
|
|
124
|
+
page.on("requestfinished", async (request) => {
|
|
125
|
+
const url = request.url();
|
|
126
|
+
const response = await request.response().catch(() => null);
|
|
127
|
+
if (!response)
|
|
128
|
+
return;
|
|
129
|
+
// Looked up after the await, because the request may have been promoted
|
|
130
|
+
// out of one log and into the other while the response was in flight.
|
|
131
|
+
const match = requests.find((r) => r.url === url && r.status === undefined) ??
|
|
132
|
+
unattributedRequests.find((r) => r.url === url && r.status === undefined);
|
|
133
|
+
if (match)
|
|
134
|
+
match.status = response.status();
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
requests,
|
|
138
|
+
get unattributedRequests() {
|
|
139
|
+
return unattributedRequests;
|
|
140
|
+
},
|
|
141
|
+
attributeTo: (writeKey) => {
|
|
142
|
+
installWriteKey = writeKey;
|
|
143
|
+
if (writeKey === null)
|
|
144
|
+
return;
|
|
145
|
+
const promoted = unattributedRequests.filter(isOurs);
|
|
146
|
+
if (promoted.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
unattributedRequests = unattributedRequests.filter((b) => !isOurs(b));
|
|
149
|
+
requests.push(...promoted);
|
|
150
|
+
// Order is evidence here: determineCookielessSeenFirst reads which mode
|
|
151
|
+
// fired first, and the second-page probe slices this log by position.
|
|
152
|
+
requests.sort((a, b) => a.timestamp - b.timestamp);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A request on a host this run has no reason to watch, that still looks like
|
|
158
|
+
* one of ours.
|
|
159
|
+
*
|
|
160
|
+
* respond.io delivers its events to dd.respond.io — a first-party CNAME onto
|
|
161
|
+
* Dreamdata — and the run reported "no request reached Dreamdata in any phase"
|
|
162
|
+
* about a site whose every page view was being delivered and accepted. The
|
|
163
|
+
* host cannot be inferred, and requiring --tracking-host means the default
|
|
164
|
+
* answer for a custom-domain install is a confident false negative.
|
|
165
|
+
*
|
|
166
|
+
* This is deliberately only a CANDIDATE. The shape it matches is Segment's
|
|
167
|
+
* analytics.js envelope, which is what Dreamdata's Segment-compatible bundle
|
|
168
|
+
* sends AND what a self-hosted Segment proxy on events.customer.com sends.
|
|
169
|
+
* Nothing here decides the request is Dreamdata's; the writeKey does that,
|
|
170
|
+
* against the key the installed bundle carries (see attributeTo).
|
|
171
|
+
* api.segment.io is excluded outright — real Segment already has its own
|
|
172
|
+
* evidence path (thirdPartySegmentSignals) and must not be reachable
|
|
173
|
+
* through this one.
|
|
174
|
+
*/
|
|
175
|
+
function isRequestCandidate(url, payload) {
|
|
176
|
+
if (extractWriteKeys(payload).length === 0)
|
|
177
|
+
return false;
|
|
178
|
+
try {
|
|
179
|
+
return new URL(url).hostname.toLowerCase() !== THIRD_PARTY_SEGMENT_TRACKING_HOST;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* A passive counter for requests this phase sent to Segment's own collection
|
|
187
|
+
* endpoint (api.segment.io) — the client-side half of a real Segment install.
|
|
188
|
+
*
|
|
189
|
+
* Deliberately not folded into `attachNetworkCollector`: that function parses
|
|
190
|
+
* every Dreamdata request's payload for endpoint/mode/identity, none of which
|
|
191
|
+
* applies here. All this needs to answer is "did the site talk to Segment at
|
|
192
|
+
* all", so it stays a bare counter rather than a second copy of the request
|
|
193
|
+
* log.
|
|
194
|
+
*/
|
|
195
|
+
export function attachThirdPartySegmentCollector(page) {
|
|
196
|
+
let count = 0;
|
|
197
|
+
page.on("request", (request) => {
|
|
198
|
+
try {
|
|
199
|
+
if (new URL(request.url()).hostname.toLowerCase() === THIRD_PARTY_SEGMENT_TRACKING_HOST)
|
|
200
|
+
count += 1;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Not a parseable URL, so not a match either.
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
return { count: () => count };
|
|
207
|
+
}
|
|
208
|
+
export function summarizeDreamdataRequestCounts(requests) {
|
|
209
|
+
const counts = {};
|
|
210
|
+
for (const req of requests) {
|
|
211
|
+
const key = req.endpointName ?? "unknown";
|
|
212
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
213
|
+
}
|
|
214
|
+
return counts;
|
|
215
|
+
}
|
|
216
|
+
export function summarizeModeCounts(requests) {
|
|
217
|
+
return {
|
|
218
|
+
cookielessRequestCount: requests.filter((r) => r.mode === "cookieless").length,
|
|
219
|
+
normalRequestCount: requests.filter((r) => r.mode === "normal").length,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** Every distinct writeKey actually seen on the wire, in first-seen order. */
|
|
223
|
+
export function summarizeObservedWriteKeys(requests) {
|
|
224
|
+
return [...new Set(requests.flatMap((r) => extractWriteKeys(r.payload)))];
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* The analytics builds an event names in its own `context.library`.
|
|
228
|
+
*
|
|
229
|
+
* One running copy of the script stamps one build string on every event it
|
|
230
|
+
* sends, however many that is and whichever page view they belong to. So a
|
|
231
|
+
* second string is a second copy running — the one duplicate-install signal
|
|
232
|
+
* that no timing artefact can imitate, because two loads of one page, a
|
|
233
|
+
* queued request delivered late and a retried delivery all still come from
|
|
234
|
+
* the same build.
|
|
235
|
+
*/
|
|
236
|
+
export function extractLibraries(payload) {
|
|
237
|
+
const builds = eventsOf(payload)
|
|
238
|
+
.map((event) => asBag(asBag(event.context)?.library))
|
|
239
|
+
.filter((library) => library !== undefined)
|
|
240
|
+
.map((library) => ({
|
|
241
|
+
name: typeof library.name === "string" ? library.name : "unknown",
|
|
242
|
+
version: typeof library.version === "string" ? library.version : "unknown",
|
|
243
|
+
}));
|
|
244
|
+
return [...new Map(builds.map((build) => [formatLibraryBuild(build), build])).values()];
|
|
245
|
+
}
|
|
246
|
+
/** `name@version`, the form used everywhere a build is reported to a human. */
|
|
247
|
+
export function formatLibraryBuild({ name, version }) {
|
|
248
|
+
return `${name}@${version}`;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Every distinct build seen on the wire, in first-seen order.
|
|
252
|
+
*
|
|
253
|
+
* Recorded because it is invisible in the counts: two copies of the script
|
|
254
|
+
* double every page view while each individual request still looks correct.
|
|
255
|
+
*/
|
|
256
|
+
export function summarizeObservedLibraries(requests) {
|
|
257
|
+
return [...new Set(requests.flatMap((r) => extractLibraries(r.payload).map(formatLibraryBuild)))];
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* true = cookieless fired first, false = normal fired first, null = one or
|
|
261
|
+
* both modes never produced a request (not enough evidence either way).
|
|
262
|
+
*
|
|
263
|
+
* Descriptive only — kept in the JSON report for a human reading a timeline,
|
|
264
|
+
* and deliberately not shown to the model. The cookieless snippet stands down
|
|
265
|
+
* when it finds the normal tag present and unblocked, and the normal bundle
|
|
266
|
+
* deletes the cookieless global once it initializes, so the two are meant to
|
|
267
|
+
* be mutually exclusive rather than ordered. Whether BOTH fired in one phase
|
|
268
|
+
* is the finding; which came first is a race with no correct answer.
|
|
269
|
+
*/
|
|
270
|
+
export function determineCookielessSeenFirst(requests) {
|
|
271
|
+
const firstCookieless = requests.find((r) => r.mode === "cookieless")?.timestamp;
|
|
272
|
+
const firstNormal = requests.find((r) => r.mode === "normal")?.timestamp;
|
|
273
|
+
if (firstCookieless === undefined || firstNormal === undefined)
|
|
274
|
+
return null;
|
|
275
|
+
return firstCookieless <= firstNormal;
|
|
276
|
+
}
|
|
277
|
+
function parseEventTime(value) {
|
|
278
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
279
|
+
return value;
|
|
280
|
+
if (typeof value === "string") {
|
|
281
|
+
const parsed = Date.parse(value);
|
|
282
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* When the BROWSER created this event, from the payload's own `timestamp`,
|
|
288
|
+
* as opposed to when it reached the wire.
|
|
289
|
+
*
|
|
290
|
+
* The two are usually milliseconds apart and the distinction never matters —
|
|
291
|
+
* until a navigation happens in between. Earliest wins for a batch: the
|
|
292
|
+
* envelope belongs to the page that produced its oldest event.
|
|
293
|
+
*/
|
|
294
|
+
export function eventCreatedAt(payload) {
|
|
295
|
+
const times = eventsOf(payload)
|
|
296
|
+
.map((event) => parseEventTime(event.timestamp))
|
|
297
|
+
.filter((ms) => ms !== null);
|
|
298
|
+
return times.length === 0 ? null : Math.min(...times);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Splits a request log at a navigation by which page view each event was
|
|
302
|
+
* CREATED on, not which one it happened to be delivered during.
|
|
303
|
+
*
|
|
304
|
+
* A request can still be sitting in the bundle's queue when a navigation
|
|
305
|
+
* starts and only reach the network afterwards. Attributing it by delivery
|
|
306
|
+
* time counts it against the page it landed on, which reads as that page view
|
|
307
|
+
* being tracked twice with no second loader anywhere to explain it — and, on
|
|
308
|
+
* a second-page probe, as the first page's tracking having "survived" a
|
|
309
|
+
* navigation it never actually made.
|
|
310
|
+
*
|
|
311
|
+
* An event with no readable creation time stays with the page it arrived on:
|
|
312
|
+
* dropping on an unknown would invent silence that was never observed.
|
|
313
|
+
*
|
|
314
|
+
* Not a complete separation, and cannot be: the bundle stamps `timestamp` when
|
|
315
|
+
* it DISPATCHES an event, not when page() was called, so an event that page()
|
|
316
|
+
* queued before a navigation carries a stamp taken after it — observed on
|
|
317
|
+
* maxio.com, where the request the accept click produced was stamped mid-reload
|
|
318
|
+
* and so reads as the reloaded page's own. Anything that must not be fooled by
|
|
319
|
+
* that has to be structural instead; page-view-counted-once is, by leaving the
|
|
320
|
+
* banner-answering entry page unjudged.
|
|
321
|
+
*/
|
|
322
|
+
/**
|
|
323
|
+
* Splits the requests seen since the second-page walk began into the three
|
|
324
|
+
* things they can be.
|
|
325
|
+
*
|
|
326
|
+
* The walk is no longer one navigation. A candidate that lands on an
|
|
327
|
+
* uninstrumented sub-site is left behind by going BACK to the first page, and
|
|
328
|
+
* that return load fires a page view of its own — a request belonging to
|
|
329
|
+
* neither page, which credited to either one would invent a duplicate page
|
|
330
|
+
* view on the first page or fake tracking on the second.
|
|
331
|
+
*
|
|
332
|
+
* `carriedOver` keeps its old meaning: created before the walk started at
|
|
333
|
+
* all, so it belongs to the first page whenever it was finally delivered.
|
|
334
|
+
* `discarded` is everything the retries produced in between. With no retries
|
|
335
|
+
* `acceptedAt` equals `walkStartedAt`, nothing is discarded, and this is the
|
|
336
|
+
* same split as before.
|
|
337
|
+
*/
|
|
338
|
+
export function partitionSecondPageRequests(requests, walkStartedAt, acceptedAt) {
|
|
339
|
+
const fired = [];
|
|
340
|
+
const carriedOver = [];
|
|
341
|
+
const discarded = [];
|
|
342
|
+
for (const request of requests) {
|
|
343
|
+
const createdAt = eventCreatedAt(request.payload) ?? request.timestamp;
|
|
344
|
+
if (createdAt < walkStartedAt)
|
|
345
|
+
carriedOver.push(request);
|
|
346
|
+
else if (createdAt < acceptedAt)
|
|
347
|
+
discarded.push(request);
|
|
348
|
+
else
|
|
349
|
+
fired.push(request);
|
|
350
|
+
}
|
|
351
|
+
return { fired, carriedOver, discarded };
|
|
352
|
+
}
|
|
353
|
+
export function partitionCarriedOverRequests(requests, navigationStartedAt) {
|
|
354
|
+
const fired = [];
|
|
355
|
+
const carriedOver = [];
|
|
356
|
+
for (const request of requests) {
|
|
357
|
+
const createdAt = eventCreatedAt(request.payload);
|
|
358
|
+
if (createdAt !== null && createdAt < navigationStartedAt)
|
|
359
|
+
carriedOver.push(request);
|
|
360
|
+
else
|
|
361
|
+
fired.push(request);
|
|
362
|
+
}
|
|
363
|
+
return { fired, carriedOver };
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/evidence/network.ts"],"names":[],"mappings":"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"}
|