@statsig/web-analytics 3.20.3 → 3.20.4

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.3",
3
+ "version": "3.20.4",
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.3",
13
- "@statsig/js-client": "3.20.3",
12
+ "@statsig/client-core": "3.20.4",
13
+ "@statsig/js-client": "3.20.4",
14
14
  "web-vitals": "5.0.3"
15
15
  },
16
16
  "jsdelivr": "./build/statsig-web-analytics.min.js",
@@ -13,6 +13,8 @@ const metadataUtils_1 = require("./utils/metadataUtils");
13
13
  const AUTO_EVENT_MAPPING = {
14
14
  submit: AutoCaptureEvent_1.AutoCaptureEventName.FORM_SUBMIT,
15
15
  click: AutoCaptureEvent_1.AutoCaptureEventName.CLICK,
16
+ copy: AutoCaptureEvent_1.AutoCaptureEventName.COPY,
17
+ cut: AutoCaptureEvent_1.AutoCaptureEventName.COPY,
16
18
  };
17
19
  class StatsigAutoCapturePlugin {
18
20
  constructor(_options) {
@@ -95,6 +97,8 @@ class AutoCapture {
95
97
  };
96
98
  (0, commonUtils_1._registerEventHandler)(doc, 'click', (e) => eventHandler(e));
97
99
  (0, commonUtils_1._registerEventHandler)(doc, 'submit', (e) => eventHandler(e));
100
+ (0, commonUtils_1._registerEventHandler)(doc, 'copy', (e) => eventHandler(e));
101
+ (0, commonUtils_1._registerEventHandler)(doc, 'cut', (e) => eventHandler(e));
98
102
  (0, commonUtils_1._registerEventHandler)(win, 'error', (e) => eventHandler(e, false));
99
103
  (0, commonUtils_1._registerEventHandler)(win, 'pagehide', () => this._tryLogPageViewEnd());
100
104
  (0, commonUtils_1._registerEventHandler)(win, 'beforeunload', () => this._tryLogPageViewEnd());
@@ -115,7 +119,7 @@ class AutoCapture {
115
119
  this._tryLogPageView();
116
120
  }
117
121
  _autoLogEvent(event) {
118
- var _a;
122
+ var _a, _b, _c;
119
123
  const eventType = (_a = event.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
120
124
  if (eventType === 'error' && event instanceof ErrorEvent) {
121
125
  this._logError(event);
@@ -125,14 +129,26 @@ class AutoCapture {
125
129
  if (!target) {
126
130
  return;
127
131
  }
128
- if (!(0, commonUtils_1._shouldLogEvent)(event, target)) {
129
- return;
130
- }
131
132
  const eventName = AUTO_EVENT_MAPPING[eventType];
132
133
  if (!eventName) {
133
134
  return;
134
135
  }
135
- const { value, metadata } = (0, eventUtils_1._gatherEventData)(target);
136
+ const isCopyEvent = eventName === AutoCaptureEvent_1.AutoCaptureEventName.COPY;
137
+ if (!(0, commonUtils_1._shouldLogEvent)(event, target, isCopyEvent)) {
138
+ return;
139
+ }
140
+ const metadata = {};
141
+ if (isCopyEvent) {
142
+ const selectedText = (_c = (_b = (0, client_core_1._getWindowSafe)()) === null || _b === void 0 ? void 0 : _b.getSelection()) === null || _c === void 0 ? void 0 : _c.toString();
143
+ if (!selectedText) {
144
+ return;
145
+ }
146
+ metadata['selectedText'] = (0, commonUtils_1._sanitizeString)(selectedText);
147
+ const clipType = event.type || 'clipboard';
148
+ metadata['clipType'] = clipType;
149
+ }
150
+ const { value, metadata: eventMetadata } = (0, eventUtils_1._gatherEventData)(target);
151
+ Object.assign(metadata, eventMetadata);
136
152
  const allMetadata = (0, metadataUtils_1._gatherAllMetadata)((0, commonUtils_1._getSafeUrl)());
137
153
  this._enqueueAutoCapture(eventName, value, Object.assign(Object.assign({}, allMetadata), metadata));
138
154
  }
@@ -10,6 +10,7 @@ export declare const AutoCaptureEventName: {
10
10
  readonly RAGE_CLICK: "auto_capture::rage_click";
11
11
  readonly WEB_VITALS: "auto_capture::web_vitals";
12
12
  readonly DEAD_CLICK: "auto_capture::dead_click";
13
+ readonly COPY: "auto_capture::copy";
13
14
  };
14
15
  export type AutoCaptureEventName = (typeof AutoCaptureEventName)[keyof typeof AutoCaptureEventName] & string;
15
16
  export type AutoCaptureEvent = StatsigEvent & {
@@ -12,4 +12,5 @@ exports.AutoCaptureEventName = {
12
12
  RAGE_CLICK: 'auto_capture::rage_click',
13
13
  WEB_VITALS: 'auto_capture::web_vitals',
14
14
  DEAD_CLICK: 'auto_capture::dead_click',
15
+ COPY: 'auto_capture::copy',
15
16
  };
@@ -7,7 +7,7 @@ interface NetworkInformation {
7
7
  export declare const interactiveElements: string[];
8
8
  export declare function _stripEmptyValues<T extends Record<string, string | number | null | undefined>>(obj: T): Partial<Record<keyof T, string | number>>;
9
9
  export declare function _getTargetNode(e: Event): Element | null;
10
- export declare function _shouldLogEvent(e: Event, el: Element): boolean;
10
+ export declare function _shouldLogEvent(e: Event, el: Element, isCopyEvent?: boolean): boolean;
11
11
  export declare function _getSafeUrl(): URL;
12
12
  export declare function _getSafeUrlString(): string;
13
13
  export declare function _getSanitizedPageUrl(): string;
@@ -16,5 +16,6 @@ export declare function _getSafeNetworkInformation(): NetworkInformation | null;
16
16
  export declare function _getSafeTimezone(): string | null;
17
17
  export declare function _getSafeTimezoneOffset(): number | null;
18
18
  export declare function _getAnchorNodeInHierarchy(node: Element | null): Element | null;
19
+ export declare function _sanitizeString(maybeString: string | null | undefined): string | null;
19
20
  export declare function throttle<T extends (...args: unknown[]) => void>(fn: T, limit: number): T;
20
21
  export {};
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.throttle = 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.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
+ 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
+ const CC_REGEX = new RegExp(`^(?:${coreCCPattern})$`);
7
+ const coreSSNPattern = `\\d{3}-?\\d{2}-?\\d{4}`;
8
+ const SSN_REGEX = new RegExp(`^(${coreSSNPattern})$`);
5
9
  exports.interactiveElements = [
6
10
  'button',
7
11
  'a',
@@ -30,7 +34,7 @@ function _getTargetNode(e) {
30
34
  return target;
31
35
  }
32
36
  exports._getTargetNode = _getTargetNode;
33
- function _shouldLogEvent(e, el) {
37
+ function _shouldLogEvent(e, el, isCopyEvent = false) {
34
38
  if (!e || !el || el.nodeType !== 1) {
35
39
  return false;
36
40
  }
@@ -40,15 +44,19 @@ function _shouldLogEvent(e, el) {
40
44
  if (classList.contains('statsig-no-capture')) {
41
45
  return false;
42
46
  }
47
+ if (isCopyEvent) {
48
+ // We don't want to force strict event filtering for copy events
49
+ return true;
50
+ }
43
51
  switch (tagName) {
44
52
  case 'html':
45
53
  return false;
46
54
  case 'form':
47
- return eventType === 'submit';
55
+ return ['submit'].indexOf(eventType) >= 0;
48
56
  case 'input':
49
57
  case 'select':
50
58
  case 'textarea':
51
- return ['change'].includes(eventType);
59
+ return ['change', 'click'].indexOf(eventType) >= 0;
52
60
  default:
53
61
  if (eventType === 'click') {
54
62
  if (tagName === 'button') {
@@ -143,6 +151,30 @@ function _getAnchorNodeInHierarchy(node) {
143
151
  return null;
144
152
  }
145
153
  exports._getAnchorNodeInHierarchy = _getAnchorNodeInHierarchy;
154
+ function _sanitizeString(maybeString) {
155
+ if (!maybeString) {
156
+ return null;
157
+ }
158
+ return maybeString
159
+ .replace(/<[^>]*>/g, '')
160
+ .trim()
161
+ .split(/(\s+)/)
162
+ .filter((s) => _shouldCaptureTextValue(s))
163
+ .join('')
164
+ .replace(/[\r\n]/g, ' ')
165
+ .replace(/[ ]+/g, ' ')
166
+ .substring(0, 255);
167
+ }
168
+ exports._sanitizeString = _sanitizeString;
169
+ function _shouldCaptureTextValue(text) {
170
+ if (CC_REGEX.test((text || '').replace(/[- ]/g, ''))) {
171
+ return false;
172
+ }
173
+ if (SSN_REGEX.test((text || '').replace(/[- ]/g, ''))) {
174
+ return false;
175
+ }
176
+ return true;
177
+ }
146
178
  function throttle(fn, limit) {
147
179
  let lastCall = 0;
148
180
  return function (...args) {
@@ -2,4 +2,5 @@ export declare function _gatherEventData(target: Element): {
2
2
  value: string;
3
3
  metadata: Record<string, unknown>;
4
4
  };
5
+ export declare function _gatherCopyEventData(e: Event): Record<string, unknown>;
5
6
  export declare function _getMetadataFromElement(target: Element): Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._getMetadataFromElement = exports._gatherEventData = void 0;
3
+ exports._getMetadataFromElement = exports._gatherCopyEventData = exports._gatherEventData = void 0;
4
4
  const client_core_1 = require("@statsig/client-core");
5
5
  const commonUtils_1 = require("./commonUtils");
6
6
  const MAX_ATTRIBUTE_LENGTH = 1000;
@@ -30,6 +30,16 @@ function _gatherEventData(target) {
30
30
  return { value, metadata };
31
31
  }
32
32
  exports._gatherEventData = _gatherEventData;
33
+ function _gatherCopyEventData(e) {
34
+ var _a, _b;
35
+ const selectedText = (_b = (_a = (0, client_core_1._getWindowSafe)()) === null || _a === void 0 ? void 0 : _a.getSelection()) === null || _b === void 0 ? void 0 : _b.toString();
36
+ const metadata = {};
37
+ metadata['selectedText'] = (0, commonUtils_1._sanitizeString)(selectedText);
38
+ const clipType = e.type || 'clipboard';
39
+ metadata['clipType'] = clipType;
40
+ return metadata;
41
+ }
42
+ exports._gatherCopyEventData = _gatherCopyEventData;
33
43
  function _getFormMetadata(target) {
34
44
  var _a;
35
45
  const metadata = {};