mixpanel-browser 2.59.0 → 2.60.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/CHANGELOG.md +5 -1
- package/README.md +1 -1
- package/dist/mixpanel-core.cjs.js +215 -49
- package/dist/mixpanel-recorder.js +1 -1
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +215 -49
- package/dist/mixpanel.amd.js +215 -49
- package/dist/mixpanel.cjs.js +215 -49
- package/dist/mixpanel.globals.js +215 -49
- package/dist/mixpanel.min.js +138 -134
- package/dist/mixpanel.module.js +215 -49
- package/dist/mixpanel.umd.js +215 -49
- package/package.json +1 -1
- package/src/autocapture/index.js +80 -9
- package/src/autocapture/utils.js +129 -38
- package/src/config.js +1 -1
- package/src/mixpanel-persistence.js +6 -2
package/src/autocapture/utils.js
CHANGED
|
@@ -70,7 +70,7 @@ function getPreviousElementSibling(el) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function getPropertiesFromElement(el) {
|
|
73
|
+
function getPropertiesFromElement(el, ev, blockAttrsSet, extraAttrs, allowElementCallback, allowSelectors) {
|
|
74
74
|
var props = {
|
|
75
75
|
'$classes': getClassName(el).split(' '),
|
|
76
76
|
'$tag_name': el.tagName.toLowerCase()
|
|
@@ -80,9 +80,9 @@ function getPropertiesFromElement(el) {
|
|
|
80
80
|
props['$id'] = elId;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
if (
|
|
84
|
-
_.each(TRACKED_ATTRS, function(attr) {
|
|
85
|
-
if (el.hasAttribute(attr)) {
|
|
83
|
+
if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors)) {
|
|
84
|
+
_.each(TRACKED_ATTRS.concat(extraAttrs), function(attr) {
|
|
85
|
+
if (el.hasAttribute(attr) && !blockAttrsSet[attr]) {
|
|
86
86
|
var attrVal = el.getAttribute(attr);
|
|
87
87
|
if (shouldTrackValue(attrVal)) {
|
|
88
88
|
props['$attr-' + attr] = attrVal;
|
|
@@ -106,8 +106,21 @@ function getPropertiesFromElement(el) {
|
|
|
106
106
|
return props;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function getPropsForDOMEvent(ev,
|
|
110
|
-
|
|
109
|
+
function getPropsForDOMEvent(ev, config) {
|
|
110
|
+
var allowElementCallback = config.allowElementCallback;
|
|
111
|
+
var allowSelectors = config.allowSelectors || [];
|
|
112
|
+
var blockAttrs = config.blockAttrs || [];
|
|
113
|
+
var blockElementCallback = config.blockElementCallback;
|
|
114
|
+
var blockSelectors = config.blockSelectors || [];
|
|
115
|
+
var captureTextContent = config.captureTextContent || false;
|
|
116
|
+
var captureExtraAttrs = config.captureExtraAttrs || [];
|
|
117
|
+
|
|
118
|
+
// convert array to set every time, as the config may have changed
|
|
119
|
+
var blockAttrsSet = {};
|
|
120
|
+
_.each(blockAttrs, function(attr) {
|
|
121
|
+
blockAttrsSet[attr] = true;
|
|
122
|
+
});
|
|
123
|
+
|
|
111
124
|
var props = null;
|
|
112
125
|
|
|
113
126
|
var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
|
|
@@ -115,7 +128,11 @@ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
|
115
128
|
target = target.parentNode;
|
|
116
129
|
}
|
|
117
130
|
|
|
118
|
-
if (
|
|
131
|
+
if (
|
|
132
|
+
shouldTrackDomEvent(target, ev) &&
|
|
133
|
+
isElementAllowed(target, ev, allowElementCallback, allowSelectors) &&
|
|
134
|
+
!isElementBlocked(target, ev, blockElementCallback, blockSelectors)
|
|
135
|
+
) {
|
|
119
136
|
var targetElementList = [target];
|
|
120
137
|
var curEl = target;
|
|
121
138
|
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
@@ -126,37 +143,20 @@ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
|
126
143
|
var elementsJson = [];
|
|
127
144
|
var href, explicitNoTrack = false;
|
|
128
145
|
_.each(targetElementList, function(el) {
|
|
129
|
-
var
|
|
146
|
+
var shouldTrackDetails = shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors);
|
|
130
147
|
|
|
131
148
|
// if the element or a parent element is an anchor tag
|
|
132
149
|
// include the href as a property
|
|
133
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
150
|
+
if (!blockAttrsSet['href'] && el.tagName.toLowerCase() === 'a') {
|
|
134
151
|
href = el.getAttribute('href');
|
|
135
|
-
href =
|
|
152
|
+
href = shouldTrackDetails && shouldTrackValue(href) && href;
|
|
136
153
|
}
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
_.each(OPT_OUT_CLASSES, function(cls) {
|
|
141
|
-
if (classes[cls]) {
|
|
142
|
-
explicitNoTrack = true;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (!explicitNoTrack) {
|
|
147
|
-
// programmatically prevent tracking of elements that match CSS selectors
|
|
148
|
-
_.each(blockSelectors, function(sel) {
|
|
149
|
-
try {
|
|
150
|
-
if (el['matches'](sel)) {
|
|
151
|
-
explicitNoTrack = true;
|
|
152
|
-
}
|
|
153
|
-
} catch (err) {
|
|
154
|
-
logger.critical('Error while checking selector: ' + sel, err);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
155
|
+
if (isElementBlocked(el, ev, blockElementCallback, blockSelectors)) {
|
|
156
|
+
explicitNoTrack = true;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
elementsJson.push(getPropertiesFromElement(el));
|
|
159
|
+
elementsJson.push(getPropertiesFromElement(el, ev, blockAttrsSet, captureExtraAttrs, allowElementCallback, allowSelectors));
|
|
160
160
|
}, this);
|
|
161
161
|
|
|
162
162
|
if (!explicitNoTrack) {
|
|
@@ -170,9 +170,17 @@ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
|
170
170
|
'$viewportHeight': Math.max(docElement['clientHeight'], window['innerHeight'] || 0),
|
|
171
171
|
'$viewportWidth': Math.max(docElement['clientWidth'], window['innerWidth'] || 0)
|
|
172
172
|
};
|
|
173
|
+
_.each(captureExtraAttrs, function(attr) {
|
|
174
|
+
if (!blockAttrsSet[attr] && target.hasAttribute(attr)) {
|
|
175
|
+
var attrVal = target.getAttribute(attr);
|
|
176
|
+
if (shouldTrackValue(attrVal)) {
|
|
177
|
+
props['$el_attr__' + attr] = attrVal;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
173
181
|
|
|
174
182
|
if (captureTextContent) {
|
|
175
|
-
elementText = getSafeText(target);
|
|
183
|
+
elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
|
|
176
184
|
if (elementText && elementText.length) {
|
|
177
185
|
props['$el_text'] = elementText;
|
|
178
186
|
}
|
|
@@ -188,14 +196,22 @@ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
|
188
196
|
}
|
|
189
197
|
// prioritize text content from "real" click target if different from original target
|
|
190
198
|
if (captureTextContent) {
|
|
191
|
-
var elementText = getSafeText(target);
|
|
199
|
+
var elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
|
|
192
200
|
if (elementText && elementText.length) {
|
|
193
201
|
props['$el_text'] = elementText;
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
if (target) {
|
|
198
|
-
|
|
206
|
+
// target may have been recalculated; check allowlists and blocklists again
|
|
207
|
+
if (
|
|
208
|
+
!isElementAllowed(target, ev, allowElementCallback, allowSelectors) ||
|
|
209
|
+
isElementBlocked(target, ev, blockElementCallback, blockSelectors)
|
|
210
|
+
) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
var targetProps = getPropertiesFromElement(target, ev, blockAttrsSet, captureExtraAttrs, allowElementCallback, allowSelectors);
|
|
199
215
|
props['$target'] = targetProps;
|
|
200
216
|
// pull up more props onto main event props
|
|
201
217
|
props['$el_classes'] = targetProps['$classes'];
|
|
@@ -211,19 +227,20 @@ function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
|
|
214
|
-
|
|
230
|
+
/**
|
|
215
231
|
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
216
232
|
* Concats textContent of each of the element's text node children; this avoids potential
|
|
217
233
|
* collection of sensitive data that could happen if we used element.textContent and the
|
|
218
234
|
* element had sensitive child elements, since element.textContent includes child content.
|
|
219
235
|
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
220
236
|
* @param {Element} el - element to get the text of
|
|
237
|
+
* @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
|
|
221
238
|
* @returns {string} the element's direct text content
|
|
222
239
|
*/
|
|
223
|
-
function getSafeText(el) {
|
|
240
|
+
function getSafeText(el, ev, allowElementCallback, allowSelectors) {
|
|
224
241
|
var elText = '';
|
|
225
242
|
|
|
226
|
-
if (
|
|
243
|
+
if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) && el.childNodes && el.childNodes.length) {
|
|
227
244
|
_.each(el.childNodes, function(child) {
|
|
228
245
|
if (isTextNode(child) && child.textContent) {
|
|
229
246
|
elText += _.trim(child.textContent)
|
|
@@ -262,6 +279,75 @@ function guessRealClickTarget(ev) {
|
|
|
262
279
|
return target;
|
|
263
280
|
}
|
|
264
281
|
|
|
282
|
+
function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
283
|
+
if (allowElementCallback) {
|
|
284
|
+
try {
|
|
285
|
+
if (!allowElementCallback(el, ev)) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
} catch (err) {
|
|
289
|
+
logger.critical('Error while checking element in allowElementCallback', err);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!allowSelectors.length) {
|
|
295
|
+
// no allowlist; all elements are fair game
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (var i = 0; i < allowSelectors.length; i++) {
|
|
300
|
+
var sel = allowSelectors[i];
|
|
301
|
+
try {
|
|
302
|
+
if (el['matches'](sel)) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
logger.critical('Error while checking selector: ' + sel, err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
313
|
+
var i;
|
|
314
|
+
|
|
315
|
+
if (blockElementCallback) {
|
|
316
|
+
try {
|
|
317
|
+
if (blockElementCallback(el, ev)) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
} catch (err) {
|
|
321
|
+
logger.critical('Error while checking element in blockElementCallback', err);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (blockSelectors && blockSelectors.length) {
|
|
327
|
+
// programmatically prevent tracking of elements that match CSS selectors
|
|
328
|
+
for (i = 0; i < blockSelectors.length; i++) {
|
|
329
|
+
var sel = blockSelectors[i];
|
|
330
|
+
try {
|
|
331
|
+
if (el['matches'](sel)) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
} catch (err) {
|
|
335
|
+
logger.critical('Error while checking selector: ' + sel, err);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// allow users to programmatically prevent tracking of elements by adding default classes such as 'mp-no-track'
|
|
341
|
+
var classes = getClasses(el);
|
|
342
|
+
for (i = 0; i < OPT_OUT_CLASSES.length; i++) {
|
|
343
|
+
if (classes[OPT_OUT_CLASSES[i]]) {
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
265
351
|
/*
|
|
266
352
|
* Check whether a DOM node has nodeType Node.ELEMENT_NODE
|
|
267
353
|
* @param {Node} node - node to check
|
|
@@ -336,11 +422,16 @@ function shouldTrackDomEvent(el, ev) {
|
|
|
336
422
|
* Check whether a DOM element should be "tracked" or if it may contain sensitive data
|
|
337
423
|
* using a variety of heuristics.
|
|
338
424
|
* @param {Element} el - element to check
|
|
425
|
+
* @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
|
|
339
426
|
* @returns {boolean} whether the element should be tracked
|
|
340
427
|
*/
|
|
341
|
-
function
|
|
428
|
+
function shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) {
|
|
342
429
|
var i;
|
|
343
430
|
|
|
431
|
+
if (!isElementAllowed(el, ev, allowElementCallback, allowSelectors)) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
|
|
344
435
|
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
345
436
|
var classes = getClasses(curEl);
|
|
346
437
|
for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
|
|
@@ -428,7 +519,7 @@ export {
|
|
|
428
519
|
getSafeText,
|
|
429
520
|
logger,
|
|
430
521
|
minDOMApisSupported,
|
|
431
|
-
shouldTrackDomEvent,
|
|
522
|
+
shouldTrackDomEvent, shouldTrackElementDetails, shouldTrackValue,
|
|
432
523
|
EV_CHANGE, EV_CLICK, EV_HASHCHANGE, EV_MP_LOCATION_CHANGE, EV_POPSTATE,
|
|
433
524
|
EV_SCROLLEND, EV_SUBMIT
|
|
434
525
|
};
|
package/src/config.js
CHANGED
|
@@ -345,8 +345,12 @@ MixpanelPersistence.prototype._add_to_people_queue = function(queue, data) {
|
|
|
345
345
|
if (!(k in union_q)) {
|
|
346
346
|
union_q[k] = [];
|
|
347
347
|
}
|
|
348
|
-
//
|
|
349
|
-
|
|
348
|
+
// Prevent duplicate values
|
|
349
|
+
_.each(v, function(item) {
|
|
350
|
+
if (!_.include(union_q[k], item)) {
|
|
351
|
+
union_q[k].push(item);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
350
354
|
}
|
|
351
355
|
});
|
|
352
356
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|