@faststats/web 0.7.0 → 0.8.1
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 +67 -0
- package/dist/chunks/{api-urls-Dp2XNjRF.js → api-urls-DWk43fu6.js} +12 -5
- package/dist/chunks/extensions-DW7tJY0T.d.ts +51 -0
- package/dist/chunks/{identifiers-BvRNbiwA.js → identifiers-KgQDX74Q.js} +3 -3
- package/dist/chunks/{send-data-DR1JUXka.js → send-data-DFZnsaL2.js} +63 -45
- package/dist/error.d.ts +7 -2
- package/dist/error.js +36 -14
- package/dist/index.d.ts +54 -72
- package/dist/index.js +341 -363
- package/dist/outbound-links.d.ts +10 -0
- package/dist/outbound-links.js +62 -0
- package/dist/replay.d.ts +130 -2
- package/dist/replay.js +81 -32
- package/dist/web-vitals.d.ts +7 -1
- package/dist/web-vitals.js +39 -12
- package/package.json +11 -12
- package/dist/chunks/replay-BeC2XA4N.d.ts +0 -124
- package/dist/feature-flags.d.ts +0 -18
- package/dist/feature-flags.js +0 -32
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
|
+
//#region src/outbound-links.d.ts
|
|
3
|
+
declare function outboundLinks(): AnalyticsExtension;
|
|
4
|
+
declare class OutboundLinkTracker {
|
|
5
|
+
private lastActivation;
|
|
6
|
+
getHref(event: Event): string | null;
|
|
7
|
+
private isDuplicate;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { OutboundLinkTracker, outboundLinks };
|
|
@@ -0,0 +1,62 @@
|
|
|
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 };
|
package/dist/replay.d.ts
CHANGED
|
@@ -1,2 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
2
|
+
import { record } from "@rrweb/record";
|
|
3
|
+
import { LogLevel } from "@rrweb/rrweb-plugin-console-record";
|
|
4
|
+
//#region ../../node_modules/.bun/@rrweb+types@2.1.1/node_modules/@rrweb/types/dist/index.d.ts
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
FontFace: typeof FontFace;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/replay-mutation-throttler.d.ts
|
|
12
|
+
type ReplayMutationThrottleOptions = {
|
|
13
|
+
enabled?: boolean;
|
|
14
|
+
bucketSize?: number;
|
|
15
|
+
refillRate?: number;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/replay.d.ts
|
|
19
|
+
type RecordOptions = NonNullable<Parameters<typeof record>[0]>;
|
|
20
|
+
interface ReplayTrackerOptions {
|
|
21
|
+
siteKey: string;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
debug?: boolean;
|
|
24
|
+
trackHash?: boolean;
|
|
25
|
+
cookieless?: boolean;
|
|
26
|
+
compress?: boolean;
|
|
27
|
+
flushInterval?: number;
|
|
28
|
+
maxEvents?: number;
|
|
29
|
+
maxBatchSizeBytes?: number;
|
|
30
|
+
maxLowLatencyBatchSizeBytes?: number;
|
|
31
|
+
maxPendingBatches?: number;
|
|
32
|
+
maxQueueSizeBytes?: number;
|
|
33
|
+
minReplayLengthMs?: number;
|
|
34
|
+
sampling?: RecordOptions["sampling"];
|
|
35
|
+
slimDOMOptions?: RecordOptions["slimDOMOptions"];
|
|
36
|
+
maskAllInputs?: boolean;
|
|
37
|
+
maskInputOptions?: RecordOptions["maskInputOptions"];
|
|
38
|
+
blockClass?: string;
|
|
39
|
+
blockSelector?: string;
|
|
40
|
+
maskTextClass?: string;
|
|
41
|
+
maskTextSelector?: string;
|
|
42
|
+
checkoutEveryNms?: number;
|
|
43
|
+
checkoutEveryNth?: number;
|
|
44
|
+
recordConsole?: boolean;
|
|
45
|
+
consoleLevel?: LogLevel[];
|
|
46
|
+
consoleLengthThreshold?: number;
|
|
47
|
+
consoleStringLengthLimit?: number;
|
|
48
|
+
mutationThrottle?: ReplayMutationThrottleOptions;
|
|
49
|
+
}
|
|
50
|
+
type SessionReplayExtensionOptions = Omit<ReplayTrackerOptions, "siteKey" | "baseUrl" | "debug" | "trackHash" | "cookieless">;
|
|
51
|
+
declare function sessionReplay(options?: SessionReplayExtensionOptions): AnalyticsExtension;
|
|
52
|
+
declare class ReplayTracker {
|
|
53
|
+
private readonly options;
|
|
54
|
+
private readonly endpoint;
|
|
55
|
+
private readonly compressionSupported;
|
|
56
|
+
private readonly flushInterval;
|
|
57
|
+
private readonly maxEvents;
|
|
58
|
+
private readonly maxBatchSizeBytes;
|
|
59
|
+
private readonly maxLowLatencyBatchSizeBytes;
|
|
60
|
+
private readonly maxPendingBatches;
|
|
61
|
+
private readonly maxQueueSizeBytes;
|
|
62
|
+
private readonly minReplayLengthMs;
|
|
63
|
+
private readonly events;
|
|
64
|
+
private readonly pending;
|
|
65
|
+
private readonly minLengthBlockedBatches;
|
|
66
|
+
private queuedEventsSizeBytes;
|
|
67
|
+
private pendingSizeBytes;
|
|
68
|
+
private viewId;
|
|
69
|
+
private viewUrl;
|
|
70
|
+
private chunkStartedAt;
|
|
71
|
+
private started;
|
|
72
|
+
private disposed;
|
|
73
|
+
private startTime;
|
|
74
|
+
private sequence;
|
|
75
|
+
private intervalId;
|
|
76
|
+
private flushTask;
|
|
77
|
+
private retryTask;
|
|
78
|
+
private retryAttempt;
|
|
79
|
+
private minLengthFlushTask;
|
|
80
|
+
private stopRecording?;
|
|
81
|
+
private activeSessionId;
|
|
82
|
+
private sending;
|
|
83
|
+
private inFlightBatch;
|
|
84
|
+
private queuedFlush;
|
|
85
|
+
private unsubscribeRotation?;
|
|
86
|
+
private lastCookielessMode;
|
|
87
|
+
private terminalFlushRequested;
|
|
88
|
+
private overflowed;
|
|
89
|
+
private replayEventSequence;
|
|
90
|
+
private readonly mutationThrottler;
|
|
91
|
+
constructor(options: ReplayTrackerOptions);
|
|
92
|
+
private log;
|
|
93
|
+
private clearFlushTimers;
|
|
94
|
+
private clearQueues;
|
|
95
|
+
private dropMinLengthBlockedBatches;
|
|
96
|
+
private unblockSessionBatches;
|
|
97
|
+
private finalizeSessionPending;
|
|
98
|
+
private nextBatchSequence;
|
|
99
|
+
setCookielessMode(cookieless: boolean): void;
|
|
100
|
+
start(): void;
|
|
101
|
+
trackPageChange(url?: string): void;
|
|
102
|
+
onPageHidden(persisted?: boolean, terminal?: boolean): void;
|
|
103
|
+
onPageShow(persisted?: boolean): void;
|
|
104
|
+
private beginRecording;
|
|
105
|
+
stop(discard?: boolean): void;
|
|
106
|
+
private onSessionRotated;
|
|
107
|
+
private subscribeToSessionRotation;
|
|
108
|
+
private applySessionRotation;
|
|
109
|
+
private beginReplayStream;
|
|
110
|
+
private resetSessionTiming;
|
|
111
|
+
private onEvent;
|
|
112
|
+
private onUnload;
|
|
113
|
+
private requestTerminalFlush;
|
|
114
|
+
private hasReachedMinLength;
|
|
115
|
+
private requestFlush;
|
|
116
|
+
private scheduleMinLengthFlush;
|
|
117
|
+
private enqueueEventsIfReady;
|
|
118
|
+
private takeEventBatches;
|
|
119
|
+
private createBatch;
|
|
120
|
+
private eventSizeBytes;
|
|
121
|
+
private batchSizeBytes;
|
|
122
|
+
private removePending;
|
|
123
|
+
private enqueueBatch;
|
|
124
|
+
private flush;
|
|
125
|
+
private scheduleRetry;
|
|
126
|
+
private retryDelay;
|
|
127
|
+
private sendBatch;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { ReplayTrackerOptions, SessionReplayExtensionOptions, ReplayTracker as default, sessionReplay };
|
package/dist/replay.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import { t as getAnonymousId } from "./chunks/identifiers-
|
|
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-DFZnsaL2.js";
|
|
3
|
+
import { t as getAnonymousId } from "./chunks/identifiers-KgQDX74Q.js";
|
|
4
4
|
import { record } from "@rrweb/record";
|
|
5
|
+
import { getRecordConsolePlugin } from "@rrweb/rrweb-plugin-console-record";
|
|
5
6
|
//#region src/replay-mutation-throttler.ts
|
|
6
7
|
const INCREMENTAL_SNAPSHOT_EVENT_TYPE = 3;
|
|
7
8
|
const MUTATION_SOURCE_TYPE = 0;
|
|
@@ -52,18 +53,39 @@ var ReplayMutationThrottler = class {
|
|
|
52
53
|
//#endregion
|
|
53
54
|
//#region src/replay.ts
|
|
54
55
|
const MAX_TIMEOUT_MS = 2147483647;
|
|
55
|
-
const DEFAULT_MAX_QUEUE_SIZE_BYTES =
|
|
56
|
-
const DEFAULT_MAX_BATCH_SIZE_BYTES =
|
|
57
|
-
const DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES =
|
|
56
|
+
const DEFAULT_MAX_QUEUE_SIZE_BYTES = 2097152;
|
|
57
|
+
const DEFAULT_MAX_BATCH_SIZE_BYTES = 921600;
|
|
58
|
+
const DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES = 61440;
|
|
58
59
|
const RETRY_BASE_DELAY_MS = 2e3;
|
|
59
60
|
const RETRY_MAX_ATTEMPTS = 5;
|
|
60
|
-
const RETRY_COOLDOWN_MS =
|
|
61
|
+
const RETRY_COOLDOWN_MS = 3e5;
|
|
61
62
|
const VIEW_META_EVENT_TYPE = 5;
|
|
62
63
|
const RRWEB_EVENT_FULL_SNAPSHOT = 2;
|
|
63
64
|
const RRWEB_EVENT_META = 4;
|
|
64
65
|
const REPLAY_SEQUENTIAL_ID_KEY = "_faststatsSeqId";
|
|
65
66
|
const textEncoder = new TextEncoder();
|
|
66
67
|
const batchSizeCache = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
function sessionReplay(options = {}) {
|
|
69
|
+
return {
|
|
70
|
+
name: "session-replay",
|
|
71
|
+
setup(context) {
|
|
72
|
+
const tracker = new ReplayTracker({
|
|
73
|
+
...options,
|
|
74
|
+
siteKey: context.siteKey,
|
|
75
|
+
baseUrl: context.baseUrl,
|
|
76
|
+
debug: context.debug,
|
|
77
|
+
trackHash: context.trackHash,
|
|
78
|
+
cookieless: context.isCookieless()
|
|
79
|
+
});
|
|
80
|
+
context.on("pageChange", ({ url }) => tracker.trackPageChange(url));
|
|
81
|
+
context.on("pageHide", ({ persisted, terminal }) => tracker.onPageHidden(persisted, terminal));
|
|
82
|
+
context.on("pageShow", ({ persisted }) => tracker.onPageShow(persisted));
|
|
83
|
+
context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
|
|
84
|
+
tracker.start();
|
|
85
|
+
return ({ discard }) => tracker.stop(discard);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
67
89
|
var ReplayTracker = class {
|
|
68
90
|
options;
|
|
69
91
|
endpoint;
|
|
@@ -85,7 +107,6 @@ var ReplayTracker = class {
|
|
|
85
107
|
chunkStartedAt = 0;
|
|
86
108
|
started = false;
|
|
87
109
|
disposed = false;
|
|
88
|
-
startGeneration = 0;
|
|
89
110
|
startTime = 0;
|
|
90
111
|
sequence = 0;
|
|
91
112
|
intervalId = null;
|
|
@@ -94,16 +115,19 @@ var ReplayTracker = class {
|
|
|
94
115
|
retryAttempt = 0;
|
|
95
116
|
minLengthFlushTask = null;
|
|
96
117
|
stopRecording;
|
|
118
|
+
activeSessionId = "";
|
|
97
119
|
sending = false;
|
|
98
120
|
inFlightBatch = null;
|
|
99
121
|
queuedFlush = null;
|
|
100
122
|
unsubscribeRotation;
|
|
101
|
-
lastCookielessMode
|
|
123
|
+
lastCookielessMode;
|
|
102
124
|
terminalFlushRequested = false;
|
|
103
125
|
overflowed = false;
|
|
126
|
+
replayEventSequence = 0;
|
|
104
127
|
mutationThrottler;
|
|
105
128
|
constructor(options) {
|
|
106
129
|
this.options = options;
|
|
130
|
+
this.lastCookielessMode = options.cookieless ?? false;
|
|
107
131
|
this.endpoint = `${resolveBaseUrl(options.baseUrl, ANALYTICS_BASE)}${URLS.replay}`;
|
|
108
132
|
this.flushInterval = options.flushInterval ?? 15e3;
|
|
109
133
|
this.maxEvents = options.maxEvents ?? 1e3;
|
|
@@ -166,10 +190,14 @@ var ReplayTracker = class {
|
|
|
166
190
|
setCookielessMode(cookieless) {
|
|
167
191
|
if (this.lastCookielessMode === cookieless) return;
|
|
168
192
|
this.lastCookielessMode = cookieless;
|
|
193
|
+
if (this.started) this.subscribeToSessionRotation();
|
|
169
194
|
this.clearQueues();
|
|
170
195
|
this.clearFlushTimers();
|
|
171
|
-
|
|
196
|
+
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
197
|
+
this.activeSessionId = context.sessionId;
|
|
198
|
+
this.startTime = context.sessionStart;
|
|
172
199
|
this.chunkStartedAt = Date.now();
|
|
200
|
+
this.beginReplayStream();
|
|
173
201
|
}
|
|
174
202
|
start() {
|
|
175
203
|
if (this.started || typeof window === "undefined") return;
|
|
@@ -177,15 +205,15 @@ var ReplayTracker = class {
|
|
|
177
205
|
this.disposed = false;
|
|
178
206
|
this.terminalFlushRequested = false;
|
|
179
207
|
this.overflowed = false;
|
|
208
|
+
this.replayEventSequence = 0;
|
|
180
209
|
this.viewId = createId();
|
|
181
210
|
this.viewUrl = sanitizeUrl(window.location.href, this.options.trackHash ?? false);
|
|
182
|
-
|
|
211
|
+
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
212
|
+
this.activeSessionId = context.sessionId;
|
|
213
|
+
this.startTime = context.sessionStart;
|
|
183
214
|
this.chunkStartedAt = Date.now();
|
|
184
|
-
this.
|
|
185
|
-
|
|
186
|
-
});
|
|
187
|
-
const generation = ++this.startGeneration;
|
|
188
|
-
this.beginRecording(generation);
|
|
215
|
+
this.subscribeToSessionRotation();
|
|
216
|
+
this.beginRecording();
|
|
189
217
|
this.intervalId = setInterval(() => this.requestFlush("interval"), this.flushInterval);
|
|
190
218
|
window.addEventListener("beforeunload", this.onUnload);
|
|
191
219
|
this.log("Recording started");
|
|
@@ -219,11 +247,9 @@ var ReplayTracker = class {
|
|
|
219
247
|
if (!persisted || !this.started) return;
|
|
220
248
|
if (this.pending.length > 0) this.flush(false, false, "pageShow");
|
|
221
249
|
}
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
const plugins = [sequentialIdPluginModule.getRecordSequentialIdPlugin({ key: REPLAY_SEQUENTIAL_ID_KEY })];
|
|
226
|
-
if (consolePluginModule) plugins.push(consolePluginModule.getRecordConsolePlugin({
|
|
250
|
+
beginRecording() {
|
|
251
|
+
const plugins = [];
|
|
252
|
+
if (this.options.recordConsole === true) plugins.push(getRecordConsolePlugin({
|
|
227
253
|
level: this.options.consoleLevel ?? [
|
|
228
254
|
"error",
|
|
229
255
|
"warn",
|
|
@@ -267,21 +293,16 @@ var ReplayTracker = class {
|
|
|
267
293
|
blockSelector: this.options.blockSelector,
|
|
268
294
|
maskTextClass: this.options.maskTextClass,
|
|
269
295
|
maskTextSelector: this.options.maskTextSelector,
|
|
270
|
-
checkoutEveryNms: this.options.checkoutEveryNms ??
|
|
296
|
+
checkoutEveryNms: this.options.checkoutEveryNms ?? 3e5,
|
|
271
297
|
checkoutEveryNth: this.options.checkoutEveryNth,
|
|
272
298
|
plugins
|
|
273
299
|
});
|
|
274
|
-
if (generation !== this.startGeneration) {
|
|
275
|
-
stop?.();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
300
|
this.stopRecording = stop;
|
|
279
301
|
}
|
|
280
302
|
stop(discard = false) {
|
|
281
303
|
if (!this.started) return;
|
|
282
304
|
this.started = false;
|
|
283
305
|
this.disposed = true;
|
|
284
|
-
this.startGeneration++;
|
|
285
306
|
this.unsubscribeRotation?.();
|
|
286
307
|
this.unsubscribeRotation = void 0;
|
|
287
308
|
this.stopRecording?.();
|
|
@@ -309,14 +330,41 @@ var ReplayTracker = class {
|
|
|
309
330
|
this.applySessionRotation(prev, next);
|
|
310
331
|
if (this.pending.length > 0) queueMicrotask(() => void this.flush(false, false, "sessionRotate"));
|
|
311
332
|
}
|
|
333
|
+
subscribeToSessionRotation() {
|
|
334
|
+
this.unsubscribeRotation?.();
|
|
335
|
+
this.unsubscribeRotation = onSessionRotated(this.options.siteKey, this.lastCookielessMode, (prev, next) => this.onSessionRotated(prev, next));
|
|
336
|
+
}
|
|
312
337
|
applySessionRotation(prev, next) {
|
|
338
|
+
if (this.activeSessionId === next.sessionId) return;
|
|
313
339
|
this.enqueueEventsIfReady(prev, {
|
|
314
340
|
isFinal: true,
|
|
315
341
|
flushReason: "sessionRotate"
|
|
316
342
|
});
|
|
317
343
|
this.finalizeSessionPending(prev, "sessionRotate");
|
|
318
344
|
this.unblockSessionBatches(prev.sessionId);
|
|
345
|
+
this.activeSessionId = next.sessionId;
|
|
319
346
|
this.resetSessionTiming(next);
|
|
347
|
+
this.beginReplayStream();
|
|
348
|
+
}
|
|
349
|
+
beginReplayStream() {
|
|
350
|
+
this.replayEventSequence = 0;
|
|
351
|
+
const stopRecording = this.stopRecording;
|
|
352
|
+
if (!stopRecording) return;
|
|
353
|
+
try {
|
|
354
|
+
record.takeFullSnapshot(true);
|
|
355
|
+
} catch (snapshotError) {
|
|
356
|
+
this.stopRecording = void 0;
|
|
357
|
+
try {
|
|
358
|
+
stopRecording();
|
|
359
|
+
this.replayEventSequence = 0;
|
|
360
|
+
this.beginRecording();
|
|
361
|
+
} catch (restartError) {
|
|
362
|
+
this.log("Failed to restart replay stream", {
|
|
363
|
+
snapshotError,
|
|
364
|
+
restartError
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
320
368
|
}
|
|
321
369
|
resetSessionTiming(next) {
|
|
322
370
|
if (this.minLengthFlushTask) clearTimeout(this.minLengthFlushTask);
|
|
@@ -343,6 +391,7 @@ var ReplayTracker = class {
|
|
|
343
391
|
if (this.mutationThrottler && !throttled) return;
|
|
344
392
|
if (throttled) capturedEvent = throttled;
|
|
345
393
|
}
|
|
394
|
+
capturedEvent = Object.assign(capturedEvent, { [REPLAY_SEQUENTIAL_ID_KEY]: ++this.replayEventSequence });
|
|
346
395
|
this.events.push(capturedEvent);
|
|
347
396
|
this.queuedEventsSizeBytes += this.eventSizeBytes(capturedEvent);
|
|
348
397
|
let reason;
|
|
@@ -421,7 +470,7 @@ var ReplayTracker = class {
|
|
|
421
470
|
return batches;
|
|
422
471
|
}
|
|
423
472
|
createBatch(events, context, isFinal, flushReason = "manual") {
|
|
424
|
-
const identifier = getAnonymousId(this.options.siteKey);
|
|
473
|
+
const identifier = getAnonymousId(this.options.siteKey, this.lastCookielessMode);
|
|
425
474
|
const sequence = this.nextBatchSequence(context);
|
|
426
475
|
const chunkStartedAt = this.chunkStartedAt;
|
|
427
476
|
const now = Date.now();
|
|
@@ -475,9 +524,9 @@ var ReplayTracker = class {
|
|
|
475
524
|
this.pendingSizeBytes += size;
|
|
476
525
|
}
|
|
477
526
|
async flush(lowLatency, isFinal = false, flushReason = "manual") {
|
|
478
|
-
const rotation = touchActivity(this.options.siteKey);
|
|
527
|
+
const rotation = touchActivity(this.options.siteKey, this.lastCookielessMode);
|
|
479
528
|
if (rotation) this.applySessionRotation(rotation.prev, rotation.next);
|
|
480
|
-
const context = getSessionContext(this.options.siteKey);
|
|
529
|
+
const context = getSessionContext(this.options.siteKey, this.lastCookielessMode);
|
|
481
530
|
this.enqueueEventsIfReady(context, {
|
|
482
531
|
isFinal,
|
|
483
532
|
flushReason,
|
|
@@ -549,7 +598,7 @@ var ReplayTracker = class {
|
|
|
549
598
|
}
|
|
550
599
|
async sendBatch(batch, lowLatency) {
|
|
551
600
|
const json = JSON.stringify(batch);
|
|
552
|
-
const lowLatencySafe = lowLatency && textEncoder.encode(json).byteLength <=
|
|
601
|
+
const lowLatencySafe = lowLatency && textEncoder.encode(json).byteLength <= 61440;
|
|
553
602
|
const sendOptions = {
|
|
554
603
|
useBeacon: lowLatencySafe,
|
|
555
604
|
keepalive: lowLatencySafe
|
|
@@ -576,4 +625,4 @@ var ReplayTracker = class {
|
|
|
576
625
|
}
|
|
577
626
|
};
|
|
578
627
|
//#endregion
|
|
579
|
-
export { ReplayTracker as default };
|
|
628
|
+
export { ReplayTracker as default, sessionReplay };
|
package/dist/web-vitals.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as AnalyticsExtension } from "./chunks/extensions-DW7tJY0T.js";
|
|
1
2
|
//#region src/web-vitals.d.ts
|
|
2
3
|
interface WebVitalsOptions {
|
|
3
4
|
siteKey: string;
|
|
@@ -5,7 +6,10 @@ interface WebVitalsOptions {
|
|
|
5
6
|
debug?: boolean;
|
|
6
7
|
attribution?: boolean;
|
|
7
8
|
trackHash?: boolean;
|
|
9
|
+
cookieless?: boolean;
|
|
8
10
|
}
|
|
11
|
+
type WebVitalsExtensionOptions = Pick<WebVitalsOptions, "attribution">;
|
|
12
|
+
declare function webVitals(options?: WebVitalsExtensionOptions): AnalyticsExtension;
|
|
9
13
|
declare class WebVitalsTracker {
|
|
10
14
|
private readonly options;
|
|
11
15
|
private readonly endpoint;
|
|
@@ -14,7 +18,9 @@ declare class WebVitalsTracker {
|
|
|
14
18
|
private flushing;
|
|
15
19
|
private initialUrl;
|
|
16
20
|
private session;
|
|
21
|
+
private cookieless;
|
|
17
22
|
constructor(options: WebVitalsOptions);
|
|
23
|
+
setCookielessMode(cookieless: boolean): void;
|
|
18
24
|
start(): Promise<void>;
|
|
19
25
|
stop(discard?: boolean): void;
|
|
20
26
|
onPageHidden(persisted?: boolean): void;
|
|
@@ -22,4 +28,4 @@ declare class WebVitalsTracker {
|
|
|
22
28
|
private flush;
|
|
23
29
|
}
|
|
24
30
|
//#endregion
|
|
25
|
-
export { WebVitalsOptions, WebVitalsTracker as default };
|
|
31
|
+
export { WebVitalsExtensionOptions, WebVitalsOptions, WebVitalsTracker as default, webVitals };
|
package/dist/web-vitals.js
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { r as getSessionContext, t as sendData } from "./chunks/send-data-
|
|
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-DFZnsaL2.js";
|
|
3
3
|
//#region src/web-vitals.ts
|
|
4
|
+
function webVitals(options = {}) {
|
|
5
|
+
return {
|
|
6
|
+
name: "web-vitals",
|
|
7
|
+
async setup(context) {
|
|
8
|
+
const tracker = new WebVitalsTracker({
|
|
9
|
+
...options,
|
|
10
|
+
siteKey: context.siteKey,
|
|
11
|
+
baseUrl: context.baseUrl,
|
|
12
|
+
debug: context.debug,
|
|
13
|
+
trackHash: context.trackHash,
|
|
14
|
+
cookieless: context.isCookieless()
|
|
15
|
+
});
|
|
16
|
+
context.on("pageHide", ({ persisted }) => tracker.onPageHidden(persisted));
|
|
17
|
+
context.on("consentChange", ({ cookieless }) => tracker.setCookielessMode(cookieless));
|
|
18
|
+
await tracker.start();
|
|
19
|
+
return ({ discard }) => tracker.stop(discard);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
4
23
|
const METRIC_NAMES = /* @__PURE__ */ new Set([
|
|
5
24
|
"CLS",
|
|
6
25
|
"INP",
|
|
@@ -16,23 +35,31 @@ var WebVitalsTracker = class {
|
|
|
16
35
|
flushing = false;
|
|
17
36
|
initialUrl = "";
|
|
18
37
|
session = null;
|
|
38
|
+
cookieless;
|
|
19
39
|
constructor(options) {
|
|
20
40
|
this.options = options;
|
|
21
41
|
this.endpoint = `${resolveBaseUrl(options.baseUrl, ANALYTICS_BASE)}${URLS.vitals}`;
|
|
42
|
+
this.cookieless = options.cookieless ?? false;
|
|
43
|
+
}
|
|
44
|
+
setCookielessMode(cookieless) {
|
|
45
|
+
if (this.cookieless === cookieless) return;
|
|
46
|
+
this.cookieless = cookieless;
|
|
47
|
+
this.metrics.clear();
|
|
48
|
+
this.session = this.started ? getSessionContext(this.options.siteKey, cookieless) : null;
|
|
22
49
|
}
|
|
23
50
|
async start() {
|
|
24
51
|
if (this.started || typeof window === "undefined") return;
|
|
25
52
|
this.started = true;
|
|
26
53
|
this.initialUrl = sanitizeUrl(window.location.href, this.options.trackHash ?? false);
|
|
27
|
-
this.session = getSessionContext(this.options.siteKey);
|
|
28
|
-
const
|
|
54
|
+
this.session = getSessionContext(this.options.siteKey, this.cookieless);
|
|
55
|
+
const vitals = this.options.attribution ? await import("web-vitals/attribution") : await import("web-vitals");
|
|
29
56
|
if (!this.started) return;
|
|
30
57
|
const changes = { reportAllChanges: true };
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
vitals.onCLS(this.onMetric, changes);
|
|
59
|
+
vitals.onINP(this.onMetric, changes);
|
|
60
|
+
vitals.onLCP(this.onMetric, changes);
|
|
61
|
+
vitals.onFCP(this.onMetric);
|
|
62
|
+
vitals.onTTFB(this.onMetric);
|
|
36
63
|
}
|
|
37
64
|
stop(discard = false) {
|
|
38
65
|
if (!this.started) return;
|
|
@@ -49,7 +76,7 @@ var WebVitalsTracker = class {
|
|
|
49
76
|
const name = metric.name;
|
|
50
77
|
if (!METRIC_NAMES.has(name) || !Number.isFinite(metric.value) || metric.value < 0) return;
|
|
51
78
|
const { id, rating, delta, navigationType } = metric;
|
|
52
|
-
const attribution = "attribution" in metric && metric.attribution ? metric.attribution : void 0;
|
|
79
|
+
const attribution = this.options.attribution && "attribution" in metric && metric.attribution ? metric.attribution : void 0;
|
|
53
80
|
this.metrics.set(name, {
|
|
54
81
|
metric: name,
|
|
55
82
|
value: metric.value,
|
|
@@ -67,7 +94,7 @@ var WebVitalsTracker = class {
|
|
|
67
94
|
this.flushing = true;
|
|
68
95
|
const batch = this.metrics;
|
|
69
96
|
this.metrics = /* @__PURE__ */ new Map();
|
|
70
|
-
const session = this.session ?? getSessionContext(this.options.siteKey);
|
|
97
|
+
const session = this.session ?? getSessionContext(this.options.siteKey, this.cookieless);
|
|
71
98
|
let sent = false;
|
|
72
99
|
try {
|
|
73
100
|
sent = await sendData({
|
|
@@ -91,4 +118,4 @@ var WebVitalsTracker = class {
|
|
|
91
118
|
}
|
|
92
119
|
};
|
|
93
120
|
//#endregion
|
|
94
|
-
export { WebVitalsTracker as default };
|
|
121
|
+
export { WebVitalsTracker as default, webVitals };
|
package/package.json
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
"import": "./dist/index.js",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./
|
|
17
|
-
"types": "./dist/
|
|
18
|
-
"import": "./dist/
|
|
19
|
-
"default": "./dist/
|
|
16
|
+
"./outbound-links": {
|
|
17
|
+
"types": "./dist/outbound-links.d.ts",
|
|
18
|
+
"import": "./dist/outbound-links.js",
|
|
19
|
+
"default": "./dist/outbound-links.js"
|
|
20
20
|
},
|
|
21
21
|
"./replay": {
|
|
22
22
|
"types": "./dist/replay.d.ts",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
|
-
"version": "0.
|
|
45
|
+
"version": "0.8.1",
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsdown && bun run check-size",
|
|
48
48
|
"dev": "bun run build",
|
|
@@ -51,15 +51,14 @@
|
|
|
51
51
|
"check-size": "node scripts/check-bundle-size.mjs"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@rrweb/types": "^2.
|
|
55
|
-
"@types/bun": "
|
|
56
|
-
"tsdown": "^0.22.
|
|
54
|
+
"@rrweb/types": "^2.1.1",
|
|
55
|
+
"@types/bun": "^1.4.0",
|
|
56
|
+
"tsdown": "^0.22.14",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@rrweb/record": "^2.1.
|
|
61
|
-
"@rrweb/rrweb-plugin-console-record": "^2.1.
|
|
62
|
-
"
|
|
63
|
-
"web-vitals": "^6.0.0"
|
|
60
|
+
"@rrweb/record": "^2.1.1",
|
|
61
|
+
"@rrweb/rrweb-plugin-console-record": "^2.1.1",
|
|
62
|
+
"web-vitals": "^6.2.1"
|
|
64
63
|
}
|
|
65
64
|
}
|