@faststats/web 0.13.0 → 0.13.2

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/index.js CHANGED
@@ -1,63 +1,7 @@
1
- import { a as resolveBaseUrl, i as getPageContext, n as URLS, o as sanitizeUrl, r as getClientContext, t as sendData } from "./chunks/send-data-C76HbZ8d.js";
2
- import { a as touchActivity, i as resetSession, n as getSessionContext, t as createId } from "./chunks/session-manager-DUpF3mi3.js";
3
- import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-DOtve3E5.js";
4
- //#region src/extensions.ts
5
- var ExtensionHost = class {
6
- context;
7
- log;
8
- handlers = /* @__PURE__ */ new Map();
9
- cleanups = [];
10
- stopped = false;
11
- constructor(extensions, context, log) {
12
- this.context = context;
13
- this.log = log;
14
- for (const extension of extensions) this.setup(extension);
15
- }
16
- emit(hook, event) {
17
- if (this.stopped) return;
18
- for (const handler of this.handlers.get(hook) ?? []) try {
19
- handler(event)?.catch((error) => this.log(`${hook} failed`, error));
20
- } catch (error) {
21
- this.log(`${hook} failed`, error);
22
- }
23
- }
24
- stop(discard = false) {
25
- if (this.stopped) return;
26
- this.stopped = true;
27
- for (const cleanup of this.cleanups.reverse()) try {
28
- cleanup({ discard });
29
- } catch (error) {
30
- this.log("cleanup failed", error);
31
- }
32
- this.cleanups.length = 0;
33
- this.handlers.clear();
34
- }
35
- async setup(extension) {
36
- if (this.stopped) return;
37
- const subscriptions = [];
38
- try {
39
- const cleanup = await extension.setup({
40
- ...this.context,
41
- on: (hook, handler) => {
42
- if (this.stopped) return () => {};
43
- const handlers = this.handlers.get(hook) ?? /* @__PURE__ */ new Set();
44
- this.handlers.set(hook, handlers);
45
- handlers.add(handler);
46
- const off = () => handlers.delete(handler);
47
- subscriptions.push(off);
48
- return off;
49
- }
50
- });
51
- if (this.stopped) cleanup?.({ discard: true });
52
- else if (cleanup) this.cleanups.push(cleanup);
53
- } catch (error) {
54
- for (const off of subscriptions) off();
55
- this.log(`${extension.name} setup failed`, error);
56
- }
57
- }
58
- };
59
- //#endregion
60
- //#region src/outbound-links.ts
1
+ import { a as resolveBaseUrl, i as getPageContext, n as URLS, o as sanitizeUrl, r as getClientContext, s as ExtensionHost, t as sendData } from "./chunks/send-data-BCVUgwry.js";
2
+ import { a as touchActivity, i as resetSession, n as getSessionContext, t as createId } from "./chunks/session-manager-DBWYTyfd.js";
3
+ import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-BHYhM7DM.js";
4
+ // src/outbound-links.ts
61
5
  const DEDUPE_MS = 500;
62
6
  function outboundHref(event) {
63
7
  for (const node of event.composedPath()) if (node instanceof HTMLAnchorElement && (node.protocol === "http:" || node.protocol === "https:") && node.host !== location.host) return node.href;
@@ -85,41 +29,32 @@ function trackOutboundLinks(report) {
85
29
  document.removeEventListener("auxclick", onClick, true);
86
30
  };
87
31
  }
88
- //#endregion
89
- //#region src/analytics.ts
32
+ // src/analytics.ts
90
33
  const navigationListeners = /* @__PURE__ */ new Set();
91
- const historyPatches = {};
92
- function patchHistory(method) {
93
- if (historyPatches[method]?.owner === history) return;
94
- const original = history[method];
95
- const wrapped = function(data, unused, url) {
96
- Reflect.apply(original, this, [
97
- data,
98
- unused,
99
- url
100
- ]);
101
- for (const listener of navigationListeners) listener();
102
- };
103
- history[method] = wrapped;
104
- historyPatches[method] = {
105
- owner: history,
106
- original,
107
- wrapped
108
- };
109
- }
34
+ const historyPatches = /* @__PURE__ */ new Map();
110
35
  function observeHistory(listener) {
111
36
  navigationListeners.add(listener);
112
- patchHistory("pushState");
113
- patchHistory("replaceState");
37
+ for (const method of ["pushState", "replaceState"]) {
38
+ if (historyPatches.get(method)?.target === history) continue;
39
+ const original = history[method];
40
+ const wrapped = function(...args) {
41
+ Reflect.apply(original, this, args);
42
+ for (const listener of navigationListeners) listener();
43
+ };
44
+ history[method] = wrapped;
45
+ historyPatches.set(method, {
46
+ target: history,
47
+ original,
48
+ wrapped
49
+ });
50
+ }
114
51
  return () => {
115
52
  navigationListeners.delete(listener);
116
53
  if (navigationListeners.size) return;
117
- for (const method of ["pushState", "replaceState"]) {
118
- const patch = historyPatches[method];
119
- if (patch && history[method] === patch.wrapped) {
120
- history[method] = patch.original;
121
- delete historyPatches[method];
122
- }
54
+ for (const [method, patch] of historyPatches) {
55
+ if (history[method] !== patch.wrapped) continue;
56
+ history[method] = patch.original;
57
+ historyPatches.delete(method);
123
58
  }
124
59
  };
125
60
  }
@@ -190,9 +125,8 @@ var WebAnalytics = class {
190
125
  context,
191
126
  duration: 0,
192
127
  startedAt: null,
193
- checkedAt: Date.now(),
194
128
  activeAt: Date.now(),
195
- reportedDuration: -1,
129
+ reportedDuration: 0,
196
130
  beats: 0
197
131
  };
198
132
  this.send("pageview", {
@@ -257,21 +191,19 @@ var WebAnalytics = class {
257
191
  setConsentMode(mode) {
258
192
  if (mode === this.consentMode) return;
259
193
  const wasTracking = this.canTrack();
260
- const wasCookieless = this.isCookieless();
261
- const willTrack = mode !== "denied";
262
- const willBeCookieless = this.options.cookieless === true || mode === "anonymous";
263
- if (wasTracking && willTrack && wasCookieless !== willBeCookieless) this.leavePage();
194
+ const identityChanges = this.isCookieless() !== this.isCookieless(mode);
195
+ if (mode !== "denied" && identityChanges) this.leavePage();
264
196
  this.consentMode = mode;
265
- if (!willTrack) {
197
+ if (mode === "denied") {
266
198
  this.leavePage();
267
199
  this.pendingPageview = null;
268
200
  this.stopExtensions(true);
269
- return;
201
+ } else if (!wasTracking) {
202
+ if (this.state === "started") this.activate("consent");
203
+ } else {
204
+ this.extensions?.emit("consentChange", { cookieless: this.isCookieless() });
205
+ if (identityChanges) this.pageview({ trigger: "consent" });
270
206
  }
271
- if (wasTracking) {
272
- this.extensions?.emit("consentChange", { cookieless: willBeCookieless });
273
- if (wasCookieless !== willBeCookieless) this.pageview({ trigger: "consent" });
274
- } else if (this.state === "started") this.activate("consent");
275
207
  }
276
208
  getAnonymousId() {
277
209
  return this.canTrack() ? getAnonymousId(this.options.siteKey, this.isCookieless()) : "";
@@ -283,8 +215,8 @@ var WebAnalytics = class {
283
215
  canTrack() {
284
216
  return this.consentMode !== "denied";
285
217
  }
286
- isCookieless() {
287
- return this.options.cookieless === true || this.consentMode === "anonymous";
218
+ isCookieless(mode = this.consentMode) {
219
+ return this.options.cookieless === true || mode === "anonymous";
288
220
  }
289
221
  isActive() {
290
222
  return this.state === "started" && this.canTrack();
@@ -328,7 +260,7 @@ var WebAnalytics = class {
328
260
  this.bind(window, "pagehide", this.onPageHide);
329
261
  this.bind(window, "pageshow", this.onPageShow);
330
262
  this.bind(window, "focus", this.onResume);
331
- this.bind(window, "blur", () => this.pauseVisit());
263
+ this.bind(window, "blur", this.onBlur);
332
264
  this.bind(window, "popstate", this.onNavigate);
333
265
  for (const type of [
334
266
  "pointerdown",
@@ -345,7 +277,6 @@ var WebAnalytics = class {
345
277
  }
346
278
  onNavigate = () => this.pageview({ trigger: "navigation" });
347
279
  onVisibilityChange = () => {
348
- if (!this.canTrack()) return;
349
280
  if (document.visibilityState === "hidden") {
350
281
  this.pauseVisit();
351
282
  this.extensions?.emit("pageHide", {
@@ -357,7 +288,6 @@ var WebAnalytics = class {
357
288
  this.onResume();
358
289
  };
359
290
  onPageHide = (event) => {
360
- if (!this.canTrack()) return;
361
291
  const persisted = event.persisted;
362
292
  if (persisted) this.pauseVisit();
363
293
  else this.leavePage();
@@ -367,7 +297,6 @@ var WebAnalytics = class {
367
297
  });
368
298
  };
369
299
  onPageShow = (event) => {
370
- if (!this.canTrack()) return;
371
300
  this.onResume();
372
301
  this.extensions?.emit("pageShow", { persisted: event.persisted });
373
302
  };
@@ -375,6 +304,13 @@ var WebAnalytics = class {
375
304
  this.pageview(this.pendingPageview ?? { trigger: "session" });
376
305
  this.resumeVisit();
377
306
  };
307
+ onBlur = () => {
308
+ const visit = this.visit;
309
+ const blurredAt = performance.now();
310
+ setTimeout(() => {
311
+ if (this.visit === visit && !document.hasFocus()) this.pauseVisit(false, blurredAt);
312
+ });
313
+ };
378
314
  onActivity = () => {
379
315
  this.lastInputAt = performance.now();
380
316
  if (this.visit?.startedAt === null && document.hasFocus()) this.onResume();
@@ -382,30 +318,24 @@ var WebAnalytics = class {
382
318
  isIdle() {
383
319
  const now = performance.now();
384
320
  if (now - this.lastInputAt < IDLE_MS) return false;
385
- const playing = [...document.querySelectorAll("video,audio")].some((media) => !media.paused && !media.ended);
386
- if (playing) this.lastInputAt = now;
387
- return !playing;
321
+ const engaged = document.activeElement?.tagName === "IFRAME" || [...document.querySelectorAll("video,audio")].some((media) => !media.paused && !media.ended);
322
+ if (engaged) this.lastInputAt = now;
323
+ return !engaged;
388
324
  }
389
325
  leavePage() {
390
326
  this.pauseVisit(true);
391
327
  this.visit = null;
392
328
  }
393
- /**
394
- * Reports the visit's running totals: as a `page_ping` while it may still
395
- * continue, and as its single `page_leave` once it has ended.
396
- */
397
- checkpoint(leaving = false, periodic = false) {
329
+ checkpoint(leaving = false, periodic = false, now = performance.now()) {
398
330
  const visit = this.visit;
399
331
  if (!visit || !this.isActive()) return;
400
332
  if (visit.startedAt !== null) {
401
- const now = performance.now();
402
- if (now - visit.startedAt < IDLE_MS) {
403
- const active = Math.min(now, this.lastInputAt + IDLE_MS) - visit.startedAt;
404
- visit.duration += active;
405
- visit.activeAt = visit.checkedAt + active;
333
+ const activeUntil = Math.min(now, this.lastInputAt + IDLE_MS);
334
+ if (now - visit.startedAt < IDLE_MS && activeUntil > visit.startedAt) {
335
+ visit.duration += activeUntil - visit.startedAt;
336
+ visit.activeAt = Date.now() - (performance.now() - activeUntil);
406
337
  }
407
338
  visit.startedAt = now;
408
- visit.checkedAt = Date.now();
409
339
  }
410
340
  if (periodic && ++visit.beats !== 1 && visit.beats % 4 !== 3) return;
411
341
  const duration = Math.round(visit.duration);
@@ -417,16 +347,15 @@ var WebAnalytics = class {
417
347
  session_duration: Math.max(0, visit.activeAt - visit.session.sessionStart)
418
348
  }, visit.context);
419
349
  }
420
- pauseVisit(leaving = false) {
350
+ pauseVisit(leaving = false, at) {
421
351
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
422
352
  this.heartbeatTimer = null;
423
- this.checkpoint(leaving);
353
+ this.checkpoint(leaving, false, at);
424
354
  if (this.visit) this.visit.startedAt = null;
425
355
  }
426
356
  resumeVisit() {
427
357
  if (!this.isActive() || !this.visit || this.visit.startedAt !== null || document.visibilityState !== "visible" || !document.hasFocus()) return;
428
358
  this.visit.startedAt = this.lastInputAt = performance.now();
429
- this.visit.checkedAt = Date.now();
430
359
  this.heartbeatTimer = setInterval(() => {
431
360
  if (document.visibilityState !== "visible" || !document.hasFocus() || this.isIdle()) {
432
361
  this.pauseVisit();
@@ -464,8 +393,7 @@ var WebAnalytics = class {
464
393
  });
465
394
  }
466
395
  };
467
- //#endregion
468
- //#region src/client.ts
396
+ // src/client.ts
469
397
  let client;
470
398
  /** Creates an independent client. Call `start()` when it should begin tracking. */
471
399
  function createClient(options) {
@@ -502,5 +430,4 @@ function setConsentMode(mode) {
502
430
  function reportError(error) {
503
431
  client?.reportError(error);
504
432
  }
505
- //#endregion
506
433
  export { WebAnalytics, createClient, identify, init, logout, pageview, reportError, setConsentMode, shutdown, track };
package/dist/replay.d.ts CHANGED
@@ -1,22 +1,17 @@
1
- import { t as AnalyticsExtension } from "./chunks/extensions-BfubKIzk.js";
1
+ import { t as AnalyticsExtension } from "./chunks/extensions-C2k9Im6I.js";
2
2
  import { record } from "@rrweb/record";
3
3
  import { LogLevel } from "@rrweb/rrweb-plugin-console-record";
4
4
  import { NetworkRecordOptions } from "@rrweb/rrweb-plugin-network-record";
5
- //#region ../../node_modules/.bun/@rrweb+types@2.1.7/node_modules/@rrweb/types/dist/index.d.ts
6
5
  declare global {
7
6
  interface Window {
8
7
  FontFace: typeof FontFace;
9
8
  }
10
9
  }
11
- //#endregion
12
- //#region src/replay-mutation-throttler.d.ts
13
10
  type ReplayMutationThrottleOptions = {
14
11
  enabled?: boolean;
15
12
  bucketSize?: number;
16
13
  refillRate?: number;
17
14
  };
18
- //#endregion
19
- //#region src/replay.d.ts
20
15
  type RecordOptions = NonNullable<Parameters<typeof record>[0]>;
21
16
  export interface ReplayTrackerOptions {
22
17
  siteKey: string;
@@ -57,81 +52,53 @@ export type SessionReplayExtensionOptions = Omit<ReplayTrackerOptions, "siteKey"
57
52
  export declare function sessionReplay(options?: SessionReplayExtensionOptions): AnalyticsExtension;
58
53
  export default class ReplayTracker {
59
54
  private readonly options;
60
- private readonly endpoint;
61
- private readonly compress;
62
- private readonly flushInterval;
63
- private readonly maxEvents;
64
- private readonly maxBatchSizeBytes;
65
- private readonly maxLowLatencyBatchSizeBytes;
66
- private readonly maxPendingBatches;
67
- private readonly maxQueueSizeBytes;
68
- private readonly minReplayLengthMs;
69
- private readonly events;
70
- private readonly pending;
71
- private queuedEventsSizeBytes;
72
- private pendingSizeBytes;
73
- private viewId;
74
- private viewUrl;
75
- private chunkStartedAt;
55
+ private readonly limits;
56
+ private readonly uploader;
57
+ private readonly throttler;
58
+ private cookieless;
76
59
  private started;
77
- private disposed;
78
- private startTime;
79
- private intervalId;
80
- private flushTask;
81
- private retryTask;
82
- private retryAttempt;
83
- private minLengthFlushTask;
60
+ private overflowed;
84
61
  private stopRecording?;
85
- private activeSessionId;
86
- private sending;
87
- private queuedFlush;
88
- private unsubscribeRotation?;
89
- private lastCookielessMode;
90
- private needsFinalization;
91
- private initialFlushPending;
62
+ private stopFollowing?;
63
+ private intervalTimer?;
64
+ private flushTimer?;
65
+ private minLengthTimer?;
66
+ private viewId;
67
+ private viewUrl;
68
+ private session;
69
+ private streamStart;
70
+ private chunkStart;
71
+ private sequence;
72
+ private events;
73
+ private eventBytes;
92
74
  private resumed;
93
- private overflowed;
94
- private replayEventSequence;
95
- private readonly mutationThrottler;
75
+ private unfinalized;
96
76
  constructor(options: ReplayTrackerOptions);
97
- private log;
98
- private sessionContext;
99
- private sanitize;
100
- private clearFlushTimers;
101
- private dropEvents;
102
- private clearQueues;
103
- private finalizeSession;
104
- setCookielessMode(cookieless: boolean): void;
105
77
  start(): void;
78
+ stop(discard?: boolean): void;
79
+ /** Nothing captured under the old identity may be sent under the new one. */
80
+ setCookielessMode(cookieless: boolean): void;
106
81
  trackPageChange(url?: string): void;
107
82
  onPageHidden(persisted?: boolean, terminal?: boolean): void;
108
83
  onPageShow(persisted?: boolean): void;
109
- private recordingPlugins;
84
+ private log;
85
+ private sanitize;
86
+ private currentSession;
87
+ private followSession;
110
88
  private beginRecording;
111
- stop(discard?: boolean): void;
112
- private onSessionRotated;
113
- private subscribeToSessionRotation;
114
- private applySessionRotation;
115
- private beginReplayStream;
116
- private resetSessionTiming;
117
- private onEvent;
118
- private requestTerminalFlush;
119
- private hasReachedMinLength;
120
- private requestFlush;
121
- private scheduleMinLengthFlush;
122
- private enqueueEventsIfReady;
123
- private takeEventBatches;
124
- private createBatch;
125
- private eventSizeBytes;
126
- private batchSizeBytes;
127
- private removePending;
89
+ private recordingPlugins;
90
+ private takeFullSnapshot;
128
91
  private stopCapture;
129
- private enqueueBatch;
92
+ private readonly onEvent;
93
+ private beginStream;
94
+ private rotate;
95
+ private reachedMinLength;
96
+ private dropEvents;
97
+ private requestFlush;
130
98
  private flush;
131
- private sendPending;
132
- private scheduleRetry;
133
- private retryDelay;
134
- private sendBatch;
99
+ private finish;
100
+ private batchEvents;
101
+ private endStream;
102
+ private enqueue;
135
103
  }
136
- //#endregion
137
104
  export {};