@iann29/rastro 0.5.0 → 0.6.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 +26 -9
- package/agent/integration.md +40 -19
- package/agent/manifest.json +13 -7
- package/agent/manifest.schema.json +18 -8
- package/dist/client/federation.d.ts +18 -8
- package/dist/client/federation.d.ts.map +1 -1
- package/dist/client/federation.js +7 -1
- package/dist/client/federation.js.map +1 -1
- package/dist/client/identity.d.ts +11 -0
- package/dist/client/identity.d.ts.map +1 -0
- package/dist/client/identity.js +123 -0
- package/dist/client/identity.js.map +1 -0
- package/dist/client/index.d.ts +242 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +102 -3
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -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 +87 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +30 -19
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/people.d.ts +79 -0
- package/dist/component/people.d.ts.map +1 -0
- package/dist/component/people.js +249 -0
- package/dist/component/people.js.map +1 -0
- package/dist/component/reports.d.ts +10 -4
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +9 -5
- package/dist/component/reports.js.map +1 -1
- package/dist/component/schema.d.ts +39 -0
- package/dist/component/schema.js +30 -1
- package/dist/component/schema.js.map +1 -1
- package/dist/component/validators.d.ts +50 -0
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +27 -0
- package/dist/component/validators.js.map +1 -1
- package/dist/component/visitors.d.ts +38 -2
- package/dist/component/visitors.d.ts.map +1 -1
- package/dist/component/visitors.js +162 -42
- package/dist/component/visitors.js.map +1 -1
- package/dist/react/index.d.ts +9 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +36 -5
- package/dist/react/index.js.map +1 -1
- package/dist/tracker/generated.d.ts +6 -6
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +6 -6
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.d.ts +2 -1
- package/dist/tracker/tracker.d.ts.map +1 -1
- package/dist/tracker/tracker.js +41 -2
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/federation.md +24 -0
- package/docs/identity.md +307 -0
- package/docs/upgrading.md +58 -20
- package/llms.txt +6 -2
- package/package.json +5 -3
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +99 -0
- package/src/component/ingest.ts +46 -27
- package/src/component/people.ts +321 -0
- package/src/component/reports.ts +11 -4
- package/src/component/schema.ts +35 -0
- package/src/component/validators.ts +49 -0
- package/src/component/visitors.ts +232 -55
- package/src/tracker/generated.ts +6 -6
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { v } from "convex/values";
|
|
2
2
|
import { MAX_VISITOR_ALIASES } from "./constants.js";
|
|
3
3
|
import { fail } from "./errors.js";
|
|
4
|
-
import { sanitizeOpaqueId } from "./sanitize.js";
|
|
4
|
+
import { cleanString, sanitizeOpaqueId } from "./sanitize.js";
|
|
5
5
|
import type { Id } from "./_generated/dataModel.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
mergePersonAttributes,
|
|
8
|
+
profileSearchFields,
|
|
9
|
+
publicPerson,
|
|
10
|
+
syncPersonAttributes,
|
|
11
|
+
} from "./people.js";
|
|
12
|
+
import {
|
|
13
|
+
mutation,
|
|
14
|
+
query,
|
|
15
|
+
type MutationCtx,
|
|
16
|
+
type QueryCtx,
|
|
17
|
+
} from "./_generated/server.js";
|
|
18
|
+
import {
|
|
19
|
+
identifyVisitorFields,
|
|
20
|
+
visitorProfileValidator,
|
|
21
|
+
type VisitorProfile,
|
|
22
|
+
} from "./validators.js";
|
|
7
23
|
|
|
8
24
|
/**
|
|
9
25
|
* Folds an anonymous visitor id into the pseudonymous identity the host
|
|
@@ -18,73 +34,234 @@ export const link = mutation({
|
|
|
18
34
|
previousVisitorId: v.string(),
|
|
19
35
|
},
|
|
20
36
|
returns: v.object({ linked: v.boolean(), aliasCount: v.number() }),
|
|
21
|
-
handler:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
handler: linkVisitor,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
async function linkVisitor(
|
|
41
|
+
ctx: MutationCtx,
|
|
42
|
+
args: {
|
|
43
|
+
siteId: Id<"sites">;
|
|
44
|
+
visitorId: string;
|
|
45
|
+
previousVisitorId: string;
|
|
46
|
+
},
|
|
47
|
+
) {
|
|
48
|
+
const site = await ctx.db.get("sites", args.siteId);
|
|
49
|
+
if (!site) fail("NOT_FOUND", "site not found");
|
|
50
|
+
let visitorId: string;
|
|
51
|
+
let previousVisitorId: string;
|
|
52
|
+
try {
|
|
53
|
+
visitorId = sanitizeOpaqueId(args.visitorId, "visitorId");
|
|
54
|
+
previousVisitorId = sanitizeOpaqueId(
|
|
55
|
+
args.previousVisitorId,
|
|
56
|
+
"previousVisitorId",
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
fail(
|
|
60
|
+
"INVALID_ARGUMENT",
|
|
61
|
+
error instanceof Error ? error.message : "invalid visitor id",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (visitorId === previousVisitorId) {
|
|
65
|
+
fail("INVALID_ARGUMENT", "previousVisitorId must differ from visitorId");
|
|
66
|
+
}
|
|
67
|
+
if (await findAlias(ctx, args.siteId, visitorId)) {
|
|
68
|
+
fail("CONFLICT", "visitorId is already linked to another visitor");
|
|
69
|
+
}
|
|
70
|
+
const previousOwnsAliases = await ctx.db
|
|
71
|
+
.query("visitorAliases")
|
|
72
|
+
.withIndex("by_siteId_and_visitorId", (range) =>
|
|
73
|
+
range.eq("siteId", args.siteId).eq("visitorId", previousVisitorId),
|
|
74
|
+
)
|
|
75
|
+
.first();
|
|
76
|
+
const previousProfile = await profileById(
|
|
77
|
+
ctx,
|
|
78
|
+
args.siteId,
|
|
79
|
+
previousVisitorId,
|
|
80
|
+
);
|
|
81
|
+
if (previousOwnsAliases || previousProfile) {
|
|
82
|
+
fail("CONFLICT", "previousVisitorId already owns linked visitors");
|
|
83
|
+
}
|
|
84
|
+
const aliases = await ctx.db
|
|
85
|
+
.query("visitorAliases")
|
|
86
|
+
.withIndex("by_siteId_and_visitorId", (range) =>
|
|
87
|
+
range.eq("siteId", args.siteId).eq("visitorId", visitorId),
|
|
88
|
+
)
|
|
89
|
+
.take(MAX_VISITOR_ALIASES);
|
|
90
|
+
const existing = await findAlias(ctx, args.siteId, previousVisitorId);
|
|
91
|
+
if (existing) {
|
|
92
|
+
if (existing.visitorId !== visitorId) {
|
|
33
93
|
fail(
|
|
34
|
-
"
|
|
35
|
-
|
|
94
|
+
"CONFLICT",
|
|
95
|
+
"previousVisitorId is already linked to a different visitor",
|
|
36
96
|
);
|
|
37
97
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
98
|
+
return { linked: false, aliasCount: aliases.length };
|
|
99
|
+
}
|
|
100
|
+
if (aliases.length >= MAX_VISITOR_ALIASES) {
|
|
101
|
+
fail(
|
|
102
|
+
"LIMIT_EXCEEDED",
|
|
103
|
+
`a visitor can hold at most ${MAX_VISITOR_ALIASES} aliases`,
|
|
104
|
+
{
|
|
105
|
+
limit: MAX_VISITOR_ALIASES,
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
await ctx.db.insert("visitorAliases", {
|
|
110
|
+
siteId: args.siteId,
|
|
111
|
+
visitorId,
|
|
112
|
+
previousVisitorId,
|
|
113
|
+
linkedAt: Date.now(),
|
|
114
|
+
});
|
|
115
|
+
return { linked: true, aliasCount: aliases.length + 1 };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Upsert a person and link the pre-login visit in the same transaction. */
|
|
119
|
+
export const identify = mutation({
|
|
120
|
+
args: { siteId: v.id("sites"), ...identifyVisitorFields },
|
|
121
|
+
returns: visitorProfileValidator,
|
|
122
|
+
handler: async (ctx, args) => {
|
|
123
|
+
if (!(await ctx.db.get("sites", args.siteId)))
|
|
124
|
+
fail("NOT_FOUND", "site not found");
|
|
125
|
+
const visitorId = validatedVisitorId(args.visitorId);
|
|
41
126
|
if (await findAlias(ctx, args.siteId, visitorId)) {
|
|
42
127
|
fail("CONFLICT", "visitorId is already linked to another visitor");
|
|
43
128
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
.take(MAX_VISITOR_ALIASES);
|
|
59
|
-
const existing = await findAlias(ctx, args.siteId, previousVisitorId);
|
|
60
|
-
if (existing) {
|
|
61
|
-
if (existing.visitorId !== visitorId) {
|
|
129
|
+
const existing = await profileById(ctx, args.siteId, visitorId);
|
|
130
|
+
const field = (
|
|
131
|
+
value: string | null | undefined,
|
|
132
|
+
previous: string | undefined,
|
|
133
|
+
name: string,
|
|
134
|
+
max: number,
|
|
135
|
+
) => {
|
|
136
|
+
if (value === undefined) return previous;
|
|
137
|
+
if (value === null) return undefined;
|
|
138
|
+
if (value.length > max)
|
|
139
|
+
fail("INVALID_ARGUMENT", `${name} exceeds ${max} characters`);
|
|
140
|
+
const cleaned = cleanString(value, max);
|
|
141
|
+
if (!cleaned)
|
|
62
142
|
fail(
|
|
63
|
-
"
|
|
64
|
-
|
|
143
|
+
"INVALID_ARGUMENT",
|
|
144
|
+
`${name} must not be empty; use null to remove it`,
|
|
65
145
|
);
|
|
66
|
-
|
|
67
|
-
|
|
146
|
+
return cleaned;
|
|
147
|
+
};
|
|
148
|
+
const name = field(args.name, existing?.name, "name", 160);
|
|
149
|
+
const email = field(args.email, existing?.email, "email", 320);
|
|
150
|
+
const attributes = mergePersonAttributes(
|
|
151
|
+
existing?.attributes,
|
|
152
|
+
args.attributes,
|
|
153
|
+
);
|
|
154
|
+
const attributesChanged =
|
|
155
|
+
JSON.stringify(existing?.attributes ?? {}) !== JSON.stringify(attributes);
|
|
156
|
+
if (email !== undefined && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(email)) {
|
|
157
|
+
fail("INVALID_ARGUMENT", "email must be an email address");
|
|
68
158
|
}
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
159
|
+
if (args.previousVisitorId !== undefined) {
|
|
160
|
+
const previousVisitorId = validatedVisitorId(args.previousVisitorId);
|
|
161
|
+
if (previousVisitorId !== visitorId) {
|
|
162
|
+
await linkVisitor(ctx, {
|
|
163
|
+
siteId: args.siteId,
|
|
164
|
+
visitorId,
|
|
165
|
+
previousVisitorId,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
77
168
|
}
|
|
78
|
-
|
|
79
|
-
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
const changed =
|
|
171
|
+
!existing ||
|
|
172
|
+
existing.name !== name ||
|
|
173
|
+
existing.email !== email ||
|
|
174
|
+
attributesChanged;
|
|
175
|
+
const profile = {
|
|
80
176
|
visitorId,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
177
|
+
...(name === undefined ? {} : { name }),
|
|
178
|
+
...(email === undefined ? {} : { email }),
|
|
179
|
+
...(Object.keys(attributes).length ? { attributes } : {}),
|
|
180
|
+
identifiedAt: existing?.identifiedAt ?? now,
|
|
181
|
+
updatedAt: changed ? now : existing.updatedAt,
|
|
182
|
+
};
|
|
183
|
+
if (!existing)
|
|
184
|
+
await ctx.db.insert("visitorProfiles", {
|
|
185
|
+
siteId: args.siteId,
|
|
186
|
+
...profile,
|
|
187
|
+
...profileSearchFields(name, email),
|
|
188
|
+
});
|
|
189
|
+
else if (changed || existing.searchName === undefined)
|
|
190
|
+
await ctx.db.patch("visitorProfiles", existing._id, {
|
|
191
|
+
name,
|
|
192
|
+
email,
|
|
193
|
+
attributes: Object.keys(attributes).length ? attributes : undefined,
|
|
194
|
+
...profileSearchFields(name, email),
|
|
195
|
+
updatedAt: profile.updatedAt,
|
|
196
|
+
});
|
|
197
|
+
if (attributesChanged)
|
|
198
|
+
await syncPersonAttributes(ctx, args.siteId, visitorId, attributes);
|
|
199
|
+
return profile;
|
|
85
200
|
},
|
|
86
201
|
});
|
|
87
202
|
|
|
203
|
+
export const getProfile = query({
|
|
204
|
+
args: { siteId: v.id("sites"), visitorId: v.string() },
|
|
205
|
+
returns: v.union(visitorProfileValidator, v.null()),
|
|
206
|
+
handler: (ctx, args) =>
|
|
207
|
+
readVisitorProfile(ctx, args.siteId, validatedVisitorId(args.visitorId)),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
function validatedVisitorId(value: string) {
|
|
211
|
+
try {
|
|
212
|
+
if (value.length > 128) throw new Error("visitorId exceeds 128 characters");
|
|
213
|
+
return sanitizeOpaqueId(value, "visitorId");
|
|
214
|
+
} catch (error) {
|
|
215
|
+
fail(
|
|
216
|
+
"INVALID_ARGUMENT",
|
|
217
|
+
error instanceof Error ? error.message : "invalid visitorId",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function profileById(ctx: QueryCtx, siteId: Id<"sites">, visitorId: string) {
|
|
223
|
+
return ctx.db
|
|
224
|
+
.query("visitorProfiles")
|
|
225
|
+
.withIndex("by_siteId_and_visitorId", (q) =>
|
|
226
|
+
q.eq("siteId", siteId).eq("visitorId", visitorId),
|
|
227
|
+
)
|
|
228
|
+
.unique();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function readVisitorProfile(
|
|
232
|
+
ctx: QueryCtx,
|
|
233
|
+
siteId: Id<"sites">,
|
|
234
|
+
visitorId: string,
|
|
235
|
+
): Promise<VisitorProfile | null> {
|
|
236
|
+
const alias = await findAlias(ctx, siteId, visitorId);
|
|
237
|
+
const row = await profileById(ctx, siteId, alias?.visitorId ?? visitorId);
|
|
238
|
+
if (!row) return null;
|
|
239
|
+
const { _id, _creationTime, siteId: _siteId, ...profile } = publicPerson(row);
|
|
240
|
+
return profile;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Enrich a bounded report page; repeated sessions share one indexed lookup. */
|
|
244
|
+
export async function withVisitorProfiles<
|
|
245
|
+
Row extends { siteId: Id<"sites">; visitorId: string },
|
|
246
|
+
>(
|
|
247
|
+
ctx: QueryCtx,
|
|
248
|
+
rows: Row[],
|
|
249
|
+
): Promise<Array<Row & { profile?: VisitorProfile }>> {
|
|
250
|
+
const profiles = new Map<string, Promise<VisitorProfile | null>>();
|
|
251
|
+
return Promise.all(
|
|
252
|
+
rows.map(async (row) => {
|
|
253
|
+
const key = JSON.stringify([row.siteId, row.visitorId]);
|
|
254
|
+
let pending = profiles.get(key);
|
|
255
|
+
if (!pending) {
|
|
256
|
+
pending = readVisitorProfile(ctx, row.siteId, row.visitorId);
|
|
257
|
+
profiles.set(key, pending);
|
|
258
|
+
}
|
|
259
|
+
const profile = await pending;
|
|
260
|
+
return profile ? { ...row, profile } : row;
|
|
261
|
+
}),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
88
265
|
/** The identity a visitor id resolves to, followed by every alias it owns. */
|
|
89
266
|
export async function resolveVisitorIdentities(
|
|
90
267
|
ctx: QueryCtx,
|
package/src/tracker/generated.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Generated by scripts/build-tracker.mjs. Do not edit.
|
|
2
|
-
export const RASTRO_VERSION = "0.
|
|
3
|
-
export const TRACKER_SOURCE = "(()=>{const t=document.currentScript,e=t?.dataset.site,i=t?.dataset.endpoint||t?.src&&new URL(\"events\",t.src).href;if(!e||!i)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",
|
|
4
|
-
export const TRACKER_GZIP_BASE64 = "
|
|
5
|
-
export const TRACKER_RAW_BYTES =
|
|
6
|
-
export const TRACKER_GZIP_BYTES =
|
|
7
|
-
export const TRACKER_HASH = "
|
|
2
|
+
export const RASTRO_VERSION = "0.6.0";
|
|
3
|
+
export const TRACKER_SOURCE = "(()=>{const t=document.currentScript,e=t?.dataset.site,i=t?.dataset.endpoint||t?.src&&new URL(\"events\",t.src).href;if(!e||!i)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",r=new URLSearchParams(location.search),s=r.get(\"ref\")?.slice(0,64);let o;for(const t of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const e=r.get(\"utm_\"+t)?.slice(0,64);e&&((o??={})[\"utm_\"+t]=e)}const a=\"gclid gbraid wbraid msclkid ttclid twclid li_fat_id ScCid rdt_cid dclid epik srsltid mc_cid mc_eid _hsenc igsh igshid fbclid\".split(\" \").filter(t=>r.has(t)).slice(0,8);let c,d,l,h,f=t.dataset.visitor||\"\",p=Boolean(f);try{f||=localStorage._rv||=n()}catch{}try{d=sessionStorage,l=d._r,c=d._ri==f&&d._rt,h=s||d._a,h&&(d._a=h),o||=d._u&&JSON.parse(d._u),o&&(d._u=JSON.stringify(o))}catch{h=s}let v,g=0;const u=[];let m=document.referrer.split(\"/\",3).join(\"/\")||void 0;const _=t=>{const n=u.splice(0,50);if(!n.length)return;const r=()=>new Blob([JSON.stringify({siteId:e,events:n,sentAt:Date.now()})]);let s=r();for(;s.size>64e3&&n.length>1;)u.unshift(n.pop()),s=r();t&&navigator.sendBeacon(i,s)||fetch(i,{method:\"POST\",body:s,keepalive:1}).catch(()=>{}),u.length&&_(t)},y=(t,e,i)=>{const r=Date.now(),s=r-c;c=r,s<18e5||(l=n(),\"pageview\"!=t&&(S=\"\",w()));try{d._r=l,d._ri=f,d._rt=r}catch{}u.push({eventId:`${l}.${r.toString(36)}.${g}`,sessionId:l,visitorId:f||l,type:t,name:e,path:location.pathname.slice(0,256),referrer:m,timestamp:r,sequence:g++,affiliateSlug:h,...i}),clearTimeout(v),v=setTimeout(_,1200)};let S;const w=()=>{const t=location.pathname;t!=S&&(S=t,y(\"pageview\",void 0,{properties:o,clid:a.length?a:void 0}))};window.rastro=(e,i,r)=>{if(\"context\"==e)return{sessionId:l,visitorId:f||l,identified:p};if(\"identify\"==e||\"reset\"==e){if(\"identify\"==e&&(!i||i.length>128||!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(i)))return;if(\"identify\"==e&&i==f)return;_();const r=\"reset\"==e||p;if(p=\"identify\"==e,f=\"identify\"==e?i:n(),t.dataset.visitor=\"identify\"==e?f:\"\",r){h=void 0,o=void 0,m=void 0,a.length=0;try{\"reset\"==e&&(localStorage._rv=f),d._a=\"\",d._u=\"\"}catch{}}c=0,g=0,S=\"\",w()}\"event\"==e&&y(\"custom\",`${i||\"event\"}`.slice(0,80),r&&JSON.stringify(r).length>2e4?void 0:{properties:r})},document.addEventListener(\"click\",t=>{const e=t.target?.closest(\"a,button,[data-rastro-event]\");if(!e)return;const i=e.dataset.rastroEvent,n=e.href,r=n&&new URL(n),s=/^https?:$/.test(r?.protocol),o=s&&r.origin!=location.origin;y(i?\"custom\":o?\"outbound\":\"click\",i,{target:(e.dataset.rastroLabel||e.innerText||e.tagName).slice(0,64),href:s?((o?r.origin:\"\")+r.pathname).slice(0,256):void 0})},1);for(const t of[\"pushState\",\"replaceState\"]){const e=history[t];history[t]=(...t)=>{e.apply(history,t),w()}}addEventListener(\"popstate\",w);let b=0;const I=()=>{y(\"heartbeat\"),_()},L=()=>{b&&(clearInterval(b),b=0,y(\"leave\"),_(1))},E=()=>{document.hidden?L():b||(I(),b=setInterval(I,2e4))};addEventListener(\"pagehide\",L),addEventListener(\"pageshow\",E),document.addEventListener(\"visibilitychange\",E),b=setInterval(I,2e4),w(),E()})();";
|
|
4
|
+
export const TRACKER_GZIP_BASE64 = "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";
|
|
5
|
+
export const TRACKER_RAW_BYTES = 2999;
|
|
6
|
+
export const TRACKER_GZIP_BYTES = 1565;
|
|
7
|
+
export const TRACKER_HASH = "6246dc584683303a";
|
|
8
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 s=t.dataset.visitor||\"\";try{s||=localStorage._rv||=r()}catch{}const o=Date.now();let a;try{a=sessionStorage._ri===s&&o-sessionStorage._rt<18e5&&sessionStorage._r||r(),sessionStorage._r=a,sessionStorage._ri=s,sessionStorage._rt=o}catch{a=r()}const i=s||a,c=location.pathname.slice(0,256),d={},h=[],l=(t,e,n)=>{try{const r=new PerformanceObserver(t=>e(t.getEntries()));r.observe({type:t,buffered:1,...n}),h.push({observer:r,handler:e})}catch{}},f=performance.getEntriesByType?.(\"navigation\")[0];f&&f.responseStart>0&&(d.TTFB=f.responseStart),l(\"paint\",t=>{for(const e of t)\"first-contentful-paint\"===e.name&&(d.FCP=e.startTime)}),l(\"largest-contentful-paint\",t=>{const e=t[t.length-1];e&&(d.LCP=e.startTime)});let m=0,u=0,v=0;l(\"layout-shift\",t=>{for(const e of t)e.hadRecentInput||(m&&e.startTime-v<1e3&&e.startTime-u<5e3?m+=e.value:(m=e.value,u=e.startTime),v=e.startTime,d.CLS=Math.max(d.CLS||0,1e3*m))});const g=[],p=new Set,S=t=>{for(const e of t)e.interactionId&&(p.add(e.interactionId),g.push(e.duration));if(!g.length)return;g.sort((t,e)=>e-t),g.length=Math.min(g.length,10);const e=performance.interactionCount||p.size;d.INP=g[Math.min(g.length-1,Math.floor(e/50))]};l(\"event\",S,{durationThreshold:40}),l(\"first-input\",S);let y=0;const b=()=>{if(y)return;for(const{observer:t,handler:e}of h)e(t.takeRecords()),t.disconnect();const t=Date.now(),r=Object.keys(d).map((e,n)=>({eventId:`${a}.${t.toString(36)}.v${n}`,sessionId:a,visitorId:i,type:\"vital\",name:e,value:Math.round(d[e]),path:c,timestamp:t,sequence:n}));if(!r.length)return;y=1;const s=new Blob([JSON.stringify({siteId:e,events:r,sentAt:t})]),o=navigator.sendBeacon?.bind(navigator);o&&o(n,s)||fetch(n,{method:\"POST\",body:s,keepalive:1}).catch(()=>{})};document.addEventListener(\"visibilitychange\",()=>{\"hidden\"===document.visibilityState&&b()}),addEventListener(\"pagehide\",b)})();";
|
|
9
9
|
export const VITALS_GZIP_BASE64 = "H4sIAAAAAAACA3VVbW/bNhD+K6lQCORKc/ayFINcJlj6AnjImqBOPwXBSosniatEauTJnSbpvw+i7MSJ22/SkXf33N1zDwmh4rxLrfF4gkLZtKnAIE8b58DgOnW6RgYCL7iSKD0g9xqBmUMLGFVbbbDv8YJ7l8axgW8nnz9dkQi2YNBHDEc75YWDbKkz8gL6/oWhDrBxZjlld2KEkrq2RsudNMpWnz+v3l1wQvv+T4nFzkjoqyhaloAnXuADhq32Gq3r+yhaoms73/eitKks12idzIH/5bZ9LxyhQyoxLbphymrFO4nAjf1GaAgqg7sUHrzX1jy6ayGEj2M7OzrBN4vf4CyOjw763hHKjsxCsu9E98dGFHaHVk7IA2QtfN9Llob6UFvDa4mFkRVwX+oUyJz9cvaaMiW6gRXi7p6VgiADZsZRj9XtGz6O6QZcZl0lTQrXGw9uC46gOAeCPAd8b9Bp8IRSunTcThdIh20NCbJNk2XgQCULxjk3A2UFrxtfkG530yWOFdKoElwCw0PrB5aJ+jHvQaLL9rat4YKTyMitzkN5Eb2b3y+zOM64A19b42GN0uH5PI6J4re3Hy7FsyPKShLVUhuMGIrzLrOOTEXDic1OkEaZdh5nqTUIBrOmnE23hRDAx1aG0B/e3gjgfox4qyugQ4hbSpfD95xDql0agXfISzA5FrPF/XKKd3UUL1CuEnPWiDnbivkyxG9tgzNf6OxH8IEXUn2CFAyuTN1g35Mqjg9Cz7ZvFnD61NS8OYPTi+qVAL6VZQMJqfafrHmCi20Pf5nib6/WIqxgJf8l4bfv52wBpz9VdKxiApePXKsDq9aAbC1+gF0bBCfTcbYrFcek5lIp8sxOWT5xCbhqXCACpUE78l1f9/KRc28dkpHiVJzDDEfX6coOtDZkb2GL+R4uPOHgQfK3thnFrOZe/wdLxVcfb0R+dxRqtmDBlpXWOgI/n80pvR/GCQbVi9iadXvot4UDX9hSJb/OJxZNBNTj9CK2nojQivkO2yaIYacz0u7LfGjk43LhwXLZ7KSg49ai/AqfILVOjWvLkCvtU2sMpEj2peOB6jEnrjd/Q4r8K7SeKMorWRMyyQXpQi0rlXx52cmBv+yQo12j0yYnp6/pwLcvOzN82WvXSiWS7bR4pRLNglJEW42yjNi4WAmwiX2TpNvGKKLu4J6yUcWSlKGuwKOs6gSZh38aMCkkZtgN3z0bfisWu5p8IN5laTfk7o/19UfuA0qdtaQb36yVSoBN71HimAeDv2OCA72nzIqd2ljHPRh1CTK15oJvtFHk4YgubRxbYpinfZ8BpgUxrKsAC6uS6OZ6fRuxjVVt4tlXgFqWegvJYqA8qB4JAx3osHx4ZKVS70c8V9ojGHAkGju30aXGNi2kySFiwSsqtFJgRnV6cH68ukaJEMcbMurTccxa5lBoBRHb0IESuvwfweV+3/MHAAA=";
|
|
10
10
|
export const VITALS_RAW_BYTES = 2035;
|