@statsig/web-analytics 3.23.0 → 3.24.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/web-analytics",
3
- "version": "3.23.0",
3
+ "version": "3.24.0",
4
4
  "license": "ISC",
5
5
  "homepage": "https://github.com/statsig-io/js-client-monorepo",
6
6
  "repository": {
@@ -9,8 +9,8 @@
9
9
  "directory": "packages/web-analytics"
10
10
  },
11
11
  "dependencies": {
12
- "@statsig/client-core": "3.23.0",
13
- "@statsig/js-client": "3.23.0",
12
+ "@statsig/client-core": "3.24.0",
13
+ "@statsig/js-client": "3.24.0",
14
14
  "web-vitals": "5.0.3"
15
15
  },
16
16
  "jsdelivr": "./build/statsig-web-analytics.min.js",
@@ -51,7 +51,7 @@ class AutoCapture {
51
51
  const { sdkKey, errorBoundary, values } = _client.getContext();
52
52
  this._disabledEvents = (_b = (_a = values === null || values === void 0 ? void 0 : values.auto_capture_settings) === null || _a === void 0 ? void 0 : _a.disabled_events) !== null && _b !== void 0 ? _b : {};
53
53
  this._errorBoundary = errorBoundary;
54
- this._errorBoundary.wrap(this);
54
+ this._errorBoundary.wrap(this, 'autoCapture:');
55
55
  this._client.$on('values_updated', () => {
56
56
  var _a, _b;
57
57
  const values = this._client.getContext().values;
@@ -60,9 +60,9 @@ class AutoCapture {
60
60
  });
61
61
  this._engagementManager = new EngagementManager_1.EngagementManager();
62
62
  this._rageClickManager = new RageClickManager_1.default();
63
- this._webVitalsManager = new WebVitalsManager_1.WebVitalsManager(this._enqueueAutoCapture.bind(this));
64
- this._deadClickManager = new DeadClickManager_1.default(this._enqueueAutoCapture.bind(this));
65
- this._consoleLogManager = new ConsoleLogManager_1.ConsoleLogManager(this._enqueueAutoCapture.bind(this), (_c = options === null || options === void 0 ? void 0 : options.consoleLogAutoCaptureSettings) !== null && _c !== void 0 ? _c : { enabled: false });
63
+ this._webVitalsManager = new WebVitalsManager_1.WebVitalsManager(this._enqueueAutoCapture.bind(this), errorBoundary);
64
+ this._deadClickManager = new DeadClickManager_1.default(this._enqueueAutoCapture.bind(this), errorBoundary);
65
+ this._consoleLogManager = new ConsoleLogManager_1.ConsoleLogManager(this._enqueueAutoCapture.bind(this), errorBoundary, (_c = options === null || options === void 0 ? void 0 : options.consoleLogAutoCaptureSettings) !== null && _c !== void 0 ? _c : { enabled: false });
66
66
  this._eventFilterFunc = options === null || options === void 0 ? void 0 : options.eventFilterFunc;
67
67
  const doc = (0, client_core_1._getDocumentSafe)();
68
68
  if (!(0, client_core_1._isServerEnv)()) {
@@ -1,14 +1,16 @@
1
+ import { ErrorBoundary } from '@statsig/client-core';
1
2
  import { AutoCaptureEventName } from './AutoCaptureEvent';
2
3
  import { ConsoleLogAutoCaptureSettings } from './AutoCaptureOptions';
3
4
  export type ConsoleLogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug' | 'trace';
4
5
  export declare class ConsoleLogManager {
5
6
  private _enqueueFn;
7
+ private _errorBoundary;
6
8
  private _options;
7
9
  private _restoreFns;
8
10
  private _isTracking;
9
11
  private _logLevel;
10
12
  private readonly __source;
11
- constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void, _options: ConsoleLogAutoCaptureSettings);
13
+ constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void, _errorBoundary: ErrorBoundary, _options: ConsoleLogAutoCaptureSettings);
12
14
  startTracking(): void;
13
15
  stopTracking(): void;
14
16
  private _patchConsole;
@@ -15,9 +15,10 @@ const ConsoleLogPriority = {
15
15
  error: 50,
16
16
  };
17
17
  class ConsoleLogManager {
18
- constructor(_enqueueFn, _options) {
18
+ constructor(_enqueueFn, _errorBoundary, _options) {
19
19
  var _a;
20
20
  this._enqueueFn = _enqueueFn;
21
+ this._errorBoundary = _errorBoundary;
21
22
  this._options = _options;
22
23
  this._restoreFns = [];
23
24
  this._isTracking = false;
@@ -26,13 +27,19 @@ class ConsoleLogManager {
26
27
  this._logLevel = (_a = this._options.logLevel) !== null && _a !== void 0 ? _a : 'info';
27
28
  }
28
29
  startTracking() {
29
- if (this._isTracking || !this._options.enabled)
30
- return;
31
- const win = (0, client_core_1._getWindowSafe)();
32
- if (!win)
33
- return;
34
- this._isTracking = true;
35
- this._patchConsole();
30
+ try {
31
+ if (this._isTracking || !this._options.enabled)
32
+ return;
33
+ const win = (0, client_core_1._getWindowSafe)();
34
+ if (!win)
35
+ return;
36
+ this._patchConsole();
37
+ this._isTracking = true;
38
+ }
39
+ catch (error) {
40
+ client_core_1.Log.error('Error starting console log tracking', error);
41
+ this._errorBoundary.logError('autoCapture:ConsoleLogManager', error);
42
+ }
36
43
  }
37
44
  stopTracking() {
38
45
  if (!this._isTracking)
@@ -62,6 +69,7 @@ class ConsoleLogManager {
62
69
  }
63
70
  catch (err) {
64
71
  original('console observer error:', err, ...args);
72
+ this._errorBoundary.logError('autoCapture:ConsoleLogManager', err);
65
73
  }
66
74
  finally {
67
75
  inStack = false;
@@ -1,3 +1,4 @@
1
+ import { ErrorBoundary } from '@statsig/client-core';
1
2
  import { AutoCaptureEventName } from './AutoCaptureEvent';
2
3
  export declare const DeadClickConfig: {
3
4
  CLICK_CHECK_TIMEOUT: number;
@@ -16,13 +17,14 @@ export interface PossibleDeadClick {
16
17
  }
17
18
  export default class DeadClickManager {
18
19
  private _enqueueFn;
20
+ private _errorBoundary;
19
21
  private _lastMutationTime;
20
22
  private _lastSelectionChangeTime;
21
23
  private _clickCheckTimer;
22
24
  private _observer;
23
25
  private _clicks;
24
26
  private _deadClickConfig;
25
- constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void);
27
+ constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void, _errorBoundary: ErrorBoundary);
26
28
  startTracking(): void;
27
29
  private _handleClick;
28
30
  private _handleScroll;
@@ -15,8 +15,9 @@ exports.DeadClickConfig = {
15
15
  };
16
16
  // A dead click is a click that fires an event but produces no meaningful change within a set timeframe.
17
17
  class DeadClickManager {
18
- constructor(_enqueueFn) {
18
+ constructor(_enqueueFn, _errorBoundary) {
19
19
  this._enqueueFn = _enqueueFn;
20
+ this._errorBoundary = _errorBoundary;
20
21
  this._lastMutationTime = 0;
21
22
  this._lastSelectionChangeTime = 0;
22
23
  this._clicks = [];
@@ -31,25 +32,31 @@ class DeadClickManager {
31
32
  }, 50);
32
33
  }
33
34
  startTracking() {
34
- const win = (0, client_core_1._getWindowSafe)();
35
- if (!win) {
36
- return;
35
+ try {
36
+ const win = (0, client_core_1._getWindowSafe)();
37
+ if (!win) {
38
+ return;
39
+ }
40
+ // `capture: true` - Needed to listen to scroll events on all scrollable elements, not just the window.
41
+ // docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
42
+ //
43
+ // `passive: true` - Indicates the scroll handler won’t call preventDefault(),
44
+ // allowing the browser to optimize scrolling performance by not blocking it.
45
+ // docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive
46
+ win.addEventListener('click', (event) => this._handleClick(event), {
47
+ capture: true,
48
+ });
49
+ win.addEventListener('scroll', () => this._handleScroll(), {
50
+ capture: true,
51
+ passive: true,
52
+ });
53
+ win.addEventListener('selectionchange', () => this._handleSelectionChange());
54
+ this._setupMutationObserver();
55
+ }
56
+ catch (error) {
57
+ client_core_1.Log.error('Error starting dead click tracking', error);
58
+ this._errorBoundary.logError('autoCapture:DeadClickManager', error);
37
59
  }
38
- // `capture: true` - Needed to listen to scroll events on all scrollable elements, not just the window.
39
- // docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
40
- //
41
- // `passive: true` - Indicates the scroll handler won’t call preventDefault(),
42
- // allowing the browser to optimize scrolling performance by not blocking it.
43
- // docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive
44
- win.addEventListener('click', (event) => this._handleClick(event), {
45
- capture: true,
46
- });
47
- win.addEventListener('scroll', () => this._handleScroll(), {
48
- capture: true,
49
- passive: true,
50
- });
51
- win.addEventListener('selectionchange', () => this._handleSelectionChange());
52
- this._setupMutationObserver();
53
60
  }
54
61
  _handleClick(event) {
55
62
  var _a, _b;
@@ -1,8 +1,11 @@
1
+ import { ErrorBoundary } from '@statsig/client-core';
1
2
  import { AutoCaptureEventName } from './AutoCaptureEvent';
2
3
  export declare class WebVitalsManager {
3
4
  private _enqueueFn;
5
+ private _errorBoundary;
4
6
  private _isInitialized;
5
- constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void);
7
+ private _metricEvent;
8
+ constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void, _errorBoundary: ErrorBoundary);
6
9
  startTracking(): void;
7
10
  private _handleMetric;
8
11
  private _enqueueWebVitalsAutoCaptureEvent;
@@ -7,27 +7,44 @@ const AutoCaptureEvent_1 = require("./AutoCaptureEvent");
7
7
  const commonUtils_1 = require("./utils/commonUtils");
8
8
  const VALID_METRIC_NAMES = ['CLS', 'FCP', 'LCP', 'TTFB'];
9
9
  class WebVitalsManager {
10
- constructor(_enqueueFn) {
10
+ constructor(_enqueueFn, _errorBoundary) {
11
11
  this._enqueueFn = _enqueueFn;
12
+ this._errorBoundary = _errorBoundary;
12
13
  this._isInitialized = false;
14
+ this._metricEvent = {
15
+ url: (0, commonUtils_1._getSafeUrlString)(),
16
+ sanitizedUrl: (0, commonUtils_1._getSanitizedPageUrl)(),
17
+ metrics: [],
18
+ firstMetricTimestamp: undefined,
19
+ };
13
20
  }
14
21
  startTracking() {
15
22
  var _a, _b;
16
- if (this._isInitialized) {
17
- return;
23
+ try {
24
+ if (this._isInitialized) {
25
+ return;
26
+ }
27
+ const protocol = (_b = (_a = (0, client_core_1._getWindowSafe)()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.protocol;
28
+ if (protocol !== 'https:' && protocol !== 'http:') {
29
+ return;
30
+ }
31
+ (0, web_vitals_1.onCLS)((metric) => this._handleMetric(metric));
32
+ (0, web_vitals_1.onFCP)((metric) => this._handleMetric(metric));
33
+ (0, web_vitals_1.onLCP)((metric) => this._handleMetric(metric));
34
+ (0, web_vitals_1.onTTFB)((metric) => this._handleMetric(metric));
35
+ this._isInitialized = true;
18
36
  }
19
- const protocol = (_b = (_a = (0, client_core_1._getWindowSafe)()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.protocol;
20
- if (protocol !== 'https:' && protocol !== 'http:') {
21
- return;
37
+ catch (error) {
38
+ client_core_1.Log.error('Error starting web vitals tracking', error);
39
+ this._errorBoundary.logError('autoCapture:WebVitalsManager', error);
22
40
  }
23
- (0, web_vitals_1.onCLS)((metric) => this._handleMetric(metric));
24
- (0, web_vitals_1.onFCP)((metric) => this._handleMetric(metric));
25
- (0, web_vitals_1.onLCP)((metric) => this._handleMetric(metric));
26
- (0, web_vitals_1.onTTFB)((metric) => this._handleMetric(metric));
27
- this._isInitialized = true;
28
41
  }
29
42
  _handleMetric(metric) {
30
- if (metric === undefined || (metric === null || metric === void 0 ? void 0 : metric.name) === undefined) {
43
+ if (this._metricEvent.firstMetricTimestamp === undefined) {
44
+ this._metricEvent.firstMetricTimestamp = Date.now();
45
+ }
46
+ if (metric === undefined ||
47
+ (metric === null || metric === void 0 ? void 0 : metric.name) === undefined) {
31
48
  return;
32
49
  }
33
50
  const currentUrl = (0, commonUtils_1._getSafeUrlString)();
@@ -38,18 +55,40 @@ class WebVitalsManager {
38
55
  if (!VALID_METRIC_NAMES.includes(metric.name)) {
39
56
  return;
40
57
  }
41
- this._enqueueWebVitalsAutoCaptureEvent(metric, currentUrl);
58
+ if (currentUrl !== this._metricEvent.url) {
59
+ this._enqueueWebVitalsAutoCaptureEvent();
60
+ this._metricEvent.url = currentUrl;
61
+ }
62
+ const metricData = metric;
63
+ this._metricEvent.metrics.push({
64
+ name: metricData.name,
65
+ value: metricData.value,
66
+ delta: metricData.delta,
67
+ id: metricData.id,
68
+ });
69
+ if (this._metricEvent.metrics.length === VALID_METRIC_NAMES.length) {
70
+ this._enqueueWebVitalsAutoCaptureEvent();
71
+ }
42
72
  }
43
- _enqueueWebVitalsAutoCaptureEvent(metric, url) {
44
- if (url === '') {
73
+ _enqueueWebVitalsAutoCaptureEvent() {
74
+ if (this._metricEvent.url === '' ||
75
+ this._metricEvent.metrics.length === 0) {
45
76
  return;
46
77
  }
47
- this._enqueueFn(AutoCaptureEvent_1.AutoCaptureEventName.WEB_VITALS, url, {
48
- name: metric.name,
49
- value: metric.value,
50
- delta: metric.delta,
51
- id: metric.id,
78
+ const flattenedMetrics = {};
79
+ this._metricEvent.metrics.forEach((metric) => {
80
+ const prefix = metric.name.toLowerCase();
81
+ flattenedMetrics[`${prefix}_value`] = metric.value;
82
+ flattenedMetrics[`${prefix}_delta`] = metric.delta;
83
+ flattenedMetrics[`${prefix}_id`] = metric.id;
52
84
  });
85
+ this._enqueueFn(AutoCaptureEvent_1.AutoCaptureEventName.WEB_VITALS, this._metricEvent.sanitizedUrl, Object.assign(Object.assign({}, flattenedMetrics), { first_metric_timestamp: this._metricEvent.firstMetricTimestamp }));
86
+ this._metricEvent = {
87
+ url: (0, commonUtils_1._getSafeUrlString)(),
88
+ sanitizedUrl: (0, commonUtils_1._getSanitizedPageUrl)(),
89
+ metrics: [],
90
+ firstMetricTimestamp: undefined,
91
+ };
53
92
  }
54
93
  }
55
94
  exports.WebVitalsManager = WebVitalsManager;