@faststats/web 0.10.3 → 0.12.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 +54 -6
- package/dist/chunks/{extensions-DW7tJY0T.d.ts → extensions-BfubKIzk.d.ts} +4 -11
- package/dist/chunks/{identifiers-BgGtAQdp.js → identifiers-CpmDmpDv.js} +1 -1
- package/dist/chunks/send-data-DYZZjeM-.js +84 -0
- package/dist/chunks/{send-data-DAVUdfNm.js → session-manager-CFQEWbgo.js} +5 -35
- package/dist/error.d.ts +5 -2
- package/dist/error.js +47 -32
- package/dist/flags.d.ts +79 -0
- package/dist/flags.js +145 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +84 -50
- package/dist/replay.d.ts +6 -4
- package/dist/replay.js +86 -97
- package/dist/web-vitals.d.ts +1 -1
- package/dist/web-vitals.js +5 -7
- package/package.json +11 -11
- package/dist/chunks/api-urls-DWk43fu6.js +0 -53
- package/dist/outbound-links.d.ts +0 -9
- package/dist/outbound-links.js +0 -62
package/dist/replay.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as
|
|
2
|
-
import {
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
1
|
+
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as sendData } from "./chunks/send-data-DYZZjeM-.js";
|
|
2
|
+
import { a as touchActivity, c as setStorageItem, n as getSessionContext, o as getStorageItem, r as onSessionRotated, t as createId } from "./chunks/session-manager-CFQEWbgo.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-CpmDmpDv.js";
|
|
4
4
|
import { record } from "@rrweb/record";
|
|
5
5
|
import { getRecordConsolePlugin } from "@rrweb/rrweb-plugin-console-record";
|
|
6
6
|
import { getRecordNetworkPlugin } from "@rrweb/rrweb-plugin-network-record";
|
|
@@ -57,57 +57,47 @@ const counters = /* @__PURE__ */ new Map();
|
|
|
57
57
|
function sameRecording(counter, context) {
|
|
58
58
|
return counter?.sessionId === context.sessionId && counter.windowId === context.windowId;
|
|
59
59
|
}
|
|
60
|
-
function readCounter(
|
|
60
|
+
function readCounter(siteKey, cookieless) {
|
|
61
|
+
const raw = cookieless ? null : getStorageItem("sessionStorage", sequenceKey(siteKey));
|
|
62
|
+
if (raw === null) return { corrupt: false };
|
|
61
63
|
try {
|
|
62
|
-
const parsed = JSON.parse(
|
|
63
|
-
if (parsed && typeof parsed.sessionId === "string" && typeof parsed.windowId === "string" && typeof parsed.next === "number" && Number.isSafeInteger(parsed.next) && parsed.next >= 0
|
|
64
|
+
const parsed = JSON.parse(raw);
|
|
65
|
+
if (parsed && typeof parsed.sessionId === "string" && typeof parsed.windowId === "string" && typeof parsed.next === "number" && Number.isSafeInteger(parsed.next) && parsed.next >= 0) return {
|
|
66
|
+
corrupt: false,
|
|
67
|
+
counter: parsed
|
|
68
|
+
};
|
|
64
69
|
} catch {}
|
|
70
|
+
return { corrupt: true };
|
|
65
71
|
}
|
|
66
72
|
const sequenceKey = (siteKey) => `faststats_replay_sequence_v1_${siteKey}`;
|
|
67
73
|
/** Whether this recording already sent batches, e.g. from an earlier page load. */
|
|
68
74
|
function hasReplaySequence(siteKey, context, cookieless) {
|
|
69
|
-
|
|
70
|
-
return [counters.get(siteKey), raw === null ? void 0 : readCounter(raw)].some((counter) => sameRecording(counter, context) && counter.next > 0);
|
|
75
|
+
return [counters.get(siteKey), readCounter(siteKey, cookieless).counter].some((counter) => sameRecording(counter, context) && counter.next > 0);
|
|
71
76
|
}
|
|
77
|
+
/** Next batch sequence for this recording, or undefined when it cannot be trusted. */
|
|
72
78
|
function nextReplaySequence(siteKey, context, cookieless) {
|
|
73
|
-
const key = sequenceKey(siteKey);
|
|
74
79
|
const cached = counters.get(siteKey);
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const next = legacy === null ? 0 : Number(legacy);
|
|
84
|
-
if (!Number.isSafeInteger(next) || next < 0 || legacy !== null && !/^\d+$/.test(legacy)) return;
|
|
85
|
-
counter = {
|
|
86
|
-
sessionId: context.sessionId,
|
|
87
|
-
windowId: context.windowId,
|
|
88
|
-
next,
|
|
89
|
-
version: legacy === null ? 1 : 0
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (!Number.isSafeInteger(counter.next + 1)) return;
|
|
93
|
-
const sequence = counter.next;
|
|
94
|
-
const updated = {
|
|
95
|
-
...counter,
|
|
80
|
+
const stored = readCounter(siteKey, cookieless);
|
|
81
|
+
if (stored.corrupt && !sameRecording(cached, context)) return;
|
|
82
|
+
let sequence = sameRecording(cached, context) ? cached.next : 0;
|
|
83
|
+
if (sameRecording(stored.counter, context)) sequence = Math.max(sequence, stored.counter.next);
|
|
84
|
+
if (!Number.isSafeInteger(sequence + 1)) return;
|
|
85
|
+
const counter = {
|
|
86
|
+
sessionId: context.sessionId,
|
|
87
|
+
windowId: context.windowId,
|
|
96
88
|
next: sequence + 1
|
|
97
89
|
};
|
|
98
|
-
counters.set(siteKey,
|
|
99
|
-
if (!cookieless
|
|
100
|
-
return
|
|
101
|
-
sequence,
|
|
102
|
-
...counter.version === 1 ? { sequenceContractVersion: 1 } : {}
|
|
103
|
-
};
|
|
90
|
+
counters.set(siteKey, counter);
|
|
91
|
+
if (!cookieless) setStorageItem("sessionStorage", sequenceKey(siteKey), JSON.stringify(counter));
|
|
92
|
+
return sequence;
|
|
104
93
|
}
|
|
105
94
|
//#endregion
|
|
106
95
|
//#region src/replay.ts
|
|
107
96
|
const MAX_TIMEOUT_MS = 2147483647;
|
|
108
97
|
const DEFAULT_MAX_QUEUE_SIZE_BYTES = 2097152;
|
|
109
98
|
const DEFAULT_MAX_BATCH_SIZE_BYTES = 921600;
|
|
110
|
-
const
|
|
99
|
+
const KEEPALIVE_LIMIT_BYTES = 61440;
|
|
100
|
+
const DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES = 57344;
|
|
111
101
|
const RETRY_BASE_DELAY_MS = 2e3;
|
|
112
102
|
const RETRY_MAX_ATTEMPTS = 5;
|
|
113
103
|
const RETRY_COOLDOWN_MS = 3e5;
|
|
@@ -116,7 +106,12 @@ const RRWEB_EVENT_FULL_SNAPSHOT = 2;
|
|
|
116
106
|
const RRWEB_EVENT_META = 4;
|
|
117
107
|
const REPLAY_SEQUENTIAL_ID_KEY = "_faststatsSeqId";
|
|
118
108
|
const textEncoder = new TextEncoder();
|
|
109
|
+
const eventSizeCache = /* @__PURE__ */ new WeakMap();
|
|
119
110
|
const batchSizeCache = /* @__PURE__ */ new WeakMap();
|
|
111
|
+
async function gzip(json) {
|
|
112
|
+
const stream = new Blob([json]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
113
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
114
|
+
}
|
|
120
115
|
function boundedInteger(value, fallback, min, max) {
|
|
121
116
|
if (value === void 0 || !Number.isFinite(value) || value < min) return fallback;
|
|
122
117
|
return Math.min(Math.trunc(value), max);
|
|
@@ -145,7 +140,7 @@ function sessionReplay(options = {}) {
|
|
|
145
140
|
var ReplayTracker = class {
|
|
146
141
|
options;
|
|
147
142
|
endpoint;
|
|
148
|
-
|
|
143
|
+
compress;
|
|
149
144
|
flushInterval;
|
|
150
145
|
maxEvents;
|
|
151
146
|
maxBatchSizeBytes;
|
|
@@ -183,8 +178,9 @@ var ReplayTracker = class {
|
|
|
183
178
|
constructor(options) {
|
|
184
179
|
this.options = options;
|
|
185
180
|
this.lastCookielessMode = options.cookieless ?? false;
|
|
186
|
-
this.endpoint = `${resolveBaseUrl(options.baseUrl
|
|
187
|
-
this.
|
|
181
|
+
this.endpoint = `${resolveBaseUrl(options.baseUrl)}${URLS.replay}`;
|
|
182
|
+
this.compress = (options.compress ?? true) && typeof window !== "undefined" && "CompressionStream" in window;
|
|
183
|
+
this.flushInterval = boundedInteger(options.flushInterval, 5e3, 1, MAX_TIMEOUT_MS);
|
|
188
184
|
this.maxEvents = boundedInteger(options.maxEvents, 1e3, 1, 1e5);
|
|
189
185
|
this.maxBatchSizeBytes = boundedInteger(options.maxBatchSizeBytes, DEFAULT_MAX_BATCH_SIZE_BYTES, 1, 33554432);
|
|
190
186
|
this.maxLowLatencyBatchSizeBytes = boundedInteger(options.maxLowLatencyBatchSizeBytes, DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES, 1, DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES);
|
|
@@ -196,6 +192,12 @@ var ReplayTracker = class {
|
|
|
196
192
|
log(...args) {
|
|
197
193
|
if (this.options.debug) console.log("[Replay]", ...args);
|
|
198
194
|
}
|
|
195
|
+
sessionContext() {
|
|
196
|
+
return getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
197
|
+
}
|
|
198
|
+
sanitize(url) {
|
|
199
|
+
return sanitizeUrl(url, this.options.trackHash ?? false);
|
|
200
|
+
}
|
|
199
201
|
clearFlushTimers() {
|
|
200
202
|
if (this.flushTask) clearTimeout(this.flushTask);
|
|
201
203
|
if (this.retryTask) clearTimeout(this.retryTask);
|
|
@@ -205,10 +207,13 @@ var ReplayTracker = class {
|
|
|
205
207
|
this.retryAttempt = 0;
|
|
206
208
|
this.minLengthFlushTask = null;
|
|
207
209
|
}
|
|
208
|
-
|
|
209
|
-
this.needsFinalization = false;
|
|
210
|
+
dropEvents() {
|
|
210
211
|
this.events.length = 0;
|
|
211
212
|
this.queuedEventsSizeBytes = 0;
|
|
213
|
+
}
|
|
214
|
+
clearQueues() {
|
|
215
|
+
this.needsFinalization = false;
|
|
216
|
+
this.dropEvents();
|
|
212
217
|
this.pending.length = 0;
|
|
213
218
|
this.pendingSizeBytes = 0;
|
|
214
219
|
}
|
|
@@ -223,7 +228,7 @@ var ReplayTracker = class {
|
|
|
223
228
|
if (this.started) this.subscribeToSessionRotation();
|
|
224
229
|
this.clearQueues();
|
|
225
230
|
this.clearFlushTimers();
|
|
226
|
-
const context =
|
|
231
|
+
const context = this.sessionContext();
|
|
227
232
|
this.activeSessionId = context.sessionId;
|
|
228
233
|
this.resetSessionTiming(context);
|
|
229
234
|
this.beginReplayStream();
|
|
@@ -236,20 +241,19 @@ var ReplayTracker = class {
|
|
|
236
241
|
this.overflowed = false;
|
|
237
242
|
this.replayEventSequence = 0;
|
|
238
243
|
this.viewId = createId();
|
|
239
|
-
this.viewUrl =
|
|
240
|
-
const context =
|
|
244
|
+
this.viewUrl = this.sanitize(window.location.href);
|
|
245
|
+
const context = this.sessionContext();
|
|
241
246
|
this.activeSessionId = context.sessionId;
|
|
242
247
|
this.resetSessionTiming(context);
|
|
243
248
|
this.subscribeToSessionRotation();
|
|
244
249
|
this.beginRecording();
|
|
245
250
|
this.intervalId = setInterval(() => this.requestFlush("interval"), this.flushInterval);
|
|
246
|
-
window.addEventListener("beforeunload", this.onUnload);
|
|
247
251
|
this.log("Recording started");
|
|
248
252
|
}
|
|
249
253
|
trackPageChange(url) {
|
|
250
254
|
if (!this.started) return;
|
|
251
255
|
this.viewId = createId();
|
|
252
|
-
const href =
|
|
256
|
+
const href = this.sanitize(url ?? window.location.href);
|
|
253
257
|
this.viewUrl = href;
|
|
254
258
|
this.onEvent({
|
|
255
259
|
type: VIEW_META_EVENT_TYPE,
|
|
@@ -293,7 +297,7 @@ var ReplayTracker = class {
|
|
|
293
297
|
}));
|
|
294
298
|
if (this.options.recordNetwork === true) {
|
|
295
299
|
const options = this.options.networkOptions;
|
|
296
|
-
const base = resolveBaseUrl(this.options.baseUrl
|
|
300
|
+
const base = resolveBaseUrl(this.options.baseUrl);
|
|
297
301
|
const endpoints = new Set(Object.values(URLS).map((path) => {
|
|
298
302
|
const url = new URL(`${base}${path}`, window.location.href);
|
|
299
303
|
return `${url.origin}${url.pathname}`;
|
|
@@ -356,19 +360,12 @@ var ReplayTracker = class {
|
|
|
356
360
|
if (this.intervalId) clearInterval(this.intervalId);
|
|
357
361
|
this.clearFlushTimers();
|
|
358
362
|
this.intervalId = null;
|
|
359
|
-
window.removeEventListener("beforeunload", this.onUnload);
|
|
360
363
|
if (discard) {
|
|
361
364
|
this.clearQueues();
|
|
362
365
|
this.log("Recording discarded");
|
|
363
366
|
return;
|
|
364
367
|
}
|
|
365
|
-
|
|
366
|
-
this.events.length = 0;
|
|
367
|
-
this.queuedEventsSizeBytes = 0;
|
|
368
|
-
this.needsFinalization = false;
|
|
369
|
-
this.log(`Recording too short (${Date.now() - this.startTime}ms), discarding events`);
|
|
370
|
-
}
|
|
371
|
-
this.flush(true, true, "stop");
|
|
368
|
+
this.requestTerminalFlush("stop");
|
|
372
369
|
this.log("Recording stopped");
|
|
373
370
|
}
|
|
374
371
|
onSessionRotated(prev, next) {
|
|
@@ -383,10 +380,7 @@ var ReplayTracker = class {
|
|
|
383
380
|
applySessionRotation(prev, next) {
|
|
384
381
|
if (this.activeSessionId === next.sessionId) return;
|
|
385
382
|
if (this.hasReachedMinLength()) this.enqueueEventsIfReady(prev, "sessionRotate");
|
|
386
|
-
else
|
|
387
|
-
this.events.length = 0;
|
|
388
|
-
this.queuedEventsSizeBytes = 0;
|
|
389
|
-
}
|
|
383
|
+
else this.dropEvents();
|
|
390
384
|
this.finalizeSession(prev, "sessionRotate");
|
|
391
385
|
this.needsFinalization = false;
|
|
392
386
|
this.activeSessionId = next.sessionId;
|
|
@@ -430,7 +424,7 @@ var ReplayTracker = class {
|
|
|
430
424
|
...event,
|
|
431
425
|
data: {
|
|
432
426
|
...data,
|
|
433
|
-
href:
|
|
427
|
+
href: this.sanitize(data.href)
|
|
434
428
|
}
|
|
435
429
|
};
|
|
436
430
|
}
|
|
@@ -457,22 +451,19 @@ var ReplayTracker = class {
|
|
|
457
451
|
if (reason) this.requestFlush(reason);
|
|
458
452
|
else this.scheduleMinLengthFlush();
|
|
459
453
|
};
|
|
460
|
-
onUnload = () => {
|
|
461
|
-
this.flush(true, false, "unload");
|
|
462
|
-
};
|
|
463
454
|
requestTerminalFlush(reason) {
|
|
464
455
|
if (!this.hasReachedMinLength()) {
|
|
465
|
-
this.
|
|
466
|
-
this.queuedEventsSizeBytes = 0;
|
|
456
|
+
this.dropEvents();
|
|
467
457
|
this.needsFinalization = false;
|
|
468
|
-
|
|
458
|
+
this.log(`Recording too short (${Date.now() - this.startTime}ms), discarding events`);
|
|
459
|
+
if (this.pending.length === 0) return;
|
|
469
460
|
}
|
|
470
461
|
this.flush(true, true, reason);
|
|
471
462
|
}
|
|
472
463
|
hasReachedMinLength() {
|
|
473
464
|
return this.resumed || this.minReplayLengthMs <= 0 || Date.now() - this.startTime >= this.minReplayLengthMs;
|
|
474
465
|
}
|
|
475
|
-
requestFlush
|
|
466
|
+
requestFlush(reason) {
|
|
476
467
|
if (this.flushTask || this.events.length === 0) return;
|
|
477
468
|
if (this.minLengthFlushTask) {
|
|
478
469
|
clearTimeout(this.minLengthFlushTask);
|
|
@@ -482,7 +473,7 @@ var ReplayTracker = class {
|
|
|
482
473
|
this.flushTask = null;
|
|
483
474
|
this.flush(false, false, reason);
|
|
484
475
|
}, 0);
|
|
485
|
-
}
|
|
476
|
+
}
|
|
486
477
|
scheduleMinLengthFlush() {
|
|
487
478
|
if (!this.initialFlushPending || this.events.length === 0 || this.minReplayLengthMs <= 0) return;
|
|
488
479
|
if (this.hasReachedMinLength()) {
|
|
@@ -498,7 +489,7 @@ var ReplayTracker = class {
|
|
|
498
489
|
this.requestFlush("minLength");
|
|
499
490
|
}, Math.min(remainingMs, MAX_TIMEOUT_MS));
|
|
500
491
|
}
|
|
501
|
-
enqueueEventsIfReady(context, flushReason
|
|
492
|
+
enqueueEventsIfReady(context, flushReason, maxBatchSizeBytes = this.maxBatchSizeBytes) {
|
|
502
493
|
if (this.events.length === 0) return;
|
|
503
494
|
if (!this.hasReachedMinLength()) {
|
|
504
495
|
this.scheduleMinLengthFlush();
|
|
@@ -532,10 +523,10 @@ var ReplayTracker = class {
|
|
|
532
523
|
if (current.length > 0) batches.push(current);
|
|
533
524
|
return batches;
|
|
534
525
|
}
|
|
535
|
-
createBatch(events, context, isFinal, flushReason
|
|
526
|
+
createBatch(events, context, isFinal, flushReason) {
|
|
536
527
|
const identifier = getAnonymousId(this.options.siteKey, this.lastCookielessMode);
|
|
537
528
|
const sequence = nextReplaySequence(this.options.siteKey, context, this.lastCookielessMode);
|
|
538
|
-
if (
|
|
529
|
+
if (sequence === void 0) {
|
|
539
530
|
this.stopCapture("Replay sequence state is invalid or exhausted");
|
|
540
531
|
return;
|
|
541
532
|
}
|
|
@@ -551,17 +542,23 @@ var ReplayTracker = class {
|
|
|
551
542
|
chunkStartedAt,
|
|
552
543
|
chunkEndedAt: now,
|
|
553
544
|
...identifier ? { identifier } : {},
|
|
554
|
-
batchId: `${context.sessionId}-${sequence
|
|
555
|
-
|
|
545
|
+
batchId: `${context.sessionId}-${sequence}-${chunkStartedAt}`,
|
|
546
|
+
sequence,
|
|
547
|
+
sequenceContractVersion: 1,
|
|
556
548
|
timestamp: now,
|
|
557
|
-
url: this.viewUrl ||
|
|
549
|
+
url: this.viewUrl || this.sanitize(window.location.href),
|
|
558
550
|
...isFinal ? { isFinal: true } : {},
|
|
559
551
|
flushReason,
|
|
560
552
|
events
|
|
561
553
|
};
|
|
562
554
|
}
|
|
563
555
|
eventSizeBytes(event) {
|
|
564
|
-
|
|
556
|
+
let size = eventSizeCache.get(event);
|
|
557
|
+
if (size === void 0) {
|
|
558
|
+
size = textEncoder.encode(JSON.stringify(event)).byteLength + 1;
|
|
559
|
+
eventSizeCache.set(event, size);
|
|
560
|
+
}
|
|
561
|
+
return size;
|
|
565
562
|
}
|
|
566
563
|
batchSizeBytes(batch) {
|
|
567
564
|
const cached = batchSizeCache.get(batch);
|
|
@@ -595,7 +592,7 @@ var ReplayTracker = class {
|
|
|
595
592
|
}
|
|
596
593
|
const rotation = touchActivity(this.options.siteKey, this.lastCookielessMode);
|
|
597
594
|
if (rotation) this.applySessionRotation(rotation.prev, rotation.next);
|
|
598
|
-
const context =
|
|
595
|
+
const context = this.sessionContext();
|
|
599
596
|
this.enqueueEventsIfReady(context, flushReason, lowLatency ? this.maxLowLatencyBatchSizeBytes : this.maxBatchSizeBytes);
|
|
600
597
|
if (!isFinal) return this.sendPending(lowLatency);
|
|
601
598
|
this.finalizeSession(context, flushReason);
|
|
@@ -647,33 +644,25 @@ var ReplayTracker = class {
|
|
|
647
644
|
}
|
|
648
645
|
async sendBatch(batch, lowLatency) {
|
|
649
646
|
const json = JSON.stringify(batch);
|
|
650
|
-
const
|
|
651
|
-
const
|
|
647
|
+
const keepalive = lowLatency && this.batchSizeBytes(batch) <= KEEPALIVE_LIMIT_BYTES;
|
|
648
|
+
const send = (data, contentType, query = "") => sendData({
|
|
649
|
+
url: `${this.endpoint}${query}`,
|
|
650
|
+
data,
|
|
651
|
+
contentType,
|
|
652
652
|
debug: this.options.debug,
|
|
653
653
|
debugPrefix: "[Replay]",
|
|
654
|
-
useBeacon:
|
|
655
|
-
keepalive
|
|
654
|
+
useBeacon: keepalive,
|
|
655
|
+
keepalive,
|
|
656
656
|
settleRejected: true
|
|
657
|
-
};
|
|
658
|
-
if (!lowLatency &&
|
|
659
|
-
const
|
|
660
|
-
const compressed = new Uint8Array(await new Response(stream).arrayBuffer());
|
|
657
|
+
});
|
|
658
|
+
if (!lowLatency && this.compress) try {
|
|
659
|
+
const compressed = await gzip(json);
|
|
661
660
|
this.log(`Compressed ${json.length}B -> ${compressed.byteLength}B (${Math.round(compressed.byteLength / json.length * 100)}%)`);
|
|
662
|
-
return
|
|
663
|
-
url: `${this.endpoint}?encoding=gzip`,
|
|
664
|
-
data: compressed,
|
|
665
|
-
contentType: "application/octet-stream",
|
|
666
|
-
...sendOptions
|
|
667
|
-
});
|
|
661
|
+
return send(compressed, "application/octet-stream", "?encoding=gzip");
|
|
668
662
|
} catch {
|
|
669
663
|
this.log("Compression failed, using uncompressed");
|
|
670
664
|
}
|
|
671
|
-
return
|
|
672
|
-
url: this.endpoint,
|
|
673
|
-
data: json,
|
|
674
|
-
contentType: "application/json",
|
|
675
|
-
...sendOptions
|
|
676
|
-
});
|
|
665
|
+
return send(json, "application/json");
|
|
677
666
|
}
|
|
678
667
|
};
|
|
679
668
|
//#endregion
|
package/dist/web-vitals.d.ts
CHANGED
package/dist/web-vitals.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as
|
|
2
|
-
import {
|
|
1
|
+
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as sendData } from "./chunks/send-data-DYZZjeM-.js";
|
|
2
|
+
import { n as getSessionContext } from "./chunks/session-manager-CFQEWbgo.js";
|
|
3
3
|
import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals/attribution";
|
|
4
4
|
//#region src/utils/web-vitals-collector.ts
|
|
5
5
|
const collectors = /* @__PURE__ */ new WeakMap();
|
|
@@ -72,7 +72,7 @@ var WebVitalsTracker = class {
|
|
|
72
72
|
cookieless;
|
|
73
73
|
constructor(options) {
|
|
74
74
|
this.options = options;
|
|
75
|
-
this.endpoint = `${resolveBaseUrl(options.baseUrl
|
|
75
|
+
this.endpoint = `${resolveBaseUrl(options.baseUrl)}${URLS.vitals}`;
|
|
76
76
|
this.cookieless = options.cookieless ?? false;
|
|
77
77
|
}
|
|
78
78
|
setCookielessMode(cookieless) {
|
|
@@ -104,10 +104,8 @@ var WebVitalsTracker = class {
|
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
onMetric = (metric) => {
|
|
107
|
-
if (!this.started) return;
|
|
108
|
-
|
|
109
|
-
if (!METRIC_NAMES.has(name) || !Number.isFinite(metric.value) || metric.value < 0) return;
|
|
110
|
-
this.metrics.set(name, metric);
|
|
107
|
+
if (!this.started || !METRIC_NAMES.has(metric.name) || !Number.isFinite(metric.value) || metric.value < 0) return;
|
|
108
|
+
this.metrics.set(metric.name, metric);
|
|
111
109
|
};
|
|
112
110
|
discard() {
|
|
113
111
|
this.generation++;
|
package/package.json
CHANGED
|
@@ -13,11 +13,6 @@
|
|
|
13
13
|
"import": "./dist/index.js",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./outbound-links": {
|
|
17
|
-
"types": "./dist/outbound-links.d.ts",
|
|
18
|
-
"import": "./dist/outbound-links.js",
|
|
19
|
-
"default": "./dist/outbound-links.js"
|
|
20
|
-
},
|
|
21
16
|
"./replay": {
|
|
22
17
|
"types": "./dist/replay.d.ts",
|
|
23
18
|
"import": "./dist/replay.js",
|
|
@@ -28,6 +23,11 @@
|
|
|
28
23
|
"import": "./dist/error.js",
|
|
29
24
|
"default": "./dist/error.js"
|
|
30
25
|
},
|
|
26
|
+
"./flags": {
|
|
27
|
+
"types": "./dist/flags.d.ts",
|
|
28
|
+
"import": "./dist/flags.js",
|
|
29
|
+
"default": "./dist/flags.js"
|
|
30
|
+
},
|
|
31
31
|
"./web-vitals": {
|
|
32
32
|
"types": "./dist/web-vitals.d.ts",
|
|
33
33
|
"import": "./dist/web-vitals.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.
|
|
45
|
+
"version": "0.12.0",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown && bun run check-size",
|
|
48
48
|
"dev": "bun run build",
|
|
@@ -51,15 +51,15 @@
|
|
|
51
51
|
"check-size": "node scripts/check-bundle-size.mjs"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@rrweb/types": "^2.1.
|
|
54
|
+
"@rrweb/types": "^2.1.7",
|
|
55
55
|
"@types/bun": "^1.4.2",
|
|
56
56
|
"tsdown": "^0.23.0",
|
|
57
57
|
"typescript": "^7.0.2"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@rrweb/record": "^2.1.
|
|
61
|
-
"@rrweb/rrweb-plugin-console-record": "^2.1.
|
|
62
|
-
"@rrweb/rrweb-plugin-network-record": "^2.1.
|
|
63
|
-
"web-vitals": "^6.2.
|
|
60
|
+
"@rrweb/record": "^2.1.7",
|
|
61
|
+
"@rrweb/rrweb-plugin-console-record": "^2.1.7",
|
|
62
|
+
"@rrweb/rrweb-plugin-network-record": "^2.1.7",
|
|
63
|
+
"web-vitals": "^6.2.3"
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
//#region src/utils/api-urls.ts
|
|
2
|
-
const ANALYTICS_BASE = "https://metrics.faststats.dev";
|
|
3
|
-
function resolveBaseUrl(input, fallback) {
|
|
4
|
-
return input?.replace(/\/+$/, "") || fallback;
|
|
5
|
-
}
|
|
6
|
-
const URLS = {
|
|
7
|
-
events: "/v1/web",
|
|
8
|
-
identify: "/v1/identify",
|
|
9
|
-
replay: "/v1/replay",
|
|
10
|
-
vitals: "/v1/vitals"
|
|
11
|
-
};
|
|
12
|
-
function sanitizeUrl(url, includeHash = false) {
|
|
13
|
-
try {
|
|
14
|
-
const parsed = new URL(url, location.href);
|
|
15
|
-
return `${parsed.origin}${parsed.pathname}${includeHash ? parsed.hash : ""}`;
|
|
16
|
-
} catch {
|
|
17
|
-
return url.split(includeHash ? "?" : /[?#]/, 1)[0] ?? "";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
const UTM_KEYS = [
|
|
21
|
-
"utm_source",
|
|
22
|
-
"utm_medium",
|
|
23
|
-
"utm_campaign",
|
|
24
|
-
"utm_term",
|
|
25
|
-
"utm_content"
|
|
26
|
-
];
|
|
27
|
-
function getReferrer() {
|
|
28
|
-
const raw = document.referrer;
|
|
29
|
-
if (!raw) return null;
|
|
30
|
-
try {
|
|
31
|
-
const pageOrigin = new URL(location.href).origin;
|
|
32
|
-
if (new URL(raw, location.href).origin === pageOrigin) return null;
|
|
33
|
-
} catch {}
|
|
34
|
-
return sanitizeUrl(raw);
|
|
35
|
-
}
|
|
36
|
-
function getPageContext(includeHash = false) {
|
|
37
|
-
const hash = includeHash ? location.hash : "";
|
|
38
|
-
const ctx = {
|
|
39
|
-
page: `${location.pathname}${hash}`,
|
|
40
|
-
url: sanitizeUrl(location.href, includeHash),
|
|
41
|
-
referrer: getReferrer(),
|
|
42
|
-
title: document.title || ""
|
|
43
|
-
};
|
|
44
|
-
if (!location.search) return ctx;
|
|
45
|
-
const sp = new URLSearchParams(location.search);
|
|
46
|
-
for (const key of UTM_KEYS) {
|
|
47
|
-
const value = sp.get(key);
|
|
48
|
-
if (value) ctx[key] = value;
|
|
49
|
-
}
|
|
50
|
-
return ctx;
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
export { sanitizeUrl as a, resolveBaseUrl as i, URLS as n, getPageContext as r, ANALYTICS_BASE as t };
|
package/dist/outbound-links.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
|
-
//#region src/outbound-links.d.ts
|
|
3
|
-
export declare function outboundLinks(): AnalyticsExtension;
|
|
4
|
-
export declare class OutboundLinkTracker {
|
|
5
|
-
private lastActivation;
|
|
6
|
-
getHref(event: Event): string | null;
|
|
7
|
-
private isDuplicate;
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
package/dist/outbound-links.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { a as sanitizeUrl } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
//#region src/outbound-links.ts
|
|
3
|
-
function outboundLinks() {
|
|
4
|
-
return {
|
|
5
|
-
name: "outbound-links",
|
|
6
|
-
setup(context) {
|
|
7
|
-
const tracker = new OutboundLinkTracker();
|
|
8
|
-
const onClick = (event) => {
|
|
9
|
-
const href = tracker.getHref(event);
|
|
10
|
-
if (href) context.capture("outbound_link", { outbound_link: sanitizeUrl(href) });
|
|
11
|
-
};
|
|
12
|
-
document.addEventListener("click", onClick, true);
|
|
13
|
-
document.addEventListener("auxclick", onClick, true);
|
|
14
|
-
return () => {
|
|
15
|
-
document.removeEventListener("click", onClick, true);
|
|
16
|
-
document.removeEventListener("auxclick", onClick, true);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const DEDUPE_MS = 500;
|
|
22
|
-
var OutboundLinkTracker = class {
|
|
23
|
-
lastActivation = null;
|
|
24
|
-
getHref(event) {
|
|
25
|
-
if (!(event instanceof MouseEvent) || !isEligibleActivation(event)) return null;
|
|
26
|
-
const anchor = findOutboundAnchor(event);
|
|
27
|
-
if (!anchor || this.isDuplicate(event, anchor.href)) return null;
|
|
28
|
-
return anchor.href;
|
|
29
|
-
}
|
|
30
|
-
isDuplicate(event, href) {
|
|
31
|
-
const activation = {
|
|
32
|
-
button: event.button,
|
|
33
|
-
href,
|
|
34
|
-
clientX: event.clientX,
|
|
35
|
-
clientY: event.clientY,
|
|
36
|
-
at: Date.now()
|
|
37
|
-
};
|
|
38
|
-
const previous = this.lastActivation;
|
|
39
|
-
this.lastActivation = activation;
|
|
40
|
-
return !!(previous && previous.href === activation.href && previous.button === activation.button && previous.clientX === activation.clientX && previous.clientY === activation.clientY && activation.at - previous.at <= DEDUPE_MS);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
function isEligibleActivation(event) {
|
|
44
|
-
if (event.defaultPrevented) return false;
|
|
45
|
-
if (event.type === "click") return event.button === 0;
|
|
46
|
-
if (event.type === "auxclick") return event.button === 1;
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
function findOutboundAnchor(event) {
|
|
50
|
-
for (const node of event.composedPath()) if (isOutboundAnchor(node)) return node;
|
|
51
|
-
let node = event.target;
|
|
52
|
-
while (node) {
|
|
53
|
-
if (isOutboundAnchor(node)) return node;
|
|
54
|
-
node = node.parentNode;
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
function isOutboundAnchor(node) {
|
|
59
|
-
return node instanceof HTMLAnchorElement && (node.protocol === "http:" || node.protocol === "https:") && node.host !== location.host;
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
export { OutboundLinkTracker, outboundLinks };
|