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/dist/mixpanel.umd.js
CHANGED
|
@@ -4513,7 +4513,7 @@
|
|
|
4513
4513
|
|
|
4514
4514
|
var Config = {
|
|
4515
4515
|
DEBUG: false,
|
|
4516
|
-
LIB_VERSION: '2.
|
|
4516
|
+
LIB_VERSION: '2.60.0'
|
|
4517
4517
|
};
|
|
4518
4518
|
|
|
4519
4519
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -8268,7 +8268,7 @@
|
|
|
8268
8268
|
}
|
|
8269
8269
|
}
|
|
8270
8270
|
|
|
8271
|
-
function getPropertiesFromElement(el) {
|
|
8271
|
+
function getPropertiesFromElement(el, ev, blockAttrsSet, extraAttrs, allowElementCallback, allowSelectors) {
|
|
8272
8272
|
var props = {
|
|
8273
8273
|
'$classes': getClassName(el).split(' '),
|
|
8274
8274
|
'$tag_name': el.tagName.toLowerCase()
|
|
@@ -8278,9 +8278,9 @@
|
|
|
8278
8278
|
props['$id'] = elId;
|
|
8279
8279
|
}
|
|
8280
8280
|
|
|
8281
|
-
if (
|
|
8282
|
-
_.each(TRACKED_ATTRS, function(attr) {
|
|
8283
|
-
if (el.hasAttribute(attr)) {
|
|
8281
|
+
if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors)) {
|
|
8282
|
+
_.each(TRACKED_ATTRS.concat(extraAttrs), function(attr) {
|
|
8283
|
+
if (el.hasAttribute(attr) && !blockAttrsSet[attr]) {
|
|
8284
8284
|
var attrVal = el.getAttribute(attr);
|
|
8285
8285
|
if (shouldTrackValue(attrVal)) {
|
|
8286
8286
|
props['$attr-' + attr] = attrVal;
|
|
@@ -8304,8 +8304,21 @@
|
|
|
8304
8304
|
return props;
|
|
8305
8305
|
}
|
|
8306
8306
|
|
|
8307
|
-
function getPropsForDOMEvent(ev,
|
|
8308
|
-
|
|
8307
|
+
function getPropsForDOMEvent(ev, config) {
|
|
8308
|
+
var allowElementCallback = config.allowElementCallback;
|
|
8309
|
+
var allowSelectors = config.allowSelectors || [];
|
|
8310
|
+
var blockAttrs = config.blockAttrs || [];
|
|
8311
|
+
var blockElementCallback = config.blockElementCallback;
|
|
8312
|
+
var blockSelectors = config.blockSelectors || [];
|
|
8313
|
+
var captureTextContent = config.captureTextContent || false;
|
|
8314
|
+
var captureExtraAttrs = config.captureExtraAttrs || [];
|
|
8315
|
+
|
|
8316
|
+
// convert array to set every time, as the config may have changed
|
|
8317
|
+
var blockAttrsSet = {};
|
|
8318
|
+
_.each(blockAttrs, function(attr) {
|
|
8319
|
+
blockAttrsSet[attr] = true;
|
|
8320
|
+
});
|
|
8321
|
+
|
|
8309
8322
|
var props = null;
|
|
8310
8323
|
|
|
8311
8324
|
var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
|
|
@@ -8313,7 +8326,11 @@
|
|
|
8313
8326
|
target = target.parentNode;
|
|
8314
8327
|
}
|
|
8315
8328
|
|
|
8316
|
-
if (
|
|
8329
|
+
if (
|
|
8330
|
+
shouldTrackDomEvent(target, ev) &&
|
|
8331
|
+
isElementAllowed(target, ev, allowElementCallback, allowSelectors) &&
|
|
8332
|
+
!isElementBlocked(target, ev, blockElementCallback, blockSelectors)
|
|
8333
|
+
) {
|
|
8317
8334
|
var targetElementList = [target];
|
|
8318
8335
|
var curEl = target;
|
|
8319
8336
|
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
@@ -8324,37 +8341,20 @@
|
|
|
8324
8341
|
var elementsJson = [];
|
|
8325
8342
|
var href, explicitNoTrack = false;
|
|
8326
8343
|
_.each(targetElementList, function(el) {
|
|
8327
|
-
var
|
|
8344
|
+
var shouldTrackDetails = shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors);
|
|
8328
8345
|
|
|
8329
8346
|
// if the element or a parent element is an anchor tag
|
|
8330
8347
|
// include the href as a property
|
|
8331
|
-
if (el.tagName.toLowerCase() === 'a') {
|
|
8348
|
+
if (!blockAttrsSet['href'] && el.tagName.toLowerCase() === 'a') {
|
|
8332
8349
|
href = el.getAttribute('href');
|
|
8333
|
-
href =
|
|
8350
|
+
href = shouldTrackDetails && shouldTrackValue(href) && href;
|
|
8334
8351
|
}
|
|
8335
8352
|
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
_.each(OPT_OUT_CLASSES, function(cls) {
|
|
8339
|
-
if (classes[cls]) {
|
|
8340
|
-
explicitNoTrack = true;
|
|
8341
|
-
}
|
|
8342
|
-
});
|
|
8343
|
-
|
|
8344
|
-
if (!explicitNoTrack) {
|
|
8345
|
-
// programmatically prevent tracking of elements that match CSS selectors
|
|
8346
|
-
_.each(blockSelectors, function(sel) {
|
|
8347
|
-
try {
|
|
8348
|
-
if (el['matches'](sel)) {
|
|
8349
|
-
explicitNoTrack = true;
|
|
8350
|
-
}
|
|
8351
|
-
} catch (err) {
|
|
8352
|
-
logger.critical('Error while checking selector: ' + sel, err);
|
|
8353
|
-
}
|
|
8354
|
-
});
|
|
8353
|
+
if (isElementBlocked(el, ev, blockElementCallback, blockSelectors)) {
|
|
8354
|
+
explicitNoTrack = true;
|
|
8355
8355
|
}
|
|
8356
8356
|
|
|
8357
|
-
elementsJson.push(getPropertiesFromElement(el));
|
|
8357
|
+
elementsJson.push(getPropertiesFromElement(el, ev, blockAttrsSet, captureExtraAttrs, allowElementCallback, allowSelectors));
|
|
8358
8358
|
}, this);
|
|
8359
8359
|
|
|
8360
8360
|
if (!explicitNoTrack) {
|
|
@@ -8368,9 +8368,17 @@
|
|
|
8368
8368
|
'$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
|
|
8369
8369
|
'$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
|
|
8370
8370
|
};
|
|
8371
|
+
_.each(captureExtraAttrs, function(attr) {
|
|
8372
|
+
if (!blockAttrsSet[attr] && target.hasAttribute(attr)) {
|
|
8373
|
+
var attrVal = target.getAttribute(attr);
|
|
8374
|
+
if (shouldTrackValue(attrVal)) {
|
|
8375
|
+
props['$el_attr__' + attr] = attrVal;
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
});
|
|
8371
8379
|
|
|
8372
8380
|
if (captureTextContent) {
|
|
8373
|
-
elementText = getSafeText(target);
|
|
8381
|
+
elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
|
|
8374
8382
|
if (elementText && elementText.length) {
|
|
8375
8383
|
props['$el_text'] = elementText;
|
|
8376
8384
|
}
|
|
@@ -8386,14 +8394,22 @@
|
|
|
8386
8394
|
}
|
|
8387
8395
|
// prioritize text content from "real" click target if different from original target
|
|
8388
8396
|
if (captureTextContent) {
|
|
8389
|
-
var elementText = getSafeText(target);
|
|
8397
|
+
var elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
|
|
8390
8398
|
if (elementText && elementText.length) {
|
|
8391
8399
|
props['$el_text'] = elementText;
|
|
8392
8400
|
}
|
|
8393
8401
|
}
|
|
8394
8402
|
|
|
8395
8403
|
if (target) {
|
|
8396
|
-
|
|
8404
|
+
// target may have been recalculated; check allowlists and blocklists again
|
|
8405
|
+
if (
|
|
8406
|
+
!isElementAllowed(target, ev, allowElementCallback, allowSelectors) ||
|
|
8407
|
+
isElementBlocked(target, ev, blockElementCallback, blockSelectors)
|
|
8408
|
+
) {
|
|
8409
|
+
return null;
|
|
8410
|
+
}
|
|
8411
|
+
|
|
8412
|
+
var targetProps = getPropertiesFromElement(target, ev, blockAttrsSet, captureExtraAttrs, allowElementCallback, allowSelectors);
|
|
8397
8413
|
props['$target'] = targetProps;
|
|
8398
8414
|
// pull up more props onto main event props
|
|
8399
8415
|
props['$el_classes'] = targetProps['$classes'];
|
|
@@ -8409,19 +8425,20 @@
|
|
|
8409
8425
|
}
|
|
8410
8426
|
|
|
8411
8427
|
|
|
8412
|
-
|
|
8428
|
+
/**
|
|
8413
8429
|
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
8414
8430
|
* Concats textContent of each of the element's text node children; this avoids potential
|
|
8415
8431
|
* collection of sensitive data that could happen if we used element.textContent and the
|
|
8416
8432
|
* element had sensitive child elements, since element.textContent includes child content.
|
|
8417
8433
|
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
8418
8434
|
* @param {Element} el - element to get the text of
|
|
8435
|
+
* @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
|
|
8419
8436
|
* @returns {string} the element's direct text content
|
|
8420
8437
|
*/
|
|
8421
|
-
function getSafeText(el) {
|
|
8438
|
+
function getSafeText(el, ev, allowElementCallback, allowSelectors) {
|
|
8422
8439
|
var elText = '';
|
|
8423
8440
|
|
|
8424
|
-
if (
|
|
8441
|
+
if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) && el.childNodes && el.childNodes.length) {
|
|
8425
8442
|
_.each(el.childNodes, function(child) {
|
|
8426
8443
|
if (isTextNode(child) && child.textContent) {
|
|
8427
8444
|
elText += _.trim(child.textContent)
|
|
@@ -8460,6 +8477,75 @@
|
|
|
8460
8477
|
return target;
|
|
8461
8478
|
}
|
|
8462
8479
|
|
|
8480
|
+
function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
8481
|
+
if (allowElementCallback) {
|
|
8482
|
+
try {
|
|
8483
|
+
if (!allowElementCallback(el, ev)) {
|
|
8484
|
+
return false;
|
|
8485
|
+
}
|
|
8486
|
+
} catch (err) {
|
|
8487
|
+
logger.critical('Error while checking element in allowElementCallback', err);
|
|
8488
|
+
return false;
|
|
8489
|
+
}
|
|
8490
|
+
}
|
|
8491
|
+
|
|
8492
|
+
if (!allowSelectors.length) {
|
|
8493
|
+
// no allowlist; all elements are fair game
|
|
8494
|
+
return true;
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8497
|
+
for (var i = 0; i < allowSelectors.length; i++) {
|
|
8498
|
+
var sel = allowSelectors[i];
|
|
8499
|
+
try {
|
|
8500
|
+
if (el['matches'](sel)) {
|
|
8501
|
+
return true;
|
|
8502
|
+
}
|
|
8503
|
+
} catch (err) {
|
|
8504
|
+
logger.critical('Error while checking selector: ' + sel, err);
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
return false;
|
|
8508
|
+
}
|
|
8509
|
+
|
|
8510
|
+
function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
8511
|
+
var i;
|
|
8512
|
+
|
|
8513
|
+
if (blockElementCallback) {
|
|
8514
|
+
try {
|
|
8515
|
+
if (blockElementCallback(el, ev)) {
|
|
8516
|
+
return true;
|
|
8517
|
+
}
|
|
8518
|
+
} catch (err) {
|
|
8519
|
+
logger.critical('Error while checking element in blockElementCallback', err);
|
|
8520
|
+
return true;
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8523
|
+
|
|
8524
|
+
if (blockSelectors && blockSelectors.length) {
|
|
8525
|
+
// programmatically prevent tracking of elements that match CSS selectors
|
|
8526
|
+
for (i = 0; i < blockSelectors.length; i++) {
|
|
8527
|
+
var sel = blockSelectors[i];
|
|
8528
|
+
try {
|
|
8529
|
+
if (el['matches'](sel)) {
|
|
8530
|
+
return true;
|
|
8531
|
+
}
|
|
8532
|
+
} catch (err) {
|
|
8533
|
+
logger.critical('Error while checking selector: ' + sel, err);
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8536
|
+
}
|
|
8537
|
+
|
|
8538
|
+
// allow users to programmatically prevent tracking of elements by adding default classes such as 'mp-no-track'
|
|
8539
|
+
var classes = getClasses(el);
|
|
8540
|
+
for (i = 0; i < OPT_OUT_CLASSES.length; i++) {
|
|
8541
|
+
if (classes[OPT_OUT_CLASSES[i]]) {
|
|
8542
|
+
return true;
|
|
8543
|
+
}
|
|
8544
|
+
}
|
|
8545
|
+
|
|
8546
|
+
return false;
|
|
8547
|
+
}
|
|
8548
|
+
|
|
8463
8549
|
/*
|
|
8464
8550
|
* Check whether a DOM node has nodeType Node.ELEMENT_NODE
|
|
8465
8551
|
* @param {Node} node - node to check
|
|
@@ -8534,11 +8620,16 @@
|
|
|
8534
8620
|
* Check whether a DOM element should be "tracked" or if it may contain sensitive data
|
|
8535
8621
|
* using a variety of heuristics.
|
|
8536
8622
|
* @param {Element} el - element to check
|
|
8623
|
+
* @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
|
|
8537
8624
|
* @returns {boolean} whether the element should be tracked
|
|
8538
8625
|
*/
|
|
8539
|
-
function
|
|
8626
|
+
function shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) {
|
|
8540
8627
|
var i;
|
|
8541
8628
|
|
|
8629
|
+
if (!isElementAllowed(el, ev, allowElementCallback, allowSelectors)) {
|
|
8630
|
+
return false;
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8542
8633
|
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
8543
8634
|
var classes = getClasses(curEl);
|
|
8544
8635
|
for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
|
|
@@ -8628,9 +8719,17 @@
|
|
|
8628
8719
|
var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
|
|
8629
8720
|
var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
|
|
8630
8721
|
|
|
8722
|
+
var CONFIG_ALLOW_ELEMENT_CALLBACK = 'allow_element_callback';
|
|
8723
|
+
var CONFIG_ALLOW_SELECTORS = 'allow_selectors';
|
|
8724
|
+
var CONFIG_ALLOW_URL_REGEXES = 'allow_url_regexes';
|
|
8725
|
+
var CONFIG_BLOCK_ATTRS = 'block_attrs';
|
|
8726
|
+
var CONFIG_BLOCK_ELEMENT_CALLBACK = 'block_element_callback';
|
|
8631
8727
|
var CONFIG_BLOCK_SELECTORS = 'block_selectors';
|
|
8632
8728
|
var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
|
|
8729
|
+
var CONFIG_CAPTURE_EXTRA_ATTRS = 'capture_extra_attrs';
|
|
8633
8730
|
var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
|
|
8731
|
+
var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
|
|
8732
|
+
var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
|
|
8634
8733
|
var CONFIG_TRACK_CLICK = 'click';
|
|
8635
8734
|
var CONFIG_TRACK_INPUT = 'input';
|
|
8636
8735
|
var CONFIG_TRACK_PAGEVIEW = 'pageview';
|
|
@@ -8638,7 +8737,16 @@
|
|
|
8638
8737
|
var CONFIG_TRACK_SUBMIT = 'submit';
|
|
8639
8738
|
|
|
8640
8739
|
var CONFIG_DEFAULTS = {};
|
|
8740
|
+
CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
|
|
8741
|
+
CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
|
|
8742
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
|
|
8743
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
|
|
8744
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
|
|
8745
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
|
|
8746
|
+
CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
|
|
8641
8747
|
CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
|
|
8748
|
+
CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
|
|
8749
|
+
CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
|
|
8642
8750
|
CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
|
|
8643
8751
|
CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
|
|
8644
8752
|
CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
|
|
@@ -8693,13 +8801,37 @@
|
|
|
8693
8801
|
};
|
|
8694
8802
|
|
|
8695
8803
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
8804
|
+
var i;
|
|
8805
|
+
var currentUrl = _.info.currentUrl();
|
|
8806
|
+
|
|
8807
|
+
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
8808
|
+
if (allowUrlRegexes.length) {
|
|
8809
|
+
// we're using an allowlist, only track if current URL matches
|
|
8810
|
+
var allowed = false;
|
|
8811
|
+
for (i = 0; i < allowUrlRegexes.length; i++) {
|
|
8812
|
+
var allowRegex = allowUrlRegexes[i];
|
|
8813
|
+
try {
|
|
8814
|
+
if (currentUrl.match(allowRegex)) {
|
|
8815
|
+
allowed = true;
|
|
8816
|
+
break;
|
|
8817
|
+
}
|
|
8818
|
+
} catch (err) {
|
|
8819
|
+
logger.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
8820
|
+
return true;
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
if (!allowed) {
|
|
8824
|
+
// wasn't allowed by any regex
|
|
8825
|
+
return true;
|
|
8826
|
+
}
|
|
8827
|
+
}
|
|
8828
|
+
|
|
8696
8829
|
var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
|
|
8697
8830
|
if (!blockUrlRegexes || !blockUrlRegexes.length) {
|
|
8698
8831
|
return false;
|
|
8699
8832
|
}
|
|
8700
8833
|
|
|
8701
|
-
|
|
8702
|
-
for (var i = 0; i < blockUrlRegexes.length; i++) {
|
|
8834
|
+
for (i = 0; i < blockUrlRegexes.length; i++) {
|
|
8703
8835
|
try {
|
|
8704
8836
|
if (currentUrl.match(blockUrlRegexes[i])) {
|
|
8705
8837
|
return true;
|
|
@@ -8727,11 +8859,15 @@
|
|
|
8727
8859
|
return;
|
|
8728
8860
|
}
|
|
8729
8861
|
|
|
8730
|
-
var props = getPropsForDOMEvent(
|
|
8731
|
-
|
|
8732
|
-
this.getConfig(
|
|
8733
|
-
this.getConfig(
|
|
8734
|
-
|
|
8862
|
+
var props = getPropsForDOMEvent(ev, {
|
|
8863
|
+
allowElementCallback: this.getConfig(CONFIG_ALLOW_ELEMENT_CALLBACK),
|
|
8864
|
+
allowSelectors: this.getConfig(CONFIG_ALLOW_SELECTORS),
|
|
8865
|
+
blockAttrs: this.getConfig(CONFIG_BLOCK_ATTRS),
|
|
8866
|
+
blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
|
|
8867
|
+
blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
|
|
8868
|
+
captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
|
|
8869
|
+
captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
|
|
8870
|
+
});
|
|
8735
8871
|
if (props) {
|
|
8736
8872
|
_.extend(props, DEFAULT_PROPS);
|
|
8737
8873
|
this.mp.track(mpEventName, props);
|
|
@@ -8816,13 +8952,14 @@
|
|
|
8816
8952
|
|
|
8817
8953
|
var currentUrl = _.info.currentUrl();
|
|
8818
8954
|
var shouldTrack = false;
|
|
8955
|
+
var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
|
|
8819
8956
|
var trackPageviewOption = this.pageviewTrackingConfig();
|
|
8820
8957
|
if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
|
|
8821
8958
|
shouldTrack = currentUrl !== previousTrackedUrl;
|
|
8822
8959
|
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
|
|
8823
8960
|
shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
|
|
8824
8961
|
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
|
|
8825
|
-
shouldTrack =
|
|
8962
|
+
shouldTrack = didPathChange;
|
|
8826
8963
|
}
|
|
8827
8964
|
|
|
8828
8965
|
if (shouldTrack) {
|
|
@@ -8830,6 +8967,10 @@
|
|
|
8830
8967
|
if (tracked) {
|
|
8831
8968
|
previousTrackedUrl = currentUrl;
|
|
8832
8969
|
}
|
|
8970
|
+
if (didPathChange) {
|
|
8971
|
+
this.lastScrollCheckpoint = 0;
|
|
8972
|
+
logger.log('Path change: re-initializing scroll depth checkpoints');
|
|
8973
|
+
}
|
|
8833
8974
|
}
|
|
8834
8975
|
}.bind(this)));
|
|
8835
8976
|
};
|
|
@@ -8841,6 +8982,7 @@
|
|
|
8841
8982
|
return;
|
|
8842
8983
|
}
|
|
8843
8984
|
logger.log('Initializing scroll tracking');
|
|
8985
|
+
this.lastScrollCheckpoint = 0;
|
|
8844
8986
|
|
|
8845
8987
|
this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
|
|
8846
8988
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
@@ -8850,6 +8992,11 @@
|
|
|
8850
8992
|
return;
|
|
8851
8993
|
}
|
|
8852
8994
|
|
|
8995
|
+
var shouldTrack = this.getConfig(CONFIG_SCROLL_CAPTURE_ALL);
|
|
8996
|
+
var scrollCheckpoints = (this.getConfig(CONFIG_SCROLL_CHECKPOINTS) || [])
|
|
8997
|
+
.slice()
|
|
8998
|
+
.sort(function(a, b) { return a - b; });
|
|
8999
|
+
|
|
8853
9000
|
var scrollTop = win.scrollY;
|
|
8854
9001
|
var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
|
|
8855
9002
|
try {
|
|
@@ -8857,10 +9004,25 @@
|
|
|
8857
9004
|
var scrollPercentage = Math.round((scrollTop / (scrollHeight - win.innerHeight)) * 100);
|
|
8858
9005
|
props['$scroll_height'] = scrollHeight;
|
|
8859
9006
|
props['$scroll_percentage'] = scrollPercentage;
|
|
9007
|
+
if (scrollPercentage > this.lastScrollCheckpoint) {
|
|
9008
|
+
for (var i = 0; i < scrollCheckpoints.length; i++) {
|
|
9009
|
+
var checkpoint = scrollCheckpoints[i];
|
|
9010
|
+
if (
|
|
9011
|
+
scrollPercentage >= checkpoint &&
|
|
9012
|
+
this.lastScrollCheckpoint < checkpoint
|
|
9013
|
+
) {
|
|
9014
|
+
props['$scroll_checkpoint'] = checkpoint;
|
|
9015
|
+
this.lastScrollCheckpoint = checkpoint;
|
|
9016
|
+
shouldTrack = true;
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
}
|
|
8860
9020
|
} catch (err) {
|
|
8861
9021
|
logger.critical('Error while calculating scroll percentage', err);
|
|
8862
9022
|
}
|
|
8863
|
-
|
|
9023
|
+
if (shouldTrack) {
|
|
9024
|
+
this.mp.track(MP_EV_SCROLL, props);
|
|
9025
|
+
}
|
|
8864
9026
|
}.bind(this)));
|
|
8865
9027
|
};
|
|
8866
9028
|
|
|
@@ -10129,8 +10291,12 @@
|
|
|
10129
10291
|
if (!(k in union_q)) {
|
|
10130
10292
|
union_q[k] = [];
|
|
10131
10293
|
}
|
|
10132
|
-
//
|
|
10133
|
-
|
|
10294
|
+
// Prevent duplicate values
|
|
10295
|
+
_.each(v, function(item) {
|
|
10296
|
+
if (!_.include(union_q[k], item)) {
|
|
10297
|
+
union_q[k].push(item);
|
|
10298
|
+
}
|
|
10299
|
+
});
|
|
10134
10300
|
}
|
|
10135
10301
|
});
|
|
10136
10302
|
this._pop_from_people_queue(UNSET_ACTION, q_data);
|
package/package.json
CHANGED
package/src/autocapture/index.js
CHANGED
|
@@ -13,9 +13,17 @@ var PAGEVIEW_OPTION_FULL_URL = 'full-url';
|
|
|
13
13
|
var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
|
|
14
14
|
var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
|
|
15
15
|
|
|
16
|
+
var CONFIG_ALLOW_ELEMENT_CALLBACK = 'allow_element_callback';
|
|
17
|
+
var CONFIG_ALLOW_SELECTORS = 'allow_selectors';
|
|
18
|
+
var CONFIG_ALLOW_URL_REGEXES = 'allow_url_regexes';
|
|
19
|
+
var CONFIG_BLOCK_ATTRS = 'block_attrs';
|
|
20
|
+
var CONFIG_BLOCK_ELEMENT_CALLBACK = 'block_element_callback';
|
|
16
21
|
var CONFIG_BLOCK_SELECTORS = 'block_selectors';
|
|
17
22
|
var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
|
|
23
|
+
var CONFIG_CAPTURE_EXTRA_ATTRS = 'capture_extra_attrs';
|
|
18
24
|
var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
|
|
25
|
+
var CONFIG_SCROLL_CAPTURE_ALL = 'scroll_capture_all';
|
|
26
|
+
var CONFIG_SCROLL_CHECKPOINTS = 'scroll_depth_percent_checkpoints';
|
|
19
27
|
var CONFIG_TRACK_CLICK = 'click';
|
|
20
28
|
var CONFIG_TRACK_INPUT = 'input';
|
|
21
29
|
var CONFIG_TRACK_PAGEVIEW = 'pageview';
|
|
@@ -23,7 +31,16 @@ var CONFIG_TRACK_SCROLL = 'scroll';
|
|
|
23
31
|
var CONFIG_TRACK_SUBMIT = 'submit';
|
|
24
32
|
|
|
25
33
|
var CONFIG_DEFAULTS = {};
|
|
34
|
+
CONFIG_DEFAULTS[CONFIG_ALLOW_SELECTORS] = [];
|
|
35
|
+
CONFIG_DEFAULTS[CONFIG_ALLOW_URL_REGEXES] = [];
|
|
36
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_ATTRS] = [];
|
|
37
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_ELEMENT_CALLBACK] = null;
|
|
38
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_SELECTORS] = [];
|
|
39
|
+
CONFIG_DEFAULTS[CONFIG_BLOCK_URL_REGEXES] = [];
|
|
40
|
+
CONFIG_DEFAULTS[CONFIG_CAPTURE_EXTRA_ATTRS] = [];
|
|
26
41
|
CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
|
|
42
|
+
CONFIG_DEFAULTS[CONFIG_SCROLL_CAPTURE_ALL] = false;
|
|
43
|
+
CONFIG_DEFAULTS[CONFIG_SCROLL_CHECKPOINTS] = [25, 50, 75, 100];
|
|
27
44
|
CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
|
|
28
45
|
CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
|
|
29
46
|
CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
|
|
@@ -78,13 +95,37 @@ Autocapture.prototype.getConfig = function(key) {
|
|
|
78
95
|
};
|
|
79
96
|
|
|
80
97
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
98
|
+
var i;
|
|
99
|
+
var currentUrl = _.info.currentUrl();
|
|
100
|
+
|
|
101
|
+
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
102
|
+
if (allowUrlRegexes.length) {
|
|
103
|
+
// we're using an allowlist, only track if current URL matches
|
|
104
|
+
var allowed = false;
|
|
105
|
+
for (i = 0; i < allowUrlRegexes.length; i++) {
|
|
106
|
+
var allowRegex = allowUrlRegexes[i];
|
|
107
|
+
try {
|
|
108
|
+
if (currentUrl.match(allowRegex)) {
|
|
109
|
+
allowed = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!allowed) {
|
|
118
|
+
// wasn't allowed by any regex
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
81
123
|
var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
|
|
82
124
|
if (!blockUrlRegexes || !blockUrlRegexes.length) {
|
|
83
125
|
return false;
|
|
84
126
|
}
|
|
85
127
|
|
|
86
|
-
|
|
87
|
-
for (var i = 0; i < blockUrlRegexes.length; i++) {
|
|
128
|
+
for (i = 0; i < blockUrlRegexes.length; i++) {
|
|
88
129
|
try {
|
|
89
130
|
if (currentUrl.match(blockUrlRegexes[i])) {
|
|
90
131
|
return true;
|
|
@@ -112,11 +153,15 @@ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
|
|
|
112
153
|
return;
|
|
113
154
|
}
|
|
114
155
|
|
|
115
|
-
var props = getPropsForDOMEvent(
|
|
116
|
-
|
|
117
|
-
this.getConfig(
|
|
118
|
-
this.getConfig(
|
|
119
|
-
|
|
156
|
+
var props = getPropsForDOMEvent(ev, {
|
|
157
|
+
allowElementCallback: this.getConfig(CONFIG_ALLOW_ELEMENT_CALLBACK),
|
|
158
|
+
allowSelectors: this.getConfig(CONFIG_ALLOW_SELECTORS),
|
|
159
|
+
blockAttrs: this.getConfig(CONFIG_BLOCK_ATTRS),
|
|
160
|
+
blockElementCallback: this.getConfig(CONFIG_BLOCK_ELEMENT_CALLBACK),
|
|
161
|
+
blockSelectors: this.getConfig(CONFIG_BLOCK_SELECTORS),
|
|
162
|
+
captureExtraAttrs: this.getConfig(CONFIG_CAPTURE_EXTRA_ATTRS),
|
|
163
|
+
captureTextContent: this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
|
|
164
|
+
});
|
|
120
165
|
if (props) {
|
|
121
166
|
_.extend(props, DEFAULT_PROPS);
|
|
122
167
|
this.mp.track(mpEventName, props);
|
|
@@ -201,13 +246,14 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
201
246
|
|
|
202
247
|
var currentUrl = _.info.currentUrl();
|
|
203
248
|
var shouldTrack = false;
|
|
249
|
+
var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
|
|
204
250
|
var trackPageviewOption = this.pageviewTrackingConfig();
|
|
205
251
|
if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
|
|
206
252
|
shouldTrack = currentUrl !== previousTrackedUrl;
|
|
207
253
|
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
|
|
208
254
|
shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
|
|
209
255
|
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
|
|
210
|
-
shouldTrack =
|
|
256
|
+
shouldTrack = didPathChange;
|
|
211
257
|
}
|
|
212
258
|
|
|
213
259
|
if (shouldTrack) {
|
|
@@ -215,6 +261,10 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
215
261
|
if (tracked) {
|
|
216
262
|
previousTrackedUrl = currentUrl;
|
|
217
263
|
}
|
|
264
|
+
if (didPathChange) {
|
|
265
|
+
this.lastScrollCheckpoint = 0;
|
|
266
|
+
logger.log('Path change: re-initializing scroll depth checkpoints');
|
|
267
|
+
}
|
|
218
268
|
}
|
|
219
269
|
}.bind(this)));
|
|
220
270
|
};
|
|
@@ -226,6 +276,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
226
276
|
return;
|
|
227
277
|
}
|
|
228
278
|
logger.log('Initializing scroll tracking');
|
|
279
|
+
this.lastScrollCheckpoint = 0;
|
|
229
280
|
|
|
230
281
|
this.listenerScroll = window.addEventListener(EV_SCROLLEND, safewrap(function() {
|
|
231
282
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
@@ -235,6 +286,11 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
235
286
|
return;
|
|
236
287
|
}
|
|
237
288
|
|
|
289
|
+
var shouldTrack = this.getConfig(CONFIG_SCROLL_CAPTURE_ALL);
|
|
290
|
+
var scrollCheckpoints = (this.getConfig(CONFIG_SCROLL_CHECKPOINTS) || [])
|
|
291
|
+
.slice()
|
|
292
|
+
.sort(function(a, b) { return a - b; });
|
|
293
|
+
|
|
238
294
|
var scrollTop = window.scrollY;
|
|
239
295
|
var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
|
|
240
296
|
try {
|
|
@@ -242,10 +298,25 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
242
298
|
var scrollPercentage = Math.round((scrollTop / (scrollHeight - window.innerHeight)) * 100);
|
|
243
299
|
props['$scroll_height'] = scrollHeight;
|
|
244
300
|
props['$scroll_percentage'] = scrollPercentage;
|
|
301
|
+
if (scrollPercentage > this.lastScrollCheckpoint) {
|
|
302
|
+
for (var i = 0; i < scrollCheckpoints.length; i++) {
|
|
303
|
+
var checkpoint = scrollCheckpoints[i];
|
|
304
|
+
if (
|
|
305
|
+
scrollPercentage >= checkpoint &&
|
|
306
|
+
this.lastScrollCheckpoint < checkpoint
|
|
307
|
+
) {
|
|
308
|
+
props['$scroll_checkpoint'] = checkpoint;
|
|
309
|
+
this.lastScrollCheckpoint = checkpoint;
|
|
310
|
+
shouldTrack = true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
245
314
|
} catch (err) {
|
|
246
315
|
logger.critical('Error while calculating scroll percentage', err);
|
|
247
316
|
}
|
|
248
|
-
|
|
317
|
+
if (shouldTrack) {
|
|
318
|
+
this.mp.track(MP_EV_SCROLL, props);
|
|
319
|
+
}
|
|
249
320
|
}.bind(this)));
|
|
250
321
|
};
|
|
251
322
|
|