@faststats/web 0.10.1 → 0.10.3
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/dist/chunks/{identifiers-xX7o7oZz.js → identifiers-BgGtAQdp.js} +4 -7
- package/dist/chunks/{send-data-Dplz7m4c.js → send-data-DAVUdfNm.js} +5 -9
- package/dist/error.js +3 -3
- package/dist/index.js +2 -2
- package/dist/replay.d.ts +3 -5
- package/dist/replay.js +140 -106
- package/dist/web-vitals.js +1 -1
- package/package.json +2 -2
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import { c as removeStorageItem, l as setStorageItem, n as createId, s as getStorageItem } from "./send-data-
|
|
1
|
+
import { c as removeStorageItem, l as setStorageItem, n as createId, s as getStorageItem } from "./send-data-DAVUdfNm.js";
|
|
2
2
|
//#region src/utils/identifiers.ts
|
|
3
3
|
const ANONYMOUS_ID_KEY = "faststats_anon_id_";
|
|
4
|
-
function
|
|
4
|
+
function getAnonymousId(siteKey, cookieless = false) {
|
|
5
|
+
if (cookieless) return "";
|
|
5
6
|
const key = `${ANONYMOUS_ID_KEY}${siteKey}`;
|
|
6
7
|
const existingId = getStorageItem("localStorage", key);
|
|
7
8
|
if (existingId) return existingId;
|
|
8
9
|
const newId = createId();
|
|
9
10
|
return setStorageItem("localStorage", key, newId) ? newId : "";
|
|
10
11
|
}
|
|
11
|
-
function getAnonymousId(siteKey, cookieless = false) {
|
|
12
|
-
if (cookieless) return "";
|
|
13
|
-
return getOrCreateAnonymousId(siteKey);
|
|
14
|
-
}
|
|
15
12
|
function resetAnonymousId(siteKey, cookieless) {
|
|
16
13
|
if (cookieless) return "";
|
|
17
14
|
if (!removeStorageItem("localStorage", `${ANONYMOUS_ID_KEY}${siteKey}`)) return "";
|
|
18
|
-
return
|
|
15
|
+
return getAnonymousId(siteKey);
|
|
19
16
|
}
|
|
20
17
|
//#endregion
|
|
21
18
|
export { resetAnonymousId as n, getAnonymousId as t };
|
|
@@ -150,7 +150,7 @@ function onSessionRotated(siteKey, cookieless, listener) {
|
|
|
150
150
|
//#endregion
|
|
151
151
|
//#region src/utils/send-data.ts
|
|
152
152
|
async function sendData(options) {
|
|
153
|
-
const { url, data, contentType = "application/json", headers = {}, debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, signal } = options;
|
|
153
|
+
const { url, data, contentType = "application/json", headers = {}, debug = false, debugPrefix = "[Analytics]", useBeacon = true, keepalive = true, signal, settleRejected } = options;
|
|
154
154
|
if (signal?.aborted) return false;
|
|
155
155
|
if (useBeacon && typeof globalThis.navigator?.sendBeacon === "function") try {
|
|
156
156
|
const beaconBody = data instanceof Blob || typeof Blob === "undefined" ? data : new Blob([data], { type: contentType });
|
|
@@ -160,7 +160,7 @@ async function sendData(options) {
|
|
|
160
160
|
}
|
|
161
161
|
} catch {}
|
|
162
162
|
try {
|
|
163
|
-
const
|
|
163
|
+
const { ok, status } = await fetch(url, {
|
|
164
164
|
method: "POST",
|
|
165
165
|
body: data,
|
|
166
166
|
headers: {
|
|
@@ -171,14 +171,10 @@ async function sendData(options) {
|
|
|
171
171
|
credentials: "omit",
|
|
172
172
|
signal
|
|
173
173
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const message = success ? `${debugPrefix} Sent via fetch` : `${debugPrefix} Failed: ${response.status}`;
|
|
177
|
-
console[success ? "log" : "warn"](message);
|
|
178
|
-
}
|
|
179
|
-
return success;
|
|
174
|
+
if (debug) console.log(debugPrefix, ok ? "Sent via fetch" : status);
|
|
175
|
+
return ok || !!settleRejected && status < 500 && status !== 429;
|
|
180
176
|
} catch {
|
|
181
|
-
if (debug) console.
|
|
177
|
+
if (debug) console.log(debugPrefix, "Failed to send");
|
|
182
178
|
return false;
|
|
183
179
|
}
|
|
184
180
|
}
|
package/dist/error.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { i as resolveBaseUrl, n as URLS, r as getPageContext, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
2
|
+
import { r as getSessionContext, t as sendData } from "./chunks/send-data-DAVUdfNm.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-BgGtAQdp.js";
|
|
4
4
|
//#region src/error.ts
|
|
5
5
|
const SDK_NAME = "@faststats/web";
|
|
6
|
-
const SDK_VERSION = "0.10.
|
|
6
|
+
const SDK_VERSION = "0.10.3";
|
|
7
7
|
function errorTracking(options = {}) {
|
|
8
8
|
return {
|
|
9
9
|
name: "error-tracking",
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as resolveBaseUrl, n as URLS, r as getPageContext, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { a as resetSession, n as createId, o as touchActivity, r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-
|
|
2
|
+
import { a as resetSession, n as createId, o as touchActivity, r as getSessionContext, t as sendData } from "./chunks/send-data-DAVUdfNm.js";
|
|
3
|
+
import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-BgGtAQdp.js";
|
|
4
4
|
//#region src/extensions.ts
|
|
5
5
|
var ExtensionHost = class {
|
|
6
6
|
runtime;
|
package/dist/replay.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface ReplayTrackerOptions {
|
|
|
31
31
|
maxLowLatencyBatchSizeBytes?: number;
|
|
32
32
|
maxPendingBatches?: number;
|
|
33
33
|
maxQueueSizeBytes?: number;
|
|
34
|
+
/** Minimum elapsed time per recording stream before upload. Defaults to 3000. */
|
|
34
35
|
minReplayLengthMs?: number;
|
|
35
36
|
sampling?: RecordOptions["sampling"];
|
|
36
37
|
slimDOMOptions?: RecordOptions["slimDOMOptions"];
|
|
@@ -67,7 +68,6 @@ export default class ReplayTracker {
|
|
|
67
68
|
private readonly minReplayLengthMs;
|
|
68
69
|
private readonly events;
|
|
69
70
|
private readonly pending;
|
|
70
|
-
private readonly minLengthBlockedBatches;
|
|
71
71
|
private queuedEventsSizeBytes;
|
|
72
72
|
private pendingSizeBytes;
|
|
73
73
|
private viewId;
|
|
@@ -76,7 +76,6 @@ export default class ReplayTracker {
|
|
|
76
76
|
private started;
|
|
77
77
|
private disposed;
|
|
78
78
|
private startTime;
|
|
79
|
-
private sequence;
|
|
80
79
|
private intervalId;
|
|
81
80
|
private flushTask;
|
|
82
81
|
private retryTask;
|
|
@@ -90,6 +89,7 @@ export default class ReplayTracker {
|
|
|
90
89
|
private lastCookielessMode;
|
|
91
90
|
private needsFinalization;
|
|
92
91
|
private initialFlushPending;
|
|
92
|
+
private resumed;
|
|
93
93
|
private overflowed;
|
|
94
94
|
private replayEventSequence;
|
|
95
95
|
private readonly mutationThrottler;
|
|
@@ -97,10 +97,7 @@ export default class ReplayTracker {
|
|
|
97
97
|
private log;
|
|
98
98
|
private clearFlushTimers;
|
|
99
99
|
private clearQueues;
|
|
100
|
-
private dropMinLengthBlockedBatches;
|
|
101
|
-
private unblockSessionBatches;
|
|
102
100
|
private finalizeSession;
|
|
103
|
-
private nextBatchSequence;
|
|
104
101
|
setCookielessMode(cookieless: boolean): void;
|
|
105
102
|
start(): void;
|
|
106
103
|
trackPageChange(url?: string): void;
|
|
@@ -126,6 +123,7 @@ export default class ReplayTracker {
|
|
|
126
123
|
private eventSizeBytes;
|
|
127
124
|
private batchSizeBytes;
|
|
128
125
|
private removePending;
|
|
126
|
+
private stopCapture;
|
|
129
127
|
private enqueueBatch;
|
|
130
128
|
private flush;
|
|
131
129
|
private sendPending;
|
package/dist/replay.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { i as onSessionRotated, l as setStorageItem, n as createId, o as touchActivity, r as getSessionContext, s as getStorageItem, t as sendData } from "./chunks/send-data-
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
2
|
+
import { c as removeStorageItem, i as onSessionRotated, l as setStorageItem, n as createId, o as touchActivity, r as getSessionContext, s as getStorageItem, t as sendData } from "./chunks/send-data-DAVUdfNm.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-BgGtAQdp.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";
|
|
@@ -52,6 +52,57 @@ var ReplayMutationThrottler = class {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
//#endregion
|
|
55
|
+
//#region src/replay-sequence.ts
|
|
56
|
+
const counters = /* @__PURE__ */ new Map();
|
|
57
|
+
function sameRecording(counter, context) {
|
|
58
|
+
return counter?.sessionId === context.sessionId && counter.windowId === context.windowId;
|
|
59
|
+
}
|
|
60
|
+
function readCounter(value) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(value);
|
|
63
|
+
if (parsed && typeof parsed.sessionId === "string" && typeof parsed.windowId === "string" && typeof parsed.next === "number" && Number.isSafeInteger(parsed.next) && parsed.next >= 0 && (parsed.version === 0 || parsed.version === 1)) return parsed;
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
const sequenceKey = (siteKey) => `faststats_replay_sequence_v1_${siteKey}`;
|
|
67
|
+
/** Whether this recording already sent batches, e.g. from an earlier page load. */
|
|
68
|
+
function hasReplaySequence(siteKey, context, cookieless) {
|
|
69
|
+
const raw = cookieless ? null : getStorageItem("sessionStorage", sequenceKey(siteKey));
|
|
70
|
+
return [counters.get(siteKey), raw === null ? void 0 : readCounter(raw)].some((counter) => sameRecording(counter, context) && counter.next > 0);
|
|
71
|
+
}
|
|
72
|
+
function nextReplaySequence(siteKey, context, cookieless) {
|
|
73
|
+
const key = sequenceKey(siteKey);
|
|
74
|
+
const cached = counters.get(siteKey);
|
|
75
|
+
const raw = cookieless ? null : getStorageItem("sessionStorage", key);
|
|
76
|
+
const stored = raw === null ? void 0 : readCounter(raw);
|
|
77
|
+
if (raw !== null && !stored && !sameRecording(cached, context)) return;
|
|
78
|
+
let counter = sameRecording(cached, context) ? cached : void 0;
|
|
79
|
+
if (sameRecording(stored, context) && stored && (!counter || stored.next > counter.next)) counter = stored;
|
|
80
|
+
const legacyKey = `faststats_replay_batch_sequence_${siteKey}_${context.windowId}`;
|
|
81
|
+
if (!counter) {
|
|
82
|
+
const legacy = !cookieless && !stored && !sameRecording(cached, context) ? getStorageItem("sessionStorage", legacyKey) : null;
|
|
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,
|
|
96
|
+
next: sequence + 1
|
|
97
|
+
};
|
|
98
|
+
counters.set(siteKey, updated);
|
|
99
|
+
if (!cookieless && setStorageItem("sessionStorage", key, JSON.stringify(updated))) removeStorageItem("sessionStorage", legacyKey);
|
|
100
|
+
return {
|
|
101
|
+
sequence,
|
|
102
|
+
...counter.version === 1 ? { sequenceContractVersion: 1 } : {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
55
106
|
//#region src/replay.ts
|
|
56
107
|
const MAX_TIMEOUT_MS = 2147483647;
|
|
57
108
|
const DEFAULT_MAX_QUEUE_SIZE_BYTES = 2097152;
|
|
@@ -66,6 +117,10 @@ const RRWEB_EVENT_META = 4;
|
|
|
66
117
|
const REPLAY_SEQUENTIAL_ID_KEY = "_faststatsSeqId";
|
|
67
118
|
const textEncoder = new TextEncoder();
|
|
68
119
|
const batchSizeCache = /* @__PURE__ */ new WeakMap();
|
|
120
|
+
function boundedInteger(value, fallback, min, max) {
|
|
121
|
+
if (value === void 0 || !Number.isFinite(value) || value < min) return fallback;
|
|
122
|
+
return Math.min(Math.trunc(value), max);
|
|
123
|
+
}
|
|
69
124
|
function sessionReplay(options = {}) {
|
|
70
125
|
return {
|
|
71
126
|
name: "session-replay",
|
|
@@ -100,7 +155,6 @@ var ReplayTracker = class {
|
|
|
100
155
|
minReplayLengthMs;
|
|
101
156
|
events = [];
|
|
102
157
|
pending = [];
|
|
103
|
-
minLengthBlockedBatches = /* @__PURE__ */ new WeakSet();
|
|
104
158
|
queuedEventsSizeBytes = 0;
|
|
105
159
|
pendingSizeBytes = 0;
|
|
106
160
|
viewId = createId();
|
|
@@ -109,7 +163,6 @@ var ReplayTracker = class {
|
|
|
109
163
|
started = false;
|
|
110
164
|
disposed = false;
|
|
111
165
|
startTime = 0;
|
|
112
|
-
sequence = 0;
|
|
113
166
|
intervalId = null;
|
|
114
167
|
flushTask = null;
|
|
115
168
|
retryTask = null;
|
|
@@ -123,6 +176,7 @@ var ReplayTracker = class {
|
|
|
123
176
|
lastCookielessMode;
|
|
124
177
|
needsFinalization = false;
|
|
125
178
|
initialFlushPending = true;
|
|
179
|
+
resumed = false;
|
|
126
180
|
overflowed = false;
|
|
127
181
|
replayEventSequence = 0;
|
|
128
182
|
mutationThrottler;
|
|
@@ -130,13 +184,13 @@ var ReplayTracker = class {
|
|
|
130
184
|
this.options = options;
|
|
131
185
|
this.lastCookielessMode = options.cookieless ?? false;
|
|
132
186
|
this.endpoint = `${resolveBaseUrl(options.baseUrl, ANALYTICS_BASE)}${URLS.replay}`;
|
|
133
|
-
this.flushInterval = options.flushInterval
|
|
134
|
-
this.maxEvents = options.maxEvents
|
|
135
|
-
this.maxBatchSizeBytes = options.maxBatchSizeBytes
|
|
136
|
-
this.maxLowLatencyBatchSizeBytes = options.maxLowLatencyBatchSizeBytes
|
|
137
|
-
this.maxPendingBatches = options.maxPendingBatches
|
|
138
|
-
this.maxQueueSizeBytes = options.maxQueueSizeBytes
|
|
139
|
-
this.minReplayLengthMs = options.minReplayLengthMs
|
|
187
|
+
this.flushInterval = boundedInteger(options.flushInterval, 15e3, 1, MAX_TIMEOUT_MS);
|
|
188
|
+
this.maxEvents = boundedInteger(options.maxEvents, 1e3, 1, 1e5);
|
|
189
|
+
this.maxBatchSizeBytes = boundedInteger(options.maxBatchSizeBytes, DEFAULT_MAX_BATCH_SIZE_BYTES, 1, 33554432);
|
|
190
|
+
this.maxLowLatencyBatchSizeBytes = boundedInteger(options.maxLowLatencyBatchSizeBytes, DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES, 1, DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES);
|
|
191
|
+
this.maxPendingBatches = boundedInteger(options.maxPendingBatches, 30, 1, 1e3);
|
|
192
|
+
this.maxQueueSizeBytes = boundedInteger(options.maxQueueSizeBytes, DEFAULT_MAX_QUEUE_SIZE_BYTES, 1, 33554432);
|
|
193
|
+
this.minReplayLengthMs = boundedInteger(options.minReplayLengthMs, 3e3, 0, MAX_TIMEOUT_MS);
|
|
140
194
|
this.mutationThrottler = options.mutationThrottle?.enabled === false ? null : new ReplayMutationThrottler(options.mutationThrottle);
|
|
141
195
|
}
|
|
142
196
|
log(...args) {
|
|
@@ -158,28 +212,10 @@ var ReplayTracker = class {
|
|
|
158
212
|
this.pending.length = 0;
|
|
159
213
|
this.pendingSizeBytes = 0;
|
|
160
214
|
}
|
|
161
|
-
dropMinLengthBlockedBatches() {
|
|
162
|
-
this.needsFinalization = false;
|
|
163
|
-
for (let index = this.pending.length - 1; index >= 0; index--) {
|
|
164
|
-
const batch = this.pending[index];
|
|
165
|
-
if (batch && this.minLengthBlockedBatches.has(batch)) this.removePending(batch);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
unblockSessionBatches(sessionId) {
|
|
169
|
-
for (const batch of this.pending) if (batch.sessionId === sessionId) this.minLengthBlockedBatches.delete(batch);
|
|
170
|
-
}
|
|
171
215
|
finalizeSession(context, flushReason) {
|
|
172
216
|
if (!this.needsFinalization) return;
|
|
173
|
-
this.
|
|
174
|
-
|
|
175
|
-
nextBatchSequence(context) {
|
|
176
|
-
const key = `faststats_replay_batch_sequence_${this.options.siteKey}_${context.windowId}`;
|
|
177
|
-
let sequence = this.sequence;
|
|
178
|
-
const parsed = Number.parseInt(getStorageItem("sessionStorage", key) ?? "", 10);
|
|
179
|
-
if (Number.isFinite(parsed) && parsed >= 0) sequence = parsed;
|
|
180
|
-
setStorageItem("sessionStorage", key, String(sequence + 1));
|
|
181
|
-
this.sequence = sequence + 1;
|
|
182
|
-
return sequence;
|
|
217
|
+
const batch = this.createBatch([], context, true, flushReason);
|
|
218
|
+
if (batch) this.enqueueBatch(batch);
|
|
183
219
|
}
|
|
184
220
|
setCookielessMode(cookieless) {
|
|
185
221
|
if (this.lastCookielessMode === cookieless) return;
|
|
@@ -189,8 +225,7 @@ var ReplayTracker = class {
|
|
|
189
225
|
this.clearFlushTimers();
|
|
190
226
|
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
191
227
|
this.activeSessionId = context.sessionId;
|
|
192
|
-
this.
|
|
193
|
-
this.chunkStartedAt = Date.now();
|
|
228
|
+
this.resetSessionTiming(context);
|
|
194
229
|
this.beginReplayStream();
|
|
195
230
|
}
|
|
196
231
|
start() {
|
|
@@ -204,8 +239,7 @@ var ReplayTracker = class {
|
|
|
204
239
|
this.viewUrl = sanitizeUrl(window.location.href, this.options.trackHash ?? false);
|
|
205
240
|
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
206
241
|
this.activeSessionId = context.sessionId;
|
|
207
|
-
this.
|
|
208
|
-
this.chunkStartedAt = Date.now();
|
|
242
|
+
this.resetSessionTiming(context);
|
|
209
243
|
this.subscribeToSessionRotation();
|
|
210
244
|
this.beginRecording();
|
|
211
245
|
this.intervalId = setInterval(() => this.requestFlush("interval"), this.flushInterval);
|
|
@@ -227,7 +261,7 @@ var ReplayTracker = class {
|
|
|
227
261
|
viewId: this.viewId
|
|
228
262
|
}
|
|
229
263
|
}
|
|
230
|
-
}
|
|
264
|
+
});
|
|
231
265
|
}
|
|
232
266
|
onPageHidden(persisted = false, terminal = false) {
|
|
233
267
|
if (persisted) return;
|
|
@@ -276,7 +310,7 @@ var ReplayTracker = class {
|
|
|
276
310
|
return plugins;
|
|
277
311
|
}
|
|
278
312
|
beginRecording() {
|
|
279
|
-
|
|
313
|
+
this.stopRecording = record({
|
|
280
314
|
emit: this.onEvent,
|
|
281
315
|
sampling: this.options.sampling ?? {
|
|
282
316
|
mousemove: 50,
|
|
@@ -310,7 +344,6 @@ var ReplayTracker = class {
|
|
|
310
344
|
checkoutEveryNth: this.options.checkoutEveryNth,
|
|
311
345
|
plugins: this.recordingPlugins()
|
|
312
346
|
});
|
|
313
|
-
this.stopRecording = stop;
|
|
314
347
|
}
|
|
315
348
|
stop(discard = false) {
|
|
316
349
|
if (!this.started) return;
|
|
@@ -332,8 +365,8 @@ var ReplayTracker = class {
|
|
|
332
365
|
if (!this.hasReachedMinLength()) {
|
|
333
366
|
this.events.length = 0;
|
|
334
367
|
this.queuedEventsSizeBytes = 0;
|
|
335
|
-
this.
|
|
336
|
-
this.log(`
|
|
368
|
+
this.needsFinalization = false;
|
|
369
|
+
this.log(`Recording too short (${Date.now() - this.startTime}ms), discarding events`);
|
|
337
370
|
}
|
|
338
371
|
this.flush(true, true, "stop");
|
|
339
372
|
this.log("Recording stopped");
|
|
@@ -349,12 +382,12 @@ var ReplayTracker = class {
|
|
|
349
382
|
}
|
|
350
383
|
applySessionRotation(prev, next) {
|
|
351
384
|
if (this.activeSessionId === next.sessionId) return;
|
|
352
|
-
this.enqueueEventsIfReady(prev,
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
385
|
+
if (this.hasReachedMinLength()) this.enqueueEventsIfReady(prev, "sessionRotate");
|
|
386
|
+
else {
|
|
387
|
+
this.events.length = 0;
|
|
388
|
+
this.queuedEventsSizeBytes = 0;
|
|
389
|
+
}
|
|
356
390
|
this.finalizeSession(prev, "sessionRotate");
|
|
357
|
-
this.unblockSessionBatches(prev.sessionId);
|
|
358
391
|
this.needsFinalization = false;
|
|
359
392
|
this.activeSessionId = next.sessionId;
|
|
360
393
|
this.resetSessionTiming(next);
|
|
@@ -383,11 +416,12 @@ var ReplayTracker = class {
|
|
|
383
416
|
resetSessionTiming(next) {
|
|
384
417
|
if (this.minLengthFlushTask) clearTimeout(this.minLengthFlushTask);
|
|
385
418
|
this.minLengthFlushTask = null;
|
|
386
|
-
this.
|
|
419
|
+
this.resumed = hasReplaySequence(this.options.siteKey, next, this.lastCookielessMode);
|
|
420
|
+
this.startTime = Date.now();
|
|
387
421
|
this.chunkStartedAt = Date.now();
|
|
388
422
|
this.initialFlushPending = true;
|
|
389
423
|
}
|
|
390
|
-
onEvent = (event
|
|
424
|
+
onEvent = (event) => {
|
|
391
425
|
if (this.overflowed) return;
|
|
392
426
|
let capturedEvent = event;
|
|
393
427
|
if (event.type === RRWEB_EVENT_META) {
|
|
@@ -406,9 +440,17 @@ var ReplayTracker = class {
|
|
|
406
440
|
if (this.mutationThrottler && !throttled) return;
|
|
407
441
|
if (throttled) capturedEvent = throttled;
|
|
408
442
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
443
|
+
const sequencedEvent = {
|
|
444
|
+
...capturedEvent,
|
|
445
|
+
[REPLAY_SEQUENTIAL_ID_KEY]: ++this.replayEventSequence
|
|
446
|
+
};
|
|
447
|
+
const eventBytes = this.eventSizeBytes(sequencedEvent);
|
|
448
|
+
if (this.pendingSizeBytes + this.queuedEventsSizeBytes + eventBytes > this.maxQueueSizeBytes) {
|
|
449
|
+
this.stopCapture("Replay buffer limit reached");
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.events.push(sequencedEvent);
|
|
453
|
+
this.queuedEventsSizeBytes += eventBytes;
|
|
412
454
|
let reason;
|
|
413
455
|
if (this.events.length >= this.maxEvents) reason = "maxEvents";
|
|
414
456
|
else if (this.queuedEventsSizeBytes >= this.maxBatchSizeBytes) reason = "maxBytes";
|
|
@@ -422,13 +464,13 @@ var ReplayTracker = class {
|
|
|
422
464
|
if (!this.hasReachedMinLength()) {
|
|
423
465
|
this.events.length = 0;
|
|
424
466
|
this.queuedEventsSizeBytes = 0;
|
|
425
|
-
this.
|
|
467
|
+
this.needsFinalization = false;
|
|
426
468
|
return;
|
|
427
469
|
}
|
|
428
470
|
this.flush(true, true, reason);
|
|
429
471
|
}
|
|
430
472
|
hasReachedMinLength() {
|
|
431
|
-
return this.minReplayLengthMs <= 0 || Date.now() - this.startTime >= this.minReplayLengthMs;
|
|
473
|
+
return this.resumed || this.minReplayLengthMs <= 0 || Date.now() - this.startTime >= this.minReplayLengthMs;
|
|
432
474
|
}
|
|
433
475
|
requestFlush = (reason = "manual") => {
|
|
434
476
|
if (this.flushTask || this.events.length === 0) return;
|
|
@@ -456,18 +498,19 @@ var ReplayTracker = class {
|
|
|
456
498
|
this.requestFlush("minLength");
|
|
457
499
|
}, Math.min(remainingMs, MAX_TIMEOUT_MS));
|
|
458
500
|
}
|
|
459
|
-
enqueueEventsIfReady(context,
|
|
460
|
-
const { isFinal = false, flushReason = "manual", maxBatchSizeBytes = this.maxBatchSizeBytes, sealBeforeMinLength = false } = options;
|
|
501
|
+
enqueueEventsIfReady(context, flushReason = "manual", maxBatchSizeBytes = this.maxBatchSizeBytes) {
|
|
461
502
|
if (this.events.length === 0) return;
|
|
462
|
-
if (!
|
|
503
|
+
if (!this.hasReachedMinLength()) {
|
|
463
504
|
this.scheduleMinLengthFlush();
|
|
464
505
|
return;
|
|
465
506
|
}
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
507
|
+
for (const events of this.takeEventBatches(maxBatchSizeBytes)) {
|
|
508
|
+
if (this.pending.length >= this.maxPendingBatches) {
|
|
509
|
+
this.stopCapture("Replay queue limit reached");
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
const batch = this.createBatch(events, context, false, flushReason);
|
|
513
|
+
if (!batch) break;
|
|
471
514
|
this.enqueueBatch(batch);
|
|
472
515
|
}
|
|
473
516
|
this.queuedEventsSizeBytes = 0;
|
|
@@ -491,7 +534,11 @@ var ReplayTracker = class {
|
|
|
491
534
|
}
|
|
492
535
|
createBatch(events, context, isFinal, flushReason = "manual") {
|
|
493
536
|
const identifier = getAnonymousId(this.options.siteKey, this.lastCookielessMode);
|
|
494
|
-
const sequence = this.
|
|
537
|
+
const sequence = nextReplaySequence(this.options.siteKey, context, this.lastCookielessMode);
|
|
538
|
+
if (!sequence) {
|
|
539
|
+
this.stopCapture("Replay sequence state is invalid or exhausted");
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
495
542
|
const chunkStartedAt = this.chunkStartedAt;
|
|
496
543
|
const now = Date.now();
|
|
497
544
|
this.chunkStartedAt = now;
|
|
@@ -504,8 +551,8 @@ var ReplayTracker = class {
|
|
|
504
551
|
chunkStartedAt,
|
|
505
552
|
chunkEndedAt: now,
|
|
506
553
|
...identifier ? { identifier } : {},
|
|
507
|
-
batchId: `${context.sessionId}-${sequence}-${chunkStartedAt}`,
|
|
508
|
-
sequence,
|
|
554
|
+
batchId: `${context.sessionId}-${sequence.sequence}-${chunkStartedAt}`,
|
|
555
|
+
...sequence,
|
|
509
556
|
timestamp: now,
|
|
510
557
|
url: this.viewUrl || sanitizeUrl(window.location.href, this.options.trackHash ?? false),
|
|
511
558
|
...isFinal ? { isFinal: true } : {},
|
|
@@ -523,76 +570,62 @@ var ReplayTracker = class {
|
|
|
523
570
|
batchSizeCache.set(batch, size);
|
|
524
571
|
return size;
|
|
525
572
|
}
|
|
526
|
-
removePending(batch
|
|
573
|
+
removePending(batch) {
|
|
527
574
|
const index = this.pending.indexOf(batch);
|
|
528
|
-
if (index < 0) return
|
|
575
|
+
if (index < 0) return;
|
|
529
576
|
this.pending.splice(index, 1);
|
|
530
577
|
this.pendingSizeBytes = Math.max(0, this.pendingSizeBytes - this.batchSizeBytes(batch));
|
|
531
|
-
|
|
532
|
-
|
|
578
|
+
this.log(`Sent replay batch ${batch.sequence}`);
|
|
579
|
+
}
|
|
580
|
+
stopCapture(reason) {
|
|
581
|
+
this.log(reason);
|
|
582
|
+
this.overflowed = true;
|
|
583
|
+
this.stopRecording?.();
|
|
584
|
+
this.stopRecording = void 0;
|
|
533
585
|
}
|
|
534
586
|
enqueueBatch(batch) {
|
|
535
|
-
const size = this.batchSizeBytes(batch);
|
|
536
|
-
if (size > this.maxQueueSizeBytes || this.pending.length >= this.maxPendingBatches || this.pendingSizeBytes + size > this.maxQueueSizeBytes) {
|
|
537
|
-
this.log(`Replay queue limit reached at batch ${batch.sequence}; stopping recording at the last complete chain`);
|
|
538
|
-
this.overflowed = true;
|
|
539
|
-
this.stopRecording?.();
|
|
540
|
-
this.stopRecording = void 0;
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
587
|
this.needsFinalization = !batch.isFinal;
|
|
544
588
|
this.pending.push(batch);
|
|
545
|
-
this.pendingSizeBytes +=
|
|
589
|
+
this.pendingSizeBytes += this.batchSizeBytes(batch);
|
|
546
590
|
}
|
|
547
591
|
async flush(lowLatency, isFinal = false, flushReason = "manual") {
|
|
592
|
+
if (this.events.length === 0 && !isFinal) {
|
|
593
|
+
await this.sendPending(lowLatency);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
548
596
|
const rotation = touchActivity(this.options.siteKey, this.lastCookielessMode);
|
|
549
597
|
if (rotation) this.applySessionRotation(rotation.prev, rotation.next);
|
|
550
598
|
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
551
|
-
this.enqueueEventsIfReady(context,
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (this.
|
|
558
|
-
if (isFinal) for (const batch of this.pending) this.minLengthBlockedBatches.delete(batch);
|
|
559
|
-
else if (!rotation && flushReason !== "sessionRotate" && this.hasReachedMinLength()) this.unblockSessionBatches(context.sessionId);
|
|
560
|
-
await this.sendPending(lowLatency, lowLatency && isFinal);
|
|
561
|
-
}
|
|
562
|
-
async sendPending(lowLatency, bypassRetryDelay) {
|
|
563
|
-
if (this.retryTask) {
|
|
564
|
-
if (!bypassRetryDelay) return;
|
|
565
|
-
clearTimeout(this.retryTask);
|
|
566
|
-
this.retryTask = null;
|
|
567
|
-
}
|
|
599
|
+
this.enqueueEventsIfReady(context, flushReason, lowLatency ? this.maxLowLatencyBatchSizeBytes : this.maxBatchSizeBytes);
|
|
600
|
+
if (!isFinal) return this.sendPending(lowLatency);
|
|
601
|
+
this.finalizeSession(context, flushReason);
|
|
602
|
+
for (const batch of [...this.pending].reverse()) this.sendBatch(batch, true).then((sent) => sent && this.removePending(batch));
|
|
603
|
+
}
|
|
604
|
+
async sendPending(lowLatency) {
|
|
605
|
+
if (this.retryTask) return;
|
|
568
606
|
if (this.sending) {
|
|
569
|
-
|
|
570
|
-
this.queuedFlush = {
|
|
571
|
-
lowLatency: (queued?.lowLatency ?? false) || lowLatency,
|
|
572
|
-
bypassRetryDelay: (queued?.bypassRetryDelay ?? false) || bypassRetryDelay
|
|
573
|
-
};
|
|
607
|
+
this.queuedFlush = !!this.queuedFlush || lowLatency;
|
|
574
608
|
return;
|
|
575
609
|
}
|
|
576
610
|
this.sending = true;
|
|
577
611
|
try {
|
|
578
612
|
while (this.pending.length > 0) {
|
|
579
613
|
const batch = this.pending[0];
|
|
580
|
-
if (this.minLengthBlockedBatches.has(batch)) break;
|
|
581
614
|
if (!await this.sendBatch(batch, lowLatency)) {
|
|
582
615
|
this.log(`Failed to send replay batch ${batch.sequence}`);
|
|
583
616
|
if (!this.disposed) this.scheduleRetry();
|
|
584
617
|
break;
|
|
585
618
|
}
|
|
586
619
|
this.retryAttempt = 0;
|
|
587
|
-
this.removePending(batch
|
|
588
|
-
if (this.queuedFlush
|
|
620
|
+
this.removePending(batch);
|
|
621
|
+
if (this.queuedFlush) break;
|
|
589
622
|
}
|
|
590
623
|
} finally {
|
|
591
624
|
this.sending = false;
|
|
592
|
-
if (this.queuedFlush) {
|
|
593
|
-
const
|
|
625
|
+
if (this.queuedFlush !== null) {
|
|
626
|
+
const lowLatency = this.queuedFlush;
|
|
594
627
|
this.queuedFlush = null;
|
|
595
|
-
this.sendPending(
|
|
628
|
+
this.sendPending(lowLatency);
|
|
596
629
|
}
|
|
597
630
|
}
|
|
598
631
|
}
|
|
@@ -604,7 +637,7 @@ var ReplayTracker = class {
|
|
|
604
637
|
this.retryTask = setTimeout(() => {
|
|
605
638
|
this.retryTask = null;
|
|
606
639
|
if (this.retryAttempt === RETRY_MAX_ATTEMPTS) this.retryAttempt = 0;
|
|
607
|
-
this.
|
|
640
|
+
this.sendPending(false);
|
|
608
641
|
}, delay);
|
|
609
642
|
}
|
|
610
643
|
retryDelay(attempt) {
|
|
@@ -619,7 +652,8 @@ var ReplayTracker = class {
|
|
|
619
652
|
debug: this.options.debug,
|
|
620
653
|
debugPrefix: "[Replay]",
|
|
621
654
|
useBeacon: lowLatencySafe,
|
|
622
|
-
keepalive: lowLatencySafe
|
|
655
|
+
keepalive: lowLatencySafe,
|
|
656
|
+
settleRejected: true
|
|
623
657
|
};
|
|
624
658
|
if (!lowLatency && (this.options.compress ?? true) && this.compressionSupported) try {
|
|
625
659
|
const stream = new Blob([json]).stream().pipeThrough(new CompressionStream("gzip"));
|
package/dist/web-vitals.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as sanitizeUrl, i as resolveBaseUrl, n as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-DWk43fu6.js";
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
2
|
+
import { r as getSessionContext, t as sendData } from "./chunks/send-data-DAVUdfNm.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();
|
package/package.json
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.10.
|
|
45
|
+
"version": "0.10.3",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown && bun run check-size",
|
|
48
48
|
"dev": "bun run build",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"@rrweb/record": "^2.1.6",
|
|
61
61
|
"@rrweb/rrweb-plugin-console-record": "^2.1.6",
|
|
62
62
|
"@rrweb/rrweb-plugin-network-record": "^2.1.6",
|
|
63
|
-
"web-vitals": "^6.2.
|
|
63
|
+
"web-vitals": "^6.2.2"
|
|
64
64
|
}
|
|
65
65
|
}
|