@iann29/rastro 0.1.0-alpha.9 → 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/README.md +246 -57
- package/agent/integration.md +223 -65
- package/agent/manifest.json +34 -13
- package/agent/manifest.schema.json +53 -11
- package/dist/client/federation.d.ts +196 -11
- package/dist/client/federation.d.ts.map +1 -1
- package/dist/client/federation.js +160 -6
- package/dist/client/federation.js.map +1 -1
- package/dist/client/heat.d.ts +37 -0
- package/dist/client/heat.d.ts.map +1 -0
- package/dist/client/heat.js +43 -0
- package/dist/client/heat.js.map +1 -0
- package/dist/client/index.d.ts +441 -98
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +207 -17
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +6 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +50 -13
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/constants.d.ts +3 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +6 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/coverage.d.ts +9 -0
- package/dist/component/coverage.d.ts.map +1 -1
- package/dist/component/coverage.js +36 -6
- package/dist/component/coverage.js.map +1 -1
- package/dist/component/geo.d.ts +2 -0
- package/dist/component/geo.d.ts.map +1 -1
- package/dist/component/geo.js +56 -19
- package/dist/component/geo.js.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +48 -27
- package/dist/component/http.js.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +223 -304
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/localTime.d.ts +25 -0
- package/dist/component/localTime.d.ts.map +1 -0
- package/dist/component/localTime.js +126 -0
- package/dist/component/localTime.js.map +1 -0
- package/dist/component/reports.d.ts +44 -13
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +193 -38
- package/dist/component/reports.js.map +1 -1
- package/dist/component/retention.js +26 -8
- package/dist/component/retention.js.map +1 -1
- package/dist/component/rollupStore.d.ts +320 -0
- package/dist/component/rollupStore.d.ts.map +1 -0
- package/dist/component/rollupStore.js +596 -0
- package/dist/component/rollupStore.js.map +1 -0
- package/dist/component/rollups.d.ts +20 -0
- package/dist/component/rollups.d.ts.map +1 -0
- package/dist/component/rollups.js +73 -0
- package/dist/component/rollups.js.map +1 -0
- package/dist/component/sanitize.d.ts +5 -0
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +15 -0
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +345 -11
- package/dist/component/schema.js +25 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/sites.d.ts +12 -0
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +30 -0
- package/dist/component/sites.js.map +1 -1
- package/dist/component/validators.d.ts +34 -13
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +23 -2
- package/dist/component/validators.js.map +1 -1
- package/dist/tracker/generated.d.ts +3 -0
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +3 -0
- package/dist/tracker/generated.js.map +1 -1
- package/docs/federation-setup.md +104 -27
- package/docs/federation.md +168 -31
- package/docs/upgrading.md +158 -21
- package/llms.txt +8 -6
- package/package.json +2 -1
- package/src/component/_generated/api.ts +6 -0
- package/src/component/_generated/component.ts +47 -12
- package/src/component/constants.ts +6 -0
- package/src/component/coverage.ts +46 -6
- package/src/component/geo.ts +82 -28
- package/src/component/http.ts +61 -26
- package/src/component/ingest.ts +327 -439
- package/src/component/localTime.ts +167 -0
- package/src/component/reports.ts +261 -39
- package/src/component/retention.ts +25 -7
- package/src/component/rollupStore.ts +799 -0
- package/src/component/rollups.ts +82 -0
- package/src/component/sanitize.ts +14 -0
- package/src/component/schema.ts +35 -0
- package/src/component/sites.ts +32 -0
- package/src/component/validators.ts +27 -1
- package/src/tracker/generated.ts +12 -0
package/src/component/geo.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { eventBatchKey, eventBucketStart } from "./eventStore.js";
|
|
16
16
|
import { isPlainRecord } from "./guards.js";
|
|
17
17
|
import { sanitizeContext, stableHash } from "./sanitize.js";
|
|
18
|
+
import { localDayStart, localDayTimezone } from "./localTime.js";
|
|
18
19
|
import { trackerEventValidator, type IngestContext } from "./validators.js";
|
|
19
20
|
|
|
20
21
|
export type GeoIpProvider = "ipinfo" | "ipwhois";
|
|
@@ -55,6 +56,7 @@ const geoAggregateEventValidator = v.object({
|
|
|
55
56
|
sessionId: v.string(),
|
|
56
57
|
timestamp: v.number(),
|
|
57
58
|
priorCountry: v.string(),
|
|
59
|
+
pageview: v.optional(v.boolean()),
|
|
58
60
|
});
|
|
59
61
|
|
|
60
62
|
export const probe = internalAction({
|
|
@@ -132,7 +134,10 @@ export const claimEnrichment = internalMutation({
|
|
|
132
134
|
if (groups.size > MAX_BATCH_EVENT_GROUPS) {
|
|
133
135
|
return { sessionIds: [], events: [] };
|
|
134
136
|
}
|
|
135
|
-
const persistedEvents = new Map<
|
|
137
|
+
const persistedEvents = new Map<
|
|
138
|
+
string,
|
|
139
|
+
Map<string, { country: string | undefined; pageview: boolean }>
|
|
140
|
+
>();
|
|
136
141
|
await Promise.all(
|
|
137
142
|
[...groups].map(async ([key, group]) => {
|
|
138
143
|
const batches = await ctx.db
|
|
@@ -154,7 +159,10 @@ export const claimEnrichment = internalMutation({
|
|
|
154
159
|
(event) =>
|
|
155
160
|
[
|
|
156
161
|
event.eventId,
|
|
157
|
-
|
|
162
|
+
{
|
|
163
|
+
country: event.aggregateCountry ?? event.country,
|
|
164
|
+
pageview: event.type === "pageview",
|
|
165
|
+
},
|
|
158
166
|
] as const,
|
|
159
167
|
),
|
|
160
168
|
),
|
|
@@ -230,6 +238,7 @@ export const claimEnrichment = internalMutation({
|
|
|
230
238
|
sessionId: string;
|
|
231
239
|
timestamp: number;
|
|
232
240
|
priorCountry: string;
|
|
241
|
+
pageview: boolean;
|
|
233
242
|
}> = [];
|
|
234
243
|
for (const event of args.events) {
|
|
235
244
|
const key = `${eventBatchKey(event.sessionId, eventBucketStart(event.timestamp))}:${event.eventId}`;
|
|
@@ -245,14 +254,15 @@ export const claimEnrichment = internalMutation({
|
|
|
245
254
|
continue;
|
|
246
255
|
}
|
|
247
256
|
seenEvents.add(key);
|
|
248
|
-
const
|
|
257
|
+
const persisted = persistedEvents
|
|
249
258
|
.get(eventBatchKey(event.sessionId, eventBucketStart(event.timestamp)))
|
|
250
259
|
?.get(event.eventId);
|
|
251
|
-
if (
|
|
260
|
+
if (persisted?.country === undefined) continue;
|
|
252
261
|
correctionEvents.push({
|
|
253
262
|
sessionId: event.sessionId,
|
|
254
263
|
timestamp: event.timestamp,
|
|
255
|
-
priorCountry,
|
|
264
|
+
priorCountry: persisted.country,
|
|
265
|
+
pageview: persisted.pageview,
|
|
256
266
|
});
|
|
257
267
|
}
|
|
258
268
|
return { sessionIds, events: correctionEvents };
|
|
@@ -276,7 +286,10 @@ export const applyEnrichment = internalMutation({
|
|
|
276
286
|
return 0;
|
|
277
287
|
}
|
|
278
288
|
const context = sanitizeContext(args.context);
|
|
289
|
+
const site = await ctx.db.get("sites", args.siteId);
|
|
290
|
+
const timezone = localDayTimezone(site?.timezone);
|
|
279
291
|
const sessionCountries = new Map<string, string>();
|
|
292
|
+
const sessionStarts = new Map<string, number>();
|
|
280
293
|
let updated = 0;
|
|
281
294
|
for (const sessionId of new Set(args.sessionIds)) {
|
|
282
295
|
const session = await ctx.db
|
|
@@ -288,6 +301,7 @@ export const applyEnrichment = internalMutation({
|
|
|
288
301
|
if (!session || session.geoLookupAttemptedAt === undefined) continue;
|
|
289
302
|
const sessionCountry = session.country ?? context.country;
|
|
290
303
|
if (sessionCountry) sessionCountries.set(sessionId, sessionCountry);
|
|
304
|
+
sessionStarts.set(sessionId, session.startedAt);
|
|
291
305
|
const sessionGeo = missingGeo(session, context);
|
|
292
306
|
if (Object.keys(sessionGeo).length > 0) {
|
|
293
307
|
await ctx.db.patch("sessions", session._id, sessionGeo);
|
|
@@ -312,6 +326,8 @@ export const applyEnrichment = internalMutation({
|
|
|
312
326
|
args.siteId,
|
|
313
327
|
args.events,
|
|
314
328
|
sessionCountries,
|
|
329
|
+
sessionStarts,
|
|
330
|
+
timezone,
|
|
315
331
|
);
|
|
316
332
|
}
|
|
317
333
|
return updated;
|
|
@@ -546,24 +562,34 @@ async function correctCountryAggregates(
|
|
|
546
562
|
sessionId: string;
|
|
547
563
|
timestamp: number;
|
|
548
564
|
priorCountry: string;
|
|
565
|
+
pageview?: boolean;
|
|
549
566
|
}>,
|
|
550
567
|
sessionCountries: Map<string, string>,
|
|
568
|
+
sessionStarts: Map<string, number>,
|
|
569
|
+
timezone: string | undefined,
|
|
551
570
|
): Promise<void> {
|
|
552
571
|
const corrections = new Map<
|
|
553
572
|
string,
|
|
554
573
|
{
|
|
555
|
-
granularity: "hour" | "day";
|
|
574
|
+
granularity: "hour" | "day" | "localDay";
|
|
556
575
|
bucketStart: number;
|
|
557
576
|
shard: number;
|
|
558
|
-
moves: Map<string,
|
|
577
|
+
moves: Map<string, CountryMove>;
|
|
559
578
|
}
|
|
560
579
|
>();
|
|
580
|
+
const granularities: ReadonlyArray<"hour" | "day" | "localDay"> = timezone
|
|
581
|
+
? ["hour", "day", "localDay"]
|
|
582
|
+
: ["hour", "day"];
|
|
561
583
|
for (const event of events) {
|
|
562
584
|
const country = sessionCountries.get(event.sessionId);
|
|
563
585
|
if (!country || country === event.priorCountry) continue;
|
|
564
|
-
for (const granularity of
|
|
565
|
-
const
|
|
566
|
-
|
|
586
|
+
for (const granularity of granularities) {
|
|
587
|
+
const bucketStart =
|
|
588
|
+
granularity === "localDay" && timezone
|
|
589
|
+
? localDayStart(event.timestamp, timezone)
|
|
590
|
+
: Math.floor(
|
|
591
|
+
event.timestamp / (granularity === "hour" ? HOUR_MS : DAY_MS),
|
|
592
|
+
) * (granularity === "hour" ? HOUR_MS : DAY_MS);
|
|
567
593
|
const shard =
|
|
568
594
|
stableHash(event.sessionId) %
|
|
569
595
|
(granularity === "hour" ? HOURLY_AGGREGATE_SHARDS : AGGREGATE_SHARDS);
|
|
@@ -572,15 +598,23 @@ async function correctCountryAggregates(
|
|
|
572
598
|
granularity,
|
|
573
599
|
bucketStart,
|
|
574
600
|
shard,
|
|
575
|
-
moves: new Map<string,
|
|
601
|
+
moves: new Map<string, CountryMove>(),
|
|
576
602
|
};
|
|
577
603
|
const moveKey = `${event.priorCountry}:${country}`;
|
|
578
604
|
const move = correction.moves.get(moveKey) ?? {
|
|
579
605
|
from: event.priorCountry,
|
|
580
606
|
to: country,
|
|
581
607
|
count: 0,
|
|
608
|
+
pageviews: 0,
|
|
609
|
+
sessions: 0,
|
|
582
610
|
};
|
|
583
611
|
move.count += 1;
|
|
612
|
+
move.pageviews += Number(event.pageview === true);
|
|
613
|
+
// The session counter was folded with the session's first event, so it
|
|
614
|
+
// moves with the bucket that holds that event and no other.
|
|
615
|
+
move.sessions += Number(
|
|
616
|
+
sessionStarts.get(event.sessionId) === event.timestamp,
|
|
617
|
+
);
|
|
584
618
|
correction.moves.set(moveKey, move);
|
|
585
619
|
corrections.set(key, correction);
|
|
586
620
|
}
|
|
@@ -602,12 +636,7 @@ async function correctCountryAggregates(
|
|
|
602
636
|
let dimensions = bucket.dimensions;
|
|
603
637
|
let changed = false;
|
|
604
638
|
for (const move of correction.moves.values()) {
|
|
605
|
-
const corrected = moveCountryCount(
|
|
606
|
-
dimensions,
|
|
607
|
-
move.from,
|
|
608
|
-
move.to,
|
|
609
|
-
move.count,
|
|
610
|
-
);
|
|
639
|
+
const corrected = moveCountryCount(dimensions, move);
|
|
611
640
|
if (corrected) {
|
|
612
641
|
dimensions = corrected;
|
|
613
642
|
changed = true;
|
|
@@ -619,11 +648,17 @@ async function correctCountryAggregates(
|
|
|
619
648
|
}
|
|
620
649
|
}
|
|
621
650
|
|
|
651
|
+
type CountryMove = {
|
|
652
|
+
from: string;
|
|
653
|
+
to: string;
|
|
654
|
+
count: number;
|
|
655
|
+
pageviews: number;
|
|
656
|
+
sessions: number;
|
|
657
|
+
};
|
|
658
|
+
|
|
622
659
|
function moveCountryCount(
|
|
623
660
|
current: Doc<"aggregateBuckets">["dimensions"],
|
|
624
|
-
from:
|
|
625
|
-
to: string,
|
|
626
|
-
requested: number,
|
|
661
|
+
{ from, to, ...requested }: CountryMove,
|
|
627
662
|
): Doc<"aggregateBuckets">["dimensions"] | undefined {
|
|
628
663
|
const dimensions = current.map((slot) => ({ ...slot }));
|
|
629
664
|
let sourceIndex = dimensions.findIndex(
|
|
@@ -636,9 +671,15 @@ function moveCountryCount(
|
|
|
636
671
|
}
|
|
637
672
|
if (sourceIndex < 0) return undefined;
|
|
638
673
|
const source = dimensions[sourceIndex]!;
|
|
639
|
-
const moved =
|
|
640
|
-
|
|
641
|
-
|
|
674
|
+
const moved = {
|
|
675
|
+
count: Math.min(source.count, requested.count),
|
|
676
|
+
pageviews: Math.min(source.pageviews ?? 0, requested.pageviews),
|
|
677
|
+
sessions: Math.min(source.sessions ?? 0, requested.sessions),
|
|
678
|
+
};
|
|
679
|
+
if (moved.count <= 0) return undefined;
|
|
680
|
+
source.count -= moved.count;
|
|
681
|
+
if (source.pageviews !== undefined) source.pageviews -= moved.pageviews;
|
|
682
|
+
if (source.sessions !== undefined) source.sessions -= moved.sessions;
|
|
642
683
|
if (source.count === 0 && source.revenueCents === 0) {
|
|
643
684
|
dimensions.splice(sourceIndex, 1);
|
|
644
685
|
}
|
|
@@ -646,7 +687,7 @@ function moveCountryCount(
|
|
|
646
687
|
(slot) => slot.type === "country" && slot.value === to,
|
|
647
688
|
);
|
|
648
689
|
if (target) {
|
|
649
|
-
target
|
|
690
|
+
addCountryMove(target, moved);
|
|
650
691
|
return dimensions;
|
|
651
692
|
}
|
|
652
693
|
const countrySlots = dimensions.filter((slot) => slot.type === "country");
|
|
@@ -658,14 +699,16 @@ function moveCountryCount(
|
|
|
658
699
|
type: "country",
|
|
659
700
|
slot,
|
|
660
701
|
value: to,
|
|
661
|
-
count: moved,
|
|
702
|
+
count: moved.count,
|
|
662
703
|
revenueCents: 0,
|
|
704
|
+
pageviews: moved.pageviews,
|
|
705
|
+
sessions: moved.sessions,
|
|
663
706
|
});
|
|
664
707
|
return dimensions;
|
|
665
708
|
}
|
|
666
709
|
const overflow = countrySlots.find((slot) => slot.value === "(other)");
|
|
667
710
|
if (overflow) {
|
|
668
|
-
overflow
|
|
711
|
+
addCountryMove(overflow, moved);
|
|
669
712
|
return dimensions;
|
|
670
713
|
}
|
|
671
714
|
const occupied = new Set(countrySlots.map((slot) => slot.slot));
|
|
@@ -676,8 +719,10 @@ function moveCountryCount(
|
|
|
676
719
|
type: "country",
|
|
677
720
|
slot,
|
|
678
721
|
value: "(other)",
|
|
679
|
-
count: moved,
|
|
722
|
+
count: moved.count,
|
|
680
723
|
revenueCents: 0,
|
|
724
|
+
pageviews: moved.pageviews,
|
|
725
|
+
sessions: moved.sessions,
|
|
681
726
|
});
|
|
682
727
|
return dimensions;
|
|
683
728
|
}
|
|
@@ -686,10 +731,19 @@ function moveCountryCount(
|
|
|
686
731
|
if (candidate.count < replacement.count) replacement = candidate;
|
|
687
732
|
}
|
|
688
733
|
replacement.value = "(other)";
|
|
689
|
-
replacement
|
|
734
|
+
addCountryMove(replacement, moved);
|
|
690
735
|
return dimensions;
|
|
691
736
|
}
|
|
692
737
|
|
|
738
|
+
function addCountryMove(
|
|
739
|
+
slot: Doc<"aggregateBuckets">["dimensions"][number],
|
|
740
|
+
moved: { count: number; pageviews: number; sessions: number },
|
|
741
|
+
) {
|
|
742
|
+
slot.count += moved.count;
|
|
743
|
+
slot.pageviews = (slot.pageviews ?? 0) + moved.pageviews;
|
|
744
|
+
slot.sessions = (slot.sessions ?? 0) + moved.sessions;
|
|
745
|
+
}
|
|
746
|
+
|
|
693
747
|
function missingGeo(existing: GeoContext, context: GeoContext): GeoContext {
|
|
694
748
|
const coordinates =
|
|
695
749
|
(existing.latitude === undefined || existing.longitude === undefined) &&
|
package/src/component/http.ts
CHANGED
|
@@ -24,10 +24,13 @@ import { deriveVisitorKey, randomSecret } from "./identity.js";
|
|
|
24
24
|
import { classifyClient, isKnownBot } from "./useragent.js";
|
|
25
25
|
import type { TrackerEvent } from "./validators.js";
|
|
26
26
|
import {
|
|
27
|
+
RASTRO_VERSION,
|
|
27
28
|
TRACKER_GZIP_BASE64,
|
|
28
29
|
TRACKER_GZIP_BYTES,
|
|
30
|
+
TRACKER_HASH,
|
|
29
31
|
VITALS_GZIP_BASE64,
|
|
30
32
|
VITALS_GZIP_BYTES,
|
|
33
|
+
VITALS_HASH,
|
|
31
34
|
} from "../tracker/generated.js";
|
|
32
35
|
|
|
33
36
|
const TRACKER_GZIP = Uint8Array.from(atob(TRACKER_GZIP_BASE64), (character) =>
|
|
@@ -37,6 +40,52 @@ const VITALS_GZIP = Uint8Array.from(atob(VITALS_GZIP_BASE64), (character) =>
|
|
|
37
40
|
character.charCodeAt(0),
|
|
38
41
|
);
|
|
39
42
|
|
|
43
|
+
// Five minutes fresh, then up to an hour of stale-while-revalidate: a visitor
|
|
44
|
+
// who returns after a deploy gets at most one stale load before the browser's
|
|
45
|
+
// background revalidation, and that revalidation is a 304 against the ETag.
|
|
46
|
+
const SCRIPT_CACHE_CONTROL = "public, max-age=300, stale-while-revalidate=3600";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Serves one pre-gzipped module with a strong ETag from its build-time content
|
|
50
|
+
* hash. A matching If-None-Match answers 304 with the same caching and CORS
|
|
51
|
+
* headers, so a revalidation costs a header exchange instead of the script.
|
|
52
|
+
*/
|
|
53
|
+
function scriptResponse(
|
|
54
|
+
request: Request,
|
|
55
|
+
body: Uint8Array<ArrayBuffer>,
|
|
56
|
+
byteLength: number,
|
|
57
|
+
hash: string,
|
|
58
|
+
): Response {
|
|
59
|
+
const etag = `"${hash}"`;
|
|
60
|
+
const headers = {
|
|
61
|
+
ETag: etag,
|
|
62
|
+
"Cache-Control": SCRIPT_CACHE_CONTROL,
|
|
63
|
+
"Access-Control-Allow-Origin": "*",
|
|
64
|
+
Vary: "Accept-Encoding",
|
|
65
|
+
};
|
|
66
|
+
if (etagMatches(request.headers.get("if-none-match"), etag)) {
|
|
67
|
+
return new Response(null, { status: 304, headers });
|
|
68
|
+
}
|
|
69
|
+
return new Response(body, {
|
|
70
|
+
status: 200,
|
|
71
|
+
headers: {
|
|
72
|
+
...headers,
|
|
73
|
+
"Content-Type": "text/javascript; charset=utf-8",
|
|
74
|
+
"Content-Encoding": "gzip",
|
|
75
|
+
"Content-Length": String(byteLength),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** RFC 9110 If-None-Match: a list of entity tags or "*", weak markers ignored. */
|
|
81
|
+
function etagMatches(header: string | null, etag: string): boolean {
|
|
82
|
+
if (!header) return false;
|
|
83
|
+
return header.split(",").some((candidate) => {
|
|
84
|
+
const value = candidate.trim().replace(/^W\//, "");
|
|
85
|
+
return value === "*" || value === etag;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
40
89
|
const http = httpRouter();
|
|
41
90
|
|
|
42
91
|
http.route({
|
|
@@ -228,37 +277,17 @@ http.route({
|
|
|
228
277
|
http.route({
|
|
229
278
|
path: "/tracker.js",
|
|
230
279
|
method: "GET",
|
|
231
|
-
handler: httpAction(async () =>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
headers: {
|
|
235
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
236
|
-
"Content-Encoding": "gzip",
|
|
237
|
-
"Content-Length": String(TRACKER_GZIP_BYTES),
|
|
238
|
-
"Cache-Control": "public, max-age=300, stale-while-revalidate=86400",
|
|
239
|
-
"Access-Control-Allow-Origin": "*",
|
|
240
|
-
Vary: "Accept-Encoding",
|
|
241
|
-
},
|
|
242
|
-
});
|
|
243
|
-
}),
|
|
280
|
+
handler: httpAction(async (_ctx, request) =>
|
|
281
|
+
scriptResponse(request, TRACKER_GZIP, TRACKER_GZIP_BYTES, TRACKER_HASH),
|
|
282
|
+
),
|
|
244
283
|
});
|
|
245
284
|
|
|
246
285
|
http.route({
|
|
247
286
|
path: "/vitals.js",
|
|
248
287
|
method: "GET",
|
|
249
|
-
handler: httpAction(async () =>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
headers: {
|
|
253
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
254
|
-
"Content-Encoding": "gzip",
|
|
255
|
-
"Content-Length": String(VITALS_GZIP_BYTES),
|
|
256
|
-
"Cache-Control": "public, max-age=300, stale-while-revalidate=86400",
|
|
257
|
-
"Access-Control-Allow-Origin": "*",
|
|
258
|
-
Vary: "Accept-Encoding",
|
|
259
|
-
},
|
|
260
|
-
});
|
|
261
|
-
}),
|
|
288
|
+
handler: httpAction(async (_ctx, request) =>
|
|
289
|
+
scriptResponse(request, VITALS_GZIP, VITALS_GZIP_BYTES, VITALS_HASH),
|
|
290
|
+
),
|
|
262
291
|
});
|
|
263
292
|
|
|
264
293
|
http.route({
|
|
@@ -301,6 +330,11 @@ http.route({
|
|
|
301
330
|
siteMap: "dailyRollup",
|
|
302
331
|
liveDepartures: "leaveBeacon",
|
|
303
332
|
},
|
|
333
|
+
tracker: {
|
|
334
|
+
version: RASTRO_VERSION,
|
|
335
|
+
hash: TRACKER_HASH,
|
|
336
|
+
vitalsHash: VITALS_HASH,
|
|
337
|
+
},
|
|
304
338
|
},
|
|
305
339
|
200,
|
|
306
340
|
{
|
|
@@ -369,6 +403,7 @@ function parseEvent(value: unknown): TrackerEvent {
|
|
|
369
403
|
type !== "custom" &&
|
|
370
404
|
type !== "conversion" &&
|
|
371
405
|
type !== "heartbeat" &&
|
|
406
|
+
type !== "leave" &&
|
|
372
407
|
type !== "outbound" &&
|
|
373
408
|
type !== "vital"
|
|
374
409
|
) {
|