@posthog/browser-common 0.9.8 → 0.9.9
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-config.d.ts +13 -0
- package/dist/autocapture-config.js +18 -0
- package/dist/autocapture-config.mjs +1 -0
- package/dist/autocapture.d.ts +63 -0
- package/dist/autocapture.js +533 -0
- package/dist/autocapture.mjs +467 -0
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +4 -0
- package/dist/constants.mjs +2 -1
- package/dist/rageclick.d.ts +14 -0
- package/dist/rageclick.js +72 -0
- package/dist/rageclick.mjs +34 -0
- package/dist/survey-action-matcher.js +6 -1
- package/dist/survey-action-matcher.mjs +6 -1
- package/dist/survey-event-host.d.ts +2 -0
- package/dist/survey-event-receiver.d.ts +2 -1
- package/dist/survey-event-receiver.js +3 -2
- package/dist/survey-event-receiver.mjs +3 -2
- package/dist/surveys.d.ts +3 -0
- package/dist/surveys.js +8 -2
- package/dist/surveys.mjs +8 -2
- package/dist/utils/autocapture-utils.js +12 -7
- package/dist/utils/autocapture-utils.mjs +12 -7
- package/dist/utils/convert-to-url.d.ts +7 -0
- package/dist/utils/convert-to-url.js +47 -0
- package/dist/utils/convert-to-url.mjs +9 -0
- package/dist/utils/request-utils.d.ts +1 -7
- package/dist/utils/request-utils.js +2 -7
- package/dist/utils/request-utils.mjs +3 -8
- package/dist/utils/url-targeting-utils.js +2 -1
- package/dist/utils/url-targeting-utils.mjs +2 -1
- package/package.json +27 -3
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { addEventListener, each, extend } from "./utils/general-utils.mjs";
|
|
2
|
+
import { DEFAULT_AUTOCAPTURE_IGNORE_LIST, MAX_DOM_ANCESTOR_DEPTH, autocaptureCompatibleElements, elementMatchesCSSSelector, getClassNames, getDirectAndNestedSpanText, getElementsChainString, getEventTarget, getSafeText, isAngularStyleAttr, isSensitiveElement, makeSafeText, shouldCaptureDomEvent, shouldCaptureElement, shouldCaptureRageclick, shouldCaptureValue, splitClassString } from "./utils/autocapture-utils.mjs";
|
|
3
|
+
import rageclick from "./rageclick.mjs";
|
|
4
|
+
import { AUTOCAPTURE_DISABLED_SERVER_SIDE } from "./constants.mjs";
|
|
5
|
+
import { includes, isBoolean, isFunction, isNull, stripUrlHash } from "@posthog/core";
|
|
6
|
+
import { createLogger } from "./utils/logger.mjs";
|
|
7
|
+
import { convertToURL } from "./utils/convert-to-url.mjs";
|
|
8
|
+
import { isElementNode, isShadowRoot, isTag, isTextNode } from "./utils/element-utils.mjs";
|
|
9
|
+
const getWindow = ()=>"u" < typeof window ? void 0 : window;
|
|
10
|
+
const getDocument = ()=>"u" < typeof document ? void 0 : document;
|
|
11
|
+
const COPY_AUTOCAPTURE_EVENT = '$copy_autocapture';
|
|
12
|
+
const logger = createLogger('[AutoCapture]');
|
|
13
|
+
function limitText(length, text) {
|
|
14
|
+
if (text.length > length) return text.slice(0, length) + '...';
|
|
15
|
+
return text;
|
|
16
|
+
}
|
|
17
|
+
function getAugmentPropertiesFromElement(elem) {
|
|
18
|
+
const shouldCaptureEl = shouldCaptureElement(elem);
|
|
19
|
+
if (!shouldCaptureEl) return {};
|
|
20
|
+
const props = {};
|
|
21
|
+
each(elem.attributes, function(attr) {
|
|
22
|
+
if (attr.name && 0 === attr.name.indexOf('data-ph-capture-attribute')) {
|
|
23
|
+
const propertyKey = attr.name.replace('data-ph-capture-attribute-', '');
|
|
24
|
+
const propertyValue = attr.value;
|
|
25
|
+
if (propertyKey && propertyValue && shouldCaptureValue(propertyValue)) props[propertyKey] = propertyValue;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return props;
|
|
29
|
+
}
|
|
30
|
+
function previousElementSibling(el) {
|
|
31
|
+
if (el.previousElementSibling) return el.previousElementSibling;
|
|
32
|
+
let _el = el;
|
|
33
|
+
do _el = _el.previousSibling;
|
|
34
|
+
while (_el && !isElementNode(_el));
|
|
35
|
+
return _el;
|
|
36
|
+
}
|
|
37
|
+
function getDefaultProperties(eventType) {
|
|
38
|
+
return {
|
|
39
|
+
$event_type: eventType,
|
|
40
|
+
$ce_version: 1
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function getPropertiesFromElement(elem, maskAllAttributes, maskText, elementAttributeIgnorelist, disableCaptureUrlHashes = false) {
|
|
44
|
+
const tag_name = elem.tagName.toLowerCase();
|
|
45
|
+
const props = {
|
|
46
|
+
tag_name: tag_name
|
|
47
|
+
};
|
|
48
|
+
if (autocaptureCompatibleElements.indexOf(tag_name) > -1 && !maskText) if ('a' === tag_name.toLowerCase() || 'button' === tag_name.toLowerCase()) props['$el_text'] = limitText(1024, getDirectAndNestedSpanText(elem));
|
|
49
|
+
else props['$el_text'] = limitText(1024, getSafeText(elem));
|
|
50
|
+
const classes = getClassNames(elem);
|
|
51
|
+
if (!maskAllAttributes && !elementAttributeIgnorelist?.includes('class') && classes.length > 0) props['classes'] = classes.filter(function(c) {
|
|
52
|
+
return '' !== c;
|
|
53
|
+
});
|
|
54
|
+
each(elem.attributes, function(attr) {
|
|
55
|
+
if (isSensitiveElement(elem) && -1 === [
|
|
56
|
+
'name',
|
|
57
|
+
'id',
|
|
58
|
+
'class',
|
|
59
|
+
'aria-label'
|
|
60
|
+
].indexOf(attr.name)) return;
|
|
61
|
+
if (elementAttributeIgnorelist?.includes(attr.name)) return;
|
|
62
|
+
if (!maskAllAttributes && shouldCaptureValue(attr.value) && !isAngularStyleAttr(attr.name)) {
|
|
63
|
+
let value = attr.value;
|
|
64
|
+
if ('class' === attr.name) value = splitClassString(value).join(' ');
|
|
65
|
+
props['attr__' + attr.name] = limitText(1024, 'href' === attr.name && disableCaptureUrlHashes ? stripUrlHash(value) : value);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
let nthChild = 1;
|
|
69
|
+
let nthOfType = 1;
|
|
70
|
+
let currentElem = elem;
|
|
71
|
+
while(currentElem = previousElementSibling(currentElem)){
|
|
72
|
+
nthChild++;
|
|
73
|
+
if (currentElem.tagName === elem.tagName) nthOfType++;
|
|
74
|
+
}
|
|
75
|
+
props['nth_child'] = nthChild;
|
|
76
|
+
props['nth_of_type'] = nthOfType;
|
|
77
|
+
return props;
|
|
78
|
+
}
|
|
79
|
+
function autocapturePropertiesForElement(target, { e, maskAllElementAttributes, maskAllText, elementAttributeIgnoreList, elementsChainAsString, disableCaptureUrlHashes }) {
|
|
80
|
+
if (!isElementNode(target)) return {
|
|
81
|
+
props: {}
|
|
82
|
+
};
|
|
83
|
+
const targetElementList = [
|
|
84
|
+
target
|
|
85
|
+
];
|
|
86
|
+
const seen = new Set([
|
|
87
|
+
target
|
|
88
|
+
]);
|
|
89
|
+
let curEl = target;
|
|
90
|
+
while(curEl.parentNode && !isTag(curEl, 'body')){
|
|
91
|
+
if (targetElementList.length >= MAX_DOM_ANCESTOR_DEPTH) break;
|
|
92
|
+
if (isShadowRoot(curEl.parentNode)) {
|
|
93
|
+
const host = curEl.parentNode.host;
|
|
94
|
+
if (seen.has(host)) break;
|
|
95
|
+
seen.add(host);
|
|
96
|
+
targetElementList.push(host);
|
|
97
|
+
curEl = host;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!isElementNode(curEl.parentNode)) break;
|
|
101
|
+
if (seen.has(curEl.parentNode)) break;
|
|
102
|
+
seen.add(curEl.parentNode);
|
|
103
|
+
targetElementList.push(curEl.parentNode);
|
|
104
|
+
curEl = curEl.parentNode;
|
|
105
|
+
}
|
|
106
|
+
const controlIndex = 'click' === e.type && 'http://www.w3.org/2000/svg' === target.namespaceURI ? targetElementList.findIndex((el)=>isTag(el, 'button') || isTag(el, 'a')) : -1;
|
|
107
|
+
if (controlIndex > 0) maskAllText = maskAllText || targetElementList.slice(0, controlIndex + 1).some((el)=>!shouldCaptureElement(el) || isSensitiveElement(el));
|
|
108
|
+
const elementsJson = [];
|
|
109
|
+
const autocaptureAugmentProperties = {};
|
|
110
|
+
let href = false;
|
|
111
|
+
let explicitNoCapture = false;
|
|
112
|
+
each(targetElementList, (el)=>{
|
|
113
|
+
const shouldCaptureEl = shouldCaptureElement(el);
|
|
114
|
+
if (isTag(el, 'a') && !maskAllElementAttributes && !elementAttributeIgnoreList?.includes('href')) {
|
|
115
|
+
const hrefAttr = el.getAttribute('href');
|
|
116
|
+
href = shouldCaptureEl && !!hrefAttr && shouldCaptureValue(hrefAttr) ? disableCaptureUrlHashes ? stripUrlHash(hrefAttr) : hrefAttr : false;
|
|
117
|
+
}
|
|
118
|
+
const classes = getClassNames(el);
|
|
119
|
+
if (includes(classes, 'ph-no-capture')) explicitNoCapture = true;
|
|
120
|
+
elementsJson.push(getPropertiesFromElement(el, maskAllElementAttributes, maskAllText, elementAttributeIgnoreList, disableCaptureUrlHashes));
|
|
121
|
+
const augmentProperties = getAugmentPropertiesFromElement(el);
|
|
122
|
+
each(augmentProperties, (value, key)=>{
|
|
123
|
+
if (!Object.prototype.hasOwnProperty.call(autocaptureAugmentProperties, key)) autocaptureAugmentProperties[key] = value;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
if (explicitNoCapture) return {
|
|
127
|
+
props: {},
|
|
128
|
+
explicitNoCapture
|
|
129
|
+
};
|
|
130
|
+
if (controlIndex > 0) {
|
|
131
|
+
target = targetElementList[controlIndex];
|
|
132
|
+
elementsJson.splice(0, controlIndex);
|
|
133
|
+
}
|
|
134
|
+
if (!maskAllText) if (isTag(target, 'a') || isTag(target, 'button')) elementsJson[0]['$el_text'] = getDirectAndNestedSpanText(target);
|
|
135
|
+
else elementsJson[0]['$el_text'] = getSafeText(target);
|
|
136
|
+
let externalHref;
|
|
137
|
+
if (href) {
|
|
138
|
+
elementsJson[0]['attr__href'] = href;
|
|
139
|
+
const hrefHost = convertToURL(href)?.host;
|
|
140
|
+
const locationHost = getWindow()?.location?.host;
|
|
141
|
+
if (hrefHost && locationHost && hrefHost !== locationHost) externalHref = href;
|
|
142
|
+
}
|
|
143
|
+
const props = extend(getDefaultProperties(e.type), elementsChainAsString ? {} : {
|
|
144
|
+
$elements: elementsJson
|
|
145
|
+
}, {
|
|
146
|
+
$elements_chain: getElementsChainString(elementsJson)
|
|
147
|
+
}, elementsJson[0]?.['$el_text'] ? {
|
|
148
|
+
$el_text: elementsJson[0]?.['$el_text']
|
|
149
|
+
} : {}, externalHref && 'click' === e.type ? {
|
|
150
|
+
$external_click_url: externalHref
|
|
151
|
+
} : {}, autocaptureAugmentProperties);
|
|
152
|
+
return {
|
|
153
|
+
props,
|
|
154
|
+
target
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const AutocaptureExtension = 'autocapture';
|
|
158
|
+
class Autocapture {
|
|
159
|
+
_clearPointer() {
|
|
160
|
+
this._pointerDown = void 0;
|
|
161
|
+
clearTimeout(this._pointerExpiry);
|
|
162
|
+
this._pointerExpiry = void 0;
|
|
163
|
+
}
|
|
164
|
+
_isExcludedPointerTarget(target) {
|
|
165
|
+
if (!shouldCaptureElement(target)) return true;
|
|
166
|
+
const ignorelist = this._refreshConfig().css_selector_ignorelist ?? DEFAULT_AUTOCAPTURE_IGNORE_LIST;
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
let el = target;
|
|
169
|
+
while(el){
|
|
170
|
+
if (seen.size >= MAX_DOM_ANCESTOR_DEPTH || seen.has(el)) return true;
|
|
171
|
+
seen.add(el);
|
|
172
|
+
const current = el;
|
|
173
|
+
const classes = getClassNames(current);
|
|
174
|
+
if (includes(classes, 'ph-no-capture') || includes(classes, 'ph-sensitive') || isSensitiveElement(current) || ignorelist.some((selector)=>elementMatchesCSSSelector(current, selector))) return true;
|
|
175
|
+
const parent = el.parentNode;
|
|
176
|
+
el = isShadowRoot(parent) ? parent.host : parent && isElementNode(parent) ? parent : null;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
_trackPointer(e) {
|
|
181
|
+
if (!this.isEnabled || 'pointercancel' === e.type) return void this._clearPointer();
|
|
182
|
+
if ('pointerdown' === e.type) {
|
|
183
|
+
this._clearPointer();
|
|
184
|
+
const target = getEventTarget(e);
|
|
185
|
+
if (e.isPrimary && 0 === e.button && !e.ctrlKey && target && isElementNode(target)) this._pointerDown = {
|
|
186
|
+
target,
|
|
187
|
+
excluded: this._isExcludedPointerTarget(target),
|
|
188
|
+
id: e.pointerId,
|
|
189
|
+
x: e.clientX,
|
|
190
|
+
y: e.clientY,
|
|
191
|
+
released: false,
|
|
192
|
+
distinctId: this._client?.distinctId
|
|
193
|
+
};
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const down = this._pointerDown;
|
|
197
|
+
if (!down) return;
|
|
198
|
+
if (e.pointerId !== down.id || Math.abs(e.clientX - down.x) > 5 || Math.abs(e.clientY - down.y) > 5) this._clearPointer();
|
|
199
|
+
else if ('pointerup' === e.type) {
|
|
200
|
+
down.released = true;
|
|
201
|
+
this._pointerExpiry = setTimeout(()=>this._clearPointer(), 0);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
_recoverClickTarget(e) {
|
|
205
|
+
const down = this._pointerDown;
|
|
206
|
+
this._clearPointer();
|
|
207
|
+
const target = getEventTarget(e);
|
|
208
|
+
if (down?.released && down.target.isConnected && down.distinctId === this._client?.distinctId && e.pointerId === down.id && (e.detail > 0 || 'touch' === e.pointerType || 'pen' === e.pointerType) && 0 === e.button && Math.abs(e.clientX - down.x) <= 5 && Math.abs(e.clientY - down.y) <= 5 && target && (isTag(target, 'html') || isTag(target, 'body'))) {
|
|
209
|
+
if (down.excluded) return null;
|
|
210
|
+
const ignorelist = this._refreshConfig().css_selector_ignorelist ?? DEFAULT_AUTOCAPTURE_IGNORE_LIST;
|
|
211
|
+
const excludedRoot = [
|
|
212
|
+
target,
|
|
213
|
+
getDocument()?.documentElement,
|
|
214
|
+
getDocument()?.body
|
|
215
|
+
].some((root)=>{
|
|
216
|
+
if (!root) return false;
|
|
217
|
+
const classes = getClassNames(root);
|
|
218
|
+
return includes(classes, 'ph-no-capture') || includes(classes, 'ph-sensitive') || !shouldCaptureElement(root) || isSensitiveElement(root) || ignorelist.some((selector)=>elementMatchesCSSSelector(root, selector));
|
|
219
|
+
});
|
|
220
|
+
if (excludedRoot) return null;
|
|
221
|
+
return down.target;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
constructor(_configSource){
|
|
225
|
+
this._configSource = _configSource;
|
|
226
|
+
this.name = AutocaptureExtension;
|
|
227
|
+
this._initialized = false;
|
|
228
|
+
this._isDisabledServerSide = null;
|
|
229
|
+
this._hasReceivedConfigResponse = false;
|
|
230
|
+
this._elementSelectorsByOwner = new Map();
|
|
231
|
+
this._elementsChainAsString = false;
|
|
232
|
+
this._config = {
|
|
233
|
+
enabled: false,
|
|
234
|
+
rageclick: false,
|
|
235
|
+
maskAllElementAttributes: false,
|
|
236
|
+
maskAllText: false,
|
|
237
|
+
disableCaptureUrlHashes: false,
|
|
238
|
+
remoteRequestsDisabled: false
|
|
239
|
+
};
|
|
240
|
+
this._disposed = false;
|
|
241
|
+
this._configSource.refresh(this._config);
|
|
242
|
+
this.rageclicks = new rageclick(this._config.rageclick);
|
|
243
|
+
this._elementSelectors = null;
|
|
244
|
+
}
|
|
245
|
+
setup(client) {
|
|
246
|
+
this._compileUrlPatterns();
|
|
247
|
+
this._client = client;
|
|
248
|
+
const subscription = client.onRemoteConfig(this.onRemoteConfig.bind(this));
|
|
249
|
+
if (this._disposed) return void subscription.dispose();
|
|
250
|
+
this._remoteConfigSubscription = subscription;
|
|
251
|
+
this.startIfEnabled();
|
|
252
|
+
}
|
|
253
|
+
dispose() {
|
|
254
|
+
if (this._disposed) return;
|
|
255
|
+
this._disposed = true;
|
|
256
|
+
this._client = void 0;
|
|
257
|
+
this._remoteConfigSubscription?.dispose();
|
|
258
|
+
this._remoteConfigSubscription = void 0;
|
|
259
|
+
this._elementSelectorsByOwner.clear();
|
|
260
|
+
this._elementSelectors = null;
|
|
261
|
+
this._removeDomEventHandlers();
|
|
262
|
+
}
|
|
263
|
+
_refreshConfig() {
|
|
264
|
+
this._configSource.refresh(this._config);
|
|
265
|
+
return this._config;
|
|
266
|
+
}
|
|
267
|
+
_compileUrlPatterns() {
|
|
268
|
+
this._refreshConfig();
|
|
269
|
+
this._config.url_allowlist = this._config.url_allowlist?.map((url)=>new RegExp(url));
|
|
270
|
+
this._config.url_ignorelist = this._config.url_ignorelist?.map((url)=>new RegExp(url));
|
|
271
|
+
return this._config;
|
|
272
|
+
}
|
|
273
|
+
_addDomEventHandlers() {
|
|
274
|
+
const window1 = getWindow();
|
|
275
|
+
const document1 = getDocument();
|
|
276
|
+
if (!this.isBrowserSupported()) return void logger.info('Disabling Automatic Event Collection because this browser is not supported');
|
|
277
|
+
if (!window1 || !document1) return;
|
|
278
|
+
this._listenerWindow = window1;
|
|
279
|
+
this._listenerDocument = document1;
|
|
280
|
+
const handler = this._domEventHandler = (e)=>{
|
|
281
|
+
e = e || window1?.event;
|
|
282
|
+
try {
|
|
283
|
+
if ('blur' === e.type) return void this._clearPointer();
|
|
284
|
+
if (0 === e.type.indexOf('pointer')) return void this._trackPointer(e);
|
|
285
|
+
const target = 'click' === e.type ? this._recoverClickTarget(e) : void 0;
|
|
286
|
+
if (isNull(target)) return;
|
|
287
|
+
this._captureEvent(e, '$autocapture', target);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
logger.error('Failed to capture event', error);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
addEventListener(window1, 'blur', handler);
|
|
293
|
+
addEventListener(document1, 'submit', handler, {
|
|
294
|
+
capture: true
|
|
295
|
+
});
|
|
296
|
+
addEventListener(document1, 'change', handler, {
|
|
297
|
+
capture: true
|
|
298
|
+
});
|
|
299
|
+
addEventListener(document1, 'click', handler, {
|
|
300
|
+
capture: true
|
|
301
|
+
});
|
|
302
|
+
each([
|
|
303
|
+
'pointerdown',
|
|
304
|
+
'pointermove',
|
|
305
|
+
'pointerup',
|
|
306
|
+
'pointercancel'
|
|
307
|
+
], (type)=>{
|
|
308
|
+
addEventListener(document1, type, handler, {
|
|
309
|
+
capture: true
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
if (this._refreshConfig().capture_copied_text) {
|
|
313
|
+
const copiedTextHandler = this._copiedTextHandler = (e)=>{
|
|
314
|
+
e = e || window1?.event;
|
|
315
|
+
try {
|
|
316
|
+
this._captureEvent(e, COPY_AUTOCAPTURE_EVENT);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
logger.error('Failed to capture clipboard event', error);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
addEventListener(document1, 'copy', copiedTextHandler, {
|
|
322
|
+
capture: true
|
|
323
|
+
});
|
|
324
|
+
addEventListener(document1, 'cut', copiedTextHandler, {
|
|
325
|
+
capture: true
|
|
326
|
+
});
|
|
327
|
+
addEventListener(document1, 'paste', copiedTextHandler, {
|
|
328
|
+
capture: true
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
_removeDomEventHandlers() {
|
|
333
|
+
const window1 = this._listenerWindow;
|
|
334
|
+
const document1 = this._listenerDocument;
|
|
335
|
+
this._clearPointer();
|
|
336
|
+
if (this._domEventHandler) {
|
|
337
|
+
window1?.removeEventListener('blur', this._domEventHandler);
|
|
338
|
+
each([
|
|
339
|
+
'pointerdown',
|
|
340
|
+
'pointermove',
|
|
341
|
+
'pointerup',
|
|
342
|
+
'pointercancel'
|
|
343
|
+
], (type)=>{
|
|
344
|
+
document1?.removeEventListener(type, this._domEventHandler, true);
|
|
345
|
+
});
|
|
346
|
+
document1?.removeEventListener('submit', this._domEventHandler, true);
|
|
347
|
+
document1?.removeEventListener('change', this._domEventHandler, true);
|
|
348
|
+
document1?.removeEventListener('click', this._domEventHandler, true);
|
|
349
|
+
this._domEventHandler = void 0;
|
|
350
|
+
}
|
|
351
|
+
if (this._copiedTextHandler) {
|
|
352
|
+
document1?.removeEventListener('copy', this._copiedTextHandler, true);
|
|
353
|
+
document1?.removeEventListener('cut', this._copiedTextHandler, true);
|
|
354
|
+
document1?.removeEventListener('paste', this._copiedTextHandler, true);
|
|
355
|
+
this._copiedTextHandler = void 0;
|
|
356
|
+
}
|
|
357
|
+
this._listenerWindow = void 0;
|
|
358
|
+
this._listenerDocument = void 0;
|
|
359
|
+
this._initialized = false;
|
|
360
|
+
}
|
|
361
|
+
startIfEnabled() {
|
|
362
|
+
if (!this.isEnabled) this._clearPointer();
|
|
363
|
+
if (!this._disposed && this._client && this.isEnabled && !this._initialized) {
|
|
364
|
+
this._addDomEventHandlers();
|
|
365
|
+
this._initialized = true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
onRemoteConfig(result) {
|
|
369
|
+
if (this._disposed) return;
|
|
370
|
+
this._hasReceivedConfigResponse = true;
|
|
371
|
+
if (!result.ok) return void this.startIfEnabled();
|
|
372
|
+
const response = result.config;
|
|
373
|
+
if (response.elementsChainAsString) this._elementsChainAsString = response.elementsChainAsString;
|
|
374
|
+
const optOut = response['autocapture_opt_out'];
|
|
375
|
+
if (isBoolean(optOut)) {
|
|
376
|
+
this._client?.kv.set(AUTOCAPTURE_DISABLED_SERVER_SIDE, optOut);
|
|
377
|
+
this._isDisabledServerSide = optOut;
|
|
378
|
+
}
|
|
379
|
+
this.startIfEnabled();
|
|
380
|
+
}
|
|
381
|
+
setElementSelectors(selectors, owner = this) {
|
|
382
|
+
if (this._disposed) return;
|
|
383
|
+
if (selectors.size) this._elementSelectorsByOwner.set(owner, new Set(selectors));
|
|
384
|
+
else this._elementSelectorsByOwner.delete(owner);
|
|
385
|
+
const combined = new Set();
|
|
386
|
+
this._elementSelectorsByOwner.forEach((contribution)=>{
|
|
387
|
+
contribution.forEach((selector)=>combined.add(selector));
|
|
388
|
+
});
|
|
389
|
+
this._elementSelectors = combined;
|
|
390
|
+
}
|
|
391
|
+
getElementSelectors(element, additionalElement) {
|
|
392
|
+
const elementSelectors = [];
|
|
393
|
+
this._elementSelectors?.forEach((selector)=>{
|
|
394
|
+
const matchedElements = getDocument()?.querySelectorAll(selector);
|
|
395
|
+
matchedElements?.forEach((matchedElement)=>{
|
|
396
|
+
if ((element === matchedElement || additionalElement === matchedElement) && !includes(elementSelectors, selector)) elementSelectors.push(selector);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
return elementSelectors;
|
|
400
|
+
}
|
|
401
|
+
get isEnabled() {
|
|
402
|
+
if (this._disposed) return false;
|
|
403
|
+
const persistedServerDisabled = this._client?.kv.get(AUTOCAPTURE_DISABLED_SERVER_SIDE);
|
|
404
|
+
const memoryDisabled = this._isDisabledServerSide;
|
|
405
|
+
const config = this._refreshConfig();
|
|
406
|
+
if (!config.remoteRequestsDisabled && !this._hasReceivedConfigResponse) return false;
|
|
407
|
+
const clientConfigOnly = config.remoteRequestsDisabled && !this._hasReceivedConfigResponse;
|
|
408
|
+
if (isNull(memoryDisabled) && !isBoolean(persistedServerDisabled) && !clientConfigOnly) return false;
|
|
409
|
+
const disabledServer = this._isDisabledServerSide ?? !!persistedServerDisabled;
|
|
410
|
+
const disabledClient = !config.enabled;
|
|
411
|
+
return !disabledClient && !disabledServer;
|
|
412
|
+
}
|
|
413
|
+
_captureEvent(e, eventName = '$autocapture', targetOverride) {
|
|
414
|
+
if (!this.isEnabled) return void this._clearPointer();
|
|
415
|
+
let target = targetOverride || getEventTarget(e);
|
|
416
|
+
if (isTextNode(target)) target = target.parentNode || null;
|
|
417
|
+
const config = this._compileUrlPatterns();
|
|
418
|
+
if ('$autocapture' === eventName && 'click' === e.type && e instanceof MouseEvent) {
|
|
419
|
+
if (!!config.rageclick && this.rageclicks?.isRageClick(e.clientX, e.clientY, e.timeStamp || new Date().getTime())) {
|
|
420
|
+
if (shouldCaptureRageclick(target, config.rageclick)) this._captureEvent(e, '$rageclick', targetOverride);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const isClipboardAutocapture = eventName === COPY_AUTOCAPTURE_EVENT;
|
|
424
|
+
const eventConfig = isClipboardAutocapture ? {
|
|
425
|
+
...config,
|
|
426
|
+
dom_event_allowlist: void 0
|
|
427
|
+
} : config;
|
|
428
|
+
if (target && shouldCaptureDomEvent(target, e, eventConfig, isClipboardAutocapture, isClipboardAutocapture ? [
|
|
429
|
+
'copy',
|
|
430
|
+
'cut',
|
|
431
|
+
'paste'
|
|
432
|
+
] : void 0, {
|
|
433
|
+
config: {
|
|
434
|
+
get_current_url: config.getCurrentUrl
|
|
435
|
+
}
|
|
436
|
+
})) {
|
|
437
|
+
const { props, explicitNoCapture, target: attributedTarget } = autocapturePropertiesForElement(target, {
|
|
438
|
+
e,
|
|
439
|
+
maskAllElementAttributes: config.maskAllElementAttributes,
|
|
440
|
+
maskAllText: config.maskAllText,
|
|
441
|
+
elementAttributeIgnoreList: config.element_attribute_ignorelist,
|
|
442
|
+
elementsChainAsString: this._elementsChainAsString,
|
|
443
|
+
disableCaptureUrlHashes: config.disableCaptureUrlHashes
|
|
444
|
+
});
|
|
445
|
+
if (explicitNoCapture) return false;
|
|
446
|
+
const elementSelectors = this.getElementSelectors(target, attributedTarget);
|
|
447
|
+
if (elementSelectors && elementSelectors.length > 0) props['$element_selectors'] = elementSelectors;
|
|
448
|
+
if (eventName === COPY_AUTOCAPTURE_EVENT) {
|
|
449
|
+
const clipType = e.type || 'clipboard';
|
|
450
|
+
if ('paste' !== clipType) {
|
|
451
|
+
const selectedText = getWindow()?.getSelection()?.toString();
|
|
452
|
+
const selectedContent = makeSafeText(selectedText);
|
|
453
|
+
if (!selectedContent) return false;
|
|
454
|
+
props['$selected_content'] = selectedContent;
|
|
455
|
+
props['$clipboard_text_length'] = selectedText?.length ?? 0;
|
|
456
|
+
}
|
|
457
|
+
props['$copy_type'] = clipType;
|
|
458
|
+
}
|
|
459
|
+
this._client?.capture(eventName, props);
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
isBrowserSupported() {
|
|
464
|
+
return isFunction(getDocument()?.querySelectorAll);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
export { Autocapture, AutocaptureExtension, autocapturePropertiesForElement, getAugmentPropertiesFromElement, getDefaultProperties, getPropertiesFromElement, previousElementSibling };
|
package/dist/config.js
CHANGED
package/dist/config.mjs
CHANGED
package/dist/constants.d.ts
CHANGED
|
@@ -17,3 +17,4 @@ export declare const PERSISTENCE_FEATURE_FLAG_PAYLOADS = "$feature_flag_payloads
|
|
|
17
17
|
export declare const PERSISTENCE_OVERRIDE_FEATURE_FLAGS = "$override_feature_flags";
|
|
18
18
|
export declare const PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS = "$override_feature_flag_payloads";
|
|
19
19
|
export declare const DOM_EVENT_VISIBILITYCHANGE = "visibilitychange";
|
|
20
|
+
export declare const AUTOCAPTURE_DISABLED_SERVER_SIDE = "$autocapture_disabled_server_side";
|
package/dist/constants.js
CHANGED
|
@@ -46,7 +46,9 @@ const PERSISTENCE_FEATURE_FLAG_PAYLOADS = '$feature_flag_payloads';
|
|
|
46
46
|
const PERSISTENCE_OVERRIDE_FEATURE_FLAGS = '$override_feature_flags';
|
|
47
47
|
const PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS = '$override_feature_flag_payloads';
|
|
48
48
|
const DOM_EVENT_VISIBILITYCHANGE = 'visibilitychange';
|
|
49
|
+
const AUTOCAPTURE_DISABLED_SERVER_SIDE = '$autocapture_disabled_server_side';
|
|
49
50
|
__webpack_require__.d(__webpack_exports__, {}, {
|
|
51
|
+
AUTOCAPTURE_DISABLED_SERVER_SIDE: AUTOCAPTURE_DISABLED_SERVER_SIDE,
|
|
50
52
|
DOM_EVENT_VISIBILITYCHANGE: DOM_EVENT_VISIBILITYCHANGE,
|
|
51
53
|
ENABLED_FEATURE_FLAGS: ENABLED_FEATURE_FLAGS,
|
|
52
54
|
FLAG_CALL_REPORTED: FLAG_CALL_REPORTED,
|
|
@@ -67,6 +69,7 @@ __webpack_require__.d(__webpack_exports__, {}, {
|
|
|
67
69
|
TOOLBAR_CONTAINER_CLASS: TOOLBAR_CONTAINER_CLASS,
|
|
68
70
|
TOOLBAR_ID: TOOLBAR_ID
|
|
69
71
|
});
|
|
72
|
+
exports.AUTOCAPTURE_DISABLED_SERVER_SIDE = __webpack_exports__.AUTOCAPTURE_DISABLED_SERVER_SIDE;
|
|
70
73
|
exports.DOM_EVENT_VISIBILITYCHANGE = __webpack_exports__.DOM_EVENT_VISIBILITYCHANGE;
|
|
71
74
|
exports.ENABLED_FEATURE_FLAGS = __webpack_exports__.ENABLED_FEATURE_FLAGS;
|
|
72
75
|
exports.FLAG_CALL_REPORTED = __webpack_exports__.FLAG_CALL_REPORTED;
|
|
@@ -87,6 +90,7 @@ exports.STORED_PERSON_PROPERTIES_KEY = __webpack_exports__.STORED_PERSON_PROPERT
|
|
|
87
90
|
exports.TOOLBAR_CONTAINER_CLASS = __webpack_exports__.TOOLBAR_CONTAINER_CLASS;
|
|
88
91
|
exports.TOOLBAR_ID = __webpack_exports__.TOOLBAR_ID;
|
|
89
92
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
93
|
+
"AUTOCAPTURE_DISABLED_SERVER_SIDE",
|
|
90
94
|
"DOM_EVENT_VISIBILITYCHANGE",
|
|
91
95
|
"ENABLED_FEATURE_FLAGS",
|
|
92
96
|
"FLAG_CALL_REPORTED",
|
package/dist/constants.mjs
CHANGED
|
@@ -17,4 +17,5 @@ const PERSISTENCE_FEATURE_FLAG_PAYLOADS = '$feature_flag_payloads';
|
|
|
17
17
|
const PERSISTENCE_OVERRIDE_FEATURE_FLAGS = '$override_feature_flags';
|
|
18
18
|
const PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS = '$override_feature_flag_payloads';
|
|
19
19
|
const DOM_EVENT_VISIBILITYCHANGE = 'visibilitychange';
|
|
20
|
-
|
|
20
|
+
const AUTOCAPTURE_DISABLED_SERVER_SIDE = '$autocapture_disabled_server_side';
|
|
21
|
+
export { AUTOCAPTURE_DISABLED_SERVER_SIDE, DOM_EVENT_VISIBILITYCHANGE, ENABLED_FEATURE_FLAGS, FLAG_CALL_REPORTED, FLAG_CALL_REPORTED_SESSION_ID, PERSISTENCE_ACTIVE_FEATURE_FLAGS, PERSISTENCE_EARLY_ACCESS_FEATURES, PERSISTENCE_FEATURE_FLAG_DETAILS, PERSISTENCE_FEATURE_FLAG_ERRORS, PERSISTENCE_FEATURE_FLAG_EVALUATED_AT, PERSISTENCE_FEATURE_FLAG_PAYLOADS, PERSISTENCE_FEATURE_FLAG_REQUEST_ID, PERSISTENCE_MINIMAL_FLAG_CALLED_EVENTS, PERSISTENCE_OVERRIDE_FEATURE_FLAGS, PERSISTENCE_OVERRIDE_FEATURE_FLAG_PAYLOADS, SDK_DIST_CHANNEL, STORED_GROUP_PROPERTIES_KEY, STORED_PERSON_PROPERTIES_KEY, TOOLBAR_CONTAINER_CLASS, TOOLBAR_ID };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RageclickConfig } from '@posthog/types';
|
|
2
|
+
export default class RageClick {
|
|
3
|
+
clicks: {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
}[];
|
|
8
|
+
thresholdPx: number;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
clickCount: number;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
constructor(rageclickConfig: RageclickConfig | boolean);
|
|
13
|
+
isRageClick(x: number, y: number, timestamp: number): boolean;
|
|
14
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
(()=>{
|
|
16
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
17
|
+
})();
|
|
18
|
+
(()=>{
|
|
19
|
+
__webpack_require__.r = (exports1)=>{
|
|
20
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
21
|
+
value: 'Module'
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
24
|
+
value: true
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
})();
|
|
28
|
+
var __webpack_exports__ = {};
|
|
29
|
+
__webpack_require__.r(__webpack_exports__);
|
|
30
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
31
|
+
default: ()=>RageClick
|
|
32
|
+
});
|
|
33
|
+
const core_namespaceObject = require("@posthog/core");
|
|
34
|
+
const DEFAULT_THRESHOLD_PX = 30;
|
|
35
|
+
const DEFAULT_TIMEOUT_MS = 1000;
|
|
36
|
+
const DEFAULT_CLICK_COUNT = 3;
|
|
37
|
+
class RageClick {
|
|
38
|
+
constructor(rageclickConfig){
|
|
39
|
+
this.disabled = false === rageclickConfig;
|
|
40
|
+
const conf = (0, core_namespaceObject.isObject)(rageclickConfig) ? rageclickConfig : {};
|
|
41
|
+
this.thresholdPx = conf.threshold_px || DEFAULT_THRESHOLD_PX;
|
|
42
|
+
this.timeoutMs = conf.timeout_ms || DEFAULT_TIMEOUT_MS;
|
|
43
|
+
this.clickCount = conf.click_count || DEFAULT_CLICK_COUNT;
|
|
44
|
+
this.clicks = [];
|
|
45
|
+
}
|
|
46
|
+
isRageClick(x, y, timestamp) {
|
|
47
|
+
if (this.disabled) return false;
|
|
48
|
+
const lastClick = this.clicks[this.clicks.length - 1];
|
|
49
|
+
if (lastClick && Math.abs(x - lastClick.x) + Math.abs(y - lastClick.y) < this.thresholdPx && timestamp - lastClick.timestamp < this.timeoutMs) {
|
|
50
|
+
this.clicks.push({
|
|
51
|
+
x,
|
|
52
|
+
y,
|
|
53
|
+
timestamp
|
|
54
|
+
});
|
|
55
|
+
if (this.clicks.length === this.clickCount) return true;
|
|
56
|
+
} else this.clicks = [
|
|
57
|
+
{
|
|
58
|
+
x,
|
|
59
|
+
y,
|
|
60
|
+
timestamp
|
|
61
|
+
}
|
|
62
|
+
];
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports["default"] = __webpack_exports__["default"];
|
|
67
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
68
|
+
"default"
|
|
69
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
70
|
+
Object.defineProperty(exports, '__esModule', {
|
|
71
|
+
value: true
|
|
72
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isObject } from "@posthog/core";
|
|
2
|
+
const DEFAULT_THRESHOLD_PX = 30;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 1000;
|
|
4
|
+
const DEFAULT_CLICK_COUNT = 3;
|
|
5
|
+
class RageClick {
|
|
6
|
+
constructor(rageclickConfig){
|
|
7
|
+
this.disabled = false === rageclickConfig;
|
|
8
|
+
const conf = isObject(rageclickConfig) ? rageclickConfig : {};
|
|
9
|
+
this.thresholdPx = conf.threshold_px || DEFAULT_THRESHOLD_PX;
|
|
10
|
+
this.timeoutMs = conf.timeout_ms || DEFAULT_TIMEOUT_MS;
|
|
11
|
+
this.clickCount = conf.click_count || DEFAULT_CLICK_COUNT;
|
|
12
|
+
this.clicks = [];
|
|
13
|
+
}
|
|
14
|
+
isRageClick(x, y, timestamp) {
|
|
15
|
+
if (this.disabled) return false;
|
|
16
|
+
const lastClick = this.clicks[this.clicks.length - 1];
|
|
17
|
+
if (lastClick && Math.abs(x - lastClick.x) + Math.abs(y - lastClick.y) < this.thresholdPx && timestamp - lastClick.timestamp < this.timeoutMs) {
|
|
18
|
+
this.clicks.push({
|
|
19
|
+
x,
|
|
20
|
+
y,
|
|
21
|
+
timestamp
|
|
22
|
+
});
|
|
23
|
+
if (this.clicks.length === this.clickCount) return true;
|
|
24
|
+
} else this.clicks = [
|
|
25
|
+
{
|
|
26
|
+
x,
|
|
27
|
+
y,
|
|
28
|
+
timestamp
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export default RageClick;
|
|
@@ -103,7 +103,12 @@ class ActionMatcher {
|
|
|
103
103
|
}
|
|
104
104
|
_checkStepUrl(event, step) {
|
|
105
105
|
if (step?.url) {
|
|
106
|
-
|
|
106
|
+
let eventUrl = event?.properties?.$current_url;
|
|
107
|
+
if (!event?.properties || !('$current_url' in event.properties)) try {
|
|
108
|
+
eventUrl = this._host?.getActionUrl?.();
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
107
112
|
if (!eventUrl || 'string' != typeof eventUrl) return false;
|
|
108
113
|
if (!(0, elements_chain_utils_js_namespaceObject.matchString)(eventUrl, step.url, step.url_matching || 'contains')) return false;
|
|
109
114
|
}
|
|
@@ -71,7 +71,12 @@ class ActionMatcher {
|
|
|
71
71
|
}
|
|
72
72
|
_checkStepUrl(event, step) {
|
|
73
73
|
if (step?.url) {
|
|
74
|
-
|
|
74
|
+
let eventUrl = event?.properties?.$current_url;
|
|
75
|
+
if (!event?.properties || !('$current_url' in event.properties)) try {
|
|
76
|
+
eventUrl = this._host?.getActionUrl?.();
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
75
80
|
if (!eventUrl || 'string' != typeof eventUrl) return false;
|
|
76
81
|
if (!matchString(eventUrl, step.url, step.url_matching || 'contains')) return false;
|
|
77
82
|
}
|