@iann29/rastro 0.1.0-alpha.8 → 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 +274 -75
- 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 +448 -105
- 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 +53 -16
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/constants.d.ts +5 -1
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +8 -1
- 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/eventStore.d.ts +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 +49 -27
- package/dist/component/http.js.map +1 -1
- package/dist/component/ingest.d.ts +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +252 -305
- 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 +46 -15
- 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 +349 -15
- 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 +41 -20
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +24 -3
- package/dist/component/validators.js.map +1 -1
- package/dist/tracker/generated.d.ts +7 -4
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +7 -4
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.d.ts +1 -1
- package/dist/tracker/tracker.d.ts.map +1 -1
- package/dist/tracker/tracker.js +24 -2
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/federation-setup.md +104 -27
- package/docs/federation.md +168 -31
- package/docs/upgrading.md +191 -16
- package/llms.txt +9 -7
- package/package.json +2 -1
- package/src/component/_generated/api.ts +6 -0
- package/src/component/_generated/component.ts +50 -12
- package/src/component/constants.ts +8 -1
- package/src/component/coverage.ts +46 -6
- package/src/component/geo.ts +82 -28
- package/src/component/http.ts +62 -26
- package/src/component/ingest.ts +378 -440
- 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 +28 -1
- package/src/tracker/generated.ts +12 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { internal } from "./_generated/api.js";
|
|
3
|
+
import { internalMutation } from "./_generated/server.js";
|
|
4
|
+
import {
|
|
5
|
+
ROLLUP_FOLD_BATCH_DOCS,
|
|
6
|
+
ROLLUP_FOLD_LEASE_MS,
|
|
7
|
+
ROLLUP_FOLD_MAX_ROWS,
|
|
8
|
+
} from "./constants.js";
|
|
9
|
+
import {
|
|
10
|
+
countReportRollupRows,
|
|
11
|
+
createReportRollupDeltas,
|
|
12
|
+
flushReportRollups,
|
|
13
|
+
mergeQueuedRollups,
|
|
14
|
+
} from "./rollupStore.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Folds a site's queued report rollup deltas into the rollup rows. Exactly
|
|
18
|
+
* one chain runs per site: ingestion starts one when no lease is held, every
|
|
19
|
+
* run carries the token of the lease it belongs to, and a run whose token is
|
|
20
|
+
* no longer current exits without touching anything. A run that finds more
|
|
21
|
+
* queued documents than it could take continues at once; a run that drains
|
|
22
|
+
* the queue releases the lease. A batch that lands while a run is draining
|
|
23
|
+
* is never stranded: the run read the queue range the batch inserted into
|
|
24
|
+
* and the batch read the lease the run released, so one of the two retries
|
|
25
|
+
* and sees the other's work.
|
|
26
|
+
*/
|
|
27
|
+
export const fold = internalMutation({
|
|
28
|
+
args: { siteId: v.id("sites"), token: v.string() },
|
|
29
|
+
returns: v.object({
|
|
30
|
+
folded: v.number(),
|
|
31
|
+
remaining: v.boolean(),
|
|
32
|
+
stale: v.boolean(),
|
|
33
|
+
}),
|
|
34
|
+
handler: async (ctx, args) => {
|
|
35
|
+
const state = await ctx.db
|
|
36
|
+
.query("rollupFoldStates")
|
|
37
|
+
.withIndex("by_siteId", (range) => range.eq("siteId", args.siteId))
|
|
38
|
+
.unique();
|
|
39
|
+
if (!state || state.token !== args.token) {
|
|
40
|
+
return { folded: 0, remaining: false, stale: true };
|
|
41
|
+
}
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const queued = await ctx.db
|
|
44
|
+
.query("reportRollupQueue")
|
|
45
|
+
.withIndex("by_siteId_and_createdAt", (range) =>
|
|
46
|
+
range.eq("siteId", args.siteId),
|
|
47
|
+
)
|
|
48
|
+
.order("asc")
|
|
49
|
+
.take(ROLLUP_FOLD_BATCH_DOCS + 1);
|
|
50
|
+
if (queued.length === 0) {
|
|
51
|
+
if (state.leaseUntil > now) {
|
|
52
|
+
await ctx.db.patch("rollupFoldStates", state._id, { leaseUntil: now });
|
|
53
|
+
}
|
|
54
|
+
return { folded: 0, remaining: false, stale: false };
|
|
55
|
+
}
|
|
56
|
+
const deltas = createReportRollupDeltas();
|
|
57
|
+
let taken = 0;
|
|
58
|
+
for (const document of queued.slice(0, ROLLUP_FOLD_BATCH_DOCS)) {
|
|
59
|
+
mergeQueuedRollups(deltas, document);
|
|
60
|
+
taken += 1;
|
|
61
|
+
if (countReportRollupRows(deltas) >= ROLLUP_FOLD_MAX_ROWS) break;
|
|
62
|
+
}
|
|
63
|
+
await flushReportRollups(ctx, deltas);
|
|
64
|
+
for (const document of queued.slice(0, taken)) {
|
|
65
|
+
await ctx.db.delete("reportRollupQueue", document._id);
|
|
66
|
+
}
|
|
67
|
+
const remaining = queued.length > taken;
|
|
68
|
+
if (remaining) {
|
|
69
|
+
// Renew only when the lease is halfway gone, so the state document is
|
|
70
|
+
// rewritten about twice a minute under load rather than on every run.
|
|
71
|
+
if (state.leaseUntil - now < ROLLUP_FOLD_LEASE_MS / 2) {
|
|
72
|
+
await ctx.db.patch("rollupFoldStates", state._id, {
|
|
73
|
+
leaseUntil: now + ROLLUP_FOLD_LEASE_MS,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
await ctx.scheduler.runAfter(0, internal.rollups.fold, args);
|
|
77
|
+
} else {
|
|
78
|
+
await ctx.db.patch("rollupFoldStates", state._id, { leaseUntil: now });
|
|
79
|
+
}
|
|
80
|
+
return { folded: taken, remaining, stale: false };
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -121,6 +121,20 @@ export function sanitizePublicUrl(value: string): string | undefined {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* The host of a public URL the way the outbound dimension names it: lowercase,
|
|
126
|
+
* without port, path, or query. A relative path has no host.
|
|
127
|
+
*/
|
|
128
|
+
export function publicUrlHost(value: string | undefined): string | undefined {
|
|
129
|
+
if (!value || value.startsWith("/")) return undefined;
|
|
130
|
+
try {
|
|
131
|
+
const host = new URL(value).hostname.toLowerCase();
|
|
132
|
+
return host ? host.slice(0, 120) : undefined;
|
|
133
|
+
} catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
124
138
|
export function sanitizeProperties(
|
|
125
139
|
properties: EventProperties | undefined,
|
|
126
140
|
): EventProperties | undefined {
|
package/src/component/schema.ts
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
visitorSketchValidator,
|
|
12
12
|
vitalMetricValidator,
|
|
13
13
|
} from "./validators.js";
|
|
14
|
+
import {
|
|
15
|
+
reportRollupQueueFields,
|
|
16
|
+
rollupFoldStateFields,
|
|
17
|
+
} from "./rollupStore.js";
|
|
14
18
|
|
|
15
19
|
export default defineSchema({
|
|
16
20
|
sites: defineTable(siteFieldsValidator)
|
|
@@ -56,6 +60,22 @@ export default defineSchema({
|
|
|
56
60
|
eventCount: v.number(),
|
|
57
61
|
previousPath: v.optional(v.string()),
|
|
58
62
|
movedAt: v.optional(v.number()),
|
|
63
|
+
// What the live view needs to tell intent apart (since 0.1.0): where the
|
|
64
|
+
// session came in, whether the visitor had a history, the campaign and
|
|
65
|
+
// affiliate it carries, conversions so far, and the furthest funnel step
|
|
66
|
+
// the visitor has reached.
|
|
67
|
+
entryPath: v.optional(v.string()),
|
|
68
|
+
returning: v.optional(v.boolean()),
|
|
69
|
+
utmCampaign: v.optional(v.string()),
|
|
70
|
+
affiliateSlug: v.optional(v.string()),
|
|
71
|
+
conversionCount: v.optional(v.number()),
|
|
72
|
+
funnel: v.optional(
|
|
73
|
+
v.object({
|
|
74
|
+
funnelId: v.id("funnels"),
|
|
75
|
+
step: v.number(),
|
|
76
|
+
steps: v.number(),
|
|
77
|
+
}),
|
|
78
|
+
),
|
|
59
79
|
})
|
|
60
80
|
.index("by_siteId_and_sessionId", ["siteId", "sessionId"])
|
|
61
81
|
.index("by_siteId_and_expiresAt", ["siteId", "expiresAt"])
|
|
@@ -227,11 +247,26 @@ export default defineSchema({
|
|
|
227
247
|
])
|
|
228
248
|
.index("by_siteId_and_bucketStart", ["siteId", "bucketStart"]),
|
|
229
249
|
|
|
250
|
+
// Rollup deltas wait here between ingestion and the fold job; see
|
|
251
|
+
// rollupStore.ts. Rows are deleted as they are folded.
|
|
252
|
+
reportRollupQueue: defineTable(reportRollupQueueFields).index(
|
|
253
|
+
"by_siteId_and_createdAt",
|
|
254
|
+
["siteId", "createdAt"],
|
|
255
|
+
),
|
|
256
|
+
|
|
257
|
+
rollupFoldStates: defineTable(rollupFoldStateFields).index("by_siteId", [
|
|
258
|
+
"siteId",
|
|
259
|
+
]),
|
|
260
|
+
|
|
230
261
|
analyticsCoverage: defineTable({
|
|
231
262
|
siteId: v.id("sites"),
|
|
232
263
|
generation: v.literal(1),
|
|
233
264
|
rollupCompleteFrom: v.number(),
|
|
234
265
|
siteMapSince: v.optional(v.number()),
|
|
266
|
+
countersSince: v.optional(v.number()),
|
|
267
|
+
localDaySince: v.optional(v.number()),
|
|
268
|
+
// Since when the medium and outbound dimension slots are recorded.
|
|
269
|
+
dimensionsSince: v.optional(v.number()),
|
|
235
270
|
retained: v.array(v.object({ dataset: v.string(), before: v.number() })),
|
|
236
271
|
updatedAt: v.number(),
|
|
237
272
|
}).index("by_siteId", ["siteId"]),
|
package/src/component/sites.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { internalQuery, mutation, query } from "./_generated/server.js";
|
|
|
3
3
|
import { MAX_SITES_PER_OWNER } from "./constants.js";
|
|
4
4
|
import { ensureAnalyticsControl } from "./coverage.js";
|
|
5
5
|
import { fail } from "./errors.js";
|
|
6
|
+
import { resetLocalDayCoverage } from "./coverage.js";
|
|
7
|
+
import { localDayTimezone } from "./localTime.js";
|
|
6
8
|
import {
|
|
7
9
|
cleanString,
|
|
8
10
|
normalizeDomains,
|
|
@@ -173,6 +175,9 @@ export const update = mutation({
|
|
|
173
175
|
}
|
|
174
176
|
if (args.cookieless !== undefined) patch.cookieless = args.cookieless;
|
|
175
177
|
await ctx.db.patch("sites", args.siteId, patch);
|
|
178
|
+
if (args.timezone !== undefined && site.timezone !== patch.timezone) {
|
|
179
|
+
await resetLocalDayCoverage(ctx, args.siteId, patch.updatedAt);
|
|
180
|
+
}
|
|
176
181
|
return null;
|
|
177
182
|
},
|
|
178
183
|
});
|
|
@@ -185,6 +190,33 @@ export const get = query({
|
|
|
185
190
|
},
|
|
186
191
|
});
|
|
187
192
|
|
|
193
|
+
/**
|
|
194
|
+
* The calendar days a site's daily overview can answer in: the timezone its
|
|
195
|
+
* local-day buckets follow and the instant from which they are complete, or
|
|
196
|
+
* null when the site keeps none (no timezone, or a zone that never leaves
|
|
197
|
+
* UTC, whose days are the UTC days already kept).
|
|
198
|
+
*/
|
|
199
|
+
export const localDays = query({
|
|
200
|
+
args: { siteId: v.id("sites") },
|
|
201
|
+
returns: v.union(
|
|
202
|
+
v.object({ timezone: v.string(), since: v.number() }),
|
|
203
|
+
v.null(),
|
|
204
|
+
),
|
|
205
|
+
handler: async (ctx, args) => {
|
|
206
|
+
const site = await ctx.db.get("sites", args.siteId);
|
|
207
|
+
const timezone = localDayTimezone(site?.timezone);
|
|
208
|
+
if (!timezone) return null;
|
|
209
|
+
const control = await ctx.db
|
|
210
|
+
.query("analyticsCoverage")
|
|
211
|
+
.withIndex("by_siteId", (range) => range.eq("siteId", args.siteId))
|
|
212
|
+
.unique();
|
|
213
|
+
// A site that has not ingested since the upgrade has no stamp yet; its
|
|
214
|
+
// days begin with its first batch, which is later than now.
|
|
215
|
+
if (control?.localDaySince === undefined) return null;
|
|
216
|
+
return { timezone, since: control.localDaySince };
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
188
220
|
export const listByOwner = query({
|
|
189
221
|
args: {
|
|
190
222
|
ownerId: v.string(),
|
|
@@ -6,6 +6,7 @@ export const eventTypeValidator = v.union(
|
|
|
6
6
|
v.literal("custom"),
|
|
7
7
|
v.literal("conversion"),
|
|
8
8
|
v.literal("heartbeat"),
|
|
9
|
+
v.literal("leave"),
|
|
9
10
|
v.literal("outbound"),
|
|
10
11
|
v.literal("vital"),
|
|
11
12
|
);
|
|
@@ -84,6 +85,9 @@ export const sessionFieldsValidator = v.object({
|
|
|
84
85
|
sessionId: v.string(),
|
|
85
86
|
visitorId: v.string(),
|
|
86
87
|
visitorKey: v.optional(v.string()),
|
|
88
|
+
// Since 0.1.0: whether nothing was recorded under the visitor before this
|
|
89
|
+
// session (a host id linked to anonymous history at sign-up is a return).
|
|
90
|
+
newVisitor: v.optional(v.boolean()),
|
|
87
91
|
startedAt: v.number(),
|
|
88
92
|
lastSeenAt: v.number(),
|
|
89
93
|
entryPath: v.string(),
|
|
@@ -147,6 +151,10 @@ export const dimensionTypeValidator = v.union(
|
|
|
147
151
|
v.literal("browser"),
|
|
148
152
|
v.literal("event"),
|
|
149
153
|
v.literal("goal"),
|
|
154
|
+
// Since 0.1.0: the lowercase utm_medium of the session, and the destination
|
|
155
|
+
// host of an outbound click (never its path).
|
|
156
|
+
v.literal("medium"),
|
|
157
|
+
v.literal("outbound"),
|
|
150
158
|
);
|
|
151
159
|
|
|
152
160
|
export const dimensionSlotValidator = v.object({
|
|
@@ -155,6 +163,11 @@ export const dimensionSlotValidator = v.object({
|
|
|
155
163
|
value: v.string(),
|
|
156
164
|
count: v.number(),
|
|
157
165
|
revenueCents: v.number(),
|
|
166
|
+
// Slots written since alpha.11 also count the pageviews and the sessions
|
|
167
|
+
// (a session's first event) they saw, so pages rank by views and every
|
|
168
|
+
// other dimension by sessions instead of by raw event volume.
|
|
169
|
+
pageviews: v.optional(v.number()),
|
|
170
|
+
sessions: v.optional(v.number()),
|
|
158
171
|
});
|
|
159
172
|
|
|
160
173
|
export const visitorSketchValidator = v.object({
|
|
@@ -163,9 +176,17 @@ export const visitorSketchValidator = v.object({
|
|
|
163
176
|
registers: v.bytes(),
|
|
164
177
|
});
|
|
165
178
|
|
|
179
|
+
export const aggregateGranularityValidator = v.union(
|
|
180
|
+
v.literal("hour"),
|
|
181
|
+
v.literal("day"),
|
|
182
|
+
// One bucket per calendar day in the site's timezone, written alongside
|
|
183
|
+
// the UTC day for sites with a timezone (see localTime.ts).
|
|
184
|
+
v.literal("localDay"),
|
|
185
|
+
);
|
|
186
|
+
|
|
166
187
|
export const aggregateFieldsValidator = v.object({
|
|
167
188
|
siteId: v.id("sites"),
|
|
168
|
-
granularity:
|
|
189
|
+
granularity: aggregateGranularityValidator,
|
|
169
190
|
bucketStart: v.number(),
|
|
170
191
|
shard: v.number(),
|
|
171
192
|
events: v.number(),
|
|
@@ -180,6 +201,12 @@ export const aggregateFieldsValidator = v.object({
|
|
|
180
201
|
conversions: v.number(),
|
|
181
202
|
revenueCents: v.number(),
|
|
182
203
|
dimensions: v.array(dimensionSlotValidator),
|
|
204
|
+
// Engagement counters recorded since alpha.11: how much session duration
|
|
205
|
+
// grew from the activity that landed in this bucket, sessions that reached
|
|
206
|
+
// a second pageview here, and sessions of visitors never seen before.
|
|
207
|
+
durationMs: v.optional(v.number()),
|
|
208
|
+
continuedSessions: v.optional(v.number()),
|
|
209
|
+
newVisitors: v.optional(v.number()),
|
|
183
210
|
});
|
|
184
211
|
|
|
185
212
|
export const goalMatchTypeValidator = v.union(
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Generated by scripts/build-tracker.mjs. Do not edit.
|
|
2
|
+
export const RASTRO_VERSION = "0.1.0";
|
|
3
|
+
export const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const o=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search),r=n.get(\"ref\")?.slice(0,64);let a,i,c;for(const e of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const t=n.get(\"utm_\"+e)?.slice(0,64);t&&((a??={})[\"utm_\"+e]=t)}try{i=sessionStorage._r||o(),sessionStorage._r=i,c=r||sessionStorage._a,c&&(sessionStorage._a=c),a||=sessionStorage._u&&JSON.parse(sessionStorage._u),a&&(sessionStorage._u=JSON.stringify(a))}catch{i=o(),c=r}let d=e.dataset.visitor;try{d||=localStorage._rv||=o()}catch{}d||=i;let l,p=0;const g=[],h=document.referrer?new URL(document.referrer).origin:void 0,u=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o,sentAt:Date.now()})]);let r=n();for(;r.size>64e3&&o.length>1;)g.unshift(o.pop()),r=n();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),g.length&&u(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${i}.${o.toString(36)}.${p}`,sessionId:i,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(u,1200)};let v;const f=()=>{const e=location.pathname;e!==v&&(v=e,m(\"pageview\",void 0,a&&{properties:a}))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:i,visitorId:d};\"event\"===e&&m(\"custom\",`${t||\"event\"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener(\"click\",e=>{const t=e.target?.closest(\"a,button,[data-rastro-event]\");if(!t)return;const s=t.dataset.rastroEvent,o=t.href,n=o&&new URL(o),r=/^https?:$/.test(n?.protocol),a=r&&n.origin!==location.origin;m(s?\"custom\":a?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((a?n.origin:\"\")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),f()}};S(\"pushState\"),S(\"replaceState\"),addEventListener(\"popstate\",f),addEventListener(\"pagehide\",()=>{m(\"leave\"),u(1)}),addEventListener(\"pageshow\",e=>{e.persisted&&(m(\"heartbeat\"),u(1))}),setInterval(()=>{m(\"heartbeat\"),u()},2e4),f()})();";
|
|
4
|
+
export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VV227cNhD9FZswBBKeMLbjGIUEZtEgfXDhJkE3eTLcmKZGEhGJVMnROtuV/r2QVuv1JXmTZsi5nDlnyLlQ7zbGu0gHqHJvugYdSdOFgI6WJtiWgBQuZK5JRyQZLSHExxZ0eeuto77HhYzBJInD+4Ovf19xhit0FBngaBeyClhktuCH1PeHUQSkLrhsm92rsRQT1i15GbTLffP16+WHheSi7//SVM1GLo4ZA6fmHEvUwVSfddBN5LU3mqx3Mk5WAUE5WSJxFrBgYiFjbQ3yE7g4F1mNdKDBgskKH/gMwYEvrln0XTDIgDWY265hwIxuWm1Lx4ARhsniHaEjdiNm9GiXqqPmGzvGZ9koSTjXi4XaDOJ6d+ZGkRgorDdWRYzRerckH3SJ8lvoe88FvDArC0aFvn/u0GCShL+wKiNA9/2L8F2S/Ln89FG2OkR8ca0ToH8SrVPTnUjButIWa66FGIwmU22sGqs1KgwjrLnCB3asbLTkQza2mfe9GmdU7/tZ9f14dQ4zjCfsNJoaWnUyc6NU1zdQ7ekZsMAQMCx2RHvhEdIHW1qXrrzND06gU/jAc69KGdt5Nm9PxMRIL2t0JVVPSekmUo5Z3tf+jl8/638ziuEyTwm2RE89RHT0O6UfNKF0/p6LQdxsuRaU42LiWhZktP/hu4tzfJMku8zvTjNRys7FyhbEvWx9y8VEYS4yTBKnV7bU5IOM6PL3qI13PEIQfV8gmYpH2DRIlc9T9vnT8guDO5+v0wDfEVtd2xWmp4OQE9B8kv0goJyTJ0nHUQzQKI5AEMUjtPa9ZKVsu1jxzdTuZZ7eHm3sII82XpJfTrDwNxditLTD7Y68l3lqYabBZZ7mQOsWUwSnG0wJWk1V+qDc8W90PIjn7O2FgN1Y0wrINhhJN+0E9r8dOoNpe3wMuihsbTXhsu7K1ICUMg4CTI06fLEN+o54LaBWEWn338Hp2cmJGKYBreahF+rJUnxRWoaHSq2ShK8UQsNZq0tcWbxnMJNNJ8mmDb7FQBZjqgchhuzeutzfy6AjBf8IZVvw7TL5QUwphTMBN78Ab8i2S3U6myQNZ6aL5BsGt0cb6vvZO9w+APjbiYA4y31P3Sh2tDvD88W28PRx1XEQAzzoSuf5H2PgKxsJHQbOTG3NdwZ7WZFCSTqUSAtpah8xEmca7joi7+B63Aevtt2/mmq8YVvl0VPJRUUPu2N7fEoMXtH0foBTfv/C+FEgr/+piNq4SI9eSxrTuoVsgydvfC1Aq5Akbt4Hh2o/z60la3hc7EBM9YL5ju5853KW7nqMsNk2lvLntV3pO6z7nqR1DsMX/EHjD+nyo25QPH4BYCw+DYvxFdjlThkTx3tiiSekn1fXOIZTMYOzVI/xrmwkH9bXeJPtPxWXUuLILJK6bes1n32AAgouhiFbcjbKeEmakAlYjs9jW2uDO8vLabe+jZMTip+6dYmVzZHBJJ2Gsxr1agzV8VMx/OpKrPz9lkIoWwxxdOZJwhvOKtSB7lDTHGMMEpEuHWFY6Zrv8jw9JwY4w/Ntn4KL7H/zTKaM3wgAAA==";
|
|
5
|
+
export const TRACKER_RAW_BYTES = 2271;
|
|
6
|
+
export const TRACKER_GZIP_BYTES = 1177;
|
|
7
|
+
export const TRACKER_HASH = "14dc90ee3a9b5ce4";
|
|
8
|
+
export const VITALS_SOURCE = "(()=>{const t=document.currentScript,e=t?.dataset.site,n=t?.dataset.endpoint||t?.src&&new URL(\"events\",t.src).href;if(!e||!n)return;const r=()=>crypto.randomUUID?.()||Math.random()+\"\";let a;try{a=sessionStorage._r||r(),sessionStorage._r=a}catch{a=r()}const s=t.dataset.visitor||a,o=location.pathname.slice(0,256),i={},c=[],d=(t,e,n)=>{try{const r=new PerformanceObserver(t=>e(t.getEntries()));r.observe({type:t,buffered:1,...n}),c.push({observer:r,handler:e})}catch{}},h=performance.getEntriesByType?.(\"navigation\")[0];h&&h.responseStart>0&&(i.TTFB=h.responseStart),d(\"paint\",t=>{for(const e of t)\"first-contentful-paint\"===e.name&&(i.FCP=e.startTime)}),d(\"largest-contentful-paint\",t=>{const e=t[t.length-1];e&&(i.LCP=e.startTime)});let l=0,f=0,m=0;d(\"layout-shift\",t=>{for(const e of t)e.hadRecentInput||(l&&e.startTime-m<1e3&&e.startTime-f<5e3?l+=e.value:(l=e.value,f=e.startTime),m=e.startTime,i.CLS=Math.max(i.CLS||0,1e3*l))});const u=[],p=new Set,v=t=>{for(const e of t)e.interactionId&&(p.add(e.interactionId),u.push(e.duration));if(!u.length)return;u.sort((t,e)=>e-t),u.length=Math.min(u.length,10);const e=performance.interactionCount||p.size;i.INP=u[Math.min(u.length-1,Math.floor(e/50))]};d(\"event\",v,{durationThreshold:40}),d(\"first-input\",v);let g=0;const y=()=>{if(g)return;for(const{observer:t,handler:e}of c)e(t.takeRecords()),t.disconnect();const t=Date.now(),r=Object.keys(i).map((e,n)=>({eventId:`${a}.${t.toString(36)}.v${n}`,sessionId:a,visitorId:s,type:\"vital\",name:e,value:Math.round(i[e]),path:o,timestamp:t,sequence:n}));if(!r.length)return;g=1;const d=new Blob([JSON.stringify({siteId:e,events:r,sentAt:t})]),h=navigator.sendBeacon?.bind(navigator);h&&h(n,d)||fetch(n,{method:\"POST\",body:d,keepalive:1}).catch(()=>{})};document.addEventListener(\"visibilitychange\",()=>{\"hidden\"===document.visibilityState&&y()}),addEventListener(\"pagehide\",y)})();";
|
|
9
|
+
export const VITALS_GZIP_BASE64 = "H4sIAAAAAAACA3VV72/bNhD9V1IhMMiV5uxl6Qe5TLCkLeAha4I4+RQEK02eLC4SqZEnd5qk/32gZOfn+k06kXfv3b17IoSKk1Y5G/AAhXaqLsEiV7X3YHGlvKmQgcBTriXKAMiDQWD2eQSsrpyx2HV4yoNXk4mF7we31xckgS1YDAnDGKc895AtTEbeQde9s9QD1t4uxupeRCjKNxU67qXVrry9XX465YR23R8S812Q0PdJsigAD+QCfdNKESAE4+wKnZcb4H/6rvOEsjdhIXslUeWtFJ7QfiwbBD4S2Zpg0Pmuk8yJwimJxlleScytLIGHwiggM/bL8QfKjGh7psTdPdOCIANmYyMjoD2d2IQr8JnzpbQKLtcB/BY8QXECBPkG8LNFbyAQSunCczceIC02FaTI1nWWgQedzhnn3PaUKV7VISft7qRPPcul1QX4FHq6I9f3LBfVU91nhc6am6aCU04SK7dmM9BL6N3sfpFPJjn3ECpnA6xQejyZTSbE8JubL2fi1SfKNEkqaSwmDMVJmzlPRtJw4LIDpElmfMCpchbBYlYX0/G0EAJ4bOWQ+sv5lQAeYsYbUwLth7yF9Bv4v8tDqV0ZgXfIC7AbzKfz+8WY7+JNvkElhZixTMxYKWaLIX/japyG3GQ/gg88l/oaFFhc2qrGriPFZPIs9bT8OIejl6Hs4zEcnRbvBfCtLGpISbF/ZNkLXKx8/soMP79YiUHgpfyHDK9dN2NzOPqpoJHFCK6OWqsGVa0A2Vb8ALuxCF6qONulnkxIxaXW5FWcsnrUEnBd+0EIlA6bWe/6ul/OmgfnkUSJU3ECU4xXxyM70MaSfYTNZ3u48EKDz4qfuzpaRcWD+RcWhi+/Xon67k2q6ZwNsaxwzhP4+XhG6X0fJzh4SsK2rN1Dv8k9hNwVOv11NqpoFKCJ00vYdhTCRsx22JrBalqTkc2e5mMjn5YLny2Xyw4UjVuL8gGuQTmv49oy5NoE5awFhWRPHcUnicCt+04o8+Jy/Rco5A/QBGIoL2VFyGgXpB24LHX67bCVPT9skaNboTd2Q44+0J5vD1vbf9tb2VKnku1MaqnTwAanSLYGZZGwuFgpsFF9o2G62mpi7uCesuhiqWNoSggoyypFFuDvGqyC1Pa74ftXw9+I+Y6THoR3Vrg1uft9dfmVhwGlyRrSxj/CUqfARrdPPQtg8TdMsaf3lOVi5zbO8wBWn4FUzp7ytbGaPH6igwcRyzTtugxQxee2BMydTpOry9VNwtZON6lmDwCVLMwW0nlP+eB6ZBhoT/vF4y9Mav054rkwAcGCJ0ns3NoUBhuVS7uBhA23ktxoDTa60+Plp6MrlAiTSUOiP73NWckN5EZDwhraU0IX/wGQzzziUQcAAA==";
|
|
10
|
+
export const VITALS_RAW_BYTES = 1873;
|
|
11
|
+
export const VITALS_GZIP_BYTES = 1042;
|
|
12
|
+
export const VITALS_HASH = "ec87741c5dfdd0cb";
|