mixpanel-browser 2.58.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.
@@ -4513,7 +4513,7 @@
4513
4513
 
4514
4514
  var Config = {
4515
4515
  DEBUG: false,
4516
- LIB_VERSION: '2.58.0'
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
@@ -4525,11 +4525,14 @@
4525
4525
  win = {
4526
4526
  navigator: { userAgent: '', onLine: true },
4527
4527
  document: {
4528
+ createElement: function() { return {}; },
4528
4529
  location: loc,
4529
4530
  referrer: ''
4530
4531
  },
4531
4532
  screen: { width: 0, height: 0 },
4532
- location: loc
4533
+ location: loc,
4534
+ addEventListener: function() {},
4535
+ removeEventListener: function() {}
4533
4536
  };
4534
4537
  } else {
4535
4538
  win = window;
@@ -5006,6 +5009,29 @@
5006
5009
  };
5007
5010
 
5008
5011
 
5012
+ var safewrap = function(f) {
5013
+ return function() {
5014
+ try {
5015
+ return f.apply(this, arguments);
5016
+ } catch (e) {
5017
+ console$1.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
5018
+ if (Config.DEBUG){
5019
+ console$1.critical(e);
5020
+ }
5021
+ }
5022
+ };
5023
+ };
5024
+
5025
+ var safewrapClass = function(klass) {
5026
+ var proto = klass.prototype;
5027
+ for (var func in proto) {
5028
+ if (typeof(proto[func]) === 'function') {
5029
+ proto[func] = safewrap(proto[func]);
5030
+ }
5031
+ }
5032
+ };
5033
+
5034
+
5009
5035
  // UNDERSCORE
5010
5036
  // Embed part of the Underscore Library
5011
5037
  _.bind = function(func, context) {
@@ -5784,6 +5810,7 @@
5784
5810
  var BLOCKED_UA_STRS = [
5785
5811
  'ahrefsbot',
5786
5812
  'ahrefssiteaudit',
5813
+ 'amazonbot',
5787
5814
  'baiduspider',
5788
5815
  'bingbot',
5789
5816
  'bingpreview',
@@ -5793,7 +5820,7 @@
5793
5820
  'pinterest',
5794
5821
  'screaming frog',
5795
5822
  'yahoo! slurp',
5796
- 'yandexbot',
5823
+ 'yandex',
5797
5824
 
5798
5825
  // a whole bunch of goog-specific crawlers
5799
5826
  // https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
@@ -6913,7 +6940,7 @@
6913
6940
  };
6914
6941
  }
6915
6942
 
6916
- var logger$4 = console_with_prefix('lock');
6943
+ var logger$5 = console_with_prefix('lock');
6917
6944
 
6918
6945
  /**
6919
6946
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -6966,7 +6993,7 @@
6966
6993
 
6967
6994
  var delay = function(cb) {
6968
6995
  if (new Date().getTime() - startTime > timeoutMS) {
6969
- logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6996
+ logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6970
6997
  storage.removeItem(keyZ);
6971
6998
  storage.removeItem(keyY);
6972
6999
  loop();
@@ -7113,7 +7140,7 @@
7113
7140
  }, this));
7114
7141
  };
7115
7142
 
7116
- var logger$3 = console_with_prefix('batch');
7143
+ var logger$4 = console_with_prefix('batch');
7117
7144
 
7118
7145
  /**
7119
7146
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -7140,7 +7167,7 @@
7140
7167
  this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
7141
7168
  this.queueStorage.init();
7142
7169
  }
7143
- this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
7170
+ this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
7144
7171
 
7145
7172
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
7146
7173
 
@@ -7470,7 +7497,7 @@
7470
7497
  // maximum interval between request retries after exponential backoff
7471
7498
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
7472
7499
 
7473
- var logger$2 = console_with_prefix('batch');
7500
+ var logger$3 = console_with_prefix('batch');
7474
7501
 
7475
7502
  /**
7476
7503
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -7594,7 +7621,7 @@
7594
7621
  */
7595
7622
  RequestBatcher.prototype.flush = function(options) {
7596
7623
  if (this.requestInProgress) {
7597
- logger$2.log('Flush: Request already in progress');
7624
+ logger$3.log('Flush: Request already in progress');
7598
7625
  return PromisePolyfill.resolve();
7599
7626
  }
7600
7627
 
@@ -7771,7 +7798,7 @@
7771
7798
  if (options.unloading) {
7772
7799
  requestOptions.transport = 'sendBeacon';
7773
7800
  }
7774
- logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7801
+ logger$3.log('MIXPANEL REQUEST:', dataForRequest);
7775
7802
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
7776
7803
  }, this))
7777
7804
  .catch(_.bind(function(err) {
@@ -7784,7 +7811,7 @@
7784
7811
  * Log error to global logger and optional user-defined logger.
7785
7812
  */
7786
7813
  RequestBatcher.prototype.reportError = function(msg, err) {
7787
- logger$2.error.apply(logger$2.error, arguments);
7814
+ logger$3.error.apply(logger$3.error, arguments);
7788
7815
  if (this.errorReporter) {
7789
7816
  try {
7790
7817
  if (!(err instanceof Error)) {
@@ -7792,12 +7819,12 @@
7792
7819
  }
7793
7820
  this.errorReporter(msg, err);
7794
7821
  } catch(err) {
7795
- logger$2.error(err);
7822
+ logger$3.error(err);
7796
7823
  }
7797
7824
  }
7798
7825
  };
7799
7826
 
7800
- var logger$1 = console_with_prefix('recorder');
7827
+ var logger$2 = console_with_prefix('recorder');
7801
7828
  var CompressionStream = win['CompressionStream'];
7802
7829
 
7803
7830
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -7878,20 +7905,20 @@
7878
7905
 
7879
7906
  SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
7880
7907
  if (this._stopRecording !== null) {
7881
- logger$1.log('Recording already in progress, skipping startRecording.');
7908
+ logger$2.log('Recording already in progress, skipping startRecording.');
7882
7909
  return;
7883
7910
  }
7884
7911
 
7885
7912
  this.recordMaxMs = this.getConfig('record_max_ms');
7886
7913
  if (this.recordMaxMs > MAX_RECORDING_MS) {
7887
7914
  this.recordMaxMs = MAX_RECORDING_MS;
7888
- logger$1.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7915
+ logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7889
7916
  }
7890
7917
 
7891
7918
  this.recordMinMs = this.getConfig('record_min_ms');
7892
7919
  if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
7893
7920
  this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
7894
- logger$1.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7921
+ logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7895
7922
  }
7896
7923
 
7897
7924
  this.replayStartTime = new Date().getTime();
@@ -8094,18 +8121,18 @@
8094
8121
 
8095
8122
 
8096
8123
  SessionRecording.prototype.reportError = function(msg, err) {
8097
- logger$1.error.apply(logger$1.error, arguments);
8124
+ logger$2.error.apply(logger$2.error, arguments);
8098
8125
  try {
8099
8126
  if (!err && !(msg instanceof Error)) {
8100
8127
  msg = new Error(msg);
8101
8128
  }
8102
8129
  this.getConfig('error_reporter')(msg, err);
8103
8130
  } catch(err) {
8104
- logger$1.error(err);
8131
+ logger$2.error(err);
8105
8132
  }
8106
8133
  };
8107
8134
 
8108
- var logger = console_with_prefix('recorder');
8135
+ var logger$1 = console_with_prefix('recorder');
8109
8136
 
8110
8137
  /**
8111
8138
  * Recorder API: manages recordings and exposes methods public to the core Mixpanel library.
@@ -8118,17 +8145,17 @@
8118
8145
 
8119
8146
  MixpanelRecorder.prototype.startRecording = function(shouldStopBatcher) {
8120
8147
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
8121
- logger.log('Recording already in progress, skipping startRecording.');
8148
+ logger$1.log('Recording already in progress, skipping startRecording.');
8122
8149
  return;
8123
8150
  }
8124
8151
 
8125
8152
  var onIdleTimeout = _.bind(function () {
8126
- logger.log('Idle timeout reached, restarting recording.');
8153
+ logger$1.log('Idle timeout reached, restarting recording.');
8127
8154
  this.resetRecording();
8128
8155
  }, this);
8129
8156
 
8130
8157
  var onMaxLengthReached = _.bind(function () {
8131
- logger.log('Max recording length reached, stopping recording.');
8158
+ logger$1.log('Max recording length reached, stopping recording.');
8132
8159
  this.resetRecording();
8133
8160
  }, this);
8134
8161
 
@@ -8173,6 +8200,851 @@
8173
8200
 
8174
8201
  win['__mp_recorder'] = MixpanelRecorder;
8175
8202
 
8203
+ // stateless utils
8204
+
8205
+ var EV_CHANGE = 'change';
8206
+ var EV_CLICK = 'click';
8207
+ var EV_HASHCHANGE = 'hashchange';
8208
+ var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
8209
+ var EV_POPSTATE = 'popstate';
8210
+ // TODO scrollend isn't available in Safari: document or polyfill?
8211
+ var EV_SCROLLEND = 'scrollend';
8212
+ var EV_SUBMIT = 'submit';
8213
+
8214
+ var CLICK_EVENT_PROPS = [
8215
+ 'clientX', 'clientY',
8216
+ 'offsetX', 'offsetY',
8217
+ 'pageX', 'pageY',
8218
+ 'screenX', 'screenY',
8219
+ 'x', 'y'
8220
+ ];
8221
+ var OPT_IN_CLASSES = ['mp-include'];
8222
+ var OPT_OUT_CLASSES = ['mp-no-track'];
8223
+ var SENSITIVE_DATA_CLASSES = OPT_OUT_CLASSES.concat(['mp-sensitive']);
8224
+ var TRACKED_ATTRS = [
8225
+ 'aria-label', 'aria-labelledby', 'aria-describedby',
8226
+ 'href', 'name', 'role', 'title', 'type'
8227
+ ];
8228
+
8229
+ var logger = console_with_prefix('autocapture');
8230
+
8231
+
8232
+ function getClasses(el) {
8233
+ var classes = {};
8234
+ var classList = getClassName(el).split(' ');
8235
+ for (var i = 0; i < classList.length; i++) {
8236
+ var cls = classList[i];
8237
+ if (cls) {
8238
+ classes[cls] = true;
8239
+ }
8240
+ }
8241
+ return classes;
8242
+ }
8243
+
8244
+ /*
8245
+ * Get the className of an element, accounting for edge cases where element.className is an object
8246
+ * @param {Element} el - element to get the className of
8247
+ * @returns {string} the element's class
8248
+ */
8249
+ function getClassName(el) {
8250
+ switch(typeof el.className) {
8251
+ case 'string':
8252
+ return el.className;
8253
+ case 'object': // handle cases where className might be SVGAnimatedString or some other type
8254
+ return el.className.baseVal || el.getAttribute('class') || '';
8255
+ default: // future proof
8256
+ return '';
8257
+ }
8258
+ }
8259
+
8260
+ function getPreviousElementSibling(el) {
8261
+ if (el.previousElementSibling) {
8262
+ return el.previousElementSibling;
8263
+ } else {
8264
+ do {
8265
+ el = el.previousSibling;
8266
+ } while (el && !isElementNode(el));
8267
+ return el;
8268
+ }
8269
+ }
8270
+
8271
+ function getPropertiesFromElement(el, ev, blockAttrsSet, extraAttrs, allowElementCallback, allowSelectors) {
8272
+ var props = {
8273
+ '$classes': getClassName(el).split(' '),
8274
+ '$tag_name': el.tagName.toLowerCase()
8275
+ };
8276
+ var elId = el.id;
8277
+ if (elId) {
8278
+ props['$id'] = elId;
8279
+ }
8280
+
8281
+ if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors)) {
8282
+ _.each(TRACKED_ATTRS.concat(extraAttrs), function(attr) {
8283
+ if (el.hasAttribute(attr) && !blockAttrsSet[attr]) {
8284
+ var attrVal = el.getAttribute(attr);
8285
+ if (shouldTrackValue(attrVal)) {
8286
+ props['$attr-' + attr] = attrVal;
8287
+ }
8288
+ }
8289
+ });
8290
+ }
8291
+
8292
+ var nthChild = 1;
8293
+ var nthOfType = 1;
8294
+ var currentElem = el;
8295
+ while (currentElem = getPreviousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
8296
+ nthChild++;
8297
+ if (currentElem.tagName === el.tagName) {
8298
+ nthOfType++;
8299
+ }
8300
+ }
8301
+ props['$nth_child'] = nthChild;
8302
+ props['$nth_of_type'] = nthOfType;
8303
+
8304
+ return props;
8305
+ }
8306
+
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
+
8322
+ var props = null;
8323
+
8324
+ var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
8325
+ if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
8326
+ target = target.parentNode;
8327
+ }
8328
+
8329
+ if (
8330
+ shouldTrackDomEvent(target, ev) &&
8331
+ isElementAllowed(target, ev, allowElementCallback, allowSelectors) &&
8332
+ !isElementBlocked(target, ev, blockElementCallback, blockSelectors)
8333
+ ) {
8334
+ var targetElementList = [target];
8335
+ var curEl = target;
8336
+ while (curEl.parentNode && !isTag(curEl, 'body')) {
8337
+ targetElementList.push(curEl.parentNode);
8338
+ curEl = curEl.parentNode;
8339
+ }
8340
+
8341
+ var elementsJson = [];
8342
+ var href, explicitNoTrack = false;
8343
+ _.each(targetElementList, function(el) {
8344
+ var shouldTrackDetails = shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors);
8345
+
8346
+ // if the element or a parent element is an anchor tag
8347
+ // include the href as a property
8348
+ if (!blockAttrsSet['href'] && el.tagName.toLowerCase() === 'a') {
8349
+ href = el.getAttribute('href');
8350
+ href = shouldTrackDetails && shouldTrackValue(href) && href;
8351
+ }
8352
+
8353
+ if (isElementBlocked(el, ev, blockElementCallback, blockSelectors)) {
8354
+ explicitNoTrack = true;
8355
+ }
8356
+
8357
+ elementsJson.push(getPropertiesFromElement(el, ev, blockAttrsSet, captureExtraAttrs, allowElementCallback, allowSelectors));
8358
+ }, this);
8359
+
8360
+ if (!explicitNoTrack) {
8361
+ var docElement = document$1['documentElement'];
8362
+ props = {
8363
+ '$event_type': ev.type,
8364
+ '$host': win.location.host,
8365
+ '$pathname': win.location.pathname,
8366
+ '$elements': elementsJson,
8367
+ '$el_attr__href': href,
8368
+ '$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
8369
+ '$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
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
+ });
8379
+
8380
+ if (captureTextContent) {
8381
+ elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
8382
+ if (elementText && elementText.length) {
8383
+ props['$el_text'] = elementText;
8384
+ }
8385
+ }
8386
+
8387
+ if (ev.type === EV_CLICK) {
8388
+ _.each(CLICK_EVENT_PROPS, function(prop) {
8389
+ if (prop in ev) {
8390
+ props['$' + prop] = ev[prop];
8391
+ }
8392
+ });
8393
+ target = guessRealClickTarget(ev);
8394
+ }
8395
+ // prioritize text content from "real" click target if different from original target
8396
+ if (captureTextContent) {
8397
+ var elementText = getSafeText(target, ev, allowElementCallback, allowSelectors);
8398
+ if (elementText && elementText.length) {
8399
+ props['$el_text'] = elementText;
8400
+ }
8401
+ }
8402
+
8403
+ if (target) {
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);
8413
+ props['$target'] = targetProps;
8414
+ // pull up more props onto main event props
8415
+ props['$el_classes'] = targetProps['$classes'];
8416
+ _.extend(props, _.strip_empty_properties({
8417
+ '$el_id': targetProps['$id'],
8418
+ '$el_tag_name': targetProps['$tag_name']
8419
+ }));
8420
+ }
8421
+ }
8422
+ }
8423
+
8424
+ return props;
8425
+ }
8426
+
8427
+
8428
+ /**
8429
+ * Get the direct text content of an element, protecting against sensitive data collection.
8430
+ * Concats textContent of each of the element's text node children; this avoids potential
8431
+ * collection of sensitive data that could happen if we used element.textContent and the
8432
+ * element had sensitive child elements, since element.textContent includes child content.
8433
+ * Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
8434
+ * @param {Element} el - element to get the text of
8435
+ * @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
8436
+ * @returns {string} the element's direct text content
8437
+ */
8438
+ function getSafeText(el, ev, allowElementCallback, allowSelectors) {
8439
+ var elText = '';
8440
+
8441
+ if (shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) && el.childNodes && el.childNodes.length) {
8442
+ _.each(el.childNodes, function(child) {
8443
+ if (isTextNode(child) && child.textContent) {
8444
+ elText += _.trim(child.textContent)
8445
+ // scrub potentially sensitive values
8446
+ .split(/(\s+)/).filter(shouldTrackValue).join('')
8447
+ // normalize whitespace
8448
+ .replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
8449
+ // truncate
8450
+ .substring(0, 255);
8451
+ }
8452
+ });
8453
+ }
8454
+
8455
+ return _.trim(elText);
8456
+ }
8457
+
8458
+ function guessRealClickTarget(ev) {
8459
+ var target = ev.target;
8460
+ var composedPath = ev['composedPath']();
8461
+ for (var i = 0; i < composedPath.length; i++) {
8462
+ var node = composedPath[i];
8463
+ if (
8464
+ isTag(node, 'a') ||
8465
+ isTag(node, 'button') ||
8466
+ isTag(node, 'input') ||
8467
+ isTag(node, 'select') ||
8468
+ (node.getAttribute && node.getAttribute('role') === 'button')
8469
+ ) {
8470
+ target = node;
8471
+ break;
8472
+ }
8473
+ if (node === target) {
8474
+ break;
8475
+ }
8476
+ }
8477
+ return target;
8478
+ }
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
+
8549
+ /*
8550
+ * Check whether a DOM node has nodeType Node.ELEMENT_NODE
8551
+ * @param {Node} node - node to check
8552
+ * @returns {boolean} whether node is of the correct nodeType
8553
+ */
8554
+ function isElementNode(node) {
8555
+ return node && node.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
8556
+ }
8557
+
8558
+ /*
8559
+ * Check whether an element is of a given tag type.
8560
+ * Due to potential reference discrepancies (such as the webcomponents.js polyfill),
8561
+ * we want to match tagNames instead of specific references because something like
8562
+ * element === document.body won't always work because element might not be a native
8563
+ * element.
8564
+ * @param {Element} el - element to check
8565
+ * @param {string} tag - tag name (e.g., "div")
8566
+ * @returns {boolean} whether el is of the given tag type
8567
+ */
8568
+ function isTag(el, tag) {
8569
+ return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
8570
+ }
8571
+
8572
+ /*
8573
+ * Check whether a DOM node is a TEXT_NODE
8574
+ * @param {Node} node - node to check
8575
+ * @returns {boolean} whether node is of type Node.TEXT_NODE
8576
+ */
8577
+ function isTextNode(node) {
8578
+ return node && node.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
8579
+ }
8580
+
8581
+ function minDOMApisSupported() {
8582
+ try {
8583
+ var testEl = document$1.createElement('div');
8584
+ return !!testEl['matches'];
8585
+ } catch (err) {
8586
+ return false;
8587
+ }
8588
+ }
8589
+
8590
+ /*
8591
+ * Check whether a DOM event should be "tracked" or if it may contain sensitive data
8592
+ * using a variety of heuristics.
8593
+ * @param {Element} el - element to check
8594
+ * @param {Event} ev - event to check
8595
+ * @returns {boolean} whether the event should be tracked
8596
+ */
8597
+ function shouldTrackDomEvent(el, ev) {
8598
+ if (!el || isTag(el, 'html') || !isElementNode(el)) {
8599
+ return false;
8600
+ }
8601
+ var tag = el.tagName.toLowerCase();
8602
+ switch (tag) {
8603
+ case 'form':
8604
+ return ev.type === EV_SUBMIT;
8605
+ case 'input':
8606
+ if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
8607
+ return ev.type === EV_CHANGE;
8608
+ } else {
8609
+ return ev.type === EV_CLICK;
8610
+ }
8611
+ case 'select':
8612
+ case 'textarea':
8613
+ return ev.type === EV_CHANGE;
8614
+ default:
8615
+ return ev.type === EV_CLICK;
8616
+ }
8617
+ }
8618
+
8619
+ /*
8620
+ * Check whether a DOM element should be "tracked" or if it may contain sensitive data
8621
+ * using a variety of heuristics.
8622
+ * @param {Element} el - element to check
8623
+ * @param {Array<string>} allowSelectors - CSS selectors for elements that should be included
8624
+ * @returns {boolean} whether the element should be tracked
8625
+ */
8626
+ function shouldTrackElementDetails(el, ev, allowElementCallback, allowSelectors) {
8627
+ var i;
8628
+
8629
+ if (!isElementAllowed(el, ev, allowElementCallback, allowSelectors)) {
8630
+ return false;
8631
+ }
8632
+
8633
+ for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
8634
+ var classes = getClasses(curEl);
8635
+ for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
8636
+ if (classes[SENSITIVE_DATA_CLASSES[i]]) {
8637
+ return false;
8638
+ }
8639
+ }
8640
+ }
8641
+
8642
+ var elClasses = getClasses(el);
8643
+ for (i = 0; i < OPT_IN_CLASSES.length; i++) {
8644
+ if (elClasses[OPT_IN_CLASSES[i]]) {
8645
+ return true;
8646
+ }
8647
+ }
8648
+
8649
+ // don't send data from inputs or similar elements since there will always be
8650
+ // a risk of clientside javascript placing sensitive data in attributes
8651
+ if (
8652
+ isTag(el, 'input') ||
8653
+ isTag(el, 'select') ||
8654
+ isTag(el, 'textarea') ||
8655
+ el.getAttribute('contenteditable') === 'true'
8656
+ ) {
8657
+ return false;
8658
+ }
8659
+
8660
+ // don't include hidden or password fields
8661
+ var type = el.type || '';
8662
+ if (typeof type === 'string') { // it's possible for el.type to be a DOM element if el is a form with a child input[name="type"]
8663
+ switch(type.toLowerCase()) {
8664
+ case 'hidden':
8665
+ return false;
8666
+ case 'password':
8667
+ return false;
8668
+ }
8669
+ }
8670
+
8671
+ // filter out data from fields that look like sensitive fields
8672
+ var name = el.name || el.id || '';
8673
+ if (typeof name === 'string') { // 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"]
8674
+ var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
8675
+ if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
8676
+ return false;
8677
+ }
8678
+ }
8679
+
8680
+ return true;
8681
+ }
8682
+
8683
+
8684
+ /*
8685
+ * Check whether a string value should be "tracked" or if it may contain sensitive data
8686
+ * using a variety of heuristics.
8687
+ * @param {string} value - string value to check
8688
+ * @returns {boolean} whether the element should be tracked
8689
+ */
8690
+ function shouldTrackValue(value) {
8691
+ if (value === null || _.isUndefined(value)) {
8692
+ return false;
8693
+ }
8694
+
8695
+ if (typeof value === 'string') {
8696
+ value = _.trim(value);
8697
+
8698
+ // check to see if input value looks like a credit card number
8699
+ // see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
8700
+ var ccRegex = /^(?:(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}))$/;
8701
+ if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
8702
+ return false;
8703
+ }
8704
+
8705
+ // check to see if input value looks like a social security number
8706
+ var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
8707
+ if (ssnRegex.test(value)) {
8708
+ return false;
8709
+ }
8710
+ }
8711
+
8712
+ return true;
8713
+ }
8714
+
8715
+ var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
8716
+ var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
8717
+
8718
+ var PAGEVIEW_OPTION_FULL_URL = 'full-url';
8719
+ var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
8720
+ var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
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';
8727
+ var CONFIG_BLOCK_SELECTORS = 'block_selectors';
8728
+ var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
8729
+ var CONFIG_CAPTURE_EXTRA_ATTRS = 'capture_extra_attrs';
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';
8733
+ var CONFIG_TRACK_CLICK = 'click';
8734
+ var CONFIG_TRACK_INPUT = 'input';
8735
+ var CONFIG_TRACK_PAGEVIEW = 'pageview';
8736
+ var CONFIG_TRACK_SCROLL = 'scroll';
8737
+ var CONFIG_TRACK_SUBMIT = 'submit';
8738
+
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] = [];
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];
8750
+ CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
8751
+ CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
8752
+ CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
8753
+ CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
8754
+ CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
8755
+
8756
+ var DEFAULT_PROPS = {
8757
+ '$mp_autocapture': true
8758
+ };
8759
+
8760
+ var MP_EV_CLICK = '$mp_click';
8761
+ var MP_EV_INPUT = '$mp_input_change';
8762
+ var MP_EV_SCROLL = '$mp_scroll';
8763
+ var MP_EV_SUBMIT = '$mp_submit';
8764
+
8765
+ /**
8766
+ * Autocapture: manages automatic event tracking
8767
+ * @constructor
8768
+ */
8769
+ var Autocapture = function(mp) {
8770
+ this.mp = mp;
8771
+ };
8772
+
8773
+ Autocapture.prototype.init = function() {
8774
+ if (!minDOMApisSupported()) {
8775
+ logger.critical('Autocapture unavailable: missing required DOM APIs');
8776
+ return;
8777
+ }
8778
+
8779
+ this.initPageviewTracking();
8780
+ this.initClickTracking();
8781
+ this.initInputTracking();
8782
+ this.initScrollTracking();
8783
+ this.initSubmitTracking();
8784
+ };
8785
+
8786
+ Autocapture.prototype.getFullConfig = function() {
8787
+ var autocaptureConfig = this.mp.get_config(AUTOCAPTURE_CONFIG_KEY);
8788
+ if (!autocaptureConfig) {
8789
+ // Autocapture is completely off
8790
+ return {};
8791
+ } else if (_.isObject(autocaptureConfig)) {
8792
+ return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
8793
+ } else {
8794
+ // Autocapture config is non-object truthy value, return default
8795
+ return CONFIG_DEFAULTS;
8796
+ }
8797
+ };
8798
+
8799
+ Autocapture.prototype.getConfig = function(key) {
8800
+ return this.getFullConfig()[key];
8801
+ };
8802
+
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
+
8829
+ var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
8830
+ if (!blockUrlRegexes || !blockUrlRegexes.length) {
8831
+ return false;
8832
+ }
8833
+
8834
+ for (i = 0; i < blockUrlRegexes.length; i++) {
8835
+ try {
8836
+ if (currentUrl.match(blockUrlRegexes[i])) {
8837
+ return true;
8838
+ }
8839
+ } catch (err) {
8840
+ logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
8841
+ return true;
8842
+ }
8843
+ }
8844
+ return false;
8845
+ };
8846
+
8847
+ Autocapture.prototype.pageviewTrackingConfig = function() {
8848
+ // supports both autocapture config and old track_pageview config
8849
+ if (this.mp.get_config(AUTOCAPTURE_CONFIG_KEY)) {
8850
+ return this.getConfig(CONFIG_TRACK_PAGEVIEW);
8851
+ } else {
8852
+ return this.mp.get_config(LEGACY_PAGEVIEW_CONFIG_KEY);
8853
+ }
8854
+ };
8855
+
8856
+ // helper for event handlers
8857
+ Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
8858
+ if (this.currentUrlBlocked()) {
8859
+ return;
8860
+ }
8861
+
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
+ });
8871
+ if (props) {
8872
+ _.extend(props, DEFAULT_PROPS);
8873
+ this.mp.track(mpEventName, props);
8874
+ }
8875
+ };
8876
+
8877
+ Autocapture.prototype.initClickTracking = function() {
8878
+ win.removeEventListener(EV_CLICK, this.listenerClick);
8879
+
8880
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
8881
+ return;
8882
+ }
8883
+ logger.log('Initializing click tracking');
8884
+
8885
+ this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
8886
+ if (!this.getConfig(CONFIG_TRACK_CLICK)) {
8887
+ return;
8888
+ }
8889
+ this.trackDomEvent(ev, MP_EV_CLICK);
8890
+ }.bind(this));
8891
+ };
8892
+
8893
+ Autocapture.prototype.initInputTracking = function() {
8894
+ win.removeEventListener(EV_CHANGE, this.listenerChange);
8895
+
8896
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
8897
+ return;
8898
+ }
8899
+ logger.log('Initializing input tracking');
8900
+
8901
+ this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
8902
+ if (!this.getConfig(CONFIG_TRACK_INPUT)) {
8903
+ return;
8904
+ }
8905
+ this.trackDomEvent(ev, MP_EV_INPUT);
8906
+ }.bind(this));
8907
+ };
8908
+
8909
+ Autocapture.prototype.initPageviewTracking = function() {
8910
+ win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
8911
+ win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
8912
+ win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
8913
+
8914
+ if (!this.pageviewTrackingConfig()) {
8915
+ return;
8916
+ }
8917
+ logger.log('Initializing pageview tracking');
8918
+
8919
+ var previousTrackedUrl = '';
8920
+ var tracked = false;
8921
+ if (!this.currentUrlBlocked()) {
8922
+ tracked = this.mp.track_pageview(DEFAULT_PROPS);
8923
+ }
8924
+ if (tracked) {
8925
+ previousTrackedUrl = _.info.currentUrl();
8926
+ }
8927
+
8928
+ this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
8929
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
8930
+ });
8931
+ this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
8932
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
8933
+ });
8934
+ var nativePushState = win.history.pushState;
8935
+ if (typeof nativePushState === 'function') {
8936
+ win.history.pushState = function(state, unused, url) {
8937
+ nativePushState.call(win.history, state, unused, url);
8938
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
8939
+ };
8940
+ }
8941
+ var nativeReplaceState = win.history.replaceState;
8942
+ if (typeof nativeReplaceState === 'function') {
8943
+ win.history.replaceState = function(state, unused, url) {
8944
+ nativeReplaceState.call(win.history, state, unused, url);
8945
+ win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
8946
+ };
8947
+ }
8948
+ this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
8949
+ if (this.currentUrlBlocked()) {
8950
+ return;
8951
+ }
8952
+
8953
+ var currentUrl = _.info.currentUrl();
8954
+ var shouldTrack = false;
8955
+ var didPathChange = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
8956
+ var trackPageviewOption = this.pageviewTrackingConfig();
8957
+ if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
8958
+ shouldTrack = currentUrl !== previousTrackedUrl;
8959
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
8960
+ shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
8961
+ } else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
8962
+ shouldTrack = didPathChange;
8963
+ }
8964
+
8965
+ if (shouldTrack) {
8966
+ var tracked = this.mp.track_pageview(DEFAULT_PROPS);
8967
+ if (tracked) {
8968
+ previousTrackedUrl = currentUrl;
8969
+ }
8970
+ if (didPathChange) {
8971
+ this.lastScrollCheckpoint = 0;
8972
+ logger.log('Path change: re-initializing scroll depth checkpoints');
8973
+ }
8974
+ }
8975
+ }.bind(this)));
8976
+ };
8977
+
8978
+ Autocapture.prototype.initScrollTracking = function() {
8979
+ win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
8980
+
8981
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
8982
+ return;
8983
+ }
8984
+ logger.log('Initializing scroll tracking');
8985
+ this.lastScrollCheckpoint = 0;
8986
+
8987
+ this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
8988
+ if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
8989
+ return;
8990
+ }
8991
+ if (this.currentUrlBlocked()) {
8992
+ return;
8993
+ }
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
+
9000
+ var scrollTop = win.scrollY;
9001
+ var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
9002
+ try {
9003
+ var scrollHeight = document$1.body.scrollHeight;
9004
+ var scrollPercentage = Math.round((scrollTop / (scrollHeight - win.innerHeight)) * 100);
9005
+ props['$scroll_height'] = scrollHeight;
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
+ }
9020
+ } catch (err) {
9021
+ logger.critical('Error while calculating scroll percentage', err);
9022
+ }
9023
+ if (shouldTrack) {
9024
+ this.mp.track(MP_EV_SCROLL, props);
9025
+ }
9026
+ }.bind(this)));
9027
+ };
9028
+
9029
+ Autocapture.prototype.initSubmitTracking = function() {
9030
+ win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
9031
+
9032
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
9033
+ return;
9034
+ }
9035
+ logger.log('Initializing submit tracking');
9036
+
9037
+ this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
9038
+ if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
9039
+ return;
9040
+ }
9041
+ this.trackDomEvent(ev, MP_EV_SUBMIT);
9042
+ }.bind(this));
9043
+ };
9044
+
9045
+ // TODO integrate error_reporter from mixpanel instance
9046
+ safewrapClass(Autocapture);
9047
+
8176
9048
  /* eslint camelcase: "off" */
8177
9049
 
8178
9050
  /**
@@ -9419,8 +10291,12 @@
9419
10291
  if (!(k in union_q)) {
9420
10292
  union_q[k] = [];
9421
10293
  }
9422
- // We may send duplicates, the server will dedup them.
9423
- union_q[k] = union_q[k].concat(v);
10294
+ // Prevent duplicate values
10295
+ _.each(v, function(item) {
10296
+ if (!_.include(union_q[k], item)) {
10297
+ union_q[k].push(item);
10298
+ }
10299
+ });
9424
10300
  }
9425
10301
  });
9426
10302
  this._pop_from_people_queue(UNSET_ACTION, q_data);
@@ -9592,6 +10468,7 @@
9592
10468
  'api_transport': 'XHR',
9593
10469
  'api_payload_format': PAYLOAD_TYPE_BASE64,
9594
10470
  'app_host': 'https://mixpanel.com',
10471
+ 'autocapture': false,
9595
10472
  'cdn': 'https://cdn.mxpnl.com',
9596
10473
  'cross_site_cookie': false,
9597
10474
  'cross_subdomain_cookie': true,
@@ -9851,10 +10728,8 @@
9851
10728
  }, '');
9852
10729
  }
9853
10730
 
9854
- var track_pageview_option = this.get_config('track_pageview');
9855
- if (track_pageview_option) {
9856
- this._init_url_change_tracking(track_pageview_option);
9857
- }
10731
+ this.autocapture = new Autocapture(this);
10732
+ this.autocapture.init();
9858
10733
 
9859
10734
  if (this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent')) {
9860
10735
  this.start_session_recording();
@@ -9979,55 +10854,6 @@
9979
10854
  return dt.track.apply(dt, args);
9980
10855
  };
9981
10856
 
9982
- MixpanelLib.prototype._init_url_change_tracking = function(track_pageview_option) {
9983
- var previous_tracked_url = '';
9984
- var tracked = this.track_pageview();
9985
- if (tracked) {
9986
- previous_tracked_url = _.info.currentUrl();
9987
- }
9988
-
9989
- if (_.include(['full-url', 'url-with-path-and-query-string', 'url-with-path'], track_pageview_option)) {
9990
- win.addEventListener('popstate', function() {
9991
- win.dispatchEvent(new Event('mp_locationchange'));
9992
- });
9993
- win.addEventListener('hashchange', function() {
9994
- win.dispatchEvent(new Event('mp_locationchange'));
9995
- });
9996
- var nativePushState = win.history.pushState;
9997
- if (typeof nativePushState === 'function') {
9998
- win.history.pushState = function(state, unused, url) {
9999
- nativePushState.call(win.history, state, unused, url);
10000
- win.dispatchEvent(new Event('mp_locationchange'));
10001
- };
10002
- }
10003
- var nativeReplaceState = win.history.replaceState;
10004
- if (typeof nativeReplaceState === 'function') {
10005
- win.history.replaceState = function(state, unused, url) {
10006
- nativeReplaceState.call(win.history, state, unused, url);
10007
- win.dispatchEvent(new Event('mp_locationchange'));
10008
- };
10009
- }
10010
- win.addEventListener('mp_locationchange', function() {
10011
- var current_url = _.info.currentUrl();
10012
- var should_track = false;
10013
- if (track_pageview_option === 'full-url') {
10014
- should_track = current_url !== previous_tracked_url;
10015
- } else if (track_pageview_option === 'url-with-path-and-query-string') {
10016
- should_track = current_url.split('#')[0] !== previous_tracked_url.split('#')[0];
10017
- } else if (track_pageview_option === 'url-with-path') {
10018
- should_track = current_url.split('#')[0].split('?')[0] !== previous_tracked_url.split('#')[0].split('?')[0];
10019
- }
10020
-
10021
- if (should_track) {
10022
- var tracked = this.track_pageview();
10023
- if (tracked) {
10024
- previous_tracked_url = current_url;
10025
- }
10026
- }
10027
- }.bind(this));
10028
- }
10029
- };
10030
-
10031
10857
  /**
10032
10858
  * _prepare_callback() should be called by callers of _send_request for use
10033
10859
  * as the callback argument.
@@ -11285,6 +12111,10 @@
11285
12111
  this['persistence'].update_config(this['config']);
11286
12112
  }
11287
12113
  Config.DEBUG = Config.DEBUG || this.get_config('debug');
12114
+
12115
+ if ('autocapture' in config && this.autocapture) {
12116
+ this.autocapture.init();
12117
+ }
11288
12118
  }
11289
12119
  };
11290
12120