@statsig/web-analytics 3.20.2 → 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 +3 -3
- package/src/AutoCapture.d.ts +1 -0
- package/src/AutoCapture.js +24 -5
- package/src/AutoCaptureEvent.d.ts +2 -0
- package/src/AutoCaptureEvent.js +2 -0
- package/src/DeadClickManager.d.ts +34 -0
- package/src/DeadClickManager.js +162 -0
- package/src/utils/commonUtils.d.ts +4 -1
- package/src/utils/commonUtils.js +57 -4
- package/src/utils/eventUtils.d.ts +1 -0
- package/src/utils/eventUtils.js +11 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statsig/web-analytics",
|
|
3
|
-
"version": "3.20.
|
|
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.
|
|
13
|
-
"@statsig/js-client": "3.20.
|
|
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",
|
package/src/AutoCapture.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class AutoCapture {
|
|
|
21
21
|
private _rageClickManager;
|
|
22
22
|
private _pageViewLogged;
|
|
23
23
|
private _webVitalsManager;
|
|
24
|
+
private _deadClickManager;
|
|
24
25
|
constructor(_client: PrecomputedEvaluationsInterface, options?: AutoCaptureOptions);
|
|
25
26
|
private _addEventHandlers;
|
|
26
27
|
private _addPageViewTracking;
|
package/src/AutoCapture.js
CHANGED
|
@@ -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 DeadClickManager_1 = require("./DeadClickManager");
|
|
6
7
|
const EngagementManager_1 = require("./EngagementManager");
|
|
7
8
|
const RageClickManager_1 = require("./RageClickManager");
|
|
8
9
|
const WebVitalsManager_1 = require("./WebVitalsManager");
|
|
@@ -12,6 +13,8 @@ const metadataUtils_1 = require("./utils/metadataUtils");
|
|
|
12
13
|
const AUTO_EVENT_MAPPING = {
|
|
13
14
|
submit: AutoCaptureEvent_1.AutoCaptureEventName.FORM_SUBMIT,
|
|
14
15
|
click: AutoCaptureEvent_1.AutoCaptureEventName.CLICK,
|
|
16
|
+
copy: AutoCaptureEvent_1.AutoCaptureEventName.COPY,
|
|
17
|
+
cut: AutoCaptureEvent_1.AutoCaptureEventName.COPY,
|
|
15
18
|
};
|
|
16
19
|
class StatsigAutoCapturePlugin {
|
|
17
20
|
constructor(_options) {
|
|
@@ -57,6 +60,7 @@ class AutoCapture {
|
|
|
57
60
|
this._engagementManager = new EngagementManager_1.EngagementManager();
|
|
58
61
|
this._rageClickManager = new RageClickManager_1.default();
|
|
59
62
|
this._webVitalsManager = new WebVitalsManager_1.WebVitalsManager(this._enqueueAutoCapture.bind(this));
|
|
63
|
+
this._deadClickManager = new DeadClickManager_1.default(this._enqueueAutoCapture.bind(this));
|
|
60
64
|
this._eventFilterFunc = options === null || options === void 0 ? void 0 : options.eventFilterFunc;
|
|
61
65
|
const doc = (0, client_core_1._getDocumentSafe)();
|
|
62
66
|
if (!(0, client_core_1._isServerEnv)()) {
|
|
@@ -93,6 +97,8 @@ class AutoCapture {
|
|
|
93
97
|
};
|
|
94
98
|
(0, commonUtils_1._registerEventHandler)(doc, 'click', (e) => eventHandler(e));
|
|
95
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));
|
|
96
102
|
(0, commonUtils_1._registerEventHandler)(win, 'error', (e) => eventHandler(e, false));
|
|
97
103
|
(0, commonUtils_1._registerEventHandler)(win, 'pagehide', () => this._tryLogPageViewEnd());
|
|
98
104
|
(0, commonUtils_1._registerEventHandler)(win, 'beforeunload', () => this._tryLogPageViewEnd());
|
|
@@ -113,7 +119,7 @@ class AutoCapture {
|
|
|
113
119
|
this._tryLogPageView();
|
|
114
120
|
}
|
|
115
121
|
_autoLogEvent(event) {
|
|
116
|
-
var _a;
|
|
122
|
+
var _a, _b, _c;
|
|
117
123
|
const eventType = (_a = event.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
118
124
|
if (eventType === 'error' && event instanceof ErrorEvent) {
|
|
119
125
|
this._logError(event);
|
|
@@ -123,19 +129,32 @@ class AutoCapture {
|
|
|
123
129
|
if (!target) {
|
|
124
130
|
return;
|
|
125
131
|
}
|
|
126
|
-
if (!(0, commonUtils_1._shouldLogEvent)(event, target)) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
132
|
const eventName = AUTO_EVENT_MAPPING[eventType];
|
|
130
133
|
if (!eventName) {
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
133
|
-
const
|
|
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);
|
|
134
152
|
const allMetadata = (0, metadataUtils_1._gatherAllMetadata)((0, commonUtils_1._getSafeUrl)());
|
|
135
153
|
this._enqueueAutoCapture(eventName, value, Object.assign(Object.assign({}, allMetadata), metadata));
|
|
136
154
|
}
|
|
137
155
|
_initialize() {
|
|
138
156
|
this._webVitalsManager.startTracking();
|
|
157
|
+
this._deadClickManager.startTracking();
|
|
139
158
|
this._engagementManager.startInactivityTracking(() => this._tryLogPageViewEnd(true));
|
|
140
159
|
this._addEventHandlers();
|
|
141
160
|
this._addPageViewTracking();
|
|
@@ -9,6 +9,8 @@ export declare const AutoCaptureEventName: {
|
|
|
9
9
|
readonly CLICK: "auto_capture::click";
|
|
10
10
|
readonly RAGE_CLICK: "auto_capture::rage_click";
|
|
11
11
|
readonly WEB_VITALS: "auto_capture::web_vitals";
|
|
12
|
+
readonly DEAD_CLICK: "auto_capture::dead_click";
|
|
13
|
+
readonly COPY: "auto_capture::copy";
|
|
12
14
|
};
|
|
13
15
|
export type AutoCaptureEventName = (typeof AutoCaptureEventName)[keyof typeof AutoCaptureEventName] & string;
|
|
14
16
|
export type AutoCaptureEvent = StatsigEvent & {
|
package/src/AutoCaptureEvent.js
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AutoCaptureEventName } from './AutoCaptureEvent';
|
|
2
|
+
export declare const DeadClickConfig: {
|
|
3
|
+
CLICK_CHECK_TIMEOUT: number;
|
|
4
|
+
SCROLL_DELAY_MS: number;
|
|
5
|
+
SELECTION_CHANGE_DELAY_MS: number;
|
|
6
|
+
MUTATION_DELAY_MS: number;
|
|
7
|
+
ABSOLUTE_DEAD_CLICK_TIMEOUT: number;
|
|
8
|
+
};
|
|
9
|
+
export interface PossibleDeadClick {
|
|
10
|
+
timestamp: number;
|
|
11
|
+
eventTarget: HTMLElement;
|
|
12
|
+
scrollDelayMs?: number;
|
|
13
|
+
selectionChangeDelayMs?: number;
|
|
14
|
+
mutationDelayMs?: number;
|
|
15
|
+
absoluteDelayMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export default class DeadClickManager {
|
|
18
|
+
private _enqueueFn;
|
|
19
|
+
private _lastMutationTime;
|
|
20
|
+
private _lastSelectionChangeTime;
|
|
21
|
+
private _clickCheckTimer;
|
|
22
|
+
private _observer;
|
|
23
|
+
private _clicks;
|
|
24
|
+
private _deadClickConfig;
|
|
25
|
+
constructor(_enqueueFn: (eventName: AutoCaptureEventName, value: string, metadata: Record<string, unknown>) => void);
|
|
26
|
+
startTracking(): void;
|
|
27
|
+
private _handleClick;
|
|
28
|
+
private _handleScroll;
|
|
29
|
+
private _handleSelectionChange;
|
|
30
|
+
private _setupMutationObserver;
|
|
31
|
+
private _checkForDeadClick;
|
|
32
|
+
private _logDeadClick;
|
|
33
|
+
private _updateClickDelayMs;
|
|
34
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DeadClickConfig = void 0;
|
|
4
|
+
const client_core_1 = require("@statsig/client-core");
|
|
5
|
+
const AutoCaptureEvent_1 = require("./AutoCaptureEvent");
|
|
6
|
+
const commonUtils_1 = require("./utils/commonUtils");
|
|
7
|
+
const eventUtils_1 = require("./utils/eventUtils");
|
|
8
|
+
exports.DeadClickConfig = {
|
|
9
|
+
CLICK_CHECK_TIMEOUT: 1000,
|
|
10
|
+
SCROLL_DELAY_MS: 100,
|
|
11
|
+
SELECTION_CHANGE_DELAY_MS: 100,
|
|
12
|
+
MUTATION_DELAY_MS: 2500,
|
|
13
|
+
ABSOLUTE_DEAD_CLICK_TIMEOUT: 2750,
|
|
14
|
+
};
|
|
15
|
+
// A dead click is a click that fires an event but produces no meaningful change within a set timeframe.
|
|
16
|
+
class DeadClickManager {
|
|
17
|
+
constructor(_enqueueFn) {
|
|
18
|
+
this._enqueueFn = _enqueueFn;
|
|
19
|
+
this._lastMutationTime = 0;
|
|
20
|
+
this._lastSelectionChangeTime = 0;
|
|
21
|
+
this._clicks = [];
|
|
22
|
+
this._deadClickConfig = exports.DeadClickConfig;
|
|
23
|
+
this._handleScroll = (0, commonUtils_1.throttle)(() => {
|
|
24
|
+
const scrollTime = Date.now();
|
|
25
|
+
this._clicks.forEach((click) => {
|
|
26
|
+
if (!click.scrollDelayMs) {
|
|
27
|
+
click.scrollDelayMs = scrollTime - click.timestamp;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}, 50);
|
|
31
|
+
}
|
|
32
|
+
startTracking() {
|
|
33
|
+
const win = (0, client_core_1._getWindowSafe)();
|
|
34
|
+
if (!win) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// `capture: true` - Needed to listen to scroll events on all scrollable elements, not just the window.
|
|
38
|
+
// docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
|
|
39
|
+
//
|
|
40
|
+
// `passive: true` - Indicates the scroll handler won’t call preventDefault(),
|
|
41
|
+
// allowing the browser to optimize scrolling performance by not blocking it.
|
|
42
|
+
// docs: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive
|
|
43
|
+
win.addEventListener('click', (event) => this._handleClick(event), {
|
|
44
|
+
capture: true,
|
|
45
|
+
});
|
|
46
|
+
win.addEventListener('scroll', () => this._handleScroll(), {
|
|
47
|
+
capture: true,
|
|
48
|
+
passive: true,
|
|
49
|
+
});
|
|
50
|
+
win.addEventListener('selectionchange', () => this._handleSelectionChange());
|
|
51
|
+
this._setupMutationObserver();
|
|
52
|
+
}
|
|
53
|
+
_handleClick(event) {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
const eventTarget = event.target;
|
|
56
|
+
if (!eventTarget) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const click = {
|
|
60
|
+
timestamp: Date.now(),
|
|
61
|
+
eventTarget,
|
|
62
|
+
};
|
|
63
|
+
if (!commonUtils_1.interactiveElements.includes((_a = eventTarget === null || eventTarget === void 0 ? void 0 : eventTarget.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase())) {
|
|
64
|
+
this._clicks.push(click);
|
|
65
|
+
}
|
|
66
|
+
if (this._clicks.length && !this._clickCheckTimer) {
|
|
67
|
+
this._clickCheckTimer = (_b = (0, client_core_1._getWindowSafe)()) === null || _b === void 0 ? void 0 : _b.setTimeout(() => {
|
|
68
|
+
this._checkForDeadClick();
|
|
69
|
+
}, this._deadClickConfig.CLICK_CHECK_TIMEOUT);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
_handleSelectionChange() {
|
|
73
|
+
this._lastSelectionChangeTime = Date.now();
|
|
74
|
+
}
|
|
75
|
+
_setupMutationObserver() {
|
|
76
|
+
const doc = (0, client_core_1._getDocumentSafe)();
|
|
77
|
+
if (!doc) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
this._observer = new MutationObserver(() => {
|
|
81
|
+
this._lastMutationTime = Date.now();
|
|
82
|
+
});
|
|
83
|
+
this._observer.observe(doc.body, {
|
|
84
|
+
childList: true,
|
|
85
|
+
subtree: true,
|
|
86
|
+
attributes: true,
|
|
87
|
+
characterData: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
_checkForDeadClick() {
|
|
91
|
+
var _a;
|
|
92
|
+
if (!this._clicks.length) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
clearTimeout(this._clickCheckTimer);
|
|
96
|
+
this._clickCheckTimer = undefined;
|
|
97
|
+
const clicksToCheck = this._clicks;
|
|
98
|
+
this._clicks = [];
|
|
99
|
+
for (const click of clicksToCheck) {
|
|
100
|
+
this._updateClickDelayMs(click);
|
|
101
|
+
const hadScroll = click.scrollDelayMs != null &&
|
|
102
|
+
click.scrollDelayMs < this._deadClickConfig.SCROLL_DELAY_MS;
|
|
103
|
+
const hadSelectionChange = click.selectionChangeDelayMs != null &&
|
|
104
|
+
click.selectionChangeDelayMs <
|
|
105
|
+
this._deadClickConfig.SELECTION_CHANGE_DELAY_MS;
|
|
106
|
+
const hadMutation = click.mutationDelayMs != null &&
|
|
107
|
+
click.mutationDelayMs < this._deadClickConfig.MUTATION_DELAY_MS;
|
|
108
|
+
if (hadScroll || hadSelectionChange || hadMutation) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const scrollTimeout = click.scrollDelayMs != null &&
|
|
112
|
+
click.scrollDelayMs > this._deadClickConfig.SCROLL_DELAY_MS;
|
|
113
|
+
const selectionChangeTimeout = click.selectionChangeDelayMs != null &&
|
|
114
|
+
click.selectionChangeDelayMs >
|
|
115
|
+
this._deadClickConfig.SELECTION_CHANGE_DELAY_MS;
|
|
116
|
+
const mutationTimeout = click.mutationDelayMs != null &&
|
|
117
|
+
click.mutationDelayMs > this._deadClickConfig.MUTATION_DELAY_MS;
|
|
118
|
+
const absoluteTimeout = click.absoluteDelayMs != null &&
|
|
119
|
+
click.absoluteDelayMs >
|
|
120
|
+
this._deadClickConfig.ABSOLUTE_DEAD_CLICK_TIMEOUT;
|
|
121
|
+
if (scrollTimeout ||
|
|
122
|
+
selectionChangeTimeout ||
|
|
123
|
+
mutationTimeout ||
|
|
124
|
+
absoluteTimeout) {
|
|
125
|
+
this._logDeadClick(click, {
|
|
126
|
+
scrollTimeout,
|
|
127
|
+
selectionChangeTimeout,
|
|
128
|
+
mutationTimeout,
|
|
129
|
+
absoluteTimeout,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (click.absoluteDelayMs != null &&
|
|
133
|
+
click.absoluteDelayMs <
|
|
134
|
+
this._deadClickConfig.ABSOLUTE_DEAD_CLICK_TIMEOUT) {
|
|
135
|
+
this._clicks.push(click);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (this._clicks.length && !this._clickCheckTimer) {
|
|
139
|
+
this._clickCheckTimer = (_a = (0, client_core_1._getWindowSafe)()) === null || _a === void 0 ? void 0 : _a.setTimeout(() => {
|
|
140
|
+
this._checkForDeadClick();
|
|
141
|
+
}, this._deadClickConfig.CLICK_CHECK_TIMEOUT);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
_logDeadClick(click, extraMetadata) {
|
|
145
|
+
const { value, metadata } = (0, eventUtils_1._gatherEventData)(click.eventTarget);
|
|
146
|
+
this._enqueueFn(AutoCaptureEvent_1.AutoCaptureEventName.DEAD_CLICK, value, Object.assign(Object.assign(Object.assign({}, metadata), extraMetadata), { scrollDelayMs: click.scrollDelayMs, selectionChangeDelayMs: click.selectionChangeDelayMs, mutationDelayMs: click.mutationDelayMs, absoluteDelayMs: click.absoluteDelayMs }));
|
|
147
|
+
}
|
|
148
|
+
_updateClickDelayMs(click) {
|
|
149
|
+
if (!click.mutationDelayMs &&
|
|
150
|
+
this._lastMutationTime > 0 &&
|
|
151
|
+
click.timestamp <= this._lastMutationTime) {
|
|
152
|
+
click.mutationDelayMs = Date.now() - this._lastMutationTime;
|
|
153
|
+
}
|
|
154
|
+
if (!click.selectionChangeDelayMs &&
|
|
155
|
+
this._lastSelectionChangeTime > 0 &&
|
|
156
|
+
click.timestamp <= this._lastSelectionChangeTime) {
|
|
157
|
+
click.selectionChangeDelayMs = Date.now() - this._lastSelectionChangeTime;
|
|
158
|
+
}
|
|
159
|
+
click.absoluteDelayMs = Date.now() - click.timestamp;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.default = DeadClickManager;
|
|
@@ -4,9 +4,10 @@ interface NetworkInformation {
|
|
|
4
4
|
rtt: number;
|
|
5
5
|
saveData: boolean;
|
|
6
6
|
}
|
|
7
|
+
export declare const interactiveElements: string[];
|
|
7
8
|
export declare function _stripEmptyValues<T extends Record<string, string | number | null | undefined>>(obj: T): Partial<Record<keyof T, string | number>>;
|
|
8
9
|
export declare function _getTargetNode(e: Event): Element | null;
|
|
9
|
-
export declare function _shouldLogEvent(e: Event, el: Element): boolean;
|
|
10
|
+
export declare function _shouldLogEvent(e: Event, el: Element, isCopyEvent?: boolean): boolean;
|
|
10
11
|
export declare function _getSafeUrl(): URL;
|
|
11
12
|
export declare function _getSafeUrlString(): string;
|
|
12
13
|
export declare function _getSanitizedPageUrl(): string;
|
|
@@ -15,4 +16,6 @@ export declare function _getSafeNetworkInformation(): NetworkInformation | null;
|
|
|
15
16
|
export declare function _getSafeTimezone(): string | null;
|
|
16
17
|
export declare function _getSafeTimezoneOffset(): number | null;
|
|
17
18
|
export declare function _getAnchorNodeInHierarchy(node: Element | null): Element | null;
|
|
19
|
+
export declare function _sanitizeString(maybeString: string | null | undefined): string | null;
|
|
20
|
+
export declare function throttle<T extends (...args: unknown[]) => void>(fn: T, limit: number): T;
|
|
18
21
|
export {};
|
package/src/utils/commonUtils.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports._getAnchorNodeInHierarchy = exports._getSafeTimezoneOffset = exports._getSafeTimezone = exports._getSafeNetworkInformation = exports._registerEventHandler = exports._getSanitizedPageUrl = exports._getSafeUrlString = exports._getSafeUrl = exports._shouldLogEvent = exports._getTargetNode = exports._stripEmptyValues = 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})$`);
|
|
9
|
+
exports.interactiveElements = [
|
|
10
|
+
'button',
|
|
11
|
+
'a',
|
|
12
|
+
'input',
|
|
13
|
+
'select',
|
|
14
|
+
'textarea',
|
|
15
|
+
'form',
|
|
16
|
+
'select',
|
|
17
|
+
'label',
|
|
18
|
+
];
|
|
5
19
|
function _stripEmptyValues(obj) {
|
|
6
20
|
return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value != null && value !== '' && value !== undefined));
|
|
7
21
|
}
|
|
@@ -20,7 +34,7 @@ function _getTargetNode(e) {
|
|
|
20
34
|
return target;
|
|
21
35
|
}
|
|
22
36
|
exports._getTargetNode = _getTargetNode;
|
|
23
|
-
function _shouldLogEvent(e, el) {
|
|
37
|
+
function _shouldLogEvent(e, el, isCopyEvent = false) {
|
|
24
38
|
if (!e || !el || el.nodeType !== 1) {
|
|
25
39
|
return false;
|
|
26
40
|
}
|
|
@@ -30,15 +44,19 @@ function _shouldLogEvent(e, el) {
|
|
|
30
44
|
if (classList.contains('statsig-no-capture')) {
|
|
31
45
|
return false;
|
|
32
46
|
}
|
|
47
|
+
if (isCopyEvent) {
|
|
48
|
+
// We don't want to force strict event filtering for copy events
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
33
51
|
switch (tagName) {
|
|
34
52
|
case 'html':
|
|
35
53
|
return false;
|
|
36
54
|
case 'form':
|
|
37
|
-
return eventType
|
|
55
|
+
return ['submit'].indexOf(eventType) >= 0;
|
|
38
56
|
case 'input':
|
|
39
57
|
case 'select':
|
|
40
58
|
case 'textarea':
|
|
41
|
-
return ['change'].
|
|
59
|
+
return ['change', 'click'].indexOf(eventType) >= 0;
|
|
42
60
|
default:
|
|
43
61
|
if (eventType === 'click') {
|
|
44
62
|
if (tagName === 'button') {
|
|
@@ -133,3 +151,38 @@ function _getAnchorNodeInHierarchy(node) {
|
|
|
133
151
|
return null;
|
|
134
152
|
}
|
|
135
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
|
+
}
|
|
178
|
+
function throttle(fn, limit) {
|
|
179
|
+
let lastCall = 0;
|
|
180
|
+
return function (...args) {
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
if (now - lastCall >= limit) {
|
|
183
|
+
lastCall = now;
|
|
184
|
+
fn(...args);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
exports.throttle = throttle;
|
|
@@ -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>;
|
package/src/utils/eventUtils.js
CHANGED
|
@@ -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 = {};
|