@leanbase.com/js 0.1.2 → 0.2.0-alpha.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/dist/autocapture-utils.d.ts +17 -0
- package/dist/autocapture.d.ts +35 -0
- package/dist/config.d.ts +5 -0
- package/dist/constants.d.ts +54 -0
- package/dist/entrypoints/main.cjs.d.ts +4 -0
- package/dist/entrypoints/module.es.d.ts +4 -0
- package/dist/extensions/rageclick.d.ts +9 -0
- package/dist/iife.d.ts +19 -0
- package/dist/index.d.ts +2 -779
- package/dist/leanbase-logger.d.ts +6 -0
- package/dist/leanbase-persistence.d.ts +64 -0
- package/dist/leanbase.d.ts +49 -0
- package/dist/leanbase.iife.js +1 -4745
- package/dist/leanbase.iife.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -0
- package/dist/module.d.ts +780 -0
- package/dist/module.js +2 -0
- package/dist/module.js.map +1 -0
- package/dist/page-view.d.ts +29 -0
- package/dist/scroll-manager.d.ts +21 -0
- package/dist/session-props.d.ts +32 -0
- package/dist/sessionid.d.ts +50 -0
- package/dist/storage.d.ts +24 -0
- package/{src/types.ts → dist/types.d.ts} +145 -235
- package/dist/utils/blocked-uas.d.ts +17 -0
- package/dist/utils/element-utils.d.ts +5 -0
- package/dist/utils/event-utils.d.ts +22 -0
- package/dist/utils/index.d.ts +44 -0
- package/dist/utils/request-utils.d.ts +12 -0
- package/dist/utils/simple-event-emitter.d.ts +6 -0
- package/dist/utils/user-agent-utils.d.ts +18 -0
- package/dist/uuidv7.d.ts +43 -0
- package/dist/version.d.ts +1 -0
- package/lib/autocapture-utils.d.ts +17 -0
- package/{src/autocapture-utils.ts → lib/autocapture-utils.js} +196 -280
- package/lib/autocapture-utils.js.map +1 -0
- package/lib/autocapture.d.ts +35 -0
- package/lib/autocapture.js +311 -0
- package/lib/autocapture.js.map +1 -0
- package/lib/config.d.ts +5 -0
- package/lib/config.js +7 -0
- package/lib/config.js.map +1 -0
- package/lib/constants.d.ts +54 -0
- package/{src/constants.ts → lib/constants.js} +58 -55
- package/lib/constants.js.map +1 -0
- package/lib/entrypoints/main.cjs.d.ts +4 -0
- package/lib/entrypoints/main.cjs.js +3 -0
- package/lib/entrypoints/main.cjs.js.map +1 -0
- package/lib/entrypoints/module.es.d.ts +4 -0
- package/lib/entrypoints/module.es.js +3 -0
- package/lib/entrypoints/module.es.js.map +1 -0
- package/lib/extensions/rageclick.d.ts +9 -0
- package/lib/extensions/rageclick.js +27 -0
- package/lib/extensions/rageclick.js.map +1 -0
- package/lib/iife.d.ts +19 -0
- package/lib/iife.js +67 -0
- package/lib/iife.js.map +1 -0
- package/{src/index.ts → lib/index.d.ts} +2 -2
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/leanbase-logger.d.ts +6 -0
- package/lib/leanbase-logger.js +25 -0
- package/lib/leanbase-logger.js.map +1 -0
- package/lib/leanbase-persistence.d.ts +64 -0
- package/lib/leanbase-persistence.js +287 -0
- package/lib/leanbase-persistence.js.map +1 -0
- package/lib/leanbase.d.ts +49 -0
- package/lib/leanbase.js +294 -0
- package/lib/leanbase.js.map +1 -0
- package/lib/page-view.d.ts +29 -0
- package/lib/page-view.js +81 -0
- package/lib/page-view.js.map +1 -0
- package/lib/scroll-manager.d.ts +21 -0
- package/lib/scroll-manager.js +79 -0
- package/lib/scroll-manager.js.map +1 -0
- package/lib/session-props.d.ts +32 -0
- package/lib/session-props.js +73 -0
- package/lib/session-props.js.map +1 -0
- package/lib/sessionid.d.ts +50 -0
- package/{src/sessionid.ts → lib/sessionid.js} +128 -204
- package/lib/sessionid.js.map +1 -0
- package/lib/storage.d.ts +24 -0
- package/{src/storage.ts → lib/storage.js} +182 -225
- package/lib/storage.js.map +1 -0
- package/lib/types.d.ts +544 -0
- package/lib/types.js +7 -0
- package/lib/types.js.map +1 -0
- package/lib/utils/blocked-uas.d.ts +17 -0
- package/{src/utils/blocked-uas.ts → lib/utils/blocked-uas.js} +19 -48
- package/lib/utils/blocked-uas.js.map +1 -0
- package/lib/utils/element-utils.d.ts +5 -0
- package/{src/utils/element-utils.ts → lib/utils/element-utils.js} +13 -17
- package/lib/utils/element-utils.js.map +1 -0
- package/lib/utils/event-utils.d.ts +22 -0
- package/lib/utils/event-utils.js +258 -0
- package/lib/utils/event-utils.js.map +1 -0
- package/lib/utils/index.d.ts +44 -0
- package/lib/utils/index.js +183 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/request-utils.d.ts +12 -0
- package/lib/utils/request-utils.js +107 -0
- package/lib/utils/request-utils.js.map +1 -0
- package/lib/utils/simple-event-emitter.d.ts +6 -0
- package/lib/utils/simple-event-emitter.js +24 -0
- package/lib/utils/simple-event-emitter.js.map +1 -0
- package/lib/utils/user-agent-utils.d.ts +18 -0
- package/lib/utils/user-agent-utils.js +369 -0
- package/lib/utils/user-agent-utils.js.map +1 -0
- package/lib/uuidv7.d.ts +43 -0
- package/{src/uuidv7.ts → lib/uuidv7.js} +103 -131
- package/lib/uuidv7.js.map +1 -0
- package/lib/version.d.ts +1 -0
- package/lib/version.js +2 -0
- package/lib/version.js.map +1 -0
- package/package.json +23 -11
- package/dist/index.cjs +0 -3032
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs +0 -3030
- package/dist/index.mjs.map +0 -1
- package/src/autocapture.ts +0 -415
- package/src/config.ts +0 -8
- package/src/extensions/rageclick.ts +0 -34
- package/src/iife.ts +0 -87
- package/src/leanbase-logger.ts +0 -26
- package/src/leanbase-persistence.ts +0 -374
- package/src/leanbase.ts +0 -424
- package/src/page-view.ts +0 -124
- package/src/scroll-manager.ts +0 -103
- package/src/session-props.ts +0 -114
- package/src/utils/event-utils.ts +0 -304
- package/src/utils/index.ts +0 -222
- package/src/utils/request-utils.ts +0 -128
- package/src/utils/simple-event-emitter.ts +0 -27
- package/src/utils/user-agent-utils.ts +0 -357
- package/src/version.ts +0 -1
|
@@ -1,65 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export function splitClassString(s: string): string[] {
|
|
10
|
-
return s ? trim(s).split(/\s+/) : []
|
|
1
|
+
import { each, entries } from './utils';
|
|
2
|
+
import { isNullish, isString, isUndefined, isArray, isBoolean } from '@posthog/core';
|
|
3
|
+
import { isDocumentFragment, isElementNode, isTag, isTextNode } from './utils/element-utils';
|
|
4
|
+
import { includes, trim } from '@posthog/core';
|
|
5
|
+
import { logger } from './leanbase-logger';
|
|
6
|
+
export function splitClassString(s) {
|
|
7
|
+
return s ? trim(s).split(/\s+/) : [];
|
|
11
8
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return !!(url && urlsList && urlsList.some((regex) => url.match(regex)))
|
|
9
|
+
function checkForURLMatches(urlsList) {
|
|
10
|
+
const url = window?.location.href;
|
|
11
|
+
return !!(url && urlsList && urlsList.some((regex) => url.match(regex)));
|
|
16
12
|
}
|
|
17
|
-
|
|
18
13
|
/*
|
|
19
14
|
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
20
15
|
*
|
|
21
16
|
* Because this is a string it can contain unexpected characters
|
|
22
17
|
* So, this method safely splits the className and returns that array.
|
|
23
18
|
*/
|
|
24
|
-
export function getClassNames(el
|
|
25
|
-
let className = ''
|
|
19
|
+
export function getClassNames(el) {
|
|
20
|
+
let className = '';
|
|
26
21
|
switch (typeof el.className) {
|
|
27
22
|
case 'string':
|
|
28
|
-
className = el.className
|
|
29
|
-
break
|
|
23
|
+
className = el.className;
|
|
24
|
+
break;
|
|
30
25
|
// TODO: when is this ever used?
|
|
31
26
|
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
32
27
|
className =
|
|
33
|
-
(el.className && 'baseVal' in el.className ?
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
break
|
|
28
|
+
(el.className && 'baseVal' in el.className ? el.className.baseVal : null) ||
|
|
29
|
+
el.getAttribute('class') ||
|
|
30
|
+
'';
|
|
31
|
+
break;
|
|
37
32
|
default:
|
|
38
|
-
className = ''
|
|
33
|
+
className = '';
|
|
39
34
|
}
|
|
40
|
-
|
|
41
|
-
return splitClassString(className)
|
|
35
|
+
return splitClassString(className);
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
export function makeSafeText(s: string | null | undefined): string | null {
|
|
37
|
+
export function makeSafeText(s) {
|
|
45
38
|
if (isNullish(s)) {
|
|
46
|
-
return null
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// truncate
|
|
59
|
-
.substring(0, 255)
|
|
60
|
-
)
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return (trim(s)
|
|
42
|
+
// scrub potentially sensitive values
|
|
43
|
+
.split(/(\s+)/)
|
|
44
|
+
.filter((s) => shouldCaptureValue(s))
|
|
45
|
+
.join('')
|
|
46
|
+
// normalize whitespace
|
|
47
|
+
.replace(/[\r\n]/g, ' ')
|
|
48
|
+
.replace(/[ ]+/g, ' ')
|
|
49
|
+
// truncate
|
|
50
|
+
.substring(0, 255));
|
|
61
51
|
}
|
|
62
|
-
|
|
63
52
|
/*
|
|
64
53
|
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
65
54
|
* Concats textContent of each of the element's text node children; this avoids potential
|
|
@@ -69,147 +58,129 @@ export function makeSafeText(s: string | null | undefined): string | null {
|
|
|
69
58
|
* @param {Element} el - element to get the text of
|
|
70
59
|
* @returns {string} the element's direct text content
|
|
71
60
|
*/
|
|
72
|
-
export function getSafeText(el
|
|
73
|
-
let elText = ''
|
|
74
|
-
|
|
61
|
+
export function getSafeText(el) {
|
|
62
|
+
let elText = '';
|
|
75
63
|
if (shouldCaptureElement(el) && !isSensitiveElement(el) && el.childNodes && el.childNodes.length) {
|
|
76
64
|
each(el.childNodes, function (child) {
|
|
77
65
|
if (isTextNode(child) && child.textContent) {
|
|
78
|
-
elText += makeSafeText(child.textContent) ?? ''
|
|
66
|
+
elText += makeSafeText(child.textContent) ?? '';
|
|
79
67
|
}
|
|
80
|
-
})
|
|
68
|
+
});
|
|
81
69
|
}
|
|
82
|
-
|
|
83
|
-
return trim(elText)
|
|
70
|
+
return trim(elText);
|
|
84
71
|
}
|
|
85
|
-
|
|
86
|
-
export function getEventTarget(e: Event): Element | null {
|
|
72
|
+
export function getEventTarget(e) {
|
|
87
73
|
// https://developer.mozilla.org/en-US/docs/Web/API/Event/target#Compatibility_notes
|
|
88
74
|
if (isUndefined(e.target)) {
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
75
|
+
return e.srcElement || null;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (e.target?.shadowRoot) {
|
|
79
|
+
return e.composedPath()[0] || null;
|
|
93
80
|
}
|
|
94
|
-
return
|
|
81
|
+
return e.target || null;
|
|
95
82
|
}
|
|
96
83
|
}
|
|
97
|
-
|
|
98
|
-
export const autocaptureCompatibleElements = ['a', 'button', 'form', 'input', 'select', 'textarea', 'label']
|
|
99
|
-
|
|
84
|
+
export const autocaptureCompatibleElements = ['a', 'button', 'form', 'input', 'select', 'textarea', 'label'];
|
|
100
85
|
/*
|
|
101
86
|
if there is no config, then all elements are allowed
|
|
102
87
|
if there is a config, and there is an allow list, then only elements in the allow list are allowed
|
|
103
88
|
assumes that some other code is checking this element's parents
|
|
104
89
|
*/
|
|
105
|
-
function checkIfElementTreePassesElementAllowList(
|
|
106
|
-
|
|
107
|
-
autocaptureConfig: AutocaptureConfig | undefined
|
|
108
|
-
): boolean {
|
|
109
|
-
const allowlist = autocaptureConfig?.element_allowlist
|
|
90
|
+
function checkIfElementTreePassesElementAllowList(elements, autocaptureConfig) {
|
|
91
|
+
const allowlist = autocaptureConfig?.element_allowlist;
|
|
110
92
|
if (isUndefined(allowlist)) {
|
|
111
93
|
// everything is allowed, when there is no allow list
|
|
112
|
-
return true
|
|
94
|
+
return true;
|
|
113
95
|
}
|
|
114
|
-
|
|
115
96
|
// check each element in the tree
|
|
116
97
|
// if any of the elements are in the allow list, then the tree is allowed
|
|
117
98
|
for (const el of elements) {
|
|
118
99
|
if (allowlist.some((elementType) => el.tagName.toLowerCase() === elementType)) {
|
|
119
|
-
return true
|
|
100
|
+
return true;
|
|
120
101
|
}
|
|
121
102
|
}
|
|
122
|
-
|
|
123
103
|
// otherwise there is an allow list and this element tree didn't match it
|
|
124
|
-
return false
|
|
104
|
+
return false;
|
|
125
105
|
}
|
|
126
|
-
|
|
127
106
|
/*
|
|
128
107
|
if there is no selector list (i.e. it is undefined), then any elements matches
|
|
129
108
|
if there is an empty list, then no elements match
|
|
130
109
|
if there is a selector list, then check it against each element provided
|
|
131
110
|
*/
|
|
132
|
-
function checkIfElementsMatchCSSSelector(elements
|
|
111
|
+
function checkIfElementsMatchCSSSelector(elements, selectorList) {
|
|
133
112
|
if (isUndefined(selectorList)) {
|
|
134
113
|
// everything is allowed, when there is no selector list
|
|
135
|
-
return true
|
|
114
|
+
return true;
|
|
136
115
|
}
|
|
137
|
-
|
|
138
116
|
for (const el of elements) {
|
|
139
117
|
if (selectorList.some((selector) => el.matches(selector))) {
|
|
140
|
-
return true
|
|
118
|
+
return true;
|
|
141
119
|
}
|
|
142
120
|
}
|
|
143
|
-
|
|
144
|
-
return false
|
|
121
|
+
return false;
|
|
145
122
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return parentNode
|
|
123
|
+
export function getParentElement(curEl) {
|
|
124
|
+
const parentNode = curEl.parentNode;
|
|
125
|
+
if (!parentNode || !isElementNode(parentNode))
|
|
126
|
+
return false;
|
|
127
|
+
return parentNode;
|
|
151
128
|
}
|
|
152
|
-
|
|
153
129
|
// autocapture check will already filter for ph-no-capture,
|
|
154
130
|
// but we include it here to protect against future changes accidentally removing that check
|
|
155
|
-
const DEFAULT_RAGE_CLICK_IGNORE_LIST = ['.ph-no-rageclick', '.ph-no-capture']
|
|
156
|
-
export function shouldCaptureRageclick(el
|
|
131
|
+
const DEFAULT_RAGE_CLICK_IGNORE_LIST = ['.ph-no-rageclick', '.ph-no-capture'];
|
|
132
|
+
export function shouldCaptureRageclick(el, _config) {
|
|
157
133
|
if (!window || cannotCheckForAutocapture(el)) {
|
|
158
|
-
return false
|
|
134
|
+
return false;
|
|
159
135
|
}
|
|
160
|
-
|
|
161
|
-
let selectorIgnoreList: string[] | boolean
|
|
136
|
+
let selectorIgnoreList;
|
|
162
137
|
if (isBoolean(_config)) {
|
|
163
|
-
selectorIgnoreList = _config ? DEFAULT_RAGE_CLICK_IGNORE_LIST : false
|
|
164
|
-
}
|
|
165
|
-
|
|
138
|
+
selectorIgnoreList = _config ? DEFAULT_RAGE_CLICK_IGNORE_LIST : false;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
selectorIgnoreList = _config?.css_selector_ignorelist ?? DEFAULT_RAGE_CLICK_IGNORE_LIST;
|
|
166
142
|
}
|
|
167
|
-
|
|
168
143
|
if (selectorIgnoreList === false) {
|
|
169
|
-
return false
|
|
144
|
+
return false;
|
|
170
145
|
}
|
|
171
|
-
|
|
172
|
-
const { targetElementList } = getElementAndParentsForElement(el, false)
|
|
146
|
+
const { targetElementList } = getElementAndParentsForElement(el, false);
|
|
173
147
|
// we don't capture if we match the ignore list
|
|
174
|
-
return !checkIfElementsMatchCSSSelector(targetElementList, selectorIgnoreList)
|
|
148
|
+
return !checkIfElementsMatchCSSSelector(targetElementList, selectorIgnoreList);
|
|
175
149
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const getElementAndParentsForElement = (el: Element, captureOnAnyElement: false | true | undefined) => {
|
|
150
|
+
const cannotCheckForAutocapture = (el) => {
|
|
151
|
+
return !el || isTag(el, 'html') || !isElementNode(el);
|
|
152
|
+
};
|
|
153
|
+
const getElementAndParentsForElement = (el, captureOnAnyElement) => {
|
|
182
154
|
if (!window || cannotCheckForAutocapture(el)) {
|
|
183
|
-
return { parentIsUsefulElement: false, targetElementList: [] }
|
|
155
|
+
return { parentIsUsefulElement: false, targetElementList: [] };
|
|
184
156
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
let curEl: Element = el
|
|
157
|
+
let parentIsUsefulElement = false;
|
|
158
|
+
const targetElementList = [el];
|
|
159
|
+
let curEl = el;
|
|
189
160
|
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
190
161
|
// If element is a shadow root, we skip it
|
|
191
162
|
if (isDocumentFragment(curEl.parentNode)) {
|
|
192
|
-
targetElementList.push(
|
|
193
|
-
curEl =
|
|
194
|
-
continue
|
|
163
|
+
targetElementList.push(curEl.parentNode.host);
|
|
164
|
+
curEl = curEl.parentNode.host;
|
|
165
|
+
continue;
|
|
195
166
|
}
|
|
196
|
-
const parentNode = getParentElement(curEl)
|
|
197
|
-
if (!parentNode)
|
|
167
|
+
const parentNode = getParentElement(curEl);
|
|
168
|
+
if (!parentNode)
|
|
169
|
+
break;
|
|
198
170
|
if (captureOnAnyElement || autocaptureCompatibleElements.indexOf(parentNode.tagName.toLowerCase()) > -1) {
|
|
199
|
-
parentIsUsefulElement = true
|
|
200
|
-
}
|
|
201
|
-
|
|
171
|
+
parentIsUsefulElement = true;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const compStyles = window.getComputedStyle(parentNode);
|
|
202
175
|
if (compStyles && compStyles.getPropertyValue('cursor') === 'pointer') {
|
|
203
|
-
parentIsUsefulElement = true
|
|
176
|
+
parentIsUsefulElement = true;
|
|
204
177
|
}
|
|
205
178
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
curEl = parentNode
|
|
179
|
+
targetElementList.push(parentNode);
|
|
180
|
+
curEl = parentNode;
|
|
209
181
|
}
|
|
210
|
-
return { parentIsUsefulElement, targetElementList }
|
|
211
|
-
}
|
|
212
|
-
|
|
182
|
+
return { parentIsUsefulElement, targetElementList };
|
|
183
|
+
};
|
|
213
184
|
/*
|
|
214
185
|
* Check whether a DOM event should be "captured" or if it may contain sensitive data
|
|
215
186
|
* using a variety of heuristics.
|
|
@@ -220,153 +191,126 @@ const getElementAndParentsForElement = (el: Element, captureOnAnyElement: false
|
|
|
220
191
|
* @param {string[]} allowedEventTypes - event types to capture, normally just 'click', but some autocapture types react to different events, some elements have fixed events (e.g., form has "submit")
|
|
221
192
|
* @returns {boolean} whether the event should be captured
|
|
222
193
|
*/
|
|
223
|
-
export function shouldCaptureDomEvent(
|
|
224
|
-
el: Element,
|
|
225
|
-
event: Event,
|
|
226
|
-
autocaptureConfig: AutocaptureConfig | undefined = undefined,
|
|
227
|
-
captureOnAnyElement?: boolean,
|
|
228
|
-
allowedEventTypes?: string[]
|
|
229
|
-
): boolean {
|
|
194
|
+
export function shouldCaptureDomEvent(el, event, autocaptureConfig = undefined, captureOnAnyElement, allowedEventTypes) {
|
|
230
195
|
if (!window || cannotCheckForAutocapture(el)) {
|
|
231
|
-
return false
|
|
196
|
+
return false;
|
|
232
197
|
}
|
|
233
|
-
|
|
234
198
|
if (autocaptureConfig?.url_allowlist) {
|
|
235
199
|
// if the current URL is not in the allow list, don't capture
|
|
236
200
|
if (!checkForURLMatches(autocaptureConfig.url_allowlist)) {
|
|
237
|
-
return false
|
|
201
|
+
return false;
|
|
238
202
|
}
|
|
239
203
|
}
|
|
240
|
-
|
|
241
204
|
if (autocaptureConfig?.url_ignorelist) {
|
|
242
205
|
// if the current URL is in the ignore list, don't capture
|
|
243
206
|
if (checkForURLMatches(autocaptureConfig.url_ignorelist)) {
|
|
244
|
-
return false
|
|
207
|
+
return false;
|
|
245
208
|
}
|
|
246
209
|
}
|
|
247
|
-
|
|
248
210
|
if (autocaptureConfig?.dom_event_allowlist) {
|
|
249
|
-
const allowlist = autocaptureConfig.dom_event_allowlist
|
|
211
|
+
const allowlist = autocaptureConfig.dom_event_allowlist;
|
|
250
212
|
if (allowlist && !allowlist.some((eventType) => event.type === eventType)) {
|
|
251
|
-
return false
|
|
213
|
+
return false;
|
|
252
214
|
}
|
|
253
215
|
}
|
|
254
|
-
|
|
255
|
-
const { parentIsUsefulElement, targetElementList } = getElementAndParentsForElement(el, captureOnAnyElement)
|
|
256
|
-
|
|
216
|
+
const { parentIsUsefulElement, targetElementList } = getElementAndParentsForElement(el, captureOnAnyElement);
|
|
257
217
|
if (!checkIfElementTreePassesElementAllowList(targetElementList, autocaptureConfig)) {
|
|
258
|
-
return false
|
|
218
|
+
return false;
|
|
259
219
|
}
|
|
260
|
-
|
|
261
220
|
if (!checkIfElementsMatchCSSSelector(targetElementList, autocaptureConfig?.css_selector_allowlist)) {
|
|
262
|
-
return false
|
|
221
|
+
return false;
|
|
263
222
|
}
|
|
264
|
-
|
|
265
|
-
const compStyles = window.getComputedStyle(el)
|
|
223
|
+
const compStyles = window.getComputedStyle(el);
|
|
266
224
|
if (compStyles && compStyles.getPropertyValue('cursor') === 'pointer' && event.type === 'click') {
|
|
267
|
-
return true
|
|
225
|
+
return true;
|
|
268
226
|
}
|
|
269
|
-
|
|
270
|
-
const tag = el.tagName.toLowerCase()
|
|
227
|
+
const tag = el.tagName.toLowerCase();
|
|
271
228
|
switch (tag) {
|
|
272
229
|
case 'html':
|
|
273
|
-
return false
|
|
230
|
+
return false;
|
|
274
231
|
case 'form':
|
|
275
|
-
return (allowedEventTypes || ['submit']).indexOf(event.type) >= 0
|
|
232
|
+
return (allowedEventTypes || ['submit']).indexOf(event.type) >= 0;
|
|
276
233
|
case 'input':
|
|
277
234
|
case 'select':
|
|
278
235
|
case 'textarea':
|
|
279
|
-
return (allowedEventTypes || ['change', 'click']).indexOf(event.type) >= 0
|
|
236
|
+
return (allowedEventTypes || ['change', 'click']).indexOf(event.type) >= 0;
|
|
280
237
|
default:
|
|
281
|
-
if (parentIsUsefulElement)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
(autocaptureCompatibleElements.indexOf(tag) > -1 || el.getAttribute('contenteditable') === 'true')
|
|
285
|
-
)
|
|
238
|
+
if (parentIsUsefulElement)
|
|
239
|
+
return (allowedEventTypes || ['click']).indexOf(event.type) >= 0;
|
|
240
|
+
return ((allowedEventTypes || ['click']).indexOf(event.type) >= 0 &&
|
|
241
|
+
(autocaptureCompatibleElements.indexOf(tag) > -1 || el.getAttribute('contenteditable') === 'true'));
|
|
286
242
|
}
|
|
287
243
|
}
|
|
288
|
-
|
|
289
244
|
/*
|
|
290
245
|
* Check whether a DOM element should be "captured" or if it may contain sensitive data
|
|
291
246
|
* using a variety of heuristics.
|
|
292
247
|
* @param {Element} el - element to check
|
|
293
248
|
* @returns {boolean} whether the element should be captured
|
|
294
249
|
*/
|
|
295
|
-
export function shouldCaptureElement(el
|
|
296
|
-
for (let curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode
|
|
297
|
-
const classes = getClassNames(curEl)
|
|
250
|
+
export function shouldCaptureElement(el) {
|
|
251
|
+
for (let curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
252
|
+
const classes = getClassNames(curEl);
|
|
298
253
|
if (includes(classes, 'ph-sensitive') || includes(classes, 'ph-no-capture')) {
|
|
299
|
-
return false
|
|
254
|
+
return false;
|
|
300
255
|
}
|
|
301
256
|
}
|
|
302
|
-
|
|
303
257
|
if (includes(getClassNames(el), 'ph-include')) {
|
|
304
|
-
return true
|
|
258
|
+
return true;
|
|
305
259
|
}
|
|
306
|
-
|
|
307
260
|
// don't include hidden or password fields
|
|
308
|
-
const type =
|
|
261
|
+
const type = el.type || '';
|
|
309
262
|
if (isString(type)) {
|
|
310
263
|
// it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
|
|
311
264
|
switch (type.toLowerCase()) {
|
|
312
265
|
case 'hidden':
|
|
313
|
-
return false
|
|
266
|
+
return false;
|
|
314
267
|
case 'password':
|
|
315
|
-
return false
|
|
268
|
+
return false;
|
|
316
269
|
}
|
|
317
270
|
}
|
|
318
|
-
|
|
319
271
|
// filter out data from fields that look like sensitive fields
|
|
320
|
-
const name =
|
|
272
|
+
const name = el.name || el.id || '';
|
|
321
273
|
// See https://github.com/posthog/posthog-js/issues/165
|
|
322
274
|
// Under specific circumstances a bug caused .replace to be called on a DOM element
|
|
323
275
|
// instead of a string, removing the element from the page. Ensure this issue is mitigated.
|
|
324
276
|
if (isString(name)) {
|
|
325
277
|
// it's possible for el.name or el.id to be a DOM element if el is a form with a child input[name="name"]
|
|
326
|
-
const sensitiveNameRegex =
|
|
327
|
-
/^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i
|
|
278
|
+
const sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
328
279
|
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
329
|
-
return false
|
|
280
|
+
return false;
|
|
330
281
|
}
|
|
331
282
|
}
|
|
332
|
-
|
|
333
|
-
return true
|
|
283
|
+
return true;
|
|
334
284
|
}
|
|
335
|
-
|
|
336
285
|
/*
|
|
337
286
|
* Check whether a DOM element is 'sensitive' and we should only capture limited data
|
|
338
287
|
* @param {Element} el - element to check
|
|
339
288
|
* @returns {boolean} whether the element should be captured
|
|
340
289
|
*/
|
|
341
|
-
export function isSensitiveElement(el
|
|
290
|
+
export function isSensitiveElement(el) {
|
|
342
291
|
// don't send data from inputs or similar elements since there will always be
|
|
343
292
|
// a risk of clientside javascript placing sensitive data in attributes
|
|
344
|
-
const allowedInputTypes = ['button', 'checkbox', 'submit', 'reset']
|
|
345
|
-
if (
|
|
346
|
-
(isTag(el, 'input') && !allowedInputTypes.includes((el as HTMLInputElement).type)) ||
|
|
293
|
+
const allowedInputTypes = ['button', 'checkbox', 'submit', 'reset'];
|
|
294
|
+
if ((isTag(el, 'input') && !allowedInputTypes.includes(el.type)) ||
|
|
347
295
|
isTag(el, 'select') ||
|
|
348
296
|
isTag(el, 'textarea') ||
|
|
349
|
-
el.getAttribute('contenteditable') === 'true'
|
|
350
|
-
|
|
351
|
-
return true
|
|
297
|
+
el.getAttribute('contenteditable') === 'true') {
|
|
298
|
+
return true;
|
|
352
299
|
}
|
|
353
|
-
return false
|
|
300
|
+
return false;
|
|
354
301
|
}
|
|
355
|
-
|
|
356
302
|
// Define the core pattern for matching credit card numbers
|
|
357
|
-
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})
|
|
303
|
+
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})`;
|
|
358
304
|
// Create the Anchored version of the regex by adding '^' at the start and '$' at the end
|
|
359
|
-
const anchoredCCRegex = new RegExp(`^(?:${coreCCPattern})$`)
|
|
305
|
+
const anchoredCCRegex = new RegExp(`^(?:${coreCCPattern})$`);
|
|
360
306
|
// The Unanchored version is essentially the core pattern, usable as is for partial matches
|
|
361
|
-
const unanchoredCCRegex = new RegExp(coreCCPattern)
|
|
362
|
-
|
|
307
|
+
const unanchoredCCRegex = new RegExp(coreCCPattern);
|
|
363
308
|
// Define the core pattern for matching SSNs with optional dashes
|
|
364
|
-
const coreSSNPattern = `\\d{3}-?\\d{2}-?\\d{4}
|
|
309
|
+
const coreSSNPattern = `\\d{3}-?\\d{2}-?\\d{4}`;
|
|
365
310
|
// Create the Anchored version of the regex by adding '^' at the start and '$' at the end
|
|
366
|
-
const anchoredSSNRegex = new RegExp(`^(${coreSSNPattern})$`)
|
|
311
|
+
const anchoredSSNRegex = new RegExp(`^(${coreSSNPattern})$`);
|
|
367
312
|
// The Unanchored version is essentially the core pattern itself, usable for partial matches
|
|
368
|
-
const unanchoredSSNRegex = new RegExp(`(${coreSSNPattern})`)
|
|
369
|
-
|
|
313
|
+
const unanchoredSSNRegex = new RegExp(`(${coreSSNPattern})`);
|
|
370
314
|
/*
|
|
371
315
|
* Check whether a string value should be "captured" or if it may contain sensitive data
|
|
372
316
|
* using a variety of heuristics.
|
|
@@ -374,31 +318,26 @@ const unanchoredSSNRegex = new RegExp(`(${coreSSNPattern})`)
|
|
|
374
318
|
* @param {boolean} anchorRegexes - whether to anchor the regexes to the start and end of the string
|
|
375
319
|
* @returns {boolean} whether the element should be captured
|
|
376
320
|
*/
|
|
377
|
-
export function shouldCaptureValue(value
|
|
321
|
+
export function shouldCaptureValue(value, anchorRegexes = true) {
|
|
378
322
|
if (isNullish(value)) {
|
|
379
|
-
return false
|
|
323
|
+
return false;
|
|
380
324
|
}
|
|
381
|
-
|
|
382
325
|
if (isString(value)) {
|
|
383
|
-
value = trim(value)
|
|
384
|
-
|
|
326
|
+
value = trim(value);
|
|
385
327
|
// check to see if input value looks like a credit card number
|
|
386
328
|
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
387
|
-
const ccRegex = anchorRegexes ? anchoredCCRegex : unanchoredCCRegex
|
|
329
|
+
const ccRegex = anchorRegexes ? anchoredCCRegex : unanchoredCCRegex;
|
|
388
330
|
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
389
|
-
return false
|
|
331
|
+
return false;
|
|
390
332
|
}
|
|
391
|
-
|
|
392
333
|
// check to see if input value looks like a social security number
|
|
393
|
-
const ssnRegex = anchorRegexes ? anchoredSSNRegex : unanchoredSSNRegex
|
|
334
|
+
const ssnRegex = anchorRegexes ? anchoredSSNRegex : unanchoredSSNRegex;
|
|
394
335
|
if (ssnRegex.test(value)) {
|
|
395
|
-
return false
|
|
336
|
+
return false;
|
|
396
337
|
}
|
|
397
338
|
}
|
|
398
|
-
|
|
399
|
-
return true
|
|
339
|
+
return true;
|
|
400
340
|
}
|
|
401
|
-
|
|
402
341
|
/*
|
|
403
342
|
* Check whether an attribute name is an Angular style attr (either _ngcontent or _nghost)
|
|
404
343
|
* These update on each build and lead to noise in the element chain
|
|
@@ -406,13 +345,12 @@ export function shouldCaptureValue(value: string, anchorRegexes = true): boolean
|
|
|
406
345
|
* @param {string} attributeName - string value to check
|
|
407
346
|
* @returns {boolean} whether the element is an angular tag
|
|
408
347
|
*/
|
|
409
|
-
export function isAngularStyleAttr(attributeName
|
|
348
|
+
export function isAngularStyleAttr(attributeName) {
|
|
410
349
|
if (isString(attributeName)) {
|
|
411
|
-
return attributeName.substring(0, 10) === '_ngcontent' || attributeName.substring(0, 7) === '_nghost'
|
|
350
|
+
return attributeName.substring(0, 10) === '_ngcontent' || attributeName.substring(0, 7) === '_nghost';
|
|
412
351
|
}
|
|
413
|
-
return false
|
|
352
|
+
return false;
|
|
414
353
|
}
|
|
415
|
-
|
|
416
354
|
/*
|
|
417
355
|
* Iterate through children of a target element looking for span tags
|
|
418
356
|
* and return the text content of the span tags, separated by spaces,
|
|
@@ -420,105 +358,82 @@ export function isAngularStyleAttr(attributeName: string): boolean {
|
|
|
420
358
|
* @param {Element} target - element to check
|
|
421
359
|
* @returns {string} text content of the target element and its child span tags
|
|
422
360
|
*/
|
|
423
|
-
export function getDirectAndNestedSpanText(target
|
|
424
|
-
let text = getSafeText(target)
|
|
425
|
-
text = `${text} ${getNestedSpanText(target)}`.trim()
|
|
426
|
-
return shouldCaptureValue(text) ? text : ''
|
|
361
|
+
export function getDirectAndNestedSpanText(target) {
|
|
362
|
+
let text = getSafeText(target);
|
|
363
|
+
text = `${text} ${getNestedSpanText(target)}`.trim();
|
|
364
|
+
return shouldCaptureValue(text) ? text : '';
|
|
427
365
|
}
|
|
428
|
-
|
|
429
366
|
/*
|
|
430
367
|
* Iterate through children of a target element looking for span tags
|
|
431
368
|
* and return the text content of the span tags, separated by spaces
|
|
432
369
|
* @param {Element} target - element to check
|
|
433
370
|
* @returns {string} text content of span tags
|
|
434
371
|
*/
|
|
435
|
-
export function getNestedSpanText(target
|
|
436
|
-
let text = ''
|
|
372
|
+
export function getNestedSpanText(target) {
|
|
373
|
+
let text = '';
|
|
437
374
|
if (target && target.childNodes && target.childNodes.length) {
|
|
438
375
|
each(target.childNodes, function (child) {
|
|
439
376
|
if (child && child.tagName?.toLowerCase() === 'span') {
|
|
440
377
|
try {
|
|
441
|
-
const spanText = getSafeText(child)
|
|
442
|
-
text = `${text} ${spanText}`.trim()
|
|
443
|
-
|
|
378
|
+
const spanText = getSafeText(child);
|
|
379
|
+
text = `${text} ${spanText}`.trim();
|
|
444
380
|
if (child.childNodes && child.childNodes.length) {
|
|
445
|
-
text = `${text} ${getNestedSpanText(child)}`.trim()
|
|
381
|
+
text = `${text} ${getNestedSpanText(child)}`.trim();
|
|
446
382
|
}
|
|
447
|
-
}
|
|
448
|
-
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
logger.error('[AutoCapture]', e);
|
|
449
386
|
}
|
|
450
387
|
}
|
|
451
|
-
})
|
|
388
|
+
});
|
|
452
389
|
}
|
|
453
|
-
return text
|
|
390
|
+
return text;
|
|
454
391
|
}
|
|
455
|
-
|
|
456
392
|
/*
|
|
457
393
|
Back in the day storing events in Postgres we use Elements for autocapture events.
|
|
458
394
|
Now we're using elements_chain. We used to do this parsing/processing during ingestion.
|
|
459
395
|
This code is just copied over from ingestion, but we should optimize it
|
|
460
396
|
to create elements_chain string directly.
|
|
461
397
|
*/
|
|
462
|
-
export function getElementsChainString(elements
|
|
463
|
-
return elementsToString(extractElements(elements))
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// This interface is called 'Element' in plugin-scaffold https://github.com/PostHog/plugin-scaffold/blob/b07d3b879796ecc7e22deb71bf627694ba05386b/src/types.ts#L200
|
|
467
|
-
// However 'Element' is a DOM Element when run in the browser, so we have to rename it
|
|
468
|
-
interface PHElement {
|
|
469
|
-
text?: string
|
|
470
|
-
tag_name?: string
|
|
471
|
-
href?: string
|
|
472
|
-
attr_id?: string
|
|
473
|
-
attr_class?: string[]
|
|
474
|
-
nth_child?: number
|
|
475
|
-
nth_of_type?: number
|
|
476
|
-
attributes?: Record<string, any>
|
|
477
|
-
event_id?: number
|
|
478
|
-
order?: number
|
|
479
|
-
group_id?: number
|
|
398
|
+
export function getElementsChainString(elements) {
|
|
399
|
+
return elementsToString(extractElements(elements));
|
|
480
400
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
return input.replace(/"|\\"/g, '\\"')
|
|
401
|
+
function escapeQuotes(input) {
|
|
402
|
+
return input.replace(/"|\\"/g, '\\"');
|
|
484
403
|
}
|
|
485
|
-
|
|
486
|
-
function elementsToString(elements: PHElement[]): string {
|
|
404
|
+
function elementsToString(elements) {
|
|
487
405
|
const ret = elements.map((element) => {
|
|
488
|
-
let el_string = ''
|
|
406
|
+
let el_string = '';
|
|
489
407
|
if (element.tag_name) {
|
|
490
|
-
el_string += element.tag_name
|
|
408
|
+
el_string += element.tag_name;
|
|
491
409
|
}
|
|
492
410
|
if (element.attr_class) {
|
|
493
|
-
element.attr_class.sort()
|
|
411
|
+
element.attr_class.sort();
|
|
494
412
|
for (const single_class of element.attr_class) {
|
|
495
|
-
el_string += `.${single_class.replace(/"/g, '')}
|
|
413
|
+
el_string += `.${single_class.replace(/"/g, '')}`;
|
|
496
414
|
}
|
|
497
415
|
}
|
|
498
|
-
const attributes
|
|
416
|
+
const attributes = {
|
|
499
417
|
...(element.text ? { text: element.text } : {}),
|
|
500
418
|
'nth-child': element.nth_child ?? 0,
|
|
501
419
|
'nth-of-type': element.nth_of_type ?? 0,
|
|
502
420
|
...(element.href ? { href: element.href } : {}),
|
|
503
421
|
...(element.attr_id ? { attr_id: element.attr_id } : {}),
|
|
504
422
|
...element.attributes,
|
|
505
|
-
}
|
|
506
|
-
const sortedAttributes
|
|
423
|
+
};
|
|
424
|
+
const sortedAttributes = {};
|
|
507
425
|
entries(attributes)
|
|
508
426
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
509
|
-
.forEach(
|
|
510
|
-
|
|
511
|
-
)
|
|
512
|
-
el_string += ':'
|
|
427
|
+
.forEach(([key, value]) => (sortedAttributes[escapeQuotes(key.toString())] = escapeQuotes(value.toString())));
|
|
428
|
+
el_string += ':';
|
|
513
429
|
el_string += entries(sortedAttributes)
|
|
514
430
|
.map(([key, value]) => `${key}="${value}"`)
|
|
515
|
-
.join('')
|
|
516
|
-
return el_string
|
|
517
|
-
})
|
|
518
|
-
return ret.join(';')
|
|
431
|
+
.join('');
|
|
432
|
+
return el_string;
|
|
433
|
+
});
|
|
434
|
+
return ret.join(';');
|
|
519
435
|
}
|
|
520
|
-
|
|
521
|
-
function extractElements(elements: Properties[]): PHElement[] {
|
|
436
|
+
function extractElements(elements) {
|
|
522
437
|
return elements.map((el) => {
|
|
523
438
|
const response = {
|
|
524
439
|
text: el['$el_text']?.slice(0, 400),
|
|
@@ -528,23 +443,24 @@ function extractElements(elements: Properties[]): PHElement[] {
|
|
|
528
443
|
attr_id: el['attr__id'],
|
|
529
444
|
nth_child: el['nth_child'],
|
|
530
445
|
nth_of_type: el['nth_of_type'],
|
|
531
|
-
attributes: {}
|
|
532
|
-
}
|
|
533
|
-
|
|
446
|
+
attributes: {},
|
|
447
|
+
};
|
|
534
448
|
entries(el)
|
|
535
449
|
.filter(([key]) => key.indexOf('attr__') === 0)
|
|
536
|
-
.forEach(([key, value]) => (response.attributes[key] = value))
|
|
537
|
-
return response
|
|
538
|
-
})
|
|
450
|
+
.forEach(([key, value]) => (response.attributes[key] = value));
|
|
451
|
+
return response;
|
|
452
|
+
});
|
|
539
453
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const attr_class = el['attr__class']
|
|
454
|
+
function extractAttrClass(el) {
|
|
455
|
+
const attr_class = el['attr__class'];
|
|
543
456
|
if (!attr_class) {
|
|
544
|
-
return undefined
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
459
|
+
else if (isArray(attr_class)) {
|
|
460
|
+
return attr_class;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
return splitClassString(attr_class);
|
|
549
464
|
}
|
|
550
465
|
}
|
|
466
|
+
//# sourceMappingURL=autocapture-utils.js.map
|