mixpanel-browser 2.57.1 → 2.59.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/.github/workflows/tests.yml +1 -1
- package/CHANGELOG.md +8 -0
- package/dist/mixpanel-core.cjs.js +721 -56
- package/dist/mixpanel-recorder.js +19 -4
- package/dist/mixpanel-recorder.min.js +2 -2
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +721 -56
- package/dist/mixpanel.amd.js +752 -76
- package/dist/mixpanel.cjs.js +752 -76
- package/dist/mixpanel.globals.js +721 -56
- package/dist/mixpanel.min.js +134 -122
- package/dist/mixpanel.module.js +752 -76
- package/dist/mixpanel.umd.js +752 -76
- package/package.json +1 -1
- package/src/autocapture/index.js +271 -0
- package/src/autocapture/utils.js +434 -0
- package/src/config.js +1 -1
- package/src/mixpanel-core.js +9 -53
- package/src/recorder/session-recording.js +12 -1
- package/src/utils.js +27 -1
- package/src/window.js +4 -1
package/dist/mixpanel.amd.js
CHANGED
|
@@ -4509,7 +4509,7 @@ define((function () { 'use strict';
|
|
|
4509
4509
|
|
|
4510
4510
|
var Config = {
|
|
4511
4511
|
DEBUG: false,
|
|
4512
|
-
LIB_VERSION: '2.
|
|
4512
|
+
LIB_VERSION: '2.59.0'
|
|
4513
4513
|
};
|
|
4514
4514
|
|
|
4515
4515
|
// since es6 imports are static and we run unit tests from the console, window won't be defined when importing this file
|
|
@@ -4521,11 +4521,14 @@ define((function () { 'use strict';
|
|
|
4521
4521
|
win = {
|
|
4522
4522
|
navigator: { userAgent: '', onLine: true },
|
|
4523
4523
|
document: {
|
|
4524
|
+
createElement: function() { return {}; },
|
|
4524
4525
|
location: loc,
|
|
4525
4526
|
referrer: ''
|
|
4526
4527
|
},
|
|
4527
4528
|
screen: { width: 0, height: 0 },
|
|
4528
|
-
location: loc
|
|
4529
|
+
location: loc,
|
|
4530
|
+
addEventListener: function() {},
|
|
4531
|
+
removeEventListener: function() {}
|
|
4529
4532
|
};
|
|
4530
4533
|
} else {
|
|
4531
4534
|
win = window;
|
|
@@ -5002,6 +5005,29 @@ define((function () { 'use strict';
|
|
|
5002
5005
|
};
|
|
5003
5006
|
|
|
5004
5007
|
|
|
5008
|
+
var safewrap = function(f) {
|
|
5009
|
+
return function() {
|
|
5010
|
+
try {
|
|
5011
|
+
return f.apply(this, arguments);
|
|
5012
|
+
} catch (e) {
|
|
5013
|
+
console$1.critical('Implementation error. Please turn on debug and contact support@mixpanel.com.');
|
|
5014
|
+
if (Config.DEBUG){
|
|
5015
|
+
console$1.critical(e);
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
};
|
|
5019
|
+
};
|
|
5020
|
+
|
|
5021
|
+
var safewrapClass = function(klass) {
|
|
5022
|
+
var proto = klass.prototype;
|
|
5023
|
+
for (var func in proto) {
|
|
5024
|
+
if (typeof(proto[func]) === 'function') {
|
|
5025
|
+
proto[func] = safewrap(proto[func]);
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
};
|
|
5029
|
+
|
|
5030
|
+
|
|
5005
5031
|
// UNDERSCORE
|
|
5006
5032
|
// Embed part of the Underscore Library
|
|
5007
5033
|
_.bind = function(func, context) {
|
|
@@ -5780,6 +5806,7 @@ define((function () { 'use strict';
|
|
|
5780
5806
|
var BLOCKED_UA_STRS = [
|
|
5781
5807
|
'ahrefsbot',
|
|
5782
5808
|
'ahrefssiteaudit',
|
|
5809
|
+
'amazonbot',
|
|
5783
5810
|
'baiduspider',
|
|
5784
5811
|
'bingbot',
|
|
5785
5812
|
'bingpreview',
|
|
@@ -5789,7 +5816,7 @@ define((function () { 'use strict';
|
|
|
5789
5816
|
'pinterest',
|
|
5790
5817
|
'screaming frog',
|
|
5791
5818
|
'yahoo! slurp',
|
|
5792
|
-
'
|
|
5819
|
+
'yandex',
|
|
5793
5820
|
|
|
5794
5821
|
// a whole bunch of goog-specific crawlers
|
|
5795
5822
|
// https://developers.google.com/search/docs/advanced/crawling/overview-google-crawlers
|
|
@@ -6909,7 +6936,7 @@ define((function () { 'use strict';
|
|
|
6909
6936
|
};
|
|
6910
6937
|
}
|
|
6911
6938
|
|
|
6912
|
-
var logger$
|
|
6939
|
+
var logger$5 = console_with_prefix('lock');
|
|
6913
6940
|
|
|
6914
6941
|
/**
|
|
6915
6942
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -6962,7 +6989,7 @@ define((function () { 'use strict';
|
|
|
6962
6989
|
|
|
6963
6990
|
var delay = function(cb) {
|
|
6964
6991
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
6965
|
-
logger$
|
|
6992
|
+
logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
6966
6993
|
storage.removeItem(keyZ);
|
|
6967
6994
|
storage.removeItem(keyY);
|
|
6968
6995
|
loop();
|
|
@@ -7109,7 +7136,7 @@ define((function () { 'use strict';
|
|
|
7109
7136
|
}, this));
|
|
7110
7137
|
};
|
|
7111
7138
|
|
|
7112
|
-
var logger$
|
|
7139
|
+
var logger$4 = console_with_prefix('batch');
|
|
7113
7140
|
|
|
7114
7141
|
/**
|
|
7115
7142
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -7136,7 +7163,7 @@ define((function () { 'use strict';
|
|
|
7136
7163
|
this.lock = new SharedLock(storageKey, { storage: options.sharedLockStorage || window.localStorage });
|
|
7137
7164
|
this.queueStorage.init();
|
|
7138
7165
|
}
|
|
7139
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
7166
|
+
this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
|
|
7140
7167
|
|
|
7141
7168
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
7142
7169
|
|
|
@@ -7466,7 +7493,7 @@ define((function () { 'use strict';
|
|
|
7466
7493
|
// maximum interval between request retries after exponential backoff
|
|
7467
7494
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
7468
7495
|
|
|
7469
|
-
var logger$
|
|
7496
|
+
var logger$3 = console_with_prefix('batch');
|
|
7470
7497
|
|
|
7471
7498
|
/**
|
|
7472
7499
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -7590,7 +7617,7 @@ define((function () { 'use strict';
|
|
|
7590
7617
|
*/
|
|
7591
7618
|
RequestBatcher.prototype.flush = function(options) {
|
|
7592
7619
|
if (this.requestInProgress) {
|
|
7593
|
-
logger$
|
|
7620
|
+
logger$3.log('Flush: Request already in progress');
|
|
7594
7621
|
return PromisePolyfill.resolve();
|
|
7595
7622
|
}
|
|
7596
7623
|
|
|
@@ -7767,7 +7794,7 @@ define((function () { 'use strict';
|
|
|
7767
7794
|
if (options.unloading) {
|
|
7768
7795
|
requestOptions.transport = 'sendBeacon';
|
|
7769
7796
|
}
|
|
7770
|
-
logger$
|
|
7797
|
+
logger$3.log('MIXPANEL REQUEST:', dataForRequest);
|
|
7771
7798
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
7772
7799
|
}, this))
|
|
7773
7800
|
.catch(_.bind(function(err) {
|
|
@@ -7780,7 +7807,7 @@ define((function () { 'use strict';
|
|
|
7780
7807
|
* Log error to global logger and optional user-defined logger.
|
|
7781
7808
|
*/
|
|
7782
7809
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
7783
|
-
logger$
|
|
7810
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
7784
7811
|
if (this.errorReporter) {
|
|
7785
7812
|
try {
|
|
7786
7813
|
if (!(err instanceof Error)) {
|
|
@@ -7788,12 +7815,12 @@ define((function () { 'use strict';
|
|
|
7788
7815
|
}
|
|
7789
7816
|
this.errorReporter(msg, err);
|
|
7790
7817
|
} catch(err) {
|
|
7791
|
-
logger$
|
|
7818
|
+
logger$3.error(err);
|
|
7792
7819
|
}
|
|
7793
7820
|
}
|
|
7794
7821
|
};
|
|
7795
7822
|
|
|
7796
|
-
var logger$
|
|
7823
|
+
var logger$2 = console_with_prefix('recorder');
|
|
7797
7824
|
var CompressionStream = win['CompressionStream'];
|
|
7798
7825
|
|
|
7799
7826
|
var RECORDER_BATCHER_LIB_CONFIG = {
|
|
@@ -7840,6 +7867,7 @@ define((function () { 'use strict';
|
|
|
7840
7867
|
|
|
7841
7868
|
this.seqNo = 0;
|
|
7842
7869
|
this.replayStartTime = null;
|
|
7870
|
+
this.replayStartUrl = null;
|
|
7843
7871
|
this.batchStartUrl = null;
|
|
7844
7872
|
|
|
7845
7873
|
this.idleTimeoutId = null;
|
|
@@ -7873,24 +7901,25 @@ define((function () { 'use strict';
|
|
|
7873
7901
|
|
|
7874
7902
|
SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
7875
7903
|
if (this._stopRecording !== null) {
|
|
7876
|
-
logger$
|
|
7904
|
+
logger$2.log('Recording already in progress, skipping startRecording.');
|
|
7877
7905
|
return;
|
|
7878
7906
|
}
|
|
7879
7907
|
|
|
7880
7908
|
this.recordMaxMs = this.getConfig('record_max_ms');
|
|
7881
7909
|
if (this.recordMaxMs > MAX_RECORDING_MS) {
|
|
7882
7910
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
7883
|
-
logger$
|
|
7911
|
+
logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
7884
7912
|
}
|
|
7885
7913
|
|
|
7886
7914
|
this.recordMinMs = this.getConfig('record_min_ms');
|
|
7887
7915
|
if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
7888
7916
|
this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
7889
|
-
logger$
|
|
7917
|
+
logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
7890
7918
|
}
|
|
7891
7919
|
|
|
7892
7920
|
this.replayStartTime = new Date().getTime();
|
|
7893
7921
|
this.batchStartUrl = _.info.currentUrl();
|
|
7922
|
+
this.replayStartUrl = _.info.currentUrl();
|
|
7894
7923
|
|
|
7895
7924
|
if (shouldStopBatcher || this.recordMinMs > 0) {
|
|
7896
7925
|
// the primary case for shouldStopBatcher is when we're starting recording after a reset
|
|
@@ -7927,9 +7956,17 @@ define((function () { 'use strict';
|
|
|
7927
7956
|
'blockClass': this.getConfig('record_block_class'),
|
|
7928
7957
|
'blockSelector': blockSelector,
|
|
7929
7958
|
'collectFonts': this.getConfig('record_collect_fonts'),
|
|
7959
|
+
'dataURLOptions': { // canvas image options (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
|
|
7960
|
+
'type': 'image/webp',
|
|
7961
|
+
'quality': 0.6
|
|
7962
|
+
},
|
|
7930
7963
|
'maskAllInputs': true,
|
|
7931
7964
|
'maskTextClass': this.getConfig('record_mask_text_class'),
|
|
7932
|
-
'maskTextSelector': this.getConfig('record_mask_text_selector')
|
|
7965
|
+
'maskTextSelector': this.getConfig('record_mask_text_selector'),
|
|
7966
|
+
'recordCanvas': this.getConfig('record_canvas'),
|
|
7967
|
+
'sampling': {
|
|
7968
|
+
'canvas': 15
|
|
7969
|
+
}
|
|
7933
7970
|
});
|
|
7934
7971
|
|
|
7935
7972
|
if (typeof this._stopRecording !== 'function') {
|
|
@@ -8047,6 +8084,7 @@ define((function () { 'use strict';
|
|
|
8047
8084
|
'replay_id': replayId,
|
|
8048
8085
|
'replay_length_ms': replayLengthMs,
|
|
8049
8086
|
'replay_start_time': this.replayStartTime / 1000,
|
|
8087
|
+
'replay_start_url': this.replayStartUrl,
|
|
8050
8088
|
'seq': this.seqNo
|
|
8051
8089
|
};
|
|
8052
8090
|
var eventsJson = _.JSONEncode(data);
|
|
@@ -8079,18 +8117,18 @@ define((function () { 'use strict';
|
|
|
8079
8117
|
|
|
8080
8118
|
|
|
8081
8119
|
SessionRecording.prototype.reportError = function(msg, err) {
|
|
8082
|
-
logger$
|
|
8120
|
+
logger$2.error.apply(logger$2.error, arguments);
|
|
8083
8121
|
try {
|
|
8084
8122
|
if (!err && !(msg instanceof Error)) {
|
|
8085
8123
|
msg = new Error(msg);
|
|
8086
8124
|
}
|
|
8087
8125
|
this.getConfig('error_reporter')(msg, err);
|
|
8088
8126
|
} catch(err) {
|
|
8089
|
-
logger$
|
|
8127
|
+
logger$2.error(err);
|
|
8090
8128
|
}
|
|
8091
8129
|
};
|
|
8092
8130
|
|
|
8093
|
-
var logger = console_with_prefix('recorder');
|
|
8131
|
+
var logger$1 = console_with_prefix('recorder');
|
|
8094
8132
|
|
|
8095
8133
|
/**
|
|
8096
8134
|
* Recorder API: manages recordings and exposes methods public to the core Mixpanel library.
|
|
@@ -8103,17 +8141,17 @@ define((function () { 'use strict';
|
|
|
8103
8141
|
|
|
8104
8142
|
MixpanelRecorder.prototype.startRecording = function(shouldStopBatcher) {
|
|
8105
8143
|
if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
|
|
8106
|
-
logger.log('Recording already in progress, skipping startRecording.');
|
|
8144
|
+
logger$1.log('Recording already in progress, skipping startRecording.');
|
|
8107
8145
|
return;
|
|
8108
8146
|
}
|
|
8109
8147
|
|
|
8110
8148
|
var onIdleTimeout = _.bind(function () {
|
|
8111
|
-
logger.log('Idle timeout reached, restarting recording.');
|
|
8149
|
+
logger$1.log('Idle timeout reached, restarting recording.');
|
|
8112
8150
|
this.resetRecording();
|
|
8113
8151
|
}, this);
|
|
8114
8152
|
|
|
8115
8153
|
var onMaxLengthReached = _.bind(function () {
|
|
8116
|
-
logger.log('Max recording length reached, stopping recording.');
|
|
8154
|
+
logger$1.log('Max recording length reached, stopping recording.');
|
|
8117
8155
|
this.resetRecording();
|
|
8118
8156
|
}, this);
|
|
8119
8157
|
|
|
@@ -8158,6 +8196,689 @@ define((function () { 'use strict';
|
|
|
8158
8196
|
|
|
8159
8197
|
win['__mp_recorder'] = MixpanelRecorder;
|
|
8160
8198
|
|
|
8199
|
+
// stateless utils
|
|
8200
|
+
|
|
8201
|
+
var EV_CHANGE = 'change';
|
|
8202
|
+
var EV_CLICK = 'click';
|
|
8203
|
+
var EV_HASHCHANGE = 'hashchange';
|
|
8204
|
+
var EV_MP_LOCATION_CHANGE = 'mp_locationchange';
|
|
8205
|
+
var EV_POPSTATE = 'popstate';
|
|
8206
|
+
// TODO scrollend isn't available in Safari: document or polyfill?
|
|
8207
|
+
var EV_SCROLLEND = 'scrollend';
|
|
8208
|
+
var EV_SUBMIT = 'submit';
|
|
8209
|
+
|
|
8210
|
+
var CLICK_EVENT_PROPS = [
|
|
8211
|
+
'clientX', 'clientY',
|
|
8212
|
+
'offsetX', 'offsetY',
|
|
8213
|
+
'pageX', 'pageY',
|
|
8214
|
+
'screenX', 'screenY',
|
|
8215
|
+
'x', 'y'
|
|
8216
|
+
];
|
|
8217
|
+
var OPT_IN_CLASSES = ['mp-include'];
|
|
8218
|
+
var OPT_OUT_CLASSES = ['mp-no-track'];
|
|
8219
|
+
var SENSITIVE_DATA_CLASSES = OPT_OUT_CLASSES.concat(['mp-sensitive']);
|
|
8220
|
+
var TRACKED_ATTRS = [
|
|
8221
|
+
'aria-label', 'aria-labelledby', 'aria-describedby',
|
|
8222
|
+
'href', 'name', 'role', 'title', 'type'
|
|
8223
|
+
];
|
|
8224
|
+
|
|
8225
|
+
var logger = console_with_prefix('autocapture');
|
|
8226
|
+
|
|
8227
|
+
|
|
8228
|
+
function getClasses(el) {
|
|
8229
|
+
var classes = {};
|
|
8230
|
+
var classList = getClassName(el).split(' ');
|
|
8231
|
+
for (var i = 0; i < classList.length; i++) {
|
|
8232
|
+
var cls = classList[i];
|
|
8233
|
+
if (cls) {
|
|
8234
|
+
classes[cls] = true;
|
|
8235
|
+
}
|
|
8236
|
+
}
|
|
8237
|
+
return classes;
|
|
8238
|
+
}
|
|
8239
|
+
|
|
8240
|
+
/*
|
|
8241
|
+
* Get the className of an element, accounting for edge cases where element.className is an object
|
|
8242
|
+
* @param {Element} el - element to get the className of
|
|
8243
|
+
* @returns {string} the element's class
|
|
8244
|
+
*/
|
|
8245
|
+
function getClassName(el) {
|
|
8246
|
+
switch(typeof el.className) {
|
|
8247
|
+
case 'string':
|
|
8248
|
+
return el.className;
|
|
8249
|
+
case 'object': // handle cases where className might be SVGAnimatedString or some other type
|
|
8250
|
+
return el.className.baseVal || el.getAttribute('class') || '';
|
|
8251
|
+
default: // future proof
|
|
8252
|
+
return '';
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
8256
|
+
function getPreviousElementSibling(el) {
|
|
8257
|
+
if (el.previousElementSibling) {
|
|
8258
|
+
return el.previousElementSibling;
|
|
8259
|
+
} else {
|
|
8260
|
+
do {
|
|
8261
|
+
el = el.previousSibling;
|
|
8262
|
+
} while (el && !isElementNode(el));
|
|
8263
|
+
return el;
|
|
8264
|
+
}
|
|
8265
|
+
}
|
|
8266
|
+
|
|
8267
|
+
function getPropertiesFromElement(el) {
|
|
8268
|
+
var props = {
|
|
8269
|
+
'$classes': getClassName(el).split(' '),
|
|
8270
|
+
'$tag_name': el.tagName.toLowerCase()
|
|
8271
|
+
};
|
|
8272
|
+
var elId = el.id;
|
|
8273
|
+
if (elId) {
|
|
8274
|
+
props['$id'] = elId;
|
|
8275
|
+
}
|
|
8276
|
+
|
|
8277
|
+
if (shouldTrackElement(el)) {
|
|
8278
|
+
_.each(TRACKED_ATTRS, function(attr) {
|
|
8279
|
+
if (el.hasAttribute(attr)) {
|
|
8280
|
+
var attrVal = el.getAttribute(attr);
|
|
8281
|
+
if (shouldTrackValue(attrVal)) {
|
|
8282
|
+
props['$attr-' + attr] = attrVal;
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
});
|
|
8286
|
+
}
|
|
8287
|
+
|
|
8288
|
+
var nthChild = 1;
|
|
8289
|
+
var nthOfType = 1;
|
|
8290
|
+
var currentElem = el;
|
|
8291
|
+
while (currentElem = getPreviousElementSibling(currentElem)) { // eslint-disable-line no-cond-assign
|
|
8292
|
+
nthChild++;
|
|
8293
|
+
if (currentElem.tagName === el.tagName) {
|
|
8294
|
+
nthOfType++;
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
props['$nth_child'] = nthChild;
|
|
8298
|
+
props['$nth_of_type'] = nthOfType;
|
|
8299
|
+
|
|
8300
|
+
return props;
|
|
8301
|
+
}
|
|
8302
|
+
|
|
8303
|
+
function getPropsForDOMEvent(ev, blockSelectors, captureTextContent) {
|
|
8304
|
+
blockSelectors = blockSelectors || [];
|
|
8305
|
+
var props = null;
|
|
8306
|
+
|
|
8307
|
+
var target = typeof ev.target === 'undefined' ? ev.srcElement : ev.target;
|
|
8308
|
+
if (isTextNode(target)) { // defeat Safari bug (see: http://www.quirksmode.org/js/events_properties.html)
|
|
8309
|
+
target = target.parentNode;
|
|
8310
|
+
}
|
|
8311
|
+
|
|
8312
|
+
if (shouldTrackDomEvent(target, ev)) {
|
|
8313
|
+
var targetElementList = [target];
|
|
8314
|
+
var curEl = target;
|
|
8315
|
+
while (curEl.parentNode && !isTag(curEl, 'body')) {
|
|
8316
|
+
targetElementList.push(curEl.parentNode);
|
|
8317
|
+
curEl = curEl.parentNode;
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
var elementsJson = [];
|
|
8321
|
+
var href, explicitNoTrack = false;
|
|
8322
|
+
_.each(targetElementList, function(el) {
|
|
8323
|
+
var shouldTrackEl = shouldTrackElement(el);
|
|
8324
|
+
|
|
8325
|
+
// if the element or a parent element is an anchor tag
|
|
8326
|
+
// include the href as a property
|
|
8327
|
+
if (el.tagName.toLowerCase() === 'a') {
|
|
8328
|
+
href = el.getAttribute('href');
|
|
8329
|
+
href = shouldTrackEl && shouldTrackValue(href) && href;
|
|
8330
|
+
}
|
|
8331
|
+
|
|
8332
|
+
// allow users to programmatically prevent tracking of elements by adding classes such as 'mp-no-track'
|
|
8333
|
+
var classes = getClasses(el);
|
|
8334
|
+
_.each(OPT_OUT_CLASSES, function(cls) {
|
|
8335
|
+
if (classes[cls]) {
|
|
8336
|
+
explicitNoTrack = true;
|
|
8337
|
+
}
|
|
8338
|
+
});
|
|
8339
|
+
|
|
8340
|
+
if (!explicitNoTrack) {
|
|
8341
|
+
// programmatically prevent tracking of elements that match CSS selectors
|
|
8342
|
+
_.each(blockSelectors, function(sel) {
|
|
8343
|
+
try {
|
|
8344
|
+
if (el['matches'](sel)) {
|
|
8345
|
+
explicitNoTrack = true;
|
|
8346
|
+
}
|
|
8347
|
+
} catch (err) {
|
|
8348
|
+
logger.critical('Error while checking selector: ' + sel, err);
|
|
8349
|
+
}
|
|
8350
|
+
});
|
|
8351
|
+
}
|
|
8352
|
+
|
|
8353
|
+
elementsJson.push(getPropertiesFromElement(el));
|
|
8354
|
+
}, this);
|
|
8355
|
+
|
|
8356
|
+
if (!explicitNoTrack) {
|
|
8357
|
+
var docElement = document$1['documentElement'];
|
|
8358
|
+
props = {
|
|
8359
|
+
'$event_type': ev.type,
|
|
8360
|
+
'$host': win.location.host,
|
|
8361
|
+
'$pathname': win.location.pathname,
|
|
8362
|
+
'$elements': elementsJson,
|
|
8363
|
+
'$el_attr__href': href,
|
|
8364
|
+
'$viewportHeight': Math.max(docElement['clientHeight'], win['innerHeight'] || 0),
|
|
8365
|
+
'$viewportWidth': Math.max(docElement['clientWidth'], win['innerWidth'] || 0)
|
|
8366
|
+
};
|
|
8367
|
+
|
|
8368
|
+
if (captureTextContent) {
|
|
8369
|
+
elementText = getSafeText(target);
|
|
8370
|
+
if (elementText && elementText.length) {
|
|
8371
|
+
props['$el_text'] = elementText;
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
|
|
8375
|
+
if (ev.type === EV_CLICK) {
|
|
8376
|
+
_.each(CLICK_EVENT_PROPS, function(prop) {
|
|
8377
|
+
if (prop in ev) {
|
|
8378
|
+
props['$' + prop] = ev[prop];
|
|
8379
|
+
}
|
|
8380
|
+
});
|
|
8381
|
+
target = guessRealClickTarget(ev);
|
|
8382
|
+
}
|
|
8383
|
+
// prioritize text content from "real" click target if different from original target
|
|
8384
|
+
if (captureTextContent) {
|
|
8385
|
+
var elementText = getSafeText(target);
|
|
8386
|
+
if (elementText && elementText.length) {
|
|
8387
|
+
props['$el_text'] = elementText;
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8390
|
+
|
|
8391
|
+
if (target) {
|
|
8392
|
+
var targetProps = getPropertiesFromElement(target);
|
|
8393
|
+
props['$target'] = targetProps;
|
|
8394
|
+
// pull up more props onto main event props
|
|
8395
|
+
props['$el_classes'] = targetProps['$classes'];
|
|
8396
|
+
_.extend(props, _.strip_empty_properties({
|
|
8397
|
+
'$el_id': targetProps['$id'],
|
|
8398
|
+
'$el_tag_name': targetProps['$tag_name']
|
|
8399
|
+
}));
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8402
|
+
}
|
|
8403
|
+
|
|
8404
|
+
return props;
|
|
8405
|
+
}
|
|
8406
|
+
|
|
8407
|
+
|
|
8408
|
+
/*
|
|
8409
|
+
* Get the direct text content of an element, protecting against sensitive data collection.
|
|
8410
|
+
* Concats textContent of each of the element's text node children; this avoids potential
|
|
8411
|
+
* collection of sensitive data that could happen if we used element.textContent and the
|
|
8412
|
+
* element had sensitive child elements, since element.textContent includes child content.
|
|
8413
|
+
* Scrubs values that look like they could be sensitive (i.e. cc or ssn number).
|
|
8414
|
+
* @param {Element} el - element to get the text of
|
|
8415
|
+
* @returns {string} the element's direct text content
|
|
8416
|
+
*/
|
|
8417
|
+
function getSafeText(el) {
|
|
8418
|
+
var elText = '';
|
|
8419
|
+
|
|
8420
|
+
if (shouldTrackElement(el) && el.childNodes && el.childNodes.length) {
|
|
8421
|
+
_.each(el.childNodes, function(child) {
|
|
8422
|
+
if (isTextNode(child) && child.textContent) {
|
|
8423
|
+
elText += _.trim(child.textContent)
|
|
8424
|
+
// scrub potentially sensitive values
|
|
8425
|
+
.split(/(\s+)/).filter(shouldTrackValue).join('')
|
|
8426
|
+
// normalize whitespace
|
|
8427
|
+
.replace(/[\r\n]/g, ' ').replace(/[ ]+/g, ' ')
|
|
8428
|
+
// truncate
|
|
8429
|
+
.substring(0, 255);
|
|
8430
|
+
}
|
|
8431
|
+
});
|
|
8432
|
+
}
|
|
8433
|
+
|
|
8434
|
+
return _.trim(elText);
|
|
8435
|
+
}
|
|
8436
|
+
|
|
8437
|
+
function guessRealClickTarget(ev) {
|
|
8438
|
+
var target = ev.target;
|
|
8439
|
+
var composedPath = ev['composedPath']();
|
|
8440
|
+
for (var i = 0; i < composedPath.length; i++) {
|
|
8441
|
+
var node = composedPath[i];
|
|
8442
|
+
if (
|
|
8443
|
+
isTag(node, 'a') ||
|
|
8444
|
+
isTag(node, 'button') ||
|
|
8445
|
+
isTag(node, 'input') ||
|
|
8446
|
+
isTag(node, 'select') ||
|
|
8447
|
+
(node.getAttribute && node.getAttribute('role') === 'button')
|
|
8448
|
+
) {
|
|
8449
|
+
target = node;
|
|
8450
|
+
break;
|
|
8451
|
+
}
|
|
8452
|
+
if (node === target) {
|
|
8453
|
+
break;
|
|
8454
|
+
}
|
|
8455
|
+
}
|
|
8456
|
+
return target;
|
|
8457
|
+
}
|
|
8458
|
+
|
|
8459
|
+
/*
|
|
8460
|
+
* Check whether a DOM node has nodeType Node.ELEMENT_NODE
|
|
8461
|
+
* @param {Node} node - node to check
|
|
8462
|
+
* @returns {boolean} whether node is of the correct nodeType
|
|
8463
|
+
*/
|
|
8464
|
+
function isElementNode(node) {
|
|
8465
|
+
return node && node.nodeType === 1; // Node.ELEMENT_NODE - use integer constant for browser portability
|
|
8466
|
+
}
|
|
8467
|
+
|
|
8468
|
+
/*
|
|
8469
|
+
* Check whether an element is of a given tag type.
|
|
8470
|
+
* Due to potential reference discrepancies (such as the webcomponents.js polyfill),
|
|
8471
|
+
* we want to match tagNames instead of specific references because something like
|
|
8472
|
+
* element === document.body won't always work because element might not be a native
|
|
8473
|
+
* element.
|
|
8474
|
+
* @param {Element} el - element to check
|
|
8475
|
+
* @param {string} tag - tag name (e.g., "div")
|
|
8476
|
+
* @returns {boolean} whether el is of the given tag type
|
|
8477
|
+
*/
|
|
8478
|
+
function isTag(el, tag) {
|
|
8479
|
+
return el && el.tagName && el.tagName.toLowerCase() === tag.toLowerCase();
|
|
8480
|
+
}
|
|
8481
|
+
|
|
8482
|
+
/*
|
|
8483
|
+
* Check whether a DOM node is a TEXT_NODE
|
|
8484
|
+
* @param {Node} node - node to check
|
|
8485
|
+
* @returns {boolean} whether node is of type Node.TEXT_NODE
|
|
8486
|
+
*/
|
|
8487
|
+
function isTextNode(node) {
|
|
8488
|
+
return node && node.nodeType === 3; // Node.TEXT_NODE - use integer constant for browser portability
|
|
8489
|
+
}
|
|
8490
|
+
|
|
8491
|
+
function minDOMApisSupported() {
|
|
8492
|
+
try {
|
|
8493
|
+
var testEl = document$1.createElement('div');
|
|
8494
|
+
return !!testEl['matches'];
|
|
8495
|
+
} catch (err) {
|
|
8496
|
+
return false;
|
|
8497
|
+
}
|
|
8498
|
+
}
|
|
8499
|
+
|
|
8500
|
+
/*
|
|
8501
|
+
* Check whether a DOM event should be "tracked" or if it may contain sensitive data
|
|
8502
|
+
* using a variety of heuristics.
|
|
8503
|
+
* @param {Element} el - element to check
|
|
8504
|
+
* @param {Event} ev - event to check
|
|
8505
|
+
* @returns {boolean} whether the event should be tracked
|
|
8506
|
+
*/
|
|
8507
|
+
function shouldTrackDomEvent(el, ev) {
|
|
8508
|
+
if (!el || isTag(el, 'html') || !isElementNode(el)) {
|
|
8509
|
+
return false;
|
|
8510
|
+
}
|
|
8511
|
+
var tag = el.tagName.toLowerCase();
|
|
8512
|
+
switch (tag) {
|
|
8513
|
+
case 'form':
|
|
8514
|
+
return ev.type === EV_SUBMIT;
|
|
8515
|
+
case 'input':
|
|
8516
|
+
if (['button', 'submit'].indexOf(el.getAttribute('type')) === -1) {
|
|
8517
|
+
return ev.type === EV_CHANGE;
|
|
8518
|
+
} else {
|
|
8519
|
+
return ev.type === EV_CLICK;
|
|
8520
|
+
}
|
|
8521
|
+
case 'select':
|
|
8522
|
+
case 'textarea':
|
|
8523
|
+
return ev.type === EV_CHANGE;
|
|
8524
|
+
default:
|
|
8525
|
+
return ev.type === EV_CLICK;
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
|
|
8529
|
+
/*
|
|
8530
|
+
* Check whether a DOM element should be "tracked" or if it may contain sensitive data
|
|
8531
|
+
* using a variety of heuristics.
|
|
8532
|
+
* @param {Element} el - element to check
|
|
8533
|
+
* @returns {boolean} whether the element should be tracked
|
|
8534
|
+
*/
|
|
8535
|
+
function shouldTrackElement(el) {
|
|
8536
|
+
var i;
|
|
8537
|
+
|
|
8538
|
+
for (var curEl = el; curEl.parentNode && !isTag(curEl, 'body'); curEl = curEl.parentNode) {
|
|
8539
|
+
var classes = getClasses(curEl);
|
|
8540
|
+
for (i = 0; i < SENSITIVE_DATA_CLASSES.length; i++) {
|
|
8541
|
+
if (classes[SENSITIVE_DATA_CLASSES[i]]) {
|
|
8542
|
+
return false;
|
|
8543
|
+
}
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
8546
|
+
|
|
8547
|
+
var elClasses = getClasses(el);
|
|
8548
|
+
for (i = 0; i < OPT_IN_CLASSES.length; i++) {
|
|
8549
|
+
if (elClasses[OPT_IN_CLASSES[i]]) {
|
|
8550
|
+
return true;
|
|
8551
|
+
}
|
|
8552
|
+
}
|
|
8553
|
+
|
|
8554
|
+
// don't send data from inputs or similar elements since there will always be
|
|
8555
|
+
// a risk of clientside javascript placing sensitive data in attributes
|
|
8556
|
+
if (
|
|
8557
|
+
isTag(el, 'input') ||
|
|
8558
|
+
isTag(el, 'select') ||
|
|
8559
|
+
isTag(el, 'textarea') ||
|
|
8560
|
+
el.getAttribute('contenteditable') === 'true'
|
|
8561
|
+
) {
|
|
8562
|
+
return false;
|
|
8563
|
+
}
|
|
8564
|
+
|
|
8565
|
+
// don't include hidden or password fields
|
|
8566
|
+
var type = el.type || '';
|
|
8567
|
+
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"]
|
|
8568
|
+
switch(type.toLowerCase()) {
|
|
8569
|
+
case 'hidden':
|
|
8570
|
+
return false;
|
|
8571
|
+
case 'password':
|
|
8572
|
+
return false;
|
|
8573
|
+
}
|
|
8574
|
+
}
|
|
8575
|
+
|
|
8576
|
+
// filter out data from fields that look like sensitive fields
|
|
8577
|
+
var name = el.name || el.id || '';
|
|
8578
|
+
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"]
|
|
8579
|
+
var sensitiveNameRegex = /^cc|cardnum|ccnum|creditcard|csc|cvc|cvv|exp|pass|pwd|routing|seccode|securitycode|securitynum|socialsec|socsec|ssn/i;
|
|
8580
|
+
if (sensitiveNameRegex.test(name.replace(/[^a-zA-Z0-9]/g, ''))) {
|
|
8581
|
+
return false;
|
|
8582
|
+
}
|
|
8583
|
+
}
|
|
8584
|
+
|
|
8585
|
+
return true;
|
|
8586
|
+
}
|
|
8587
|
+
|
|
8588
|
+
|
|
8589
|
+
/*
|
|
8590
|
+
* Check whether a string value should be "tracked" or if it may contain sensitive data
|
|
8591
|
+
* using a variety of heuristics.
|
|
8592
|
+
* @param {string} value - string value to check
|
|
8593
|
+
* @returns {boolean} whether the element should be tracked
|
|
8594
|
+
*/
|
|
8595
|
+
function shouldTrackValue(value) {
|
|
8596
|
+
if (value === null || _.isUndefined(value)) {
|
|
8597
|
+
return false;
|
|
8598
|
+
}
|
|
8599
|
+
|
|
8600
|
+
if (typeof value === 'string') {
|
|
8601
|
+
value = _.trim(value);
|
|
8602
|
+
|
|
8603
|
+
// check to see if input value looks like a credit card number
|
|
8604
|
+
// see: https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s20.html
|
|
8605
|
+
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}))$/;
|
|
8606
|
+
if (ccRegex.test((value || '').replace(/[- ]/g, ''))) {
|
|
8607
|
+
return false;
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8610
|
+
// check to see if input value looks like a social security number
|
|
8611
|
+
var ssnRegex = /(^\d{3}-?\d{2}-?\d{4}$)/;
|
|
8612
|
+
if (ssnRegex.test(value)) {
|
|
8613
|
+
return false;
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8616
|
+
|
|
8617
|
+
return true;
|
|
8618
|
+
}
|
|
8619
|
+
|
|
8620
|
+
var AUTOCAPTURE_CONFIG_KEY = 'autocapture';
|
|
8621
|
+
var LEGACY_PAGEVIEW_CONFIG_KEY = 'track_pageview';
|
|
8622
|
+
|
|
8623
|
+
var PAGEVIEW_OPTION_FULL_URL = 'full-url';
|
|
8624
|
+
var PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING = 'url-with-path-and-query-string';
|
|
8625
|
+
var PAGEVIEW_OPTION_URL_WITH_PATH = 'url-with-path';
|
|
8626
|
+
|
|
8627
|
+
var CONFIG_BLOCK_SELECTORS = 'block_selectors';
|
|
8628
|
+
var CONFIG_BLOCK_URL_REGEXES = 'block_url_regexes';
|
|
8629
|
+
var CONFIG_CAPTURE_TEXT_CONTENT = 'capture_text_content';
|
|
8630
|
+
var CONFIG_TRACK_CLICK = 'click';
|
|
8631
|
+
var CONFIG_TRACK_INPUT = 'input';
|
|
8632
|
+
var CONFIG_TRACK_PAGEVIEW = 'pageview';
|
|
8633
|
+
var CONFIG_TRACK_SCROLL = 'scroll';
|
|
8634
|
+
var CONFIG_TRACK_SUBMIT = 'submit';
|
|
8635
|
+
|
|
8636
|
+
var CONFIG_DEFAULTS = {};
|
|
8637
|
+
CONFIG_DEFAULTS[CONFIG_CAPTURE_TEXT_CONTENT] = false;
|
|
8638
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_CLICK] = true;
|
|
8639
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_INPUT] = true;
|
|
8640
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_PAGEVIEW] = PAGEVIEW_OPTION_FULL_URL;
|
|
8641
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_SCROLL] = true;
|
|
8642
|
+
CONFIG_DEFAULTS[CONFIG_TRACK_SUBMIT] = true;
|
|
8643
|
+
|
|
8644
|
+
var DEFAULT_PROPS = {
|
|
8645
|
+
'$mp_autocapture': true
|
|
8646
|
+
};
|
|
8647
|
+
|
|
8648
|
+
var MP_EV_CLICK = '$mp_click';
|
|
8649
|
+
var MP_EV_INPUT = '$mp_input_change';
|
|
8650
|
+
var MP_EV_SCROLL = '$mp_scroll';
|
|
8651
|
+
var MP_EV_SUBMIT = '$mp_submit';
|
|
8652
|
+
|
|
8653
|
+
/**
|
|
8654
|
+
* Autocapture: manages automatic event tracking
|
|
8655
|
+
* @constructor
|
|
8656
|
+
*/
|
|
8657
|
+
var Autocapture = function(mp) {
|
|
8658
|
+
this.mp = mp;
|
|
8659
|
+
};
|
|
8660
|
+
|
|
8661
|
+
Autocapture.prototype.init = function() {
|
|
8662
|
+
if (!minDOMApisSupported()) {
|
|
8663
|
+
logger.critical('Autocapture unavailable: missing required DOM APIs');
|
|
8664
|
+
return;
|
|
8665
|
+
}
|
|
8666
|
+
|
|
8667
|
+
this.initPageviewTracking();
|
|
8668
|
+
this.initClickTracking();
|
|
8669
|
+
this.initInputTracking();
|
|
8670
|
+
this.initScrollTracking();
|
|
8671
|
+
this.initSubmitTracking();
|
|
8672
|
+
};
|
|
8673
|
+
|
|
8674
|
+
Autocapture.prototype.getFullConfig = function() {
|
|
8675
|
+
var autocaptureConfig = this.mp.get_config(AUTOCAPTURE_CONFIG_KEY);
|
|
8676
|
+
if (!autocaptureConfig) {
|
|
8677
|
+
// Autocapture is completely off
|
|
8678
|
+
return {};
|
|
8679
|
+
} else if (_.isObject(autocaptureConfig)) {
|
|
8680
|
+
return _.extend({}, CONFIG_DEFAULTS, autocaptureConfig);
|
|
8681
|
+
} else {
|
|
8682
|
+
// Autocapture config is non-object truthy value, return default
|
|
8683
|
+
return CONFIG_DEFAULTS;
|
|
8684
|
+
}
|
|
8685
|
+
};
|
|
8686
|
+
|
|
8687
|
+
Autocapture.prototype.getConfig = function(key) {
|
|
8688
|
+
return this.getFullConfig()[key];
|
|
8689
|
+
};
|
|
8690
|
+
|
|
8691
|
+
Autocapture.prototype.currentUrlBlocked = function() {
|
|
8692
|
+
var blockUrlRegexes = this.getConfig(CONFIG_BLOCK_URL_REGEXES) || [];
|
|
8693
|
+
if (!blockUrlRegexes || !blockUrlRegexes.length) {
|
|
8694
|
+
return false;
|
|
8695
|
+
}
|
|
8696
|
+
|
|
8697
|
+
var currentUrl = _.info.currentUrl();
|
|
8698
|
+
for (var i = 0; i < blockUrlRegexes.length; i++) {
|
|
8699
|
+
try {
|
|
8700
|
+
if (currentUrl.match(blockUrlRegexes[i])) {
|
|
8701
|
+
return true;
|
|
8702
|
+
}
|
|
8703
|
+
} catch (err) {
|
|
8704
|
+
logger.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
8705
|
+
return true;
|
|
8706
|
+
}
|
|
8707
|
+
}
|
|
8708
|
+
return false;
|
|
8709
|
+
};
|
|
8710
|
+
|
|
8711
|
+
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
8712
|
+
// supports both autocapture config and old track_pageview config
|
|
8713
|
+
if (this.mp.get_config(AUTOCAPTURE_CONFIG_KEY)) {
|
|
8714
|
+
return this.getConfig(CONFIG_TRACK_PAGEVIEW);
|
|
8715
|
+
} else {
|
|
8716
|
+
return this.mp.get_config(LEGACY_PAGEVIEW_CONFIG_KEY);
|
|
8717
|
+
}
|
|
8718
|
+
};
|
|
8719
|
+
|
|
8720
|
+
// helper for event handlers
|
|
8721
|
+
Autocapture.prototype.trackDomEvent = function(ev, mpEventName) {
|
|
8722
|
+
if (this.currentUrlBlocked()) {
|
|
8723
|
+
return;
|
|
8724
|
+
}
|
|
8725
|
+
|
|
8726
|
+
var props = getPropsForDOMEvent(
|
|
8727
|
+
ev,
|
|
8728
|
+
this.getConfig(CONFIG_BLOCK_SELECTORS),
|
|
8729
|
+
this.getConfig(CONFIG_CAPTURE_TEXT_CONTENT)
|
|
8730
|
+
);
|
|
8731
|
+
if (props) {
|
|
8732
|
+
_.extend(props, DEFAULT_PROPS);
|
|
8733
|
+
this.mp.track(mpEventName, props);
|
|
8734
|
+
}
|
|
8735
|
+
};
|
|
8736
|
+
|
|
8737
|
+
Autocapture.prototype.initClickTracking = function() {
|
|
8738
|
+
win.removeEventListener(EV_CLICK, this.listenerClick);
|
|
8739
|
+
|
|
8740
|
+
if (!this.getConfig(CONFIG_TRACK_CLICK)) {
|
|
8741
|
+
return;
|
|
8742
|
+
}
|
|
8743
|
+
logger.log('Initializing click tracking');
|
|
8744
|
+
|
|
8745
|
+
this.listenerClick = win.addEventListener(EV_CLICK, function(ev) {
|
|
8746
|
+
if (!this.getConfig(CONFIG_TRACK_CLICK)) {
|
|
8747
|
+
return;
|
|
8748
|
+
}
|
|
8749
|
+
this.trackDomEvent(ev, MP_EV_CLICK);
|
|
8750
|
+
}.bind(this));
|
|
8751
|
+
};
|
|
8752
|
+
|
|
8753
|
+
Autocapture.prototype.initInputTracking = function() {
|
|
8754
|
+
win.removeEventListener(EV_CHANGE, this.listenerChange);
|
|
8755
|
+
|
|
8756
|
+
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
8757
|
+
return;
|
|
8758
|
+
}
|
|
8759
|
+
logger.log('Initializing input tracking');
|
|
8760
|
+
|
|
8761
|
+
this.listenerChange = win.addEventListener(EV_CHANGE, function(ev) {
|
|
8762
|
+
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
8763
|
+
return;
|
|
8764
|
+
}
|
|
8765
|
+
this.trackDomEvent(ev, MP_EV_INPUT);
|
|
8766
|
+
}.bind(this));
|
|
8767
|
+
};
|
|
8768
|
+
|
|
8769
|
+
Autocapture.prototype.initPageviewTracking = function() {
|
|
8770
|
+
win.removeEventListener(EV_POPSTATE, this.listenerPopstate);
|
|
8771
|
+
win.removeEventListener(EV_HASHCHANGE, this.listenerHashchange);
|
|
8772
|
+
win.removeEventListener(EV_MP_LOCATION_CHANGE, this.listenerLocationchange);
|
|
8773
|
+
|
|
8774
|
+
if (!this.pageviewTrackingConfig()) {
|
|
8775
|
+
return;
|
|
8776
|
+
}
|
|
8777
|
+
logger.log('Initializing pageview tracking');
|
|
8778
|
+
|
|
8779
|
+
var previousTrackedUrl = '';
|
|
8780
|
+
var tracked = false;
|
|
8781
|
+
if (!this.currentUrlBlocked()) {
|
|
8782
|
+
tracked = this.mp.track_pageview(DEFAULT_PROPS);
|
|
8783
|
+
}
|
|
8784
|
+
if (tracked) {
|
|
8785
|
+
previousTrackedUrl = _.info.currentUrl();
|
|
8786
|
+
}
|
|
8787
|
+
|
|
8788
|
+
this.listenerPopstate = win.addEventListener(EV_POPSTATE, function() {
|
|
8789
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
8790
|
+
});
|
|
8791
|
+
this.listenerHashchange = win.addEventListener(EV_HASHCHANGE, function() {
|
|
8792
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
8793
|
+
});
|
|
8794
|
+
var nativePushState = win.history.pushState;
|
|
8795
|
+
if (typeof nativePushState === 'function') {
|
|
8796
|
+
win.history.pushState = function(state, unused, url) {
|
|
8797
|
+
nativePushState.call(win.history, state, unused, url);
|
|
8798
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
8799
|
+
};
|
|
8800
|
+
}
|
|
8801
|
+
var nativeReplaceState = win.history.replaceState;
|
|
8802
|
+
if (typeof nativeReplaceState === 'function') {
|
|
8803
|
+
win.history.replaceState = function(state, unused, url) {
|
|
8804
|
+
nativeReplaceState.call(win.history, state, unused, url);
|
|
8805
|
+
win.dispatchEvent(new Event(EV_MP_LOCATION_CHANGE));
|
|
8806
|
+
};
|
|
8807
|
+
}
|
|
8808
|
+
this.listenerLocationchange = win.addEventListener(EV_MP_LOCATION_CHANGE, safewrap(function() {
|
|
8809
|
+
if (this.currentUrlBlocked()) {
|
|
8810
|
+
return;
|
|
8811
|
+
}
|
|
8812
|
+
|
|
8813
|
+
var currentUrl = _.info.currentUrl();
|
|
8814
|
+
var shouldTrack = false;
|
|
8815
|
+
var trackPageviewOption = this.pageviewTrackingConfig();
|
|
8816
|
+
if (trackPageviewOption === PAGEVIEW_OPTION_FULL_URL) {
|
|
8817
|
+
shouldTrack = currentUrl !== previousTrackedUrl;
|
|
8818
|
+
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH_AND_QUERY_STRING) {
|
|
8819
|
+
shouldTrack = currentUrl.split('#')[0] !== previousTrackedUrl.split('#')[0];
|
|
8820
|
+
} else if (trackPageviewOption === PAGEVIEW_OPTION_URL_WITH_PATH) {
|
|
8821
|
+
shouldTrack = currentUrl.split('#')[0].split('?')[0] !== previousTrackedUrl.split('#')[0].split('?')[0];
|
|
8822
|
+
}
|
|
8823
|
+
|
|
8824
|
+
if (shouldTrack) {
|
|
8825
|
+
var tracked = this.mp.track_pageview(DEFAULT_PROPS);
|
|
8826
|
+
if (tracked) {
|
|
8827
|
+
previousTrackedUrl = currentUrl;
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
}.bind(this)));
|
|
8831
|
+
};
|
|
8832
|
+
|
|
8833
|
+
Autocapture.prototype.initScrollTracking = function() {
|
|
8834
|
+
win.removeEventListener(EV_SCROLLEND, this.listenerScroll);
|
|
8835
|
+
|
|
8836
|
+
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
8837
|
+
return;
|
|
8838
|
+
}
|
|
8839
|
+
logger.log('Initializing scroll tracking');
|
|
8840
|
+
|
|
8841
|
+
this.listenerScroll = win.addEventListener(EV_SCROLLEND, safewrap(function() {
|
|
8842
|
+
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
8843
|
+
return;
|
|
8844
|
+
}
|
|
8845
|
+
if (this.currentUrlBlocked()) {
|
|
8846
|
+
return;
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
var scrollTop = win.scrollY;
|
|
8850
|
+
var props = _.extend({'$scroll_top': scrollTop}, DEFAULT_PROPS);
|
|
8851
|
+
try {
|
|
8852
|
+
var scrollHeight = document$1.body.scrollHeight;
|
|
8853
|
+
var scrollPercentage = Math.round((scrollTop / (scrollHeight - win.innerHeight)) * 100);
|
|
8854
|
+
props['$scroll_height'] = scrollHeight;
|
|
8855
|
+
props['$scroll_percentage'] = scrollPercentage;
|
|
8856
|
+
} catch (err) {
|
|
8857
|
+
logger.critical('Error while calculating scroll percentage', err);
|
|
8858
|
+
}
|
|
8859
|
+
this.mp.track(MP_EV_SCROLL, props);
|
|
8860
|
+
}.bind(this)));
|
|
8861
|
+
};
|
|
8862
|
+
|
|
8863
|
+
Autocapture.prototype.initSubmitTracking = function() {
|
|
8864
|
+
win.removeEventListener(EV_SUBMIT, this.listenerSubmit);
|
|
8865
|
+
|
|
8866
|
+
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
8867
|
+
return;
|
|
8868
|
+
}
|
|
8869
|
+
logger.log('Initializing submit tracking');
|
|
8870
|
+
|
|
8871
|
+
this.listenerSubmit = win.addEventListener(EV_SUBMIT, function(ev) {
|
|
8872
|
+
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
8873
|
+
return;
|
|
8874
|
+
}
|
|
8875
|
+
this.trackDomEvent(ev, MP_EV_SUBMIT);
|
|
8876
|
+
}.bind(this));
|
|
8877
|
+
};
|
|
8878
|
+
|
|
8879
|
+
// TODO integrate error_reporter from mixpanel instance
|
|
8880
|
+
safewrapClass(Autocapture);
|
|
8881
|
+
|
|
8161
8882
|
/* eslint camelcase: "off" */
|
|
8162
8883
|
|
|
8163
8884
|
/**
|
|
@@ -9577,6 +10298,7 @@ define((function () { 'use strict';
|
|
|
9577
10298
|
'api_transport': 'XHR',
|
|
9578
10299
|
'api_payload_format': PAYLOAD_TYPE_BASE64,
|
|
9579
10300
|
'app_host': 'https://mixpanel.com',
|
|
10301
|
+
'autocapture': false,
|
|
9580
10302
|
'cdn': 'https://cdn.mxpnl.com',
|
|
9581
10303
|
'cross_site_cookie': false,
|
|
9582
10304
|
'cross_subdomain_cookie': true,
|
|
@@ -9619,6 +10341,7 @@ define((function () { 'use strict';
|
|
|
9619
10341
|
'hooks': {},
|
|
9620
10342
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
9621
10343
|
'record_block_selector': 'img, video',
|
|
10344
|
+
'record_canvas': false,
|
|
9622
10345
|
'record_collect_fonts': false,
|
|
9623
10346
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
9624
10347
|
'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
|
|
@@ -9835,10 +10558,8 @@ define((function () { 'use strict';
|
|
|
9835
10558
|
}, '');
|
|
9836
10559
|
}
|
|
9837
10560
|
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
this._init_url_change_tracking(track_pageview_option);
|
|
9841
|
-
}
|
|
10561
|
+
this.autocapture = new Autocapture(this);
|
|
10562
|
+
this.autocapture.init();
|
|
9842
10563
|
|
|
9843
10564
|
if (this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent')) {
|
|
9844
10565
|
this.start_session_recording();
|
|
@@ -9963,55 +10684,6 @@ define((function () { 'use strict';
|
|
|
9963
10684
|
return dt.track.apply(dt, args);
|
|
9964
10685
|
};
|
|
9965
10686
|
|
|
9966
|
-
MixpanelLib.prototype._init_url_change_tracking = function(track_pageview_option) {
|
|
9967
|
-
var previous_tracked_url = '';
|
|
9968
|
-
var tracked = this.track_pageview();
|
|
9969
|
-
if (tracked) {
|
|
9970
|
-
previous_tracked_url = _.info.currentUrl();
|
|
9971
|
-
}
|
|
9972
|
-
|
|
9973
|
-
if (_.include(['full-url', 'url-with-path-and-query-string', 'url-with-path'], track_pageview_option)) {
|
|
9974
|
-
win.addEventListener('popstate', function() {
|
|
9975
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
9976
|
-
});
|
|
9977
|
-
win.addEventListener('hashchange', function() {
|
|
9978
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
9979
|
-
});
|
|
9980
|
-
var nativePushState = win.history.pushState;
|
|
9981
|
-
if (typeof nativePushState === 'function') {
|
|
9982
|
-
win.history.pushState = function(state, unused, url) {
|
|
9983
|
-
nativePushState.call(win.history, state, unused, url);
|
|
9984
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
9985
|
-
};
|
|
9986
|
-
}
|
|
9987
|
-
var nativeReplaceState = win.history.replaceState;
|
|
9988
|
-
if (typeof nativeReplaceState === 'function') {
|
|
9989
|
-
win.history.replaceState = function(state, unused, url) {
|
|
9990
|
-
nativeReplaceState.call(win.history, state, unused, url);
|
|
9991
|
-
win.dispatchEvent(new Event('mp_locationchange'));
|
|
9992
|
-
};
|
|
9993
|
-
}
|
|
9994
|
-
win.addEventListener('mp_locationchange', function() {
|
|
9995
|
-
var current_url = _.info.currentUrl();
|
|
9996
|
-
var should_track = false;
|
|
9997
|
-
if (track_pageview_option === 'full-url') {
|
|
9998
|
-
should_track = current_url !== previous_tracked_url;
|
|
9999
|
-
} else if (track_pageview_option === 'url-with-path-and-query-string') {
|
|
10000
|
-
should_track = current_url.split('#')[0] !== previous_tracked_url.split('#')[0];
|
|
10001
|
-
} else if (track_pageview_option === 'url-with-path') {
|
|
10002
|
-
should_track = current_url.split('#')[0].split('?')[0] !== previous_tracked_url.split('#')[0].split('?')[0];
|
|
10003
|
-
}
|
|
10004
|
-
|
|
10005
|
-
if (should_track) {
|
|
10006
|
-
var tracked = this.track_pageview();
|
|
10007
|
-
if (tracked) {
|
|
10008
|
-
previous_tracked_url = current_url;
|
|
10009
|
-
}
|
|
10010
|
-
}
|
|
10011
|
-
}.bind(this));
|
|
10012
|
-
}
|
|
10013
|
-
};
|
|
10014
|
-
|
|
10015
10687
|
/**
|
|
10016
10688
|
* _prepare_callback() should be called by callers of _send_request for use
|
|
10017
10689
|
* as the callback argument.
|
|
@@ -11269,6 +11941,10 @@ define((function () { 'use strict';
|
|
|
11269
11941
|
this['persistence'].update_config(this['config']);
|
|
11270
11942
|
}
|
|
11271
11943
|
Config.DEBUG = Config.DEBUG || this.get_config('debug');
|
|
11944
|
+
|
|
11945
|
+
if ('autocapture' in config && this.autocapture) {
|
|
11946
|
+
this.autocapture.init();
|
|
11947
|
+
}
|
|
11272
11948
|
}
|
|
11273
11949
|
};
|
|
11274
11950
|
|