@statsig/web-analytics 3.20.4 → 3.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statsig/web-analytics",
3
- "version": "3.20.4",
3
+ "version": "3.21.1",
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.20.4",
13
- "@statsig/js-client": "3.20.4",
12
+ "@statsig/client-core": "3.21.1",
13
+ "@statsig/js-client": "3.21.1",
14
14
  "web-vitals": "5.0.3"
15
15
  },
16
16
  "jsdelivr": "./build/statsig-web-analytics.min.js",
@@ -1,8 +1,5 @@
1
1
  import { PrecomputedEvaluationsInterface, StatsigPlugin } from '@statsig/client-core';
2
- import { AutoCaptureEvent } from './AutoCaptureEvent';
3
- export type AutoCaptureOptions = {
4
- eventFilterFunc?: (event: AutoCaptureEvent) => boolean;
5
- };
2
+ import { AutoCaptureOptions } from './AutoCaptureOptions';
6
3
  export declare class StatsigAutoCapturePlugin implements StatsigPlugin<PrecomputedEvaluationsInterface> {
7
4
  private _options?;
8
5
  readonly __plugin = "auto-capture";
@@ -22,6 +19,7 @@ export declare class AutoCapture {
22
19
  private _pageViewLogged;
23
20
  private _webVitalsManager;
24
21
  private _deadClickManager;
22
+ private _consoleLogManager;
25
23
  constructor(_client: PrecomputedEvaluationsInterface, options?: AutoCaptureOptions);
26
24
  private _addEventHandlers;
27
25
  private _addPageViewTracking;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutoCapture = exports.runStatsigAutoCapture = exports.StatsigAutoCapturePlugin = void 0;
4
4
  const client_core_1 = require("@statsig/client-core");
5
5
  const AutoCaptureEvent_1 = require("./AutoCaptureEvent");
6
+ const ConsoleLogManager_1 = require("./ConsoleLogManager");
6
7
  const DeadClickManager_1 = require("./DeadClickManager");
7
8
  const EngagementManager_1 = require("./EngagementManager");
8
9
  const RageClickManager_1 = require("./RageClickManager");
@@ -41,7 +42,7 @@ function runStatsigAutoCapture(client, options) {
41
42
  exports.runStatsigAutoCapture = runStatsigAutoCapture;
42
43
  class AutoCapture {
43
44
  constructor(_client, options) {
44
- var _a, _b, _c;
45
+ var _a, _b, _c, _d;
45
46
  this._client = _client;
46
47
  this._disabledEvents = {};
47
48
  this._previousLoggedPageViewUrl = null;
@@ -61,11 +62,12 @@ class AutoCapture {
61
62
  this._rageClickManager = new RageClickManager_1.default();
62
63
  this._webVitalsManager = new WebVitalsManager_1.WebVitalsManager(this._enqueueAutoCapture.bind(this));
63
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 });
64
66
  this._eventFilterFunc = options === null || options === void 0 ? void 0 : options.eventFilterFunc;
65
67
  const doc = (0, client_core_1._getDocumentSafe)();
66
68
  if (!(0, client_core_1._isServerEnv)()) {
67
69
  __STATSIG__ = (0, client_core_1._getStatsigGlobal)();
68
- const instances = (_c = __STATSIG__.acInstances) !== null && _c !== void 0 ? _c : {};
70
+ const instances = (_d = __STATSIG__.acInstances) !== null && _d !== void 0 ? _d : {};
69
71
  instances[sdkKey] = this;
70
72
  __STATSIG__.acInstances = instances;
71
73
  }
@@ -155,6 +157,7 @@ class AutoCapture {
155
157
  _initialize() {
156
158
  this._webVitalsManager.startTracking();
157
159
  this._deadClickManager.startTracking();
160
+ this._consoleLogManager.startTracking();
158
161
  this._engagementManager.startInactivityTracking(() => this._tryLogPageViewEnd(true));
159
162
  this._addEventHandlers();
160
163
  this._addPageViewTracking();
@@ -11,6 +11,7 @@ export declare const AutoCaptureEventName: {
11
11
  readonly WEB_VITALS: "auto_capture::web_vitals";
12
12
  readonly DEAD_CLICK: "auto_capture::dead_click";
13
13
  readonly COPY: "auto_capture::copy";
14
+ readonly CONSOLE_LOG: "statsig::log_line";
14
15
  };
15
16
  export type AutoCaptureEventName = (typeof AutoCaptureEventName)[keyof typeof AutoCaptureEventName] & string;
16
17
  export type AutoCaptureEvent = StatsigEvent & {
@@ -13,4 +13,6 @@ exports.AutoCaptureEventName = {
13
13
  WEB_VITALS: 'auto_capture::web_vitals',
14
14
  DEAD_CLICK: 'auto_capture::dead_click',
15
15
  COPY: 'auto_capture::copy',
16
+ // log line is a special event name used to populate logs metrics explorer
17
+ CONSOLE_LOG: 'statsig::log_line',
16
18
  };
@@ -0,0 +1,14 @@
1
+ import { AutoCaptureEvent } from './AutoCaptureEvent';
2
+ import { ConsoleLogLevel } from './ConsoleLogManager';
3
+ export type AutoCaptureOptions = {
4
+ eventFilterFunc?: (event: AutoCaptureEvent) => boolean;
5
+ consoleLogAutoCaptureSettings?: ConsoleLogAutoCaptureSettings;
6
+ };
7
+ export type ConsoleLogAutoCaptureSettings = {
8
+ enabled: boolean;
9
+ logLevel?: ConsoleLogLevel;
10
+ service?: string;
11
+ version?: string;
12
+ sampleRate?: number;
13
+ resourceMetadata?: Record<string, string | number | boolean>;
14
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import { AutoCaptureEventName } from './AutoCaptureEvent';
2
+ import { ConsoleLogAutoCaptureSettings } from './AutoCaptureOptions';
3
+ export type ConsoleLogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug' | 'trace';
4
+ export declare class ConsoleLogManager {
5
+ private _enqueueFn;
6
+ private _options;
7
+ private _restoreFns;
8
+ private _isTracking;
9
+ private _logLevel;
10
+ private readonly __source;
11
+ constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void, _options: ConsoleLogAutoCaptureSettings);
12
+ startTracking(): void;
13
+ stopTracking(): void;
14
+ private _patchConsole;
15
+ private _enqueueConsoleLog;
16
+ private _shouldLog;
17
+ private _safeStringify;
18
+ private _getStackTrace;
19
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConsoleLogManager = void 0;
4
+ /* eslint-disable no-console */
5
+ const client_core_1 = require("@statsig/client-core");
6
+ const AutoCaptureEvent_1 = require("./AutoCaptureEvent");
7
+ const commonUtils_1 = require("./utils/commonUtils");
8
+ const metadataUtils_1 = require("./utils/metadataUtils");
9
+ const ConsoleLogPriority = {
10
+ trace: 10,
11
+ debug: 20,
12
+ log: 30, // log & info are the same priority
13
+ info: 30,
14
+ warn: 40,
15
+ error: 50,
16
+ };
17
+ class ConsoleLogManager {
18
+ constructor(_enqueueFn, _options) {
19
+ var _a;
20
+ this._enqueueFn = _enqueueFn;
21
+ this._options = _options;
22
+ this._restoreFns = [];
23
+ this._isTracking = false;
24
+ this._logLevel = 'info';
25
+ this.__source = 'js-auto-capture';
26
+ this._logLevel = (_a = this._options.logLevel) !== null && _a !== void 0 ? _a : 'info';
27
+ }
28
+ 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();
36
+ }
37
+ stopTracking() {
38
+ if (!this._isTracking)
39
+ return;
40
+ this._restoreFns.forEach((restore) => restore());
41
+ this._restoreFns = [];
42
+ this._isTracking = false;
43
+ }
44
+ _patchConsole() {
45
+ Object.entries(ConsoleLogPriority).forEach(([level, priority]) => {
46
+ if (priority < ConsoleLogPriority[this._logLevel])
47
+ return;
48
+ if (!console[level])
49
+ return;
50
+ const original = console[level].bind(console);
51
+ let inStack = false;
52
+ const restore = (0, commonUtils_1.patch)(console, level, (originalFn) => {
53
+ return (...args) => {
54
+ originalFn(...args);
55
+ if (inStack)
56
+ return;
57
+ inStack = true;
58
+ try {
59
+ const payload = args.map((a) => this._safeStringify(a));
60
+ const trace = this._getStackTrace();
61
+ this._enqueueConsoleLog(level, payload, trace);
62
+ }
63
+ catch (err) {
64
+ original('console observer error:', err, ...args);
65
+ }
66
+ finally {
67
+ inStack = false;
68
+ }
69
+ };
70
+ });
71
+ this._restoreFns.push(restore);
72
+ });
73
+ }
74
+ _enqueueConsoleLog(level, payload, trace) {
75
+ var _a, _b, _c;
76
+ if (!this._shouldLog())
77
+ return;
78
+ const metadata = Object.assign(Object.assign({ status: level === 'log' ? 'info' : level, log_level: level, payload,
79
+ trace, timestamp: Date.now(), service: (_a = this._options.service) !== null && _a !== void 0 ? _a : '', version: (_b = this._options.version) !== null && _b !== void 0 ? _b : '', source: this.__source }, ((_c = this._options.resourceMetadata) !== null && _c !== void 0 ? _c : {})), (0, metadataUtils_1._gatherAllMetadata)((0, commonUtils_1._getSafeUrl)()));
80
+ this._enqueueFn(AutoCaptureEvent_1.AutoCaptureEventName.CONSOLE_LOG, payload.join(' '), metadata);
81
+ }
82
+ _shouldLog() {
83
+ if (!this._options.sampleRate ||
84
+ typeof this._options.sampleRate !== 'number' ||
85
+ this._options.sampleRate <= 0 ||
86
+ this._options.sampleRate >= 1) {
87
+ return true;
88
+ }
89
+ return Math.random() < this._options.sampleRate;
90
+ }
91
+ _safeStringify(val) {
92
+ try {
93
+ if (typeof val === 'string')
94
+ return val;
95
+ if (typeof val === 'object' && val !== null)
96
+ return JSON.stringify(val);
97
+ return String(val);
98
+ }
99
+ catch (_a) {
100
+ return '[Unserializable]';
101
+ }
102
+ }
103
+ _getStackTrace() {
104
+ var _a, _b;
105
+ try {
106
+ return (_b = (_a = new Error().stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(2)) !== null && _b !== void 0 ? _b : [];
107
+ }
108
+ catch (_c) {
109
+ return [];
110
+ }
111
+ }
112
+ }
113
+ exports.ConsoleLogManager = ConsoleLogManager;
@@ -18,4 +18,5 @@ export declare function _getSafeTimezoneOffset(): number | null;
18
18
  export declare function _getAnchorNodeInHierarchy(node: Element | null): Element | null;
19
19
  export declare function _sanitizeString(maybeString: string | null | undefined): string | null;
20
20
  export declare function throttle<T extends (...args: unknown[]) => void>(fn: T, limit: number): T;
21
+ export declare function patch(source: Record<string, unknown>, name: string, replacement: (original: (...args: unknown[]) => void) => (...args: unknown[]) => void): () => void;
21
22
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.throttle = exports._sanitizeString = exports._getAnchorNodeInHierarchy = exports._getSafeTimezoneOffset = exports._getSafeTimezone = exports._getSafeNetworkInformation = exports._registerEventHandler = exports._getSanitizedPageUrl = exports._getSafeUrlString = exports._getSafeUrl = exports._shouldLogEvent = exports._getTargetNode = exports._stripEmptyValues = exports.interactiveElements = void 0;
3
+ exports.patch = exports.throttle = exports._sanitizeString = exports._getAnchorNodeInHierarchy = exports._getSafeTimezoneOffset = exports._getSafeTimezone = exports._getSafeNetworkInformation = exports._registerEventHandler = exports._getSanitizedPageUrl = exports._getSafeUrlString = exports._getSafeUrl = exports._shouldLogEvent = exports._getTargetNode = exports._stripEmptyValues = exports.interactiveElements = void 0;
4
4
  const client_core_1 = require("@statsig/client-core");
5
5
  const coreCCPattern = `(4[0-9]{12}(?:[0-9]{3})?)|(5[1-5][0-9]{14})|(6(?:011|5[0-9]{2})[0-9]{12})|(3[47][0-9]{13})|(3(?:0[0-5]|[68][0-9])[0-9]{11})|((?:2131|1800|35[0-9]{3})[0-9]{11})`;
6
6
  const CC_REGEX = new RegExp(`^(?:${coreCCPattern})$`);
@@ -186,3 +186,38 @@ function throttle(fn, limit) {
186
186
  };
187
187
  }
188
188
  exports.throttle = throttle;
189
+ // copy from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts
190
+ function patch(source, name, replacement) {
191
+ try {
192
+ if (!source[name])
193
+ return () => {
194
+ // noop
195
+ };
196
+ const original = source[name];
197
+ const wrapped = replacement(original);
198
+ // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
199
+ // otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
200
+ if (typeof wrapped === 'function') {
201
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
202
+ wrapped.prototype = wrapped.prototype || {};
203
+ Object.defineProperties(wrapped, {
204
+ __statsig_original__: {
205
+ enumerable: false,
206
+ value: original,
207
+ },
208
+ });
209
+ }
210
+ source[name] = wrapped;
211
+ return () => {
212
+ source[name] = original;
213
+ };
214
+ }
215
+ catch (err) {
216
+ return () => {
217
+ // noop
218
+ };
219
+ // This can throw if multiple fill happens on a global object like XMLHttpRequest
220
+ // Fixes https://github.com/getsentry/sentry-javascript/issues/2043
221
+ }
222
+ }
223
+ exports.patch = patch;