@faststats/web 0.13.0 → 0.13.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/dist/index.js CHANGED
@@ -1,62 +1,6 @@
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
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-B7P3f5PT.js";
2
+ import { a as touchActivity, i as resetSession, n as getSessionContext, t as createId } from "./chunks/session-manager-VM5RZj9H.js";
3
+ import { n as resetAnonymousId, t as getAnonymousId } from "./chunks/identifiers-3X8XI1pD.js";
60
4
  //#region src/outbound-links.ts
61
5
  const DEDUPE_MS = 500;
62
6
  function outboundHref(event) {
@@ -88,38 +32,30 @@ function trackOutboundLinks(report) {
88
32
  //#endregion
89
33
  //#region src/analytics.ts
90
34
  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
- }
35
+ const historyPatches = /* @__PURE__ */ new Map();
110
36
  function observeHistory(listener) {
111
37
  navigationListeners.add(listener);
112
- patchHistory("pushState");
113
- patchHistory("replaceState");
38
+ for (const method of ["pushState", "replaceState"]) {
39
+ if (historyPatches.get(method)?.target === history) continue;
40
+ const original = history[method];
41
+ const wrapped = function(...args) {
42
+ Reflect.apply(original, this, args);
43
+ for (const listener of navigationListeners) listener();
44
+ };
45
+ history[method] = wrapped;
46
+ historyPatches.set(method, {
47
+ target: history,
48
+ original,
49
+ wrapped
50
+ });
51
+ }
114
52
  return () => {
115
53
  navigationListeners.delete(listener);
116
54
  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
- }
55
+ for (const [method, patch] of historyPatches) {
56
+ if (history[method] !== patch.wrapped) continue;
57
+ history[method] = patch.original;
58
+ historyPatches.delete(method);
123
59
  }
124
60
  };
125
61
  }
@@ -190,9 +126,8 @@ var WebAnalytics = class {
190
126
  context,
191
127
  duration: 0,
192
128
  startedAt: null,
193
- checkedAt: Date.now(),
194
129
  activeAt: Date.now(),
195
- reportedDuration: -1,
130
+ reportedDuration: 0,
196
131
  beats: 0
197
132
  };
198
133
  this.send("pageview", {
@@ -257,21 +192,19 @@ var WebAnalytics = class {
257
192
  setConsentMode(mode) {
258
193
  if (mode === this.consentMode) return;
259
194
  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();
195
+ const identityChanges = this.isCookieless() !== this.isCookieless(mode);
196
+ if (mode !== "denied" && identityChanges) this.leavePage();
264
197
  this.consentMode = mode;
265
- if (!willTrack) {
198
+ if (mode === "denied") {
266
199
  this.leavePage();
267
200
  this.pendingPageview = null;
268
201
  this.stopExtensions(true);
269
- return;
202
+ } else if (!wasTracking) {
203
+ if (this.state === "started") this.activate("consent");
204
+ } else {
205
+ this.extensions?.emit("consentChange", { cookieless: this.isCookieless() });
206
+ if (identityChanges) this.pageview({ trigger: "consent" });
270
207
  }
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
208
  }
276
209
  getAnonymousId() {
277
210
  return this.canTrack() ? getAnonymousId(this.options.siteKey, this.isCookieless()) : "";
@@ -283,8 +216,8 @@ var WebAnalytics = class {
283
216
  canTrack() {
284
217
  return this.consentMode !== "denied";
285
218
  }
286
- isCookieless() {
287
- return this.options.cookieless === true || this.consentMode === "anonymous";
219
+ isCookieless(mode = this.consentMode) {
220
+ return this.options.cookieless === true || mode === "anonymous";
288
221
  }
289
222
  isActive() {
290
223
  return this.state === "started" && this.canTrack();
@@ -328,7 +261,7 @@ var WebAnalytics = class {
328
261
  this.bind(window, "pagehide", this.onPageHide);
329
262
  this.bind(window, "pageshow", this.onPageShow);
330
263
  this.bind(window, "focus", this.onResume);
331
- this.bind(window, "blur", () => this.pauseVisit());
264
+ this.bind(window, "blur", this.onBlur);
332
265
  this.bind(window, "popstate", this.onNavigate);
333
266
  for (const type of [
334
267
  "pointerdown",
@@ -345,7 +278,6 @@ var WebAnalytics = class {
345
278
  }
346
279
  onNavigate = () => this.pageview({ trigger: "navigation" });
347
280
  onVisibilityChange = () => {
348
- if (!this.canTrack()) return;
349
281
  if (document.visibilityState === "hidden") {
350
282
  this.pauseVisit();
351
283
  this.extensions?.emit("pageHide", {
@@ -357,7 +289,6 @@ var WebAnalytics = class {
357
289
  this.onResume();
358
290
  };
359
291
  onPageHide = (event) => {
360
- if (!this.canTrack()) return;
361
292
  const persisted = event.persisted;
362
293
  if (persisted) this.pauseVisit();
363
294
  else this.leavePage();
@@ -367,7 +298,6 @@ var WebAnalytics = class {
367
298
  });
368
299
  };
369
300
  onPageShow = (event) => {
370
- if (!this.canTrack()) return;
371
301
  this.onResume();
372
302
  this.extensions?.emit("pageShow", { persisted: event.persisted });
373
303
  };
@@ -375,6 +305,13 @@ var WebAnalytics = class {
375
305
  this.pageview(this.pendingPageview ?? { trigger: "session" });
376
306
  this.resumeVisit();
377
307
  };
308
+ onBlur = () => {
309
+ const visit = this.visit;
310
+ const blurredAt = performance.now();
311
+ setTimeout(() => {
312
+ if (this.visit === visit && !document.hasFocus()) this.pauseVisit(false, blurredAt);
313
+ });
314
+ };
378
315
  onActivity = () => {
379
316
  this.lastInputAt = performance.now();
380
317
  if (this.visit?.startedAt === null && document.hasFocus()) this.onResume();
@@ -382,30 +319,24 @@ var WebAnalytics = class {
382
319
  isIdle() {
383
320
  const now = performance.now();
384
321
  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;
322
+ const engaged = document.activeElement?.tagName === "IFRAME" || [...document.querySelectorAll("video,audio")].some((media) => !media.paused && !media.ended);
323
+ if (engaged) this.lastInputAt = now;
324
+ return !engaged;
388
325
  }
389
326
  leavePage() {
390
327
  this.pauseVisit(true);
391
328
  this.visit = null;
392
329
  }
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) {
330
+ checkpoint(leaving = false, periodic = false, now = performance.now()) {
398
331
  const visit = this.visit;
399
332
  if (!visit || !this.isActive()) return;
400
333
  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;
334
+ const activeUntil = Math.min(now, this.lastInputAt + IDLE_MS);
335
+ if (now - visit.startedAt < IDLE_MS && activeUntil > visit.startedAt) {
336
+ visit.duration += activeUntil - visit.startedAt;
337
+ visit.activeAt = Date.now() - (performance.now() - activeUntil);
406
338
  }
407
339
  visit.startedAt = now;
408
- visit.checkedAt = Date.now();
409
340
  }
410
341
  if (periodic && ++visit.beats !== 1 && visit.beats % 4 !== 3) return;
411
342
  const duration = Math.round(visit.duration);
@@ -417,16 +348,15 @@ var WebAnalytics = class {
417
348
  session_duration: Math.max(0, visit.activeAt - visit.session.sessionStart)
418
349
  }, visit.context);
419
350
  }
420
- pauseVisit(leaving = false) {
351
+ pauseVisit(leaving = false, at) {
421
352
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
422
353
  this.heartbeatTimer = null;
423
- this.checkpoint(leaving);
354
+ this.checkpoint(leaving, false, at);
424
355
  if (this.visit) this.visit.startedAt = null;
425
356
  }
426
357
  resumeVisit() {
427
358
  if (!this.isActive() || !this.visit || this.visit.startedAt !== null || document.visibilityState !== "visible" || !document.hasFocus()) return;
428
359
  this.visit.startedAt = this.lastInputAt = performance.now();
429
- this.visit.checkedAt = Date.now();
430
360
  this.heartbeatTimer = setInterval(() => {
431
361
  if (document.visibilityState !== "visible" || !document.hasFocus() || this.isIdle()) {
432
362
  this.pauseVisit();
package/dist/replay.d.ts CHANGED
@@ -57,81 +57,54 @@ export type SessionReplayExtensionOptions = Omit<ReplayTrackerOptions, "siteKey"
57
57
  export declare function sessionReplay(options?: SessionReplayExtensionOptions): AnalyticsExtension;
58
58
  export default class ReplayTracker {
59
59
  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;
60
+ private readonly limits;
61
+ private readonly uploader;
62
+ private readonly throttler;
63
+ private cookieless;
76
64
  private started;
77
- private disposed;
78
- private startTime;
79
- private intervalId;
80
- private flushTask;
81
- private retryTask;
82
- private retryAttempt;
83
- private minLengthFlushTask;
65
+ private overflowed;
84
66
  private stopRecording?;
85
- private activeSessionId;
86
- private sending;
87
- private queuedFlush;
88
- private unsubscribeRotation?;
89
- private lastCookielessMode;
90
- private needsFinalization;
91
- private initialFlushPending;
67
+ private stopFollowing?;
68
+ private intervalTimer?;
69
+ private flushTimer?;
70
+ private minLengthTimer?;
71
+ private viewId;
72
+ private viewUrl;
73
+ private session;
74
+ private streamStart;
75
+ private chunkStart;
76
+ private sequence;
77
+ private events;
78
+ private eventBytes;
92
79
  private resumed;
93
- private overflowed;
94
- private replayEventSequence;
95
- private readonly mutationThrottler;
80
+ private unfinalized;
96
81
  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
82
  start(): void;
83
+ stop(discard?: boolean): void;
84
+ /** Nothing captured under the old identity may be sent under the new one. */
85
+ setCookielessMode(cookieless: boolean): void;
106
86
  trackPageChange(url?: string): void;
107
87
  onPageHidden(persisted?: boolean, terminal?: boolean): void;
108
88
  onPageShow(persisted?: boolean): void;
109
- private recordingPlugins;
89
+ private log;
90
+ private sanitize;
91
+ private currentSession;
92
+ private followSession;
110
93
  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;
94
+ private recordingPlugins;
95
+ private takeFullSnapshot;
128
96
  private stopCapture;
129
- private enqueueBatch;
97
+ private readonly onEvent;
98
+ private beginStream;
99
+ private rotate;
100
+ private reachedMinLength;
101
+ private dropEvents;
102
+ private requestFlush;
130
103
  private flush;
131
- private sendPending;
132
- private scheduleRetry;
133
- private retryDelay;
134
- private sendBatch;
104
+ private finish;
105
+ private batchEvents;
106
+ private endStream;
107
+ private enqueue;
135
108
  }
136
109
  //#endregion
137
110
  export {};