@faststats/web 0.2.6 → 0.2.8
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/CHANGELOG.md +25 -0
- package/dist/chunks/analytics-Cmawx5f9.js +1 -0
- package/dist/chunks/api-urls-BrkcoElX.js +1 -0
- package/dist/{error-Df3cDcoP.js → chunks/error-1K0dai1m.js} +2 -2
- package/dist/chunks/feature-flags-6rZlmhfu.d.ts +18 -0
- package/dist/chunks/feature-flags-BPRTgvIh.js +1 -0
- package/dist/chunks/replay-CtMtn0C-.js +1 -0
- package/dist/chunks/replay-IhsP2Ab4.d.ts +77 -0
- package/dist/chunks/rolldown-runtime-MP-BAFHD.js +1 -0
- package/dist/chunks/types-C3vW7XGe.js +1 -0
- package/dist/chunks/web-vitals-BooBPWJC.js +1 -0
- package/dist/error.d.ts +44 -0
- package/dist/error.js +1 -0
- package/dist/feature-flags.d.ts +2 -0
- package/dist/feature-flags.js +1 -0
- package/dist/index.d.ts +140 -0
- package/dist/index.js +1 -0
- package/dist/replay.d.ts +2 -0
- package/dist/replay.js +1 -0
- package/dist/web-vitals.d.ts +27 -0
- package/dist/web-vitals.js +1 -0
- package/package.json +32 -11
- package/scripts/check-bundle-size.mjs +90 -9
- package/src/analytics.ts +47 -41
- package/src/entries/error.ts +6 -0
- package/src/entries/feature-flags.ts +5 -0
- package/src/entries/main.ts +21 -0
- package/src/entries/replay.ts +1 -0
- package/src/entries/web-vitals.ts +1 -0
- package/src/env.d.ts +2 -0
- package/src/error.ts +5 -0
- package/src/replay.ts +110 -62
- package/src/sdk.ts +8 -0
- package/src/utils/types.ts +1 -1
- package/src/web-vitals.ts +32 -14
- package/tests/replay.test.ts +178 -16
- package/tsdown.config.ts +16 -1
- package/dist/analytics-D_dgxSVU.js +0 -1
- package/dist/module.d.ts +0 -531
- package/dist/module.js +0 -1
- package/dist/replay-alCILAnP.js +0 -1
- package/dist/types-Df70G0eM.js +0 -1
- package/dist/web-vitals-gcUR-TX_.js +0 -1
- package/src/module.ts +0 -25
package/src/web-vitals.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type MetricWithAttribution,
|
|
3
|
-
onCLS,
|
|
4
|
-
onFCP,
|
|
5
|
-
onINP,
|
|
6
|
-
onLCP,
|
|
7
|
-
onTTFB,
|
|
8
|
-
} from "web-vitals/attribution";
|
|
1
|
+
import type { Metric, MetricWithAttribution } from "web-vitals";
|
|
9
2
|
import { webVitalsEventsUrl } from "./utils/api-urls";
|
|
10
3
|
import { getOrCreateSessionId } from "./utils/identifiers";
|
|
11
4
|
import { normalizeSamplingPercentage } from "./utils/types";
|
|
@@ -15,6 +8,7 @@ export interface WebVitalsOptions {
|
|
|
15
8
|
baseUrl?: string;
|
|
16
9
|
debug?: boolean;
|
|
17
10
|
samplingPercentage?: number;
|
|
11
|
+
attribution?: boolean;
|
|
18
12
|
}
|
|
19
13
|
|
|
20
14
|
type MetricName = "CLS" | "INP" | "LCP" | "FCP" | "TTFB";
|
|
@@ -24,7 +18,19 @@ type MetricState = {
|
|
|
24
18
|
attributes: Record<string, unknown>;
|
|
25
19
|
};
|
|
26
20
|
|
|
27
|
-
|
|
21
|
+
type AnyMetric = Metric | MetricWithAttribution;
|
|
22
|
+
type Observer = (cb: (metric: AnyMetric) => void) => void;
|
|
23
|
+
|
|
24
|
+
async function loadObservers(attribution: boolean): Promise<Observer[]> {
|
|
25
|
+
if (attribution) {
|
|
26
|
+
const { onCLS, onFCP, onINP, onLCP, onTTFB } = await import(
|
|
27
|
+
"web-vitals/attribution"
|
|
28
|
+
);
|
|
29
|
+
return [onCLS, onFCP, onINP, onLCP, onTTFB] as Observer[];
|
|
30
|
+
}
|
|
31
|
+
const { onCLS, onFCP, onINP, onLCP, onTTFB } = await import("web-vitals");
|
|
32
|
+
return [onCLS, onFCP, onINP, onLCP, onTTFB] as Observer[];
|
|
33
|
+
}
|
|
28
34
|
|
|
29
35
|
export default class WebVitalsTracker {
|
|
30
36
|
private readonly endpoint: string;
|
|
@@ -58,9 +64,7 @@ export default class WebVitalsTracker {
|
|
|
58
64
|
|
|
59
65
|
this.log("Tracking started");
|
|
60
66
|
|
|
61
|
-
|
|
62
|
-
observe(this.captureMetric);
|
|
63
|
-
}
|
|
67
|
+
void this.observe();
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
stop(): void {
|
|
@@ -73,14 +77,28 @@ export default class WebVitalsTracker {
|
|
|
73
77
|
this.flush();
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
private async observe(): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
const observers = await loadObservers(this.options.attribution ?? false);
|
|
83
|
+
if (!this.started) return;
|
|
84
|
+
for (const observe of observers) {
|
|
85
|
+
observe(this.captureMetric);
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.log("Failed to load web-vitals", error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
private onVisibilityChange = (): void => {
|
|
77
93
|
if (document.visibilityState === "hidden") this.flush();
|
|
78
94
|
};
|
|
79
95
|
|
|
80
|
-
private captureMetric = (metric:
|
|
96
|
+
private captureMetric = (metric: AnyMetric): void => {
|
|
81
97
|
if (this.flushed || !this.sampled) return;
|
|
82
98
|
|
|
83
99
|
const name = metric.name as MetricName;
|
|
100
|
+
const attribution =
|
|
101
|
+
(metric as MetricWithAttribution).attribution ?? undefined;
|
|
84
102
|
|
|
85
103
|
this.metrics.set(name, {
|
|
86
104
|
value: metric.value,
|
|
@@ -89,7 +107,7 @@ export default class WebVitalsTracker {
|
|
|
89
107
|
rating: metric.rating,
|
|
90
108
|
delta: metric.delta,
|
|
91
109
|
navigationType: metric.navigationType,
|
|
92
|
-
...(
|
|
110
|
+
...(attribution ?? {}),
|
|
93
111
|
},
|
|
94
112
|
});
|
|
95
113
|
|
package/tests/replay.test.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { gunzipSync } from "node:zlib";
|
|
2
3
|
import ReplayTracker from "../src/replay";
|
|
4
|
+
import { getOrCreateSessionId } from "../src/utils/identifiers";
|
|
5
|
+
|
|
6
|
+
function decodeBody(data: string | Uint8Array): string {
|
|
7
|
+
if (typeof data === "string") return data;
|
|
8
|
+
return gunzipSync(data).toString("utf8");
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
class MockStorage {
|
|
5
12
|
private readonly data = new Map<string, string>();
|
|
@@ -36,13 +43,14 @@ type ReplayBatch = {
|
|
|
36
43
|
type ReplayTrackerInternals = {
|
|
37
44
|
events: ReplayEvent[];
|
|
38
45
|
pending: ReplayBatch[];
|
|
46
|
+
pendingSizeBytes: number;
|
|
39
47
|
minLengthFlushTask: ReturnType<typeof setTimeout> | null;
|
|
40
48
|
retryTask: ReturnType<typeof setTimeout> | null;
|
|
41
49
|
startTime: number;
|
|
42
50
|
onEvent: (event: ReplayEvent, isCheckout?: boolean) => void;
|
|
43
51
|
flush: (lowLatency: boolean) => Promise<void>;
|
|
44
52
|
send: (
|
|
45
|
-
data: string |
|
|
53
|
+
data: string | Uint8Array,
|
|
46
54
|
isCompressed: boolean,
|
|
47
55
|
lowLatency: boolean,
|
|
48
56
|
) => Promise<boolean>;
|
|
@@ -166,13 +174,13 @@ describe("ReplayTracker", () => {
|
|
|
166
174
|
minReplayLengthMs: 0,
|
|
167
175
|
});
|
|
168
176
|
|
|
169
|
-
|
|
170
|
-
|
|
177
|
+
const captured: { payload: ReplayBatch | null; lowLatency: boolean } = {
|
|
178
|
+
payload: null,
|
|
179
|
+
lowLatency: false,
|
|
180
|
+
};
|
|
171
181
|
tracker.send = async (data, _isCompressed, nextLowLatency) => {
|
|
172
|
-
payload = JSON.parse(
|
|
173
|
-
|
|
174
|
-
) as ReplayBatch;
|
|
175
|
-
lowLatency = nextLowLatency;
|
|
182
|
+
captured.payload = JSON.parse(decodeBody(data)) as ReplayBatch;
|
|
183
|
+
captured.lowLatency = nextLowLatency;
|
|
176
184
|
return true;
|
|
177
185
|
};
|
|
178
186
|
tracker.startTime = Date.now() - 1000;
|
|
@@ -180,30 +188,129 @@ describe("ReplayTracker", () => {
|
|
|
180
188
|
|
|
181
189
|
await tracker.flush(true);
|
|
182
190
|
|
|
183
|
-
expect(payload).not.toBeNull();
|
|
184
|
-
expect(payload?.token).toBe("site_test");
|
|
185
|
-
expect(payload?.sessionId).toBeTruthy();
|
|
186
|
-
expect(payload?.events).toHaveLength(1);
|
|
187
|
-
expect(lowLatency).toBe(true);
|
|
191
|
+
expect(captured.payload).not.toBeNull();
|
|
192
|
+
expect(captured.payload?.token).toBe("site_test");
|
|
193
|
+
expect(captured.payload?.sessionId).toBeTruthy();
|
|
194
|
+
expect(captured.payload?.events).toHaveLength(1);
|
|
195
|
+
expect(captured.lowLatency).toBe(true);
|
|
188
196
|
expect(tracker.pending.length).toBe(0);
|
|
189
197
|
});
|
|
190
198
|
|
|
191
|
-
test("
|
|
199
|
+
test("keeps the same replay session id across later batches", async () => {
|
|
192
200
|
const tracker = createTracker({
|
|
193
201
|
minReplayLengthMs: 0,
|
|
194
202
|
});
|
|
195
203
|
|
|
196
|
-
|
|
204
|
+
const payloads: ReplayBatch[] = [];
|
|
197
205
|
tracker.send = async (data) => {
|
|
198
|
-
|
|
206
|
+
payloads.push(JSON.parse(decodeBody(data)) as ReplayBatch);
|
|
199
207
|
return true;
|
|
200
208
|
};
|
|
209
|
+
|
|
210
|
+
(tracker as unknown as { sessionId?: string }).sessionId =
|
|
211
|
+
getOrCreateSessionId();
|
|
201
212
|
tracker.startTime = Date.now() - 1000;
|
|
202
213
|
tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
|
|
214
|
+
await tracker.flush(false);
|
|
203
215
|
|
|
216
|
+
const storage = globalThis.sessionStorage as unknown as MockStorage;
|
|
217
|
+
storage.setItem("session_timestamp", "0");
|
|
218
|
+
|
|
219
|
+
tracker.onEvent({ type: 3, timestamp: Date.now(), data: {} }, false);
|
|
204
220
|
await tracker.flush(false);
|
|
205
221
|
|
|
206
|
-
expect(
|
|
222
|
+
expect(payloads).toHaveLength(2);
|
|
223
|
+
expect(payloads[0]?.sessionId).toBeTruthy();
|
|
224
|
+
expect(payloads[1]?.sessionId).toBe(payloads[0]?.sessionId);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("sends replay as plain JSON when CompressionStream is unavailable", async () => {
|
|
228
|
+
const tracker = createTracker({
|
|
229
|
+
minReplayLengthMs: 0,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const captured: { bodyType: "string" | "blob" | null } = {
|
|
233
|
+
bodyType: null,
|
|
234
|
+
};
|
|
235
|
+
tracker.send = async (data) => {
|
|
236
|
+
captured.bodyType = typeof data === "string" ? "string" : "blob";
|
|
237
|
+
return true;
|
|
238
|
+
};
|
|
239
|
+
tracker.startTime = Date.now() - 1000;
|
|
240
|
+
tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
|
|
241
|
+
|
|
242
|
+
await tracker.flush(false);
|
|
243
|
+
|
|
244
|
+
expect(captured.bodyType).toBe("string");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("sends replay as gzip Uint8Array when CompressionStream is available", async () => {
|
|
248
|
+
setGlobal("window", {
|
|
249
|
+
...(globalThis.window as unknown as Record<string, unknown>),
|
|
250
|
+
CompressionStream: globalThis.CompressionStream,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const tracker = createTracker({
|
|
254
|
+
minReplayLengthMs: 0,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const captured: {
|
|
258
|
+
bodyType: "string" | "binary" | null;
|
|
259
|
+
compressed: boolean;
|
|
260
|
+
size: number;
|
|
261
|
+
} = { bodyType: null, compressed: false, size: 0 };
|
|
262
|
+
tracker.send = async (data, isCompressed) => {
|
|
263
|
+
captured.bodyType = typeof data === "string" ? "string" : "binary";
|
|
264
|
+
captured.compressed = isCompressed;
|
|
265
|
+
if (data instanceof Uint8Array) {
|
|
266
|
+
captured.size = data.byteLength;
|
|
267
|
+
}
|
|
268
|
+
return true;
|
|
269
|
+
};
|
|
270
|
+
tracker.startTime = Date.now() - 1000;
|
|
271
|
+
for (let i = 0; i < 10; i++) {
|
|
272
|
+
tracker.onEvent(
|
|
273
|
+
{
|
|
274
|
+
type: 2,
|
|
275
|
+
timestamp: Date.now(),
|
|
276
|
+
data: { i, payload: "x".repeat(500) },
|
|
277
|
+
},
|
|
278
|
+
false,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
await tracker.flush(false);
|
|
283
|
+
|
|
284
|
+
expect(captured.bodyType).toBe("binary");
|
|
285
|
+
expect(captured.compressed).toBe(true);
|
|
286
|
+
expect(captured.size).toBeGreaterThan(0);
|
|
287
|
+
expect(captured.size).toBeLessThan(5000);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("compresses on unload (low latency) flush", async () => {
|
|
291
|
+
setGlobal("window", {
|
|
292
|
+
...(globalThis.window as unknown as Record<string, unknown>),
|
|
293
|
+
CompressionStream: globalThis.CompressionStream,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const tracker = createTracker({
|
|
297
|
+
minReplayLengthMs: 0,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
let compressedFlag = false;
|
|
301
|
+
let lowLatencyFlag = false;
|
|
302
|
+
tracker.send = async (_data, isCompressed, lowLatency) => {
|
|
303
|
+
compressedFlag = isCompressed;
|
|
304
|
+
lowLatencyFlag = lowLatency;
|
|
305
|
+
return true;
|
|
306
|
+
};
|
|
307
|
+
tracker.startTime = Date.now() - 1000;
|
|
308
|
+
tracker.onEvent({ type: 2, timestamp: Date.now(), data: {} }, false);
|
|
309
|
+
|
|
310
|
+
await tracker.flush(true);
|
|
311
|
+
|
|
312
|
+
expect(compressedFlag).toBe(true);
|
|
313
|
+
expect(lowLatencyFlag).toBe(true);
|
|
207
314
|
});
|
|
208
315
|
|
|
209
316
|
test("keeps failed batches queued for retry", async () => {
|
|
@@ -222,4 +329,59 @@ describe("ReplayTracker", () => {
|
|
|
222
329
|
|
|
223
330
|
if (tracker.retryTask) clearTimeout(tracker.retryTask);
|
|
224
331
|
});
|
|
332
|
+
|
|
333
|
+
test("drops replay batches that exceed the queue byte limit", async () => {
|
|
334
|
+
const tracker = createTracker({
|
|
335
|
+
minReplayLengthMs: 0,
|
|
336
|
+
maxQueueSizeBytes: 600,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
let sent = 0;
|
|
340
|
+
tracker.send = async () => {
|
|
341
|
+
sent++;
|
|
342
|
+
return true;
|
|
343
|
+
};
|
|
344
|
+
tracker.startTime = Date.now() - 1000;
|
|
345
|
+
tracker.onEvent(
|
|
346
|
+
{
|
|
347
|
+
type: 2,
|
|
348
|
+
timestamp: Date.now(),
|
|
349
|
+
data: { payload: "x".repeat(1000) },
|
|
350
|
+
},
|
|
351
|
+
false,
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
await tracker.flush(false);
|
|
355
|
+
|
|
356
|
+
expect(sent).toBe(0);
|
|
357
|
+
expect(tracker.pending.length).toBe(0);
|
|
358
|
+
expect(tracker.pendingSizeBytes).toBe(0);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("keeps pending replay queue under the byte limit", async () => {
|
|
362
|
+
const tracker = createTracker({
|
|
363
|
+
minReplayLengthMs: 0,
|
|
364
|
+
maxQueueSizeBytes: 900,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
tracker.send = async () => false;
|
|
368
|
+
tracker.startTime = Date.now() - 1000;
|
|
369
|
+
|
|
370
|
+
for (let i = 0; i < 4; i++) {
|
|
371
|
+
tracker.onEvent(
|
|
372
|
+
{
|
|
373
|
+
type: 2,
|
|
374
|
+
timestamp: Date.now(),
|
|
375
|
+
data: { i, payload: "x".repeat(300) },
|
|
376
|
+
},
|
|
377
|
+
false,
|
|
378
|
+
);
|
|
379
|
+
await tracker.flush(false);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
expect(tracker.pending.length).toBeGreaterThan(0);
|
|
383
|
+
expect(tracker.pendingSizeBytes).toBeLessThanOrEqual(900);
|
|
384
|
+
|
|
385
|
+
if (tracker.retryTask) clearTimeout(tracker.retryTask);
|
|
386
|
+
});
|
|
225
387
|
});
|
package/tsdown.config.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { defineConfig } from "tsdown";
|
|
2
|
+
import pkg from "./package.json" with { type: "json" };
|
|
2
3
|
|
|
3
4
|
export default defineConfig({
|
|
4
|
-
entry:
|
|
5
|
+
entry: {
|
|
6
|
+
index: "src/entries/main.ts",
|
|
7
|
+
"feature-flags": "src/entries/feature-flags.ts",
|
|
8
|
+
replay: "src/entries/replay.ts",
|
|
9
|
+
error: "src/entries/error.ts",
|
|
10
|
+
"web-vitals": "src/entries/web-vitals.ts",
|
|
11
|
+
},
|
|
5
12
|
format: ["esm"],
|
|
6
13
|
platform: "browser",
|
|
7
14
|
outDir: "dist",
|
|
@@ -9,4 +16,12 @@ export default defineConfig({
|
|
|
9
16
|
minify: true,
|
|
10
17
|
deps: { onlyBundle: false },
|
|
11
18
|
checks: { pluginTimings: false },
|
|
19
|
+
define: {
|
|
20
|
+
__FASTSTATS_SDK_NAME__: JSON.stringify(pkg.name),
|
|
21
|
+
__FASTSTATS_SDK_VERSION__: JSON.stringify(pkg.version),
|
|
22
|
+
},
|
|
23
|
+
outputOptions: {
|
|
24
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
25
|
+
entryFileNames: "[name].js",
|
|
26
|
+
},
|
|
12
27
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,r as l,s as u,t as d,u as f}from"./types-Df70G0eM.js";async function p(e,t){if(t.projectToken&&t.projectId)throw Error(`provide either projectToken or projectId, not both`);if(t.identifier&&t.externalId)throw Error(`provide either serverId or externalId, not both`);let n=t.projectToken,r=t.projectId,i=t.identifier,o=t.externalId;if(!n&&!r)throw Error(`feature flag check requires projectToken or projectId`);if(!i&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...r?{projectId:r}:{},...i?{identifier:i}:{externalId:o}};t.attributes&&Object.keys(t.attributes).length>0&&(s.attributes=t.attributes);let c={"Content-Type":`application/json`};n&&(c.Authorization=`Bearer ${n}`);let l=a(t.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:t.signal});if(!u.ok){let e=``;try{let t=await u.json();typeof t.error==`string`&&t.error.length>0&&(e=` — ${t.error}`)}catch{}throw Error(`feature flag check failed: HTTP ${u.status}${e}`)}return await u.json()}function m(e){return e.replayOptions?.samplingPercentage!==void 0||e.sessionReplays?.sampling?.percentage!==void 0}function h(e,t,n){if(!(e.sessionReplays?.enabled??e.trackReplay??m(e)))return null;let r=e.replayOptions??{};return{siteKey:e.siteKey,baseUrl:t,debug:n,...r,samplingPercentage:d(r.samplingPercentage??e.sessionReplays?.sampling?.percentage)}}let g=null,_;function v(){return g}function y(e,t){typeof window>`u`||E()||g?.track(e,t??{})}function b(e,t,n){typeof window>`u`||E()||g?.identify(e,t,n??{})}function x(e=!0){typeof window>`u`||E()||g?.logout(e)}function S(e){if(g){g.setConsentMode(e);return}_=e}function C(){S(`granted`)}function w(){S(`denied`)}function T(e){typeof window>`u`||E()||g?.reportError(e)}function E(){if(typeof localStorage>`u`)return!1;let e=localStorage.getItem(`disable-faststats`);return e===`true`||e===`1`}async function D(e){let{url:t,data:n,contentType:r=`application/json`,headers:i={},debug:a=!1,debugPrefix:o=`[Analytics]`,useBeacon:s=!0,keepalive:c=!0}=e;if(s&&typeof navigator<`u`&&typeof navigator.sendBeacon==`function`)try{let e=n instanceof Blob?n:typeof Blob<`u`?new Blob([n],{type:r}):n;if(navigator.sendBeacon(t,e))return a&&console.log(`${o} Sent via beacon`),!0}catch{}if(typeof fetch!=`function`)return a&&console.warn(`${o} Failed to send`),!1;try{let e=await fetch(t,{method:`POST`,body:n,headers:{"Content-Type":r,...i},keepalive:c}),s=e.ok;return a&&(s?console.log(`${o} Sent via fetch`):console.warn(`${o} Failed: ${e.status}`)),s}catch{return a&&console.warn(`${o} Failed to send`),!1}}function O(e){for(;e;){if(e instanceof HTMLAnchorElement&&e.href)return e;e=e.parentNode}return null}function k(){let e={};if(!location.search)return e;let t=new URLSearchParams(location.search);for(let n of[`utm_source`,`utm_medium`,`utm_campaign`,`utm_term`,`utm_content`]){let r=t.get(n);r&&(e[n]=r)}return e}var A=class{webEndpoint;baseUrl;featureFlagsBaseUrl;debug;started=!1;destroyed=!1;pageKey=``;navTimer=null;heartbeatTimer=null;scrollDepth=0;pageEntryTime=0;pagePath=``;pageUrl=``;pageHash=``;hasLeftCurrentPage=!1;scrollHandler=null;consentMode;cookielessWhilePending;cleanupCallbacks=[];childTrackers=[];pendingReportedErrors=[];errorTracker=null;handleVisibilityChange=()=>{document.visibilityState===`hidden`?(this.leavePage(),this.stopHeartbeat()):this.startHeartbeat()};handlePageHide=()=>{this.leavePage()};handlePopState=()=>{this.navigate()};handleHashChange=()=>{this.navigate()};constructor(e){this.options=e,this.baseUrl=n(e.baseUrl),this.featureFlagsBaseUrl=r(e.featureFlagsBaseUrl),this.webEndpoint=o(this.baseUrl),this.debug=e.debug??!1,this.consentMode=e.consent?.mode??`granted`,this.cookielessWhilePending=e.consent?.cookielessWhilePending??!0,_!==void 0&&(this.consentMode=_,_=void 0),(e.autoTrack??!0)&&this.init()}log(e){this.debug&&console.log(`[Analytics] ${e}`)}init(){if(!(typeof window>`u`)){if(E()){this.log(`disabled`);return}g=this,t(this.isCookielessMode()),setTimeout(()=>void this.start(),0)}}registerCleanup(e){this.cleanupCallbacks.push(e)}addWindowListener(e,t){window.addEventListener(e,t),this.registerCleanup(()=>window.removeEventListener(e,t))}addDocumentListener(e,t){document.addEventListener(e,t),this.registerCleanup(()=>document.removeEventListener(e,t))}patchHistory(){let e=history.pushState.bind(history),t=history.replaceState.bind(history);history.pushState=(t,n,r)=>{e(t,n,r),this.navigate()},history.replaceState=(e,n,r)=>{t(e,n,r),this.navigate()},this.registerCleanup(()=>{history.pushState=e,history.replaceState=t})}ensureStarted(){return typeof window>`u`||this.destroyed||E()?!1:(this.started||this.start(),!0)}stopHeartbeat(){this.heartbeatTimer&&=(clearInterval(this.heartbeatTimer),null)}stopNavigationTimer(){this.navTimer&&=(clearTimeout(this.navTimer),null)}stopChildTrackers(){for(let e of this.childTrackers.splice(0))e.stop?.();this.errorTracker=null}registerChildTracker(e){return!this.started||this.destroyed||g!==this?(e.stop?.(),!1):(this.childTrackers.push(e),!0)}async startErrorTracker(){try{let{default:e}=await import(`./error-Df3cDcoP.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug});if(t.start(),!this.registerChildTracker(t))return;for(this.errorTracker=t;this.pendingReportedErrors.length>0;){let e=this.pendingReportedErrors.shift();e&&t.captureError(e)}this.log(`error loaded`)}catch(e){this.log(`failed to initialize error tracker: ${String(e)}`)}}async startWebVitalsTracker(){try{let{default:e}=await import(`./web-vitals-gcUR-TX_.js`);if(!this.started||this.destroyed||g!==this)return;let t=new e({siteKey:this.options.siteKey,baseUrl:this.baseUrl,debug:this.debug,samplingPercentage:d(this.options.webVitals?.sampling?.percentage)});if(t.start(),!this.registerChildTracker(t))return;this.log(`web-vitals loaded`)}catch(e){this.log(`failed to initialize web-vitals tracker: ${String(e)}`)}}async startReplayTracker(e){try{let{default:t}=await import(`./replay-alCILAnP.js`);if(!this.started||this.destroyed||g!==this)return;let n=new t(e);if(n.start(),!this.registerChildTracker(n))return;this.log(`replay loaded`)}catch(e){this.log(`failed to initialize replay tracker: ${String(e)}`)}}async start(){if(this.started||this.destroyed||typeof window>`u`)return;if(g&&g!==this){this.log(`already started by another instance`);return}if(E()){this.log(`disabled`);return}this.started=!0,g=this,t(this.isCookielessMode()),l();let e=this.options,n=h(e,this.baseUrl,this.debug);n&&this.startReplayTracker(n),e.trackErrors&&this.startErrorTracker(),e.trackWebVitals&&this.startWebVitalsTracker(),this.enterPage(),this.pageview({trigger:`load`}),this.links(),this.trackScroll(),this.startHeartbeat(),this.addDocumentListener(`visibilitychange`,this.handleVisibilityChange),this.addWindowListener(`pagehide`,this.handlePageHide),this.addWindowListener(`popstate`,this.handlePopState),e.trackHash&&this.addWindowListener(`hashchange`,this.handleHashChange),this.patchHistory()}destroy(){if(!this.destroyed){for(this.started&&typeof window<`u`&&this.leavePage(),this.pendingReportedErrors.length=0,this.stopNavigationTimer(),this.stopHeartbeat(),this.scrollHandler&&typeof window<`u`&&(window.removeEventListener(`scroll`,this.scrollHandler),this.scrollHandler=null);this.cleanupCallbacks.length>0;)this.cleanupCallbacks.pop()?.();this.stopChildTrackers(),g===this&&(g=null),this.started=!1,this.destroyed=!0}}pageview(e={}){if(!this.ensureStarted())return;let t=`${location.pathname}|${this.options.trackHash??!1?location.hash:``}`;t!==this.pageKey&&(this.pageKey=t,this.send(`pageview`,e))}track(e,t={}){this.ensureStarted()&&this.send(e,t)}identify(e,t,n={}){if(!this.ensureStarted()||this.isCookielessMode())return;let r=e.trim(),i=t.trim();!r||!i||D({url:f(this.baseUrl),data:JSON.stringify({token:this.options.siteKey,identifier:s(!1),externalId:r,email:i,name:n.name?.trim()||void 0,phone:n.phone?.trim()||void 0,avatarUrl:n.avatarUrl?.trim()||void 0,traits:n.traits??{}}),contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] identify`})}logout(e=!0){this.ensureStarted()&&(e&&c(this.isCookielessMode()),u())}setConsentMode(e){this.consentMode=e,t(this.isCookielessMode())}optIn(){this.setConsentMode(`granted`)}optOut(){this.setConsentMode(`denied`)}getConsentMode(){return this.consentMode}getAnonymousId(){return s(this.isCookielessMode())}getSessionId(){return l()}async checkFeatureFlag(e,t,n){if(typeof window>`u`||E())return{value:`false`};let r=n?.externalId?.trim();if(r)return p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,externalId:r,attributes:t,signal:n?.signal});let i=this.getAnonymousId();return i?p(e,{baseUrl:this.featureFlagsBaseUrl,projectToken:this.options.siteKey,identifier:i,attributes:t,signal:n?.signal}):{value:`false`}}reportError(e){if(this.destroyed||typeof window>`u`||E()||!(this.options.trackErrors??!1)||!this.ensureStarted())return;let t=this.errorTracker;if(t){t.captureError(e);return}this.pendingReportedErrors.length>=50&&this.pendingReportedErrors.shift(),this.pendingReportedErrors.push(e)}isCookielessMode(){return this.options.cookieless||this.consentMode===`denied`?!0:this.consentMode===`pending`?this.cookielessWhilePending:!1}send(e,t={}){if(typeof window>`u`||this.destroyed||E())return;let n=s(this.isCookielessMode()),r=JSON.stringify({token:this.options.siteKey,...n?{userId:n}:{},sessionId:l(),data:{event:e,page:location.pathname,referrer:document.referrer||null,title:document.title||``,url:location.href,...k(),...t}});this.log(e),D({url:this.webEndpoint,data:r,contentType:`text/plain`,debug:this.debug,debugPrefix:`[Analytics] ${e}`})}enterPage(){this.pageEntryTime=Date.now(),this.pagePath=location.pathname,this.pageUrl=location.href,this.pageHash=location.hash,this.scrollDepth=0,this.hasLeftCurrentPage=!1}leavePage(){if(this.destroyed||this.hasLeftCurrentPage)return;this.hasLeftCurrentPage=!0;let e=Date.now();this.send(`page_leave`,{page:this.pagePath,url:this.pageUrl,time_on_page:e-this.pageEntryTime,scroll_depth:this.scrollDepth,session_duration:e-i()})}trackScroll(){this.scrollHandler&&window.removeEventListener(`scroll`,this.scrollHandler);let e=()=>{let e=document.documentElement,t=document.body,n=window.innerHeight,r=Math.max(e.scrollHeight,t.scrollHeight);if(r<=n){this.scrollDepth=100;return}let i=Math.min(100,Math.round(((window.scrollY||e.scrollTop)+n)/r*100));i>this.scrollDepth&&(this.scrollDepth=i)};this.scrollHandler=e,e(),window.addEventListener(`scroll`,e,{passive:!0})}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{if(document.visibilityState===`hidden`){this.stopHeartbeat();return}e()},300*1e3)}navigate(){!this.started||this.destroyed||(this.stopNavigationTimer(),this.navTimer=setTimeout(()=>{this.navTimer=null;let e=location.pathname!==this.pagePath,t=(this.options.trackHash??!1)&&location.hash!==this.pageHash;!e&&!t||(this.leavePage(),this.enterPage(),this.trackScroll(),this.pageview({trigger:`navigation`}))},300))}links(){let e=e=>{let t=O(e.target);t&&t.host!==location.host&&this.track(`outbound_link`,{outbound_link:t.href})};this.addDocumentListener(`click`,e),this.addDocumentListener(`auxclick`,e)}};export{x as a,T as c,y as d,p as f,E as i,D as l,v as n,C as o,b as r,w as s,A as t,S as u};
|