@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.
@@ -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 { n as ReplayTrackerOptions, t as ReplayTracker } from "./chunks/replay-BeC2XA4N.js";
2
- export { ReplayTrackerOptions, ReplayTracker as default };
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 resolveBaseUrl, o as sanitizeUrl, r as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-Dp2XNjRF.js";
2
- import { a as onSessionRotated, c as touchActivity, d as setStorageItem, i as isCookielessMode, l as getStorageItem, n as createId, r as getSessionContext, t as sendData } from "./chunks/send-data-DR1JUXka.js";
3
- import { t as getAnonymousId } from "./chunks/identifiers-BvRNbiwA.js";
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 = 2 * 1024 * 1024;
56
- const DEFAULT_MAX_BATCH_SIZE_BYTES = 900 * 1024;
57
- const DEFAULT_MAX_LOW_LATENCY_BATCH_SIZE_BYTES = 60 * 1024;
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 = 5 * 6e4;
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 = isCookielessMode();
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
- this.startTime = getSessionContext(this.options.siteKey).sessionStart;
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
- this.startTime = getSessionContext(this.options.siteKey).sessionStart;
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.unsubscribeRotation = onSessionRotated((prev, next) => {
185
- this.onSessionRotated(prev, next);
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
- async beginRecording(generation) {
223
- const [consolePluginModule, sequentialIdPluginModule] = await Promise.all([this.options.recordConsole === true ? import("@rrweb/rrweb-plugin-console-record") : Promise.resolve(null), import("@rrweb/rrweb-plugin-sequential-id-record")]);
224
- if (generation !== this.startGeneration) return;
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 ?? 5 * 6e4,
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 <= 60 * 1024;
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 };
@@ -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 };
@@ -1,6 +1,25 @@
1
- import { a as resolveBaseUrl, o as sanitizeUrl, r as URLS, t as ANALYTICS_BASE } from "./chunks/api-urls-Dp2XNjRF.js";
2
- import { r as getSessionContext, t as sendData } from "./chunks/send-data-DR1JUXka.js";
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 mod = await (this.options.attribution ? import("web-vitals/attribution") : import("web-vitals"));
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
- mod.onCLS(this.onMetric, changes);
32
- mod.onINP(this.onMetric, changes);
33
- mod.onLCP(this.onMetric, changes);
34
- mod.onFCP(this.onMetric);
35
- mod.onTTFB(this.onMetric);
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
- "./feature-flags": {
17
- "types": "./dist/feature-flags.d.ts",
18
- "import": "./dist/feature-flags.js",
19
- "default": "./dist/feature-flags.js"
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.7.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.0.1",
55
- "@types/bun": "latest",
56
- "tsdown": "^0.22.3",
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.0",
61
- "@rrweb/rrweb-plugin-console-record": "^2.1.0",
62
- "@rrweb/rrweb-plugin-sequential-id-record": "^2.1.0",
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
  }