@faststats/web 0.2.11 → 0.2.13
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 +12 -0
- package/dist/chunks/api-urls-DaeYkG0_.js +1 -0
- package/dist/chunks/error-CDVOMiI9.js +2 -0
- package/dist/chunks/{feature-flags-BxZz_lNm.d.ts → feature-flags-BClx56v5.d.ts} +1 -0
- package/dist/chunks/feature-flags-DSOCIZHK.js +1 -0
- package/dist/chunks/identifiers-CQeWm7wi.js +1 -0
- package/dist/chunks/replay-CHFW2q4E.js +1 -0
- package/dist/chunks/web-vitals-GwPlL7Zy.js +1 -0
- package/dist/error.d.ts +5 -8
- package/dist/error.js +1 -1
- package/dist/feature-flags.d.ts +1 -1
- package/dist/feature-flags.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/replay.js +1 -1
- package/dist/web-vitals.d.ts +7 -15
- package/dist/web-vitals.js +1 -1
- package/package.json +1 -1
- package/src/analytics.ts +55 -52
- package/src/error.ts +104 -157
- package/src/feature-flags.ts +5 -2
- package/src/replay.ts +2 -2
- package/src/utils/api-urls.ts +7 -20
- package/src/utils/identifiers.ts +38 -26
- package/src/web-vitals.ts +65 -167
- package/tests/analytics.test.ts +36 -0
- package/tests/identifiers.test.ts +18 -0
- package/dist/chunks/api-urls-BrkcoElX.js +0 -1
- package/dist/chunks/error-3yIt0PgC.js +0 -2
- package/dist/chunks/feature-flags-CjnLZGxp.js +0 -1
- package/dist/chunks/identifiers-BN-atT_6.js +0 -1
- package/dist/chunks/replay-u8fo4u_R.js +0 -1
- package/dist/chunks/web-vitals-ClET5qGP.js +0 -1
package/src/web-vitals.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Metric, MetricWithAttribution } from "web-vitals";
|
|
2
|
-
import {
|
|
2
|
+
import { normalizeAnalyticsBaseUrl, URLS } from "./utils/api-urls";
|
|
3
3
|
import { getOrCreateSessionId } from "./utils/identifiers";
|
|
4
4
|
import { normalizeSamplingPercentage } from "./utils/types";
|
|
5
5
|
|
|
@@ -12,64 +12,23 @@ export interface WebVitalsOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
type MetricName = "CLS" | "INP" | "LCP" | "FCP" | "TTFB";
|
|
15
|
+
type AnyMetric = Metric | MetricWithAttribution;
|
|
15
16
|
|
|
16
|
-
type
|
|
17
|
+
type MetricPayload = {
|
|
18
|
+
metric: MetricName;
|
|
17
19
|
value: number;
|
|
18
20
|
attributes: Record<string, unknown>;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
type AnyMetric = Metric | MetricWithAttribution;
|
|
22
|
-
type ObserverOptions = {
|
|
23
|
-
reportAllChanges?: boolean;
|
|
24
|
-
};
|
|
25
|
-
type Observer = (
|
|
26
|
-
cb: (metric: AnyMetric) => void,
|
|
27
|
-
opts?: ObserverOptions,
|
|
28
|
-
) => void;
|
|
29
|
-
type ObserverTuple = [Observer, Observer, Observer, Observer, Observer];
|
|
30
|
-
type ObserverConfig = {
|
|
31
|
-
observe: Observer;
|
|
32
|
-
options?: ObserverOptions;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
async function loadObservers(attribution: boolean): Promise<ObserverConfig[]> {
|
|
36
|
-
const { onCLS, onFCP, onINP, onLCP, onTTFB } = attribution
|
|
37
|
-
? await import("web-vitals/attribution")
|
|
38
|
-
: await import("web-vitals");
|
|
39
|
-
return createObserverConfigs([onCLS, onFCP, onINP, onLCP, onTTFB]);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function createObserverConfigs([
|
|
43
|
-
onCLS,
|
|
44
|
-
onFCP,
|
|
45
|
-
onINP,
|
|
46
|
-
onLCP,
|
|
47
|
-
onTTFB,
|
|
48
|
-
]: ObserverTuple): ObserverConfig[] {
|
|
49
|
-
return [
|
|
50
|
-
{ observe: onCLS, options: { reportAllChanges: true } },
|
|
51
|
-
{ observe: onFCP },
|
|
52
|
-
{ observe: onINP, options: { reportAllChanges: true } },
|
|
53
|
-
{ observe: onLCP, options: { reportAllChanges: true } },
|
|
54
|
-
{ observe: onTTFB },
|
|
55
|
-
];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
23
|
export default class WebVitalsTracker {
|
|
59
24
|
private readonly endpoint: string;
|
|
60
|
-
private readonly metricsByUrl = new Map<
|
|
61
|
-
string,
|
|
62
|
-
Map<MetricName, MetricState>
|
|
63
|
-
>();
|
|
64
|
-
private readonly pendingFlushUrls = new Set<string>();
|
|
65
25
|
private readonly sampled: boolean;
|
|
26
|
+
private readonly metrics = new Map<MetricName, MetricPayload>();
|
|
66
27
|
private started = false;
|
|
67
|
-
private
|
|
68
|
-
private finalFlushRequested = false;
|
|
69
|
-
private currentUrl = "";
|
|
28
|
+
private url = "";
|
|
70
29
|
|
|
71
30
|
constructor(private readonly options: WebVitalsOptions) {
|
|
72
|
-
this.endpoint =
|
|
31
|
+
this.endpoint = `${normalizeAnalyticsBaseUrl(options.baseUrl)}${URLS.vitals}`;
|
|
73
32
|
this.sampled =
|
|
74
33
|
Math.random() * 100 <
|
|
75
34
|
normalizeSamplingPercentage(options.samplingPercentage);
|
|
@@ -83,77 +42,68 @@ export default class WebVitalsTracker {
|
|
|
83
42
|
if (this.debug) console.log("[WebVitals]", ...args);
|
|
84
43
|
}
|
|
85
44
|
|
|
86
|
-
start(): void {
|
|
45
|
+
async start(): Promise<void> {
|
|
87
46
|
if (this.started || typeof window === "undefined") return;
|
|
88
47
|
|
|
89
48
|
this.started = true;
|
|
90
|
-
this.
|
|
49
|
+
this.url = window.location.href;
|
|
91
50
|
|
|
92
|
-
document.addEventListener("visibilitychange", this.
|
|
51
|
+
document.addEventListener("visibilitychange", this.onHidden, {
|
|
93
52
|
passive: true,
|
|
94
53
|
});
|
|
95
54
|
window.addEventListener("pagehide", this.onPageHide, { passive: true });
|
|
96
55
|
|
|
56
|
+
const mod = this.options.attribution
|
|
57
|
+
? await import("web-vitals/attribution")
|
|
58
|
+
: await import("web-vitals");
|
|
59
|
+
|
|
60
|
+
if (!this.started) return;
|
|
61
|
+
|
|
62
|
+
mod.onCLS(this.capture, { reportAllChanges: true });
|
|
63
|
+
mod.onINP(this.capture, { reportAllChanges: true });
|
|
64
|
+
mod.onLCP(this.capture, { reportAllChanges: true });
|
|
65
|
+
mod.onFCP(this.capture);
|
|
66
|
+
mod.onTTFB(this.capture);
|
|
67
|
+
|
|
97
68
|
this.log("Tracking started");
|
|
98
|
-
void this.observe();
|
|
99
69
|
}
|
|
100
70
|
|
|
101
71
|
stop(): void {
|
|
102
72
|
if (!this.started || typeof window === "undefined") return;
|
|
103
73
|
|
|
104
74
|
this.started = false;
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
75
|
+
this.removeListeners();
|
|
76
|
+
void this.flush();
|
|
107
77
|
}
|
|
108
78
|
|
|
109
79
|
trackPageChange(url?: string): void {
|
|
110
80
|
if (!this.started || typeof window === "undefined") return;
|
|
111
|
-
const nextUrl = url ?? window.location.href;
|
|
112
|
-
if (nextUrl === this.currentUrl) return;
|
|
113
81
|
|
|
114
|
-
const
|
|
115
|
-
this.
|
|
116
|
-
this.queueFlush(previousUrl);
|
|
117
|
-
}
|
|
82
|
+
const nextUrl = url ?? window.location.href;
|
|
83
|
+
if (nextUrl === this.url) return;
|
|
118
84
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
window.removeEventListener("pagehide", this.onPageHide);
|
|
85
|
+
void this.flush();
|
|
86
|
+
this.url = nextUrl;
|
|
122
87
|
}
|
|
123
88
|
|
|
124
|
-
private
|
|
125
|
-
try {
|
|
126
|
-
const observers = await loadObservers(this.options.attribution ?? false);
|
|
127
|
-
|
|
128
|
-
if (!this.started) return;
|
|
129
|
-
|
|
130
|
-
for (const { observe, options } of observers) {
|
|
131
|
-
observe(this.captureMetric, options);
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
this.log("Failed to load web-vitals", error);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private onVisibilityChange = (): void => {
|
|
89
|
+
private onHidden = (): void => {
|
|
139
90
|
if (document.visibilityState === "hidden") {
|
|
140
|
-
this.
|
|
91
|
+
void this.flush();
|
|
141
92
|
}
|
|
142
93
|
};
|
|
143
94
|
|
|
144
95
|
private onPageHide = (): void => {
|
|
145
|
-
this.
|
|
96
|
+
void this.flush();
|
|
146
97
|
};
|
|
147
98
|
|
|
148
|
-
private
|
|
99
|
+
private capture = (metric: AnyMetric): void => {
|
|
149
100
|
if (!this.started || !this.sampled) return;
|
|
150
101
|
|
|
151
102
|
const name = metric.name as MetricName;
|
|
152
|
-
const attribution =
|
|
153
|
-
(metric as MetricWithAttribution).attribution ?? undefined;
|
|
154
|
-
const metrics = this.metricsForUrl(this.currentUrl);
|
|
103
|
+
const attribution = (metric as MetricWithAttribution).attribution;
|
|
155
104
|
|
|
156
|
-
metrics.set(name, {
|
|
105
|
+
this.metrics.set(name, {
|
|
106
|
+
metric: name,
|
|
157
107
|
value: metric.value,
|
|
158
108
|
attributes: {
|
|
159
109
|
id: metric.id,
|
|
@@ -163,99 +113,47 @@ export default class WebVitalsTracker {
|
|
|
163
113
|
...(attribution ?? {}),
|
|
164
114
|
},
|
|
165
115
|
});
|
|
166
|
-
|
|
167
|
-
this.log(`${name} captured: ${metric.value}`);
|
|
168
116
|
};
|
|
169
117
|
|
|
170
|
-
private
|
|
171
|
-
|
|
172
|
-
if (!metrics)
|
|
173
|
-
metrics = new Map();
|
|
174
|
-
this.metricsByUrl.set(url, metrics);
|
|
175
|
-
}
|
|
176
|
-
return metrics;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private queueFlush(url: string, final = false): void {
|
|
180
|
-
this.finalFlushRequested ||= final;
|
|
181
|
-
this.pendingFlushUrls.add(url);
|
|
182
|
-
void this.flushPending();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private async flushPending(): Promise<void> {
|
|
186
|
-
if (this.flushing) return;
|
|
187
|
-
if (typeof window === "undefined" || typeof fetch !== "function") {
|
|
188
|
-
this.finishFinalFlushIfNeeded();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
this.flushing = true;
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const urls = [...this.pendingFlushUrls];
|
|
196
|
-
this.pendingFlushUrls.clear();
|
|
197
|
-
for (const url of urls) {
|
|
198
|
-
if (!(await this.sendMetricsForUrl(url)))
|
|
199
|
-
this.pendingFlushUrls.add(url);
|
|
200
|
-
}
|
|
201
|
-
} finally {
|
|
202
|
-
this.flushing = false;
|
|
203
|
-
this.finishFinalFlushIfNeeded();
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
private async sendMetricsForUrl(url: string): Promise<boolean> {
|
|
208
|
-
const metrics = this.metricsByUrl.get(url);
|
|
209
|
-
if (!metrics || metrics.size === 0) return true;
|
|
210
|
-
|
|
211
|
-
const vitals = [...metrics.entries()].map(([metric, data]) => ({
|
|
212
|
-
metric,
|
|
213
|
-
value: data.value,
|
|
214
|
-
attributes: data.attributes,
|
|
215
|
-
}));
|
|
216
|
-
const body = JSON.stringify({
|
|
217
|
-
sessionId: getOrCreateSessionId(),
|
|
218
|
-
vitals,
|
|
219
|
-
metadata: { url },
|
|
220
|
-
});
|
|
118
|
+
private async flush(): Promise<void> {
|
|
119
|
+
if (typeof window === "undefined" || typeof fetch !== "function") return;
|
|
120
|
+
if (!this.metrics.size) return;
|
|
221
121
|
|
|
222
|
-
|
|
122
|
+
const vitals = Array.from(this.metrics.values());
|
|
123
|
+
const url = this.url;
|
|
124
|
+
this.metrics.clear();
|
|
223
125
|
|
|
224
126
|
try {
|
|
225
127
|
const controller = new AbortController();
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
128
|
+
const timeoutId = window.setTimeout(() => controller.abort(), 3000);
|
|
129
|
+
|
|
130
|
+
const response = await fetch(this.endpoint, {
|
|
131
|
+
method: "POST",
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
sessionId: getOrCreateSessionId(),
|
|
134
|
+
vitals,
|
|
135
|
+
metadata: { url },
|
|
136
|
+
}),
|
|
137
|
+
headers: {
|
|
138
|
+
"Content-Type": "application/json",
|
|
139
|
+
Authorization: `Bearer ${this.options.siteKey}`,
|
|
140
|
+
},
|
|
141
|
+
keepalive: true,
|
|
142
|
+
signal: controller.signal,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
clearTimeout(timeoutId);
|
|
146
|
+
|
|
147
|
+
if (!response.ok) {
|
|
148
|
+
throw new Error(`HTTP ${response.status}`);
|
|
247
149
|
}
|
|
248
|
-
return true;
|
|
249
150
|
} catch (error) {
|
|
250
151
|
this.log("Failed to send metrics", error);
|
|
251
|
-
return false;
|
|
252
152
|
}
|
|
253
153
|
}
|
|
254
154
|
|
|
255
|
-
private
|
|
256
|
-
|
|
257
|
-
this.
|
|
258
|
-
this.started = false;
|
|
259
|
-
this.cleanupListeners();
|
|
155
|
+
private removeListeners(): void {
|
|
156
|
+
document.removeEventListener("visibilitychange", this.onHidden);
|
|
157
|
+
window.removeEventListener("pagehide", this.onPageHide);
|
|
260
158
|
}
|
|
261
159
|
}
|
package/tests/analytics.test.ts
CHANGED
|
@@ -362,6 +362,7 @@ describe("WebAnalytics lifecycle", () => {
|
|
|
362
362
|
expect(checkCall?.body).toContain('"key":"beta"');
|
|
363
363
|
expect(checkCall?.body).toContain('"externalId":"user_1"');
|
|
364
364
|
expect(checkCall?.body).toContain('"identifier":');
|
|
365
|
+
expect(checkCall?.body).toContain('"sessionId":');
|
|
365
366
|
});
|
|
366
367
|
|
|
367
368
|
test("navigation sends page leave and navigation pageview", async () => {
|
|
@@ -391,6 +392,41 @@ describe("WebAnalytics lifecycle", () => {
|
|
|
391
392
|
).toBe(true);
|
|
392
393
|
});
|
|
393
394
|
|
|
395
|
+
test("visibility hide refreshes stale session before page leave", async () => {
|
|
396
|
+
const analytics = new WebAnalytics({
|
|
397
|
+
siteKey: "site_test",
|
|
398
|
+
autoTrack: false,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
await analytics.start();
|
|
402
|
+
const storage = globalThis.sessionStorage as unknown as MockStorage;
|
|
403
|
+
const sessionId = storage.getItem("session_id");
|
|
404
|
+
expect(sessionId).toBeTruthy();
|
|
405
|
+
storage.setItem("session_timestamp", "0");
|
|
406
|
+
harness.fetchCalls.length = 0;
|
|
407
|
+
|
|
408
|
+
Object.defineProperty(globalThis.document, "visibilityState", {
|
|
409
|
+
configurable: true,
|
|
410
|
+
value: "hidden",
|
|
411
|
+
});
|
|
412
|
+
for (const listener of harness.documentTarget.listeners.get(
|
|
413
|
+
"visibilitychange",
|
|
414
|
+
) ?? []) {
|
|
415
|
+
if (typeof listener === "function") {
|
|
416
|
+
listener(new Event("visibilitychange"));
|
|
417
|
+
} else {
|
|
418
|
+
listener.handleEvent(new Event("visibilitychange"));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
await wait(0);
|
|
422
|
+
|
|
423
|
+
expect(storage.getItem("session_id")).toBe(sessionId);
|
|
424
|
+
const pageLeaveCall = harness.fetchCalls.find((call) =>
|
|
425
|
+
call.body?.includes('"event":"page_leave"'),
|
|
426
|
+
);
|
|
427
|
+
expect(pageLeaveCall?.body).toContain(`"sessionId":"${sessionId}"`);
|
|
428
|
+
});
|
|
429
|
+
|
|
394
430
|
test("resolveReplayTrackerOptions honors sessionReplays config", () => {
|
|
395
431
|
const options = resolveReplayTrackerOptions(
|
|
396
432
|
{
|
|
@@ -74,4 +74,22 @@ describe("identifiers", () => {
|
|
|
74
74
|
expect(second.length).toBeGreaterThan(0);
|
|
75
75
|
expect(second).not.toBe(first);
|
|
76
76
|
});
|
|
77
|
+
|
|
78
|
+
test("storage access failures disable persisted identifiers", () => {
|
|
79
|
+
Object.defineProperty(globalThis, "localStorage", {
|
|
80
|
+
configurable: true,
|
|
81
|
+
get() {
|
|
82
|
+
throw new Error("storage blocked");
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
Object.defineProperty(globalThis, "sessionStorage", {
|
|
86
|
+
configurable: true,
|
|
87
|
+
get() {
|
|
88
|
+
throw new Error("storage blocked");
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(getOrCreateAnonymousId()).toBe("");
|
|
93
|
+
expect(getOrCreateSessionId()).toBe("");
|
|
94
|
+
});
|
|
77
95
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=`https://metrics.faststats.dev`,t=`https://flags.faststats.dev`;function n(e){return e.replace(/\/+$/,``)}function r(t){return t===void 0||t===``?e:n(t)||e}function i(e){return`${r(e)}/v1/web`}function a(e){return`${r(e)}/v1/identify`}function o(e){return`${r(e)}/v1/replay`}function s(e){return`${r(e)}/v1/vitals`}function c(e){return e===void 0||e===``?t:n(e)||t}function l(e){return`${c(e)}/v1/check`}export{o as a,c as i,a as n,i as o,r,s,l as t};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{o as t}from"./api-urls-BrkcoElX.js";import{n,t as r}from"./identifiers-BN-atT_6.js";import{t as i}from"./send-data-DL_GlsQw.js";var a=e({default:()=>d});const o=/(?:^|\()(chrome|moz|ms-browser|safari-web)-extension:\/\//,s=e=>e?.split(`
|
|
2
|
-
`).map(e=>e.trim()).filter(Boolean);function c(e){if(e instanceof Error)return{error:e.name?.trim()||`Error`,message:e.message,stack:s(e.stack),cause:c(e.cause)};if(typeof e==`string`)return{error:`Error`,message:e}}function l(e){return e?`${e.error}\0${e.message??``}\0${l(e.cause)}`:``}function u(e){let t=[e.kind,e.handled?`handled`:`unhandled`,e.message,e.filename??``,e.lineno??``,l(e.cause)].join(`\0`),n=2166136261,r=3598710387;for(let e=0;e<t.length;e++){let i=t.charCodeAt(e);n=Math.imul(n^i,16777619),r=Math.imul(r^i,2246822519)}return`err_${(n>>>0).toString(16).padStart(8,`0`)}${(r>>>0).toString(16).padStart(8,`0`)}`}var d=class{endpoint;handled=new WeakSet;queue=new Map;timer=null;started=!1;flushing=!1;constructor(e){this.options=e,this.endpoint=t(e.baseUrl)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??5e3}get maxQueueSize(){return this.options.maxQueueSize??50}log(...e){this.debug&&console.log(`[ErrorTracker]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,window.addEventListener(`error`,this.onError),window.addEventListener(`unhandledrejection`,this.onRejection),document.addEventListener(`visibilitychange`,this.onVisibilityChange),window.addEventListener(`pagehide`,this.requestFlush),this.timer=setInterval(this.requestFlush,this.flushInterval),this.log(`Started listening for errors`))}stop(){!this.started||typeof window>`u`||(this.started=!1,window.removeEventListener(`error`,this.onError),window.removeEventListener(`unhandledrejection`,this.onRejection),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.requestFlush),this.timer&&clearInterval(this.timer),this.timer=null,this.requestFlush(),this.log(`Stopped listening for errors`))}captureError(e){this.record({kind:`error`,message:e.message,stack:e.stack,handled:!0,cause:c(e.cause)},e)}onError=e=>{let t=e.error;this.record({kind:`error`,message:e.message||`Unknown error`,filename:e.filename||void 0,lineno:e.lineno||void 0,stack:t instanceof Error?t.stack:void 0,handled:!1,cause:t instanceof Error?c(t.cause):void 0},t)};onRejection=e=>{let t=e.reason;this.record({kind:`unhandledrejection`,message:t.message||`Unhandled promise rejection`,stack:t.stack||void 0,handled:!1,cause:c(t.cause)},t)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.requestFlush()};requestFlush=()=>{this.flush()};restoreErrors(e){for(let t of e)this.queue.set(t.hash,{...t,count:t.count+(this.queue.get(t.hash)?.count??0)})}record(e,t){if(t instanceof Error){if(this.handled.has(t)){this.log(`Skipping duplicate:`,t.message);return}this.handled.add(t)}if(o.test(`${e.filename??``}\n${e.stack??``}`))return;let n=u(e),r=this.queue.get(n);r?r.count+=1:this.queue.set(n,{hash:n,count:1,error:e.kind===`unhandledrejection`?`UnhandledRejection`:`Error`,message:e.message,stack:s(e.stack),handled:e.handled,...e.cause?{cause:e.cause}:{}}),this.log(`Captured error:`,e),this.queue.size>=this.maxQueueSize&&this.requestFlush()}async flush(){if(this.flushing||this.queue.size===0)return;let e=[...this.queue.values()];this.queue.clear(),this.flushing=!0;let t=r(),a=globalThis.__SOURCEMAPS_BUILD__,o=typeof a?.buildId==`string`&&a.buildId.trim()?a.buildId:void 0,s=JSON.stringify({token:this.options.siteKey,...t?{userId:t}:{},sessionId:n(),...o?{buildId:o}:{},sdkName:this.options.sdkName??`@faststats/web`,sdkVersion:this.options.sdkVersion??`0.2.11`,data:{url:location.href,page:location.pathname,referrer:document.referrer||null,title:document.title},errors:e});this.log(`Flushing errors:`,e),this.log(`Payload:`,s);try{await i({url:this.endpoint,data:s,debug:this.debug,debugPrefix:`[ErrorTracker]`})||this.restoreErrors(e)}catch{this.restoreErrors(e)}finally{this.flushing=!1}}};export{a as n,d as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{t}from"./api-urls-BrkcoElX.js";var n=e({fetchFeatureFlagEvaluation:()=>r});async function r(e,n){if(n.projectToken&&n.projectId)throw Error(`provide either projectToken or projectId, not both`);let r=n.projectToken,i=n.projectId,a=n.identifier,o=n.externalId;if(!r&&!i)throw Error(`feature flag check requires projectToken or projectId`);if(!a&&!o)throw Error(`feature flag check requires serverId or externalId`);let s={key:e,...i?{projectId:i}:{},...a?{identifier:a}:{},...o?{externalId:o}:{}};n.attributes&&Object.keys(n.attributes).length>0&&(s.attributes=n.attributes);let c={"Content-Type":`application/json`};r&&(c.Authorization=`Bearer ${r}`);let l=t(n.baseUrl),u=await fetch(l,{method:`POST`,headers:c,body:JSON.stringify(s),credentials:`omit`,signal:n.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()}export{r as n,n as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
let e=!1;function t(t){e=t}function n(t){return t??e?``:r()}function r(){if(typeof localStorage>`u`)return``;let e=localStorage.getItem(`faststats_anon_id`);if(e)return e;let t=crypto.randomUUID();return localStorage.setItem(`faststats_anon_id`,t),t}function i(e){return e||typeof localStorage>`u`?``:(localStorage.removeItem(`faststats_anon_id`),r())}function a(){if(typeof sessionStorage>`u`)return``;let e=sessionStorage.getItem(`session_id`),t=sessionStorage.getItem(`session_timestamp`);if(e&&t){if(Date.now()-Number.parseInt(t,10)<18e5)return sessionStorage.setItem(`session_timestamp`,Date.now().toString()),e;sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`)}let n=Date.now().toString(),r=crypto.randomUUID();return sessionStorage.setItem(`session_id`,r),sessionStorage.setItem(`session_timestamp`,n),sessionStorage.setItem(`session_start`,n),r}function o(){return typeof sessionStorage>`u`?``:(sessionStorage.removeItem(`session_id`),sessionStorage.removeItem(`session_timestamp`),sessionStorage.removeItem(`session_start`),a())}function s(){typeof sessionStorage>`u`||sessionStorage.getItem(`session_id`)&&sessionStorage.setItem(`session_timestamp`,Date.now().toString())}function c(){if(typeof sessionStorage>`u`)return Date.now();let e=sessionStorage.getItem(`session_start`);if(e)return Number.parseInt(e,10);let t=sessionStorage.getItem(`session_timestamp`);return t?Number.parseInt(t,10):Date.now()}export{i as a,s as i,a as n,o,c as r,t as s,n as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{a as t}from"./api-urls-BrkcoElX.js";import{n,r,t as i}from"./identifiers-BN-atT_6.js";import{t as a}from"./send-data-DL_GlsQw.js";import{t as o}from"./types-CYzR5xtT.js";import{EventType as s}from"@rrweb/types";import{record as c}from"rrweb";var l=e({default:()=>f});const u={mousemove:50,mouseInteraction:!0,scroll:150,media:800,input:`last`},d={script:!0,comment:!0,headFavicon:!0,headWhitespace:!0,headMetaDescKeywords:!0,headMetaSocial:!0,headMetaRobots:!0,headMetaHttpEquiv:!0,headMetaAuthorship:!0};var f=class{endpoint;compressionSupported=typeof window<`u`&&`CompressionStream`in window;sampled;events=[];pending=[];pendingSizeBytes=0;sessionId;started=!1;startTime=0;sequence=0;intervalId=null;flushTask=null;retryTask=null;minLengthFlushTask=null;stopRecording;sending=!1;constructor(e){this.options=e,this.endpoint=t(e.baseUrl),this.sampled=Math.random()*100<o(e.samplingPercentage)}get debug(){return this.options.debug??!1}get flushInterval(){return this.options.flushInterval??1e4}get maxEvents(){return this.options.maxEvents??500}get maxPendingBatches(){return this.options.maxPendingBatches??30}get maxQueueSizeBytes(){return this.options.maxQueueSizeBytes??2097152}get minReplayLengthMs(){return this.options.minReplayLengthMs??3e3}get shouldCompress(){return this.options.compress??!0}log(...e){this.debug&&console.log(`[Replay]`,...e)}start(){this.started||typeof window>`u`||!this.sampled||(this.started=!0,this.sessionId=n(),this.startTime=r(),this.beginRecording(),this.intervalId=setInterval(this.requestFlush,this.flushInterval),window.addEventListener(`beforeunload`,this.onUnload),window.addEventListener(`pagehide`,this.onUnload),document.addEventListener(`visibilitychange`,this.onVisibilityChange),this.log(`Recording started`))}async beginRecording(){let e=this.options.recordConsole??!0,[{getRecordSequentialIdPlugin:t},n]=await Promise.all([import(`@rrweb/rrweb-plugin-sequential-id-record`),e?import(`@rrweb/rrweb-plugin-console-record`):Promise.resolve(null)]);if(!this.started)return;let r=[t({key:`_faststatsSeqId`})];n&&r.push(n.getRecordConsolePlugin()),this.stopRecording=c({emit:this.onEvent,sampling:this.options.sampling??u,slimDOMOptions:this.options.slimDOMOptions??d,maskAllInputs:this.options.maskAllInputs??!0,maskInputOptions:this.options.maskInputOptions??{password:!0,email:!0,tel:!0},blockClass:this.options.blockClass,blockSelector:this.options.blockSelector,maskTextClass:this.options.maskTextClass,maskTextSelector:this.options.maskTextSelector,checkoutEveryNms:this.options.checkoutEveryNms??6e4,checkoutEveryNth:this.options.checkoutEveryNth,plugins:r})}stop(){if(this.started){if(this.started=!1,this.stopRecording?.(),this.stopRecording=void 0,this.intervalId&&clearInterval(this.intervalId),this.flushTask&&clearTimeout(this.flushTask),this.retryTask&&clearTimeout(this.retryTask),this.minLengthFlushTask&&clearTimeout(this.minLengthFlushTask),this.intervalId=null,this.flushTask=null,this.retryTask=null,this.minLengthFlushTask=null,window.removeEventListener(`beforeunload`,this.onUnload),window.removeEventListener(`pagehide`,this.onUnload),document.removeEventListener(`visibilitychange`,this.onVisibilityChange),!this.hasReachedMinLength()){this.events.length=0,this.sessionId=void 0,this.log(`Session too short (${Date.now()-this.startTime}ms), discarding events`);return}this.flush(!0),this.sessionId=void 0,this.log(`Recording stopped`)}}getSessionId(){return this.sessionId??n()}onEvent=(e,t)=>{if(this.events.push(e),t||this.events.length>=this.maxEvents||e.type===s.FullSnapshot&&this.hasReachedMinLength()){this.requestFlush();return}this.scheduleMinLengthFlush()};onUnload=()=>{this.flush(!0)};onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.flush(!0)};hasReachedMinLength(){return this.minReplayLengthMs<=0||Date.now()-this.startTime>=this.minReplayLengthMs}requestFlush=()=>{this.flushTask||this.sending||this.events.length===0||(this.minLengthFlushTask&&=(clearTimeout(this.minLengthFlushTask),null),this.flushTask=setTimeout(()=>{this.flushTask=null,this.flush(!1)},0))};scheduleMinLengthFlush(){if(this.minLengthFlushTask||this.events.length===0||this.hasReachedMinLength())return;let e=Math.max(0,this.minReplayLengthMs-(Date.now()-this.startTime)),t=Math.min(e,2147483647);this.minLengthFlushTask=setTimeout(()=>{this.minLengthFlushTask=null,this.requestFlush()},t)}createBatch(e){let t=i(),n=this.sequence++;return{token:this.options.siteKey,sessionId:this.getSessionId(),...t?{identifier:t}:{},batchId:this.createBatchId(n),sequence:n,timestamp:Date.now(),url:window.location.href,events:e}}createBatchId(e){let t=typeof crypto<`u`&&`randomUUID`in crypto?crypto.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;return`${this.getSessionId()??`unknown`}-${e}-${t}`}getBatchSizeBytes(e){return new TextEncoder().encode(JSON.stringify(e)).byteLength}dropOldestPendingBatch(e){let t=this.pending.shift();t&&(this.pendingSizeBytes=Math.max(0,this.pendingSizeBytes-this.getBatchSizeBytes(t)),this.log(`${e}, dropping batch ${t.sequence}`))}enqueueBatch(e){let t=this.getBatchSizeBytes(e);if(t>this.maxQueueSizeBytes){this.log(`Replay batch ${e.sequence} is ${t}B, exceeding ${this.maxQueueSizeBytes}B queue limit; dropping`);return}for(;this.pending.length>0&&this.pendingSizeBytes+t>this.maxQueueSizeBytes;)this.dropOldestPendingBatch(`Pending queue size limit reached`);for(;this.pending.length>=this.maxPendingBatches;)this.dropOldestPendingBatch(`Pending batch limit reached`);this.pending.push(e),this.pendingSizeBytes+=t}async encodeBatch(e){let t=JSON.stringify(e);if(!this.shouldCompress||!this.compressionSupported)return{data:t,isCompressed:!1};try{let e=await this.compress(t);return this.log(`Compressed ${t.length}B -> ${e.byteLength}B (${Math.round(e.byteLength/t.length*100)}%)`),{data:e,isCompressed:!0}}catch{return this.log(`Compression failed, using uncompressed`),{data:t,isCompressed:!1}}}async flush(e){if(!this.sending){if(this.events.length>0)if(!this.hasReachedMinLength())this.scheduleMinLengthFlush();else{let e=this.createBatch(this.events.splice(0));this.enqueueBatch(e)}if(this.pending.length!==0){this.sending=!0;try{for(;this.pending.length>0;){let t=this.pending[0];if(!t)break;let n=await this.encodeBatch(t);if(!await this.send(n.data,n.isCompressed,e)){this.log(`Failed to send replay batch ${t.sequence}, retrying`),this.scheduleRetry();break}this.dropOldestPendingBatch(`Sent replay batch`),e=!1}}finally{this.sending=!1}}}}scheduleRetry(){this.retryTask||=setTimeout(()=>{this.retryTask=null,this.flush(!1)},1e3)}async compress(e){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`gzip`)),n=await new Response(t).arrayBuffer();return new Uint8Array(n)}send(e,t,n){return a({url:t?`${this.endpoint}?encoding=gzip`:this.endpoint,data:e,contentType:t?`application/octet-stream`:`application/json`,debug:!1,useBeacon:!1,keepalive:n})??Promise.resolve(!1)}};export{l as n,f as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./rolldown-runtime-MP-BAFHD.js";import{s as t}from"./api-urls-BrkcoElX.js";import{n}from"./identifiers-BN-atT_6.js";import{t as r}from"./types-CYzR5xtT.js";var i=e({default:()=>s});async function a(e){let{onCLS:t,onFCP:n,onINP:r,onLCP:i,onTTFB:a}=e?await import(`web-vitals/attribution`):await import(`web-vitals`);return o([t,n,r,i,a])}function o([e,t,n,r,i]){return[{observe:e,options:{reportAllChanges:!0}},{observe:t},{observe:n,options:{reportAllChanges:!0}},{observe:r,options:{reportAllChanges:!0}},{observe:i}]}var s=class{endpoint;metricsByUrl=new Map;pendingFlushUrls=new Set;sampled;started=!1;flushing=!1;finalFlushRequested=!1;currentUrl=``;constructor(e){this.options=e,this.endpoint=t(e.baseUrl),this.sampled=Math.random()*100<r(e.samplingPercentage)}get debug(){return this.options.debug??!1}log(...e){this.debug&&console.log(`[WebVitals]`,...e)}start(){this.started||typeof window>`u`||(this.started=!0,this.currentUrl=window.location.href,document.addEventListener(`visibilitychange`,this.onVisibilityChange,{passive:!0}),window.addEventListener(`pagehide`,this.onPageHide,{passive:!0}),this.log(`Tracking started`),this.observe())}stop(){!this.started||typeof window>`u`||(this.started=!1,this.cleanupListeners(),this.queueFlush(this.currentUrl,!0))}trackPageChange(e){if(!this.started||typeof window>`u`)return;let t=e??window.location.href;if(t===this.currentUrl)return;let n=this.currentUrl;this.currentUrl=t,this.queueFlush(n)}cleanupListeners(){document.removeEventListener(`visibilitychange`,this.onVisibilityChange),window.removeEventListener(`pagehide`,this.onPageHide)}async observe(){try{let e=await a(this.options.attribution??!1);if(!this.started)return;for(let{observe:t,options:n}of e)t(this.captureMetric,n)}catch(e){this.log(`Failed to load web-vitals`,e)}}onVisibilityChange=()=>{document.visibilityState===`hidden`&&this.queueFlush(this.currentUrl,!0)};onPageHide=()=>{this.queueFlush(this.currentUrl,!0)};captureMetric=e=>{if(!this.started||!this.sampled)return;let t=e.name,n=e.attribution??void 0;this.metricsForUrl(this.currentUrl).set(t,{value:e.value,attributes:{id:e.id,rating:e.rating,delta:e.delta,navigationType:e.navigationType,...n??{}}}),this.log(`${t} captured: ${e.value}`)};metricsForUrl(e){let t=this.metricsByUrl.get(e);return t||(t=new Map,this.metricsByUrl.set(e,t)),t}queueFlush(e,t=!1){this.finalFlushRequested||=t,this.pendingFlushUrls.add(e),this.flushPending()}async flushPending(){if(!this.flushing){if(typeof window>`u`||typeof fetch!=`function`){this.finishFinalFlushIfNeeded();return}this.flushing=!0;try{let e=[...this.pendingFlushUrls];this.pendingFlushUrls.clear();for(let t of e)await this.sendMetricsForUrl(t)||this.pendingFlushUrls.add(t)}finally{this.flushing=!1,this.finishFinalFlushIfNeeded()}}}async sendMetricsForUrl(e){let t=this.metricsByUrl.get(e);if(!t||t.size===0)return!0;let r=[...t.entries()].map(([e,t])=>({metric:e,value:t.value,attributes:t.attributes})),i=JSON.stringify({sessionId:n(),vitals:r,metadata:{url:e}});this.log(`Sending metrics for ${e} (${r.length})`);try{let t=new AbortController,n=window.setTimeout(()=>t.abort(),3e3);try{let n=await fetch(this.endpoint,{method:`POST`,body:i,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.options.siteKey}`},keepalive:!0,signal:t.signal});if(!n.ok)throw Error(`HTTP ${n.status}`);this.metricsByUrl.delete(e)}finally{clearTimeout(n)}return!0}catch(e){return this.log(`Failed to send metrics`,e),!1}}finishFinalFlushIfNeeded(){this.finalFlushRequested&&(this.finalFlushRequested=!1,this.started=!1,this.cleanupListeners())}};export{i as n,s as t};
|