mixpanel-browser 2.75.0 → 2.76.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/.claude/settings.local.json +14 -0
- package/.github/workflows/integration-tests.yml +2 -2
- package/.github/workflows/unit-tests.yml +2 -2
- package/CHANGELOG.md +10 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
- package/dist/mixpanel-core.cjs.d.ts +68 -0
- package/dist/mixpanel-core.cjs.js +550 -383
- package/dist/mixpanel-recorder.js +708 -32
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +6 -62
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +68 -0
- package/dist/mixpanel-with-async-modules.cjs.js +550 -383
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +550 -383
- package/dist/mixpanel-with-recorder.d.ts +68 -0
- package/dist/mixpanel-with-recorder.js +1036 -197
- package/dist/mixpanel-with-recorder.min.d.ts +68 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +68 -0
- package/dist/mixpanel.amd.js +1038 -251
- package/dist/mixpanel.cjs.d.ts +68 -0
- package/dist/mixpanel.cjs.js +1038 -251
- package/dist/mixpanel.globals.js +550 -383
- package/dist/mixpanel.min.js +184 -181
- package/dist/mixpanel.module.d.ts +68 -0
- package/dist/mixpanel.module.js +1038 -251
- package/dist/mixpanel.umd.d.ts +68 -0
- package/dist/mixpanel.umd.js +1038 -251
- package/logo.svg +5 -0
- package/package.json +2 -1
- package/rollup.config.mjs +163 -46
- package/src/autocapture/index.js +10 -27
- package/src/config.js +9 -3
- package/src/flags/index.js +1 -2
- package/src/index.d.ts +68 -0
- package/src/mixpanel-core.js +76 -111
- package/src/recorder/index.js +1 -1
- package/src/recorder/recorder.js +5 -1
- package/src/recorder/rrweb-network-plugin.js +649 -0
- package/src/recorder/session-recording.js +31 -11
- package/src/recorder-manager.js +216 -0
- package/src/request-batcher.js +1 -1
- package/src/targeting/event-matcher.js +2 -57
- package/src/targeting/index.js +1 -1
- package/src/targeting/loader.js +1 -1
- package/src/utils.js +13 -1
- package/testServer.js +55 -0
- package/src/globals.js +0 -14
|
@@ -26,16 +26,19 @@
|
|
|
26
26
|
win = window;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
var Config = {
|
|
30
|
+
DEBUG: false,
|
|
31
|
+
LIB_VERSION: '2.76.0'
|
|
32
|
+
};
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// Window global names for async modules
|
|
34
35
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
35
|
-
|
|
36
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
37
36
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
38
37
|
|
|
38
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
39
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
40
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
41
|
+
|
|
39
42
|
function _array_like_to_array(arr, len) {
|
|
40
43
|
if (len == null || len > arr.length) len = arr.length;
|
|
41
44
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -18135,7 +18138,7 @@
|
|
|
18135
18138
|
var __publicField = function(obj, key, value) {
|
|
18136
18139
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18137
18140
|
};
|
|
18138
|
-
function patch(source, name, replacement) {
|
|
18141
|
+
function patch$3(source, name, replacement) {
|
|
18139
18142
|
try {
|
|
18140
18143
|
if (!(name in source)) {
|
|
18141
18144
|
return function() {};
|
|
@@ -18552,7 +18555,7 @@
|
|
|
18552
18555
|
if (!_logger[level]) {
|
|
18553
18556
|
return function() {};
|
|
18554
18557
|
}
|
|
18555
|
-
return patch(_logger, level, function(original) {
|
|
18558
|
+
return patch$3(_logger, level, function(original) {
|
|
18556
18559
|
var _this1 = _this;
|
|
18557
18560
|
return function() {
|
|
18558
18561
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18973,11 +18976,6 @@
|
|
|
18973
18976
|
PromisePolyfill = NpoPromise;
|
|
18974
18977
|
}
|
|
18975
18978
|
|
|
18976
|
-
var Config = {
|
|
18977
|
-
DEBUG: false,
|
|
18978
|
-
LIB_VERSION: '2.75.0'
|
|
18979
|
-
};
|
|
18980
|
-
|
|
18981
18979
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18982
18980
|
|
|
18983
18981
|
// Maximum allowed session recording length
|
|
@@ -20709,6 +20707,17 @@
|
|
|
20709
20707
|
|
|
20710
20708
|
var NOOP_FUNC = function () {};
|
|
20711
20709
|
|
|
20710
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20711
|
+
var matches = false;
|
|
20712
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20713
|
+
if (url.match(regexList[i])) {
|
|
20714
|
+
matches = true;
|
|
20715
|
+
break;
|
|
20716
|
+
}
|
|
20717
|
+
}
|
|
20718
|
+
return matches;
|
|
20719
|
+
};
|
|
20720
|
+
|
|
20712
20721
|
var JSONStringify = null, JSONParse = null;
|
|
20713
20722
|
if (typeof JSON !== 'undefined') {
|
|
20714
20723
|
JSONStringify = JSON.stringify;
|
|
@@ -21180,7 +21189,7 @@
|
|
|
21180
21189
|
};
|
|
21181
21190
|
}
|
|
21182
21191
|
|
|
21183
|
-
var logger$
|
|
21192
|
+
var logger$7 = console_with_prefix('lock');
|
|
21184
21193
|
|
|
21185
21194
|
/**
|
|
21186
21195
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21232,7 +21241,7 @@
|
|
|
21232
21241
|
|
|
21233
21242
|
var delay = function(cb) {
|
|
21234
21243
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21235
|
-
logger$
|
|
21244
|
+
logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21236
21245
|
storage.removeItem(keyZ);
|
|
21237
21246
|
storage.removeItem(keyY);
|
|
21238
21247
|
loop();
|
|
@@ -21379,7 +21388,7 @@
|
|
|
21379
21388
|
}, this));
|
|
21380
21389
|
};
|
|
21381
21390
|
|
|
21382
|
-
var logger$
|
|
21391
|
+
var logger$6 = console_with_prefix('batch');
|
|
21383
21392
|
|
|
21384
21393
|
/**
|
|
21385
21394
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21408,7 +21417,7 @@
|
|
|
21408
21417
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21409
21418
|
});
|
|
21410
21419
|
}
|
|
21411
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21420
|
+
this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
|
|
21412
21421
|
|
|
21413
21422
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21414
21423
|
|
|
@@ -21741,7 +21750,7 @@
|
|
|
21741
21750
|
// maximum interval between request retries after exponential backoff
|
|
21742
21751
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21743
21752
|
|
|
21744
|
-
var logger$
|
|
21753
|
+
var logger$5 = console_with_prefix('batch');
|
|
21745
21754
|
|
|
21746
21755
|
/**
|
|
21747
21756
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21869,7 +21878,7 @@
|
|
|
21869
21878
|
*/
|
|
21870
21879
|
RequestBatcher.prototype.flush = function(options) {
|
|
21871
21880
|
if (this.requestInProgress) {
|
|
21872
|
-
logger$
|
|
21881
|
+
logger$5.log('Flush: Request already in progress');
|
|
21873
21882
|
return PromisePolyfill.resolve();
|
|
21874
21883
|
}
|
|
21875
21884
|
|
|
@@ -22046,7 +22055,7 @@
|
|
|
22046
22055
|
if (options.unloading) {
|
|
22047
22056
|
requestOptions.transport = 'sendBeacon';
|
|
22048
22057
|
}
|
|
22049
|
-
logger$
|
|
22058
|
+
logger$5.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22050
22059
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22051
22060
|
}, this))
|
|
22052
22061
|
.catch(_.bind(function(err) {
|
|
@@ -22059,7 +22068,7 @@
|
|
|
22059
22068
|
* Log error to global logger and optional user-defined logger.
|
|
22060
22069
|
*/
|
|
22061
22070
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22062
|
-
logger$
|
|
22071
|
+
logger$5.error.apply(logger$5.error, arguments);
|
|
22063
22072
|
if (this.errorReporter) {
|
|
22064
22073
|
try {
|
|
22065
22074
|
if (!(err instanceof Error)) {
|
|
@@ -22067,7 +22076,7 @@
|
|
|
22067
22076
|
}
|
|
22068
22077
|
this.errorReporter(msg, err);
|
|
22069
22078
|
} catch(err) {
|
|
22070
|
-
logger$
|
|
22079
|
+
logger$5.error(err);
|
|
22071
22080
|
}
|
|
22072
22081
|
}
|
|
22073
22082
|
};
|
|
@@ -22189,7 +22198,7 @@
|
|
|
22189
22198
|
|
|
22190
22199
|
var MAX_DEPTH = 5;
|
|
22191
22200
|
|
|
22192
|
-
var logger$
|
|
22201
|
+
var logger$4 = console_with_prefix('autocapture');
|
|
22193
22202
|
|
|
22194
22203
|
|
|
22195
22204
|
function getClasses(el) {
|
|
@@ -22453,7 +22462,7 @@
|
|
|
22453
22462
|
return false;
|
|
22454
22463
|
}
|
|
22455
22464
|
} catch (err) {
|
|
22456
|
-
logger$
|
|
22465
|
+
logger$4.critical('Error while checking element in allowElementCallback', err);
|
|
22457
22466
|
return false;
|
|
22458
22467
|
}
|
|
22459
22468
|
}
|
|
@@ -22470,7 +22479,7 @@
|
|
|
22470
22479
|
return true;
|
|
22471
22480
|
}
|
|
22472
22481
|
} catch (err) {
|
|
22473
|
-
logger$
|
|
22482
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22474
22483
|
}
|
|
22475
22484
|
}
|
|
22476
22485
|
return false;
|
|
@@ -22485,7 +22494,7 @@
|
|
|
22485
22494
|
return true;
|
|
22486
22495
|
}
|
|
22487
22496
|
} catch (err) {
|
|
22488
|
-
logger$
|
|
22497
|
+
logger$4.critical('Error while checking element in blockElementCallback', err);
|
|
22489
22498
|
return true;
|
|
22490
22499
|
}
|
|
22491
22500
|
}
|
|
@@ -22499,7 +22508,7 @@
|
|
|
22499
22508
|
return true;
|
|
22500
22509
|
}
|
|
22501
22510
|
} catch (err) {
|
|
22502
|
-
logger$
|
|
22511
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22503
22512
|
}
|
|
22504
22513
|
}
|
|
22505
22514
|
}
|
|
@@ -23046,6 +23055,655 @@
|
|
|
23046
23055
|
}
|
|
23047
23056
|
}
|
|
23048
23057
|
|
|
23058
|
+
/**
|
|
23059
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23060
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23061
|
+
*
|
|
23062
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23063
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23064
|
+
*
|
|
23065
|
+
*/
|
|
23066
|
+
|
|
23067
|
+
var logger$3 = console_with_prefix('network-plugin');
|
|
23068
|
+
|
|
23069
|
+
/**
|
|
23070
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23071
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23072
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23073
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23074
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23075
|
+
* @param {Window} win
|
|
23076
|
+
* @returns {number}
|
|
23077
|
+
*/
|
|
23078
|
+
function getTimeOrigin(win) {
|
|
23079
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23080
|
+
}
|
|
23081
|
+
|
|
23082
|
+
/**
|
|
23083
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23084
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23085
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23086
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23087
|
+
*/
|
|
23088
|
+
|
|
23089
|
+
/**
|
|
23090
|
+
* @typedef {Record<string, string>} Headers
|
|
23091
|
+
*/
|
|
23092
|
+
|
|
23093
|
+
/**
|
|
23094
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23095
|
+
*/
|
|
23096
|
+
|
|
23097
|
+
/**
|
|
23098
|
+
* @callback networkCallback
|
|
23099
|
+
* @param {NetworkData} data
|
|
23100
|
+
* @returns {void}
|
|
23101
|
+
*/
|
|
23102
|
+
|
|
23103
|
+
/**
|
|
23104
|
+
* @callback listenerHandler
|
|
23105
|
+
* @returns {void}
|
|
23106
|
+
*/
|
|
23107
|
+
|
|
23108
|
+
/**
|
|
23109
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23110
|
+
*/
|
|
23111
|
+
|
|
23112
|
+
/**
|
|
23113
|
+
* @typedef {Object} RecordPlugin
|
|
23114
|
+
* @property {string} name
|
|
23115
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23116
|
+
* @property {NetworkRecordOptions} [options]
|
|
23117
|
+
*/
|
|
23118
|
+
|
|
23119
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23120
|
+
var defaultNetworkOptions = {
|
|
23121
|
+
initiatorTypes: [
|
|
23122
|
+
'audio',
|
|
23123
|
+
'beacon',
|
|
23124
|
+
'body',
|
|
23125
|
+
'css',
|
|
23126
|
+
'early-hint',
|
|
23127
|
+
'embed',
|
|
23128
|
+
'fetch',
|
|
23129
|
+
'frame',
|
|
23130
|
+
'iframe',
|
|
23131
|
+
'icon',
|
|
23132
|
+
'image',
|
|
23133
|
+
'img',
|
|
23134
|
+
'input',
|
|
23135
|
+
'link',
|
|
23136
|
+
'navigation',
|
|
23137
|
+
'object',
|
|
23138
|
+
'ping',
|
|
23139
|
+
'script',
|
|
23140
|
+
'track',
|
|
23141
|
+
'video',
|
|
23142
|
+
'xmlhttprequest',
|
|
23143
|
+
],
|
|
23144
|
+
ignoreRequestFn: function() { return false; },
|
|
23145
|
+
recordHeaders: {
|
|
23146
|
+
request: [],
|
|
23147
|
+
response: [],
|
|
23148
|
+
},
|
|
23149
|
+
recordBodyUrls: {
|
|
23150
|
+
request: [],
|
|
23151
|
+
response: [],
|
|
23152
|
+
},
|
|
23153
|
+
recordInitialRequests: false,
|
|
23154
|
+
};
|
|
23155
|
+
|
|
23156
|
+
/**
|
|
23157
|
+
* @param {PerformanceEntry} entry
|
|
23158
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23159
|
+
*/
|
|
23160
|
+
function isNavigationTiming(entry) {
|
|
23161
|
+
return entry.entryType === 'navigation';
|
|
23162
|
+
}
|
|
23163
|
+
|
|
23164
|
+
/**
|
|
23165
|
+
* @param {PerformanceEntry} entry
|
|
23166
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23167
|
+
*/
|
|
23168
|
+
function isResourceTiming (entry) {
|
|
23169
|
+
return entry.entryType === 'resource';
|
|
23170
|
+
}
|
|
23171
|
+
|
|
23172
|
+
function findLast(array, predicate) {
|
|
23173
|
+
var length = array.length;
|
|
23174
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23175
|
+
if (predicate(array[i])) {
|
|
23176
|
+
return array[i];
|
|
23177
|
+
}
|
|
23178
|
+
}
|
|
23179
|
+
}
|
|
23180
|
+
|
|
23181
|
+
/**
|
|
23182
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23183
|
+
* Adapted from Sentry's `fill` utility:
|
|
23184
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23185
|
+
*
|
|
23186
|
+
* @param {object} source - The object containing the method to patch
|
|
23187
|
+
* @param {string} name - The method name to patch
|
|
23188
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23189
|
+
* @returns {function} A function that restores the original method
|
|
23190
|
+
*/
|
|
23191
|
+
function patch(source, name, replacementFactory) {
|
|
23192
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23193
|
+
return function() {};
|
|
23194
|
+
}
|
|
23195
|
+
var original = source[name];
|
|
23196
|
+
var wrapped = replacementFactory(original);
|
|
23197
|
+
source[name] = wrapped;
|
|
23198
|
+
return function() {
|
|
23199
|
+
source[name] = original;
|
|
23200
|
+
};
|
|
23201
|
+
}
|
|
23202
|
+
|
|
23203
|
+
|
|
23204
|
+
/**
|
|
23205
|
+
* Maximum body size to record (1MB)
|
|
23206
|
+
*/
|
|
23207
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23208
|
+
|
|
23209
|
+
/**
|
|
23210
|
+
* Truncate string if it exceeds max size
|
|
23211
|
+
* @param {string} str
|
|
23212
|
+
* @returns {string}
|
|
23213
|
+
*/
|
|
23214
|
+
function truncateBody(str) {
|
|
23215
|
+
if (!str || typeof str !== 'string') {
|
|
23216
|
+
return str;
|
|
23217
|
+
}
|
|
23218
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23219
|
+
logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23220
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23221
|
+
}
|
|
23222
|
+
return str;
|
|
23223
|
+
}
|
|
23224
|
+
|
|
23225
|
+
/**
|
|
23226
|
+
* @param {networkCallback} cb
|
|
23227
|
+
* @param {Window} win
|
|
23228
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23229
|
+
* @returns {listenerHandler}
|
|
23230
|
+
*/
|
|
23231
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23232
|
+
if (!win.PerformanceObserver) {
|
|
23233
|
+
logger$3.error('PerformanceObserver not supported');
|
|
23234
|
+
return function() {
|
|
23235
|
+
//
|
|
23236
|
+
};
|
|
23237
|
+
}
|
|
23238
|
+
if (options.recordInitialRequests) {
|
|
23239
|
+
var initialPerformanceEntries = win.performance
|
|
23240
|
+
.getEntries()
|
|
23241
|
+
.filter(function(entry) {
|
|
23242
|
+
return isNavigationTiming(entry) ||
|
|
23243
|
+
(isResourceTiming(entry) &&
|
|
23244
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23245
|
+
});
|
|
23246
|
+
cb({
|
|
23247
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23248
|
+
return {
|
|
23249
|
+
url: entry.name,
|
|
23250
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23251
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23252
|
+
startTime: Math.round(entry.startTime),
|
|
23253
|
+
endTime: Math.round(entry.responseEnd),
|
|
23254
|
+
timeOrigin: getTimeOrigin(win),
|
|
23255
|
+
};
|
|
23256
|
+
}),
|
|
23257
|
+
isInitial: true,
|
|
23258
|
+
});
|
|
23259
|
+
}
|
|
23260
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23261
|
+
var performanceEntries = entries
|
|
23262
|
+
.getEntries()
|
|
23263
|
+
.filter(function(entry) {
|
|
23264
|
+
return isResourceTiming(entry) &&
|
|
23265
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23266
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23267
|
+
entry.initiatorType !== 'fetch';
|
|
23268
|
+
});
|
|
23269
|
+
cb({
|
|
23270
|
+
requests: performanceEntries.map(function(entry) {
|
|
23271
|
+
return {
|
|
23272
|
+
url: entry.name,
|
|
23273
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23274
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23275
|
+
startTime: Math.round(entry.startTime),
|
|
23276
|
+
endTime: Math.round(entry.responseEnd),
|
|
23277
|
+
timeOrigin: getTimeOrigin(win),
|
|
23278
|
+
};
|
|
23279
|
+
}),
|
|
23280
|
+
});
|
|
23281
|
+
});
|
|
23282
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23283
|
+
return function() {
|
|
23284
|
+
observer.disconnect();
|
|
23285
|
+
};
|
|
23286
|
+
}
|
|
23287
|
+
|
|
23288
|
+
/**
|
|
23289
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23290
|
+
* @param {'request' | 'response'} type
|
|
23291
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23292
|
+
* @param {string} headerName
|
|
23293
|
+
* @returns {boolean}
|
|
23294
|
+
*/
|
|
23295
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23296
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23297
|
+
return false;
|
|
23298
|
+
}
|
|
23299
|
+
|
|
23300
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23301
|
+
}
|
|
23302
|
+
|
|
23303
|
+
/**
|
|
23304
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23305
|
+
* @param {'request' | 'response'} type
|
|
23306
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23307
|
+
* @param {string} url
|
|
23308
|
+
* @returns {boolean}
|
|
23309
|
+
*/
|
|
23310
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23311
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23312
|
+
return false;
|
|
23313
|
+
}
|
|
23314
|
+
|
|
23315
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23316
|
+
}
|
|
23317
|
+
|
|
23318
|
+
function tryReadXHRBody(body) {
|
|
23319
|
+
if (body === null || body === undefined) {
|
|
23320
|
+
return null;
|
|
23321
|
+
}
|
|
23322
|
+
|
|
23323
|
+
var result;
|
|
23324
|
+
if (typeof body === 'string') {
|
|
23325
|
+
result = body;
|
|
23326
|
+
} else if (body instanceof Document) {
|
|
23327
|
+
result = body.textContent;
|
|
23328
|
+
} else if (body instanceof FormData) {
|
|
23329
|
+
result = _.HTTPBuildQuery(body);
|
|
23330
|
+
} else if (_.isObject(body)) {
|
|
23331
|
+
try {
|
|
23332
|
+
result = JSON.stringify(body);
|
|
23333
|
+
} catch (e) {
|
|
23334
|
+
return 'Failed to stringify response object';
|
|
23335
|
+
}
|
|
23336
|
+
} else {
|
|
23337
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23338
|
+
}
|
|
23339
|
+
|
|
23340
|
+
return truncateBody(result);
|
|
23341
|
+
}
|
|
23342
|
+
|
|
23343
|
+
/**
|
|
23344
|
+
* @param {Request | Response} r
|
|
23345
|
+
* @returns {Promise<string>}
|
|
23346
|
+
*/
|
|
23347
|
+
function tryReadFetchBody(r) {
|
|
23348
|
+
return new Promise(function(resolve) {
|
|
23349
|
+
var timeout = setTimeout(function() {
|
|
23350
|
+
resolve('Timeout while trying to read body');
|
|
23351
|
+
}, 500);
|
|
23352
|
+
try {
|
|
23353
|
+
r.clone()
|
|
23354
|
+
.text()
|
|
23355
|
+
.then(
|
|
23356
|
+
function(txt) {
|
|
23357
|
+
clearTimeout(timeout);
|
|
23358
|
+
resolve(truncateBody(txt));
|
|
23359
|
+
},
|
|
23360
|
+
function(reason) {
|
|
23361
|
+
clearTimeout(timeout);
|
|
23362
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23363
|
+
}
|
|
23364
|
+
);
|
|
23365
|
+
} catch (e) {
|
|
23366
|
+
clearTimeout(timeout);
|
|
23367
|
+
resolve('Failed to read body: ' + String(e));
|
|
23368
|
+
}
|
|
23369
|
+
});
|
|
23370
|
+
}
|
|
23371
|
+
|
|
23372
|
+
/**
|
|
23373
|
+
* @param {Window} win
|
|
23374
|
+
* @param {string} initiatorType
|
|
23375
|
+
* @param {string} url
|
|
23376
|
+
* @param {number} [after]
|
|
23377
|
+
* @param {number} [before]
|
|
23378
|
+
* @param {number} [attempt]
|
|
23379
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23380
|
+
*/
|
|
23381
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23382
|
+
if (attempt === undefined) {
|
|
23383
|
+
attempt = 0;
|
|
23384
|
+
}
|
|
23385
|
+
if (attempt > 10) {
|
|
23386
|
+
logger$3.error('Cannot find performance entry');
|
|
23387
|
+
return Promise.resolve(null);
|
|
23388
|
+
}
|
|
23389
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23390
|
+
win.performance.getEntriesByName(url)
|
|
23391
|
+
);
|
|
23392
|
+
var performanceEntry = findLast(
|
|
23393
|
+
urlPerformanceEntries,
|
|
23394
|
+
function(entry) {
|
|
23395
|
+
return isResourceTiming(entry) &&
|
|
23396
|
+
entry.initiatorType === initiatorType &&
|
|
23397
|
+
(!after || entry.startTime >= after) &&
|
|
23398
|
+
(!before || entry.startTime <= before);
|
|
23399
|
+
}
|
|
23400
|
+
);
|
|
23401
|
+
if (!performanceEntry) {
|
|
23402
|
+
return new Promise(function(resolve) {
|
|
23403
|
+
setTimeout(resolve, 50 * attempt);
|
|
23404
|
+
}).then(function() {
|
|
23405
|
+
return getRequestPerformanceEntry(
|
|
23406
|
+
win,
|
|
23407
|
+
initiatorType,
|
|
23408
|
+
url,
|
|
23409
|
+
after,
|
|
23410
|
+
before,
|
|
23411
|
+
attempt + 1
|
|
23412
|
+
);
|
|
23413
|
+
});
|
|
23414
|
+
}
|
|
23415
|
+
return Promise.resolve(performanceEntry);
|
|
23416
|
+
}
|
|
23417
|
+
|
|
23418
|
+
/**
|
|
23419
|
+
* @param {networkCallback} cb
|
|
23420
|
+
* @param {Window} win
|
|
23421
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23422
|
+
* @returns {listenerHandler}
|
|
23423
|
+
*/
|
|
23424
|
+
function initXhrObserver(cb, win, options) {
|
|
23425
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23426
|
+
return function() {
|
|
23427
|
+
//
|
|
23428
|
+
};
|
|
23429
|
+
}
|
|
23430
|
+
var restorePatch = patch(
|
|
23431
|
+
win.XMLHttpRequest.prototype,
|
|
23432
|
+
'open',
|
|
23433
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23434
|
+
return function(
|
|
23435
|
+
/** @type {string} */ method,
|
|
23436
|
+
/** @type {string | URL} */ url,
|
|
23437
|
+
/** @type {boolean} */ async,
|
|
23438
|
+
username, password
|
|
23439
|
+
) {
|
|
23440
|
+
if (async === undefined) {
|
|
23441
|
+
async = true;
|
|
23442
|
+
}
|
|
23443
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23444
|
+
var req = new Request(url, { method: method });
|
|
23445
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23446
|
+
var networkRequest = {};
|
|
23447
|
+
/** @type {number | undefined} */
|
|
23448
|
+
var after;
|
|
23449
|
+
/** @type {number | undefined} */
|
|
23450
|
+
var before;
|
|
23451
|
+
|
|
23452
|
+
/** @type {Headers} */
|
|
23453
|
+
var requestHeaders = {};
|
|
23454
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23455
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23456
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23457
|
+
requestHeaders[header] = value;
|
|
23458
|
+
}
|
|
23459
|
+
return originalSetRequestHeader(header, value);
|
|
23460
|
+
};
|
|
23461
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23462
|
+
|
|
23463
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23464
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23465
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23466
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23467
|
+
}
|
|
23468
|
+
after = win.performance.now();
|
|
23469
|
+
return originalSend(body);
|
|
23470
|
+
};
|
|
23471
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23472
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23473
|
+
return;
|
|
23474
|
+
}
|
|
23475
|
+
before = win.performance.now();
|
|
23476
|
+
/** @type {Headers} */
|
|
23477
|
+
var responseHeaders = {};
|
|
23478
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23479
|
+
if (rawHeaders) {
|
|
23480
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23481
|
+
headers.forEach(function(line) {
|
|
23482
|
+
if (!line) return;
|
|
23483
|
+
var colonIndex = line.indexOf(': ');
|
|
23484
|
+
if (colonIndex === -1) return;
|
|
23485
|
+
var header = line.substring(0, colonIndex);
|
|
23486
|
+
var value = line.substring(colonIndex + 2);
|
|
23487
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23488
|
+
responseHeaders[header] = value;
|
|
23489
|
+
}
|
|
23490
|
+
});
|
|
23491
|
+
}
|
|
23492
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23493
|
+
if (
|
|
23494
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23495
|
+
) {
|
|
23496
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23497
|
+
}
|
|
23498
|
+
getRequestPerformanceEntry(
|
|
23499
|
+
win,
|
|
23500
|
+
'xmlhttprequest',
|
|
23501
|
+
req.url,
|
|
23502
|
+
after,
|
|
23503
|
+
before
|
|
23504
|
+
)
|
|
23505
|
+
.then(function(entry) {
|
|
23506
|
+
if (!entry) {
|
|
23507
|
+
logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23508
|
+
return;
|
|
23509
|
+
}
|
|
23510
|
+
/** @type {NetworkRequest} */
|
|
23511
|
+
var request = {
|
|
23512
|
+
url: entry.name,
|
|
23513
|
+
method: req.method,
|
|
23514
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23515
|
+
status: xhr.status,
|
|
23516
|
+
startTime: Math.round(entry.startTime),
|
|
23517
|
+
endTime: Math.round(entry.responseEnd),
|
|
23518
|
+
timeOrigin: getTimeOrigin(win),
|
|
23519
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23520
|
+
requestBody: networkRequest.requestBody,
|
|
23521
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23522
|
+
responseBody: networkRequest.responseBody,
|
|
23523
|
+
};
|
|
23524
|
+
cb({ requests: [request] });
|
|
23525
|
+
})
|
|
23526
|
+
.catch(function(e) {
|
|
23527
|
+
logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23528
|
+
});
|
|
23529
|
+
});
|
|
23530
|
+
|
|
23531
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23532
|
+
};
|
|
23533
|
+
}
|
|
23534
|
+
);
|
|
23535
|
+
return function() {
|
|
23536
|
+
restorePatch();
|
|
23537
|
+
};
|
|
23538
|
+
}
|
|
23539
|
+
|
|
23540
|
+
/**
|
|
23541
|
+
* @param {networkCallback} cb
|
|
23542
|
+
* @param {Window} win
|
|
23543
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23544
|
+
* @returns {listenerHandler}
|
|
23545
|
+
*/
|
|
23546
|
+
function initFetchObserver(cb, win, options) {
|
|
23547
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23548
|
+
return function() {
|
|
23549
|
+
//
|
|
23550
|
+
};
|
|
23551
|
+
}
|
|
23552
|
+
|
|
23553
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23554
|
+
return function() {
|
|
23555
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23556
|
+
/** @type {Response | undefined} */
|
|
23557
|
+
var res;
|
|
23558
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23559
|
+
var networkRequest = {};
|
|
23560
|
+
/** @type {number | undefined} */
|
|
23561
|
+
var after;
|
|
23562
|
+
/** @type {number | undefined} */
|
|
23563
|
+
var before;
|
|
23564
|
+
|
|
23565
|
+
var originalFetchPromise;
|
|
23566
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23567
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23568
|
+
try {
|
|
23569
|
+
/** @type {Headers} */
|
|
23570
|
+
var requestHeaders = {};
|
|
23571
|
+
req.headers.forEach(function(value, header) {
|
|
23572
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23573
|
+
requestHeaders[header] = value;
|
|
23574
|
+
}
|
|
23575
|
+
});
|
|
23576
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23577
|
+
|
|
23578
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23579
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23580
|
+
.then(function(body) {
|
|
23581
|
+
networkRequest.requestBody = body;
|
|
23582
|
+
});
|
|
23583
|
+
}
|
|
23584
|
+
|
|
23585
|
+
after = win.performance.now();
|
|
23586
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23587
|
+
res = response;
|
|
23588
|
+
before = win.performance.now();
|
|
23589
|
+
|
|
23590
|
+
/** @type {Headers} */
|
|
23591
|
+
var responseHeaders = {};
|
|
23592
|
+
res.headers.forEach(function(value, header) {
|
|
23593
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23594
|
+
responseHeaders[header] = value;
|
|
23595
|
+
}
|
|
23596
|
+
});
|
|
23597
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23598
|
+
|
|
23599
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23600
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23601
|
+
.then(function(body) {
|
|
23602
|
+
networkRequest.responseBody = body;
|
|
23603
|
+
});
|
|
23604
|
+
}
|
|
23605
|
+
|
|
23606
|
+
return res;
|
|
23607
|
+
});
|
|
23608
|
+
} catch (e) {
|
|
23609
|
+
originalFetchPromise = Promise.reject(e);
|
|
23610
|
+
}
|
|
23611
|
+
|
|
23612
|
+
// await concurrently so we don't delay the fetch response
|
|
23613
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23614
|
+
.then(function () {
|
|
23615
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23616
|
+
})
|
|
23617
|
+
.then(function(entry) {
|
|
23618
|
+
if (!entry) {
|
|
23619
|
+
logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23620
|
+
return;
|
|
23621
|
+
}
|
|
23622
|
+
/** @type {NetworkRequest} */
|
|
23623
|
+
var request = {
|
|
23624
|
+
url: entry.name,
|
|
23625
|
+
method: req.method,
|
|
23626
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23627
|
+
status: res ? res.status : undefined,
|
|
23628
|
+
startTime: Math.round(entry.startTime),
|
|
23629
|
+
endTime: Math.round(entry.responseEnd),
|
|
23630
|
+
timeOrigin: getTimeOrigin(win),
|
|
23631
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23632
|
+
requestBody: networkRequest.requestBody,
|
|
23633
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23634
|
+
responseBody: networkRequest.responseBody,
|
|
23635
|
+
};
|
|
23636
|
+
cb({ requests: [request] });
|
|
23637
|
+
})
|
|
23638
|
+
.catch(function (e) {
|
|
23639
|
+
logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23640
|
+
});
|
|
23641
|
+
|
|
23642
|
+
return originalFetchPromise;
|
|
23643
|
+
};
|
|
23644
|
+
});
|
|
23645
|
+
return function() {
|
|
23646
|
+
restorePatch();
|
|
23647
|
+
};
|
|
23648
|
+
}
|
|
23649
|
+
|
|
23650
|
+
/**
|
|
23651
|
+
* @param {networkCallback} callback
|
|
23652
|
+
* @param {Window} win
|
|
23653
|
+
* @param {NetworkRecordOptions} options
|
|
23654
|
+
* @returns {listenerHandler}
|
|
23655
|
+
*/
|
|
23656
|
+
function initNetworkObserver(callback, win, options) {
|
|
23657
|
+
if (!('performance' in win)) {
|
|
23658
|
+
return function() {
|
|
23659
|
+
//
|
|
23660
|
+
};
|
|
23661
|
+
}
|
|
23662
|
+
|
|
23663
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23664
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23665
|
+
options = Object.assign({}, options, {
|
|
23666
|
+
recordHeaders: recordHeaders,
|
|
23667
|
+
recordBodyUrls: recordBodyUrls,
|
|
23668
|
+
});
|
|
23669
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23670
|
+
|
|
23671
|
+
/** @type {networkCallback} */
|
|
23672
|
+
var cb = function(data) {
|
|
23673
|
+
var requests = data.requests.filter(function(request) {
|
|
23674
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23675
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23676
|
+
});
|
|
23677
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23678
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23679
|
+
}
|
|
23680
|
+
};
|
|
23681
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23682
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23683
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23684
|
+
return function() {
|
|
23685
|
+
performanceObserver();
|
|
23686
|
+
xhrObserver();
|
|
23687
|
+
fetchObserver();
|
|
23688
|
+
};
|
|
23689
|
+
}
|
|
23690
|
+
|
|
23691
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23692
|
+
// a changed format in the mixpanel product.
|
|
23693
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23694
|
+
|
|
23695
|
+
/**
|
|
23696
|
+
* @param {NetworkRecordOptions} [options]
|
|
23697
|
+
* @returns {RecordPlugin}
|
|
23698
|
+
*/
|
|
23699
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23700
|
+
return {
|
|
23701
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23702
|
+
observer: initNetworkObserver,
|
|
23703
|
+
options: options,
|
|
23704
|
+
};
|
|
23705
|
+
};
|
|
23706
|
+
|
|
23049
23707
|
/**
|
|
23050
23708
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23051
23709
|
*/
|
|
@@ -23279,12 +23937,35 @@
|
|
|
23279
23937
|
|
|
23280
23938
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23281
23939
|
|
|
23282
|
-
|
|
23283
|
-
|
|
23284
|
-
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
23940
|
+
var plugins = [];
|
|
23941
|
+
if (this.getConfig('record_network')) {
|
|
23942
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23943
|
+
// don't track requests to Mixpanel /record API
|
|
23944
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23945
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23946
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23947
|
+
|
|
23948
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23949
|
+
}
|
|
23950
|
+
|
|
23951
|
+
if (this.getConfig('record_console')) {
|
|
23952
|
+
plugins.push(
|
|
23953
|
+
getRecordConsolePlugin({
|
|
23954
|
+
stringifyOptions: {
|
|
23955
|
+
stringLengthLimit: 1000,
|
|
23956
|
+
numOfKeysLimit: 50,
|
|
23957
|
+
depthOfLimit: 2
|
|
23958
|
+
}
|
|
23959
|
+
})
|
|
23960
|
+
);
|
|
23961
|
+
}
|
|
23962
|
+
|
|
23963
|
+
try {
|
|
23964
|
+
this._stopRecording = this._rrwebRecord({
|
|
23965
|
+
'emit': function (ev) {
|
|
23966
|
+
if (this.idleExpires && this.idleExpires < ev.timestamp) {
|
|
23967
|
+
this._onIdleTimeout();
|
|
23968
|
+
return;
|
|
23288
23969
|
}
|
|
23289
23970
|
if (isUserEvent(ev)) {
|
|
23290
23971
|
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
@@ -23317,15 +23998,7 @@
|
|
|
23317
23998
|
'sampling': {
|
|
23318
23999
|
'canvas': 15
|
|
23319
24000
|
},
|
|
23320
|
-
'plugins':
|
|
23321
|
-
getRecordConsolePlugin({
|
|
23322
|
-
stringifyOptions: {
|
|
23323
|
-
stringLengthLimit: 1000,
|
|
23324
|
-
numOfKeysLimit: 50,
|
|
23325
|
-
depthOfLimit: 2
|
|
23326
|
-
}
|
|
23327
|
-
})
|
|
23328
|
-
] : []
|
|
24001
|
+
'plugins': plugins,
|
|
23329
24002
|
});
|
|
23330
24003
|
} catch (err) {
|
|
23331
24004
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23440,6 +24113,10 @@
|
|
|
23440
24113
|
return recording;
|
|
23441
24114
|
};
|
|
23442
24115
|
|
|
24116
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24117
|
+
return this.getConfig('api_routes')['record'];
|
|
24118
|
+
};
|
|
24119
|
+
|
|
23443
24120
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23444
24121
|
var onSuccess = function (response, responseBody) {
|
|
23445
24122
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23459,7 +24136,7 @@
|
|
|
23459
24136
|
});
|
|
23460
24137
|
}.bind(this);
|
|
23461
24138
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23462
|
-
win['fetch'](apiHost + '/' + this.
|
|
24139
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23463
24140
|
'method': 'POST',
|
|
23464
24141
|
'headers': {
|
|
23465
24142
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23860,8 +24537,12 @@
|
|
|
23860
24537
|
this.startRecording({shouldStopBatcher: true});
|
|
23861
24538
|
};
|
|
23862
24539
|
|
|
24540
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24541
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24542
|
+
};
|
|
24543
|
+
|
|
23863
24544
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23864
|
-
if (this.
|
|
24545
|
+
if (this.isRecording()) {
|
|
23865
24546
|
return this.activeRecording.replayId;
|
|
23866
24547
|
} else {
|
|
23867
24548
|
return null;
|
|
@@ -23978,7 +24659,7 @@
|
|
|
23978
24659
|
observer.observe(shadowRoot, this.observerConfig);
|
|
23979
24660
|
this.shadowObservers.push(observer);
|
|
23980
24661
|
} catch (e) {
|
|
23981
|
-
logger$
|
|
24662
|
+
logger$4.critical('Error while observing shadow root', e);
|
|
23982
24663
|
}
|
|
23983
24664
|
};
|
|
23984
24665
|
|
|
@@ -23989,7 +24670,7 @@
|
|
|
23989
24670
|
}
|
|
23990
24671
|
|
|
23991
24672
|
if (!weakSetSupported()) {
|
|
23992
|
-
logger$
|
|
24673
|
+
logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
23993
24674
|
return;
|
|
23994
24675
|
}
|
|
23995
24676
|
|
|
@@ -24005,7 +24686,7 @@
|
|
|
24005
24686
|
try {
|
|
24006
24687
|
this.shadowObservers[i].disconnect();
|
|
24007
24688
|
} catch (e) {
|
|
24008
|
-
logger$
|
|
24689
|
+
logger$4.critical('Error while disconnecting shadow DOM observer', e);
|
|
24009
24690
|
}
|
|
24010
24691
|
}
|
|
24011
24692
|
this.shadowObservers = [];
|
|
@@ -24193,7 +24874,7 @@
|
|
|
24193
24874
|
|
|
24194
24875
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24195
24876
|
} catch (e) {
|
|
24196
|
-
logger$
|
|
24877
|
+
logger$4.critical('Error while setting up mutation observer', e);
|
|
24197
24878
|
}
|
|
24198
24879
|
}
|
|
24199
24880
|
|
|
@@ -24208,7 +24889,7 @@
|
|
|
24208
24889
|
);
|
|
24209
24890
|
this.shadowDOMObserver.start();
|
|
24210
24891
|
} catch (e) {
|
|
24211
|
-
logger$
|
|
24892
|
+
logger$4.critical('Error while setting up shadow DOM observer', e);
|
|
24212
24893
|
this.shadowDOMObserver = null;
|
|
24213
24894
|
}
|
|
24214
24895
|
}
|
|
@@ -24235,7 +24916,7 @@
|
|
|
24235
24916
|
try {
|
|
24236
24917
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24237
24918
|
} catch (e) {
|
|
24238
|
-
logger$
|
|
24919
|
+
logger$4.critical('Error while removing event listener', e);
|
|
24239
24920
|
}
|
|
24240
24921
|
}
|
|
24241
24922
|
this.eventListeners = [];
|
|
@@ -24244,7 +24925,7 @@
|
|
|
24244
24925
|
try {
|
|
24245
24926
|
this.mutationObserver.disconnect();
|
|
24246
24927
|
} catch (e) {
|
|
24247
|
-
logger$
|
|
24928
|
+
logger$4.critical('Error while disconnecting mutation observer', e);
|
|
24248
24929
|
}
|
|
24249
24930
|
this.mutationObserver = null;
|
|
24250
24931
|
}
|
|
@@ -24253,7 +24934,7 @@
|
|
|
24253
24934
|
try {
|
|
24254
24935
|
this.shadowDOMObserver.stop();
|
|
24255
24936
|
} catch (e) {
|
|
24256
|
-
logger$
|
|
24937
|
+
logger$4.critical('Error while stopping shadow DOM observer', e);
|
|
24257
24938
|
}
|
|
24258
24939
|
this.shadowDOMObserver = null;
|
|
24259
24940
|
}
|
|
@@ -24331,7 +25012,7 @@
|
|
|
24331
25012
|
|
|
24332
25013
|
Autocapture.prototype.init = function() {
|
|
24333
25014
|
if (!minDOMApisSupported()) {
|
|
24334
|
-
logger$
|
|
25015
|
+
logger$4.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24335
25016
|
return;
|
|
24336
25017
|
}
|
|
24337
25018
|
this.initPageListeners();
|
|
@@ -24363,27 +25044,15 @@
|
|
|
24363
25044
|
};
|
|
24364
25045
|
|
|
24365
25046
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24366
|
-
var i;
|
|
24367
25047
|
var currentUrl = _.info.currentUrl();
|
|
24368
25048
|
|
|
24369
25049
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24370
25050
|
if (allowUrlRegexes.length) {
|
|
24371
25051
|
// we're using an allowlist, only track if current URL matches
|
|
24372
|
-
|
|
24373
|
-
|
|
24374
|
-
|
|
24375
|
-
|
|
24376
|
-
if (currentUrl.match(allowRegex)) {
|
|
24377
|
-
allowed = true;
|
|
24378
|
-
break;
|
|
24379
|
-
}
|
|
24380
|
-
} catch (err) {
|
|
24381
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24382
|
-
return true;
|
|
24383
|
-
}
|
|
24384
|
-
}
|
|
24385
|
-
if (!allowed) {
|
|
24386
|
-
// wasn't allowed by any regex
|
|
25052
|
+
try {
|
|
25053
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25054
|
+
} catch (err) {
|
|
25055
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
24387
25056
|
return true;
|
|
24388
25057
|
}
|
|
24389
25058
|
}
|
|
@@ -24393,17 +25062,12 @@
|
|
|
24393
25062
|
return false;
|
|
24394
25063
|
}
|
|
24395
25064
|
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24399
|
-
|
|
24400
|
-
|
|
24401
|
-
} catch (err) {
|
|
24402
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24403
|
-
return true;
|
|
24404
|
-
}
|
|
25065
|
+
try {
|
|
25066
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25067
|
+
} catch (err) {
|
|
25068
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
25069
|
+
return true;
|
|
24405
25070
|
}
|
|
24406
|
-
return false;
|
|
24407
25071
|
};
|
|
24408
25072
|
|
|
24409
25073
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -24539,7 +25203,7 @@
|
|
|
24539
25203
|
return;
|
|
24540
25204
|
}
|
|
24541
25205
|
|
|
24542
|
-
logger$
|
|
25206
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
24543
25207
|
|
|
24544
25208
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
24545
25209
|
|
|
@@ -24565,7 +25229,7 @@
|
|
|
24565
25229
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
24566
25230
|
return;
|
|
24567
25231
|
}
|
|
24568
|
-
logger$
|
|
25232
|
+
logger$4.log('Initializing click tracking');
|
|
24569
25233
|
|
|
24570
25234
|
this.listenerClick = function(ev) {
|
|
24571
25235
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -24584,7 +25248,7 @@
|
|
|
24584
25248
|
return;
|
|
24585
25249
|
}
|
|
24586
25250
|
|
|
24587
|
-
logger$
|
|
25251
|
+
logger$4.log('Initializing dead click tracking');
|
|
24588
25252
|
if (!this._deadClickTracker) {
|
|
24589
25253
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
24590
25254
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -24618,7 +25282,7 @@
|
|
|
24618
25282
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
24619
25283
|
return;
|
|
24620
25284
|
}
|
|
24621
|
-
logger$
|
|
25285
|
+
logger$4.log('Initializing input tracking');
|
|
24622
25286
|
|
|
24623
25287
|
this.listenerChange = function(ev) {
|
|
24624
25288
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -24635,7 +25299,7 @@
|
|
|
24635
25299
|
if (!this.pageviewTrackingConfig()) {
|
|
24636
25300
|
return;
|
|
24637
25301
|
}
|
|
24638
|
-
logger$
|
|
25302
|
+
logger$4.log('Initializing pageview tracking');
|
|
24639
25303
|
|
|
24640
25304
|
var previousTrackedUrl = '';
|
|
24641
25305
|
var tracked = false;
|
|
@@ -24670,7 +25334,7 @@
|
|
|
24670
25334
|
}
|
|
24671
25335
|
if (didPathChange) {
|
|
24672
25336
|
this.lastScrollCheckpoint = 0;
|
|
24673
|
-
logger$
|
|
25337
|
+
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
24674
25338
|
}
|
|
24675
25339
|
}
|
|
24676
25340
|
}.bind(this));
|
|
@@ -24685,7 +25349,7 @@
|
|
|
24685
25349
|
return;
|
|
24686
25350
|
}
|
|
24687
25351
|
|
|
24688
|
-
logger$
|
|
25352
|
+
logger$4.log('Initializing rage click tracking');
|
|
24689
25353
|
if (!this._rageClickTracker) {
|
|
24690
25354
|
this._rageClickTracker = new RageClickTracker();
|
|
24691
25355
|
}
|
|
@@ -24715,7 +25379,7 @@
|
|
|
24715
25379
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
24716
25380
|
return;
|
|
24717
25381
|
}
|
|
24718
|
-
logger$
|
|
25382
|
+
logger$4.log('Initializing scroll tracking');
|
|
24719
25383
|
this.lastScrollCheckpoint = 0;
|
|
24720
25384
|
|
|
24721
25385
|
var scrollTrackFunction = function() {
|
|
@@ -24752,7 +25416,7 @@
|
|
|
24752
25416
|
}
|
|
24753
25417
|
}
|
|
24754
25418
|
} catch (err) {
|
|
24755
|
-
logger$
|
|
25419
|
+
logger$4.critical('Error while calculating scroll percentage', err);
|
|
24756
25420
|
}
|
|
24757
25421
|
if (shouldTrack) {
|
|
24758
25422
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -24770,7 +25434,7 @@
|
|
|
24770
25434
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
24771
25435
|
return;
|
|
24772
25436
|
}
|
|
24773
|
-
logger$
|
|
25437
|
+
logger$4.log('Initializing submit tracking');
|
|
24774
25438
|
|
|
24775
25439
|
this.listenerSubmit = function(ev) {
|
|
24776
25440
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -24792,7 +25456,7 @@
|
|
|
24792
25456
|
return;
|
|
24793
25457
|
}
|
|
24794
25458
|
|
|
24795
|
-
logger$
|
|
25459
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
24796
25460
|
this._initScrollDepthTracking();
|
|
24797
25461
|
var previousTrackedUrl = _.info.currentUrl();
|
|
24798
25462
|
|
|
@@ -25400,6 +26064,214 @@
|
|
|
25400
26064
|
/* eslint camelcase: "off" */
|
|
25401
26065
|
|
|
25402
26066
|
|
|
26067
|
+
/**
|
|
26068
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26069
|
+
* @constructor
|
|
26070
|
+
*/
|
|
26071
|
+
var RecorderManager = function(initOptions) {
|
|
26072
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26073
|
+
// but ideally we should be able to remove this dependency.
|
|
26074
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26075
|
+
|
|
26076
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26077
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26078
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26079
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26080
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26081
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26082
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26083
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26084
|
+
|
|
26085
|
+
this._recorder = null;
|
|
26086
|
+
};
|
|
26087
|
+
|
|
26088
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26089
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26090
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26091
|
+
return PromisePolyfill.resolve(false);
|
|
26092
|
+
}
|
|
26093
|
+
|
|
26094
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26095
|
+
var tab_id = this.getTabId();
|
|
26096
|
+
return recording_registry_idb.init()
|
|
26097
|
+
.then(function () {
|
|
26098
|
+
return recording_registry_idb.getAll();
|
|
26099
|
+
})
|
|
26100
|
+
.then(function (recordings) {
|
|
26101
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26102
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26103
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26104
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26105
|
+
return true;
|
|
26106
|
+
}
|
|
26107
|
+
}
|
|
26108
|
+
return false;
|
|
26109
|
+
})
|
|
26110
|
+
.catch(_.bind(function (err) {
|
|
26111
|
+
this.reportError('Error checking recording registry', err);
|
|
26112
|
+
return false;
|
|
26113
|
+
}, this));
|
|
26114
|
+
};
|
|
26115
|
+
|
|
26116
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26117
|
+
if (!win['MutationObserver']) {
|
|
26118
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26119
|
+
return PromisePolyfill.resolve();
|
|
26120
|
+
}
|
|
26121
|
+
|
|
26122
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26123
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26124
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26125
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26126
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26127
|
+
resolve();
|
|
26128
|
+
}, this));
|
|
26129
|
+
|
|
26130
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26131
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26132
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26133
|
+
} else {
|
|
26134
|
+
handleLoadedRecorder();
|
|
26135
|
+
}
|
|
26136
|
+
}, this));
|
|
26137
|
+
}, this);
|
|
26138
|
+
|
|
26139
|
+
/**
|
|
26140
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26141
|
+
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
26142
|
+
*/
|
|
26143
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26144
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26145
|
+
if (force_start || is_sampled) {
|
|
26146
|
+
return loadRecorder(true);
|
|
26147
|
+
} else {
|
|
26148
|
+
return this.shouldLoadRecorder()
|
|
26149
|
+
.then(_.bind(function (shouldLoad) {
|
|
26150
|
+
if (shouldLoad) {
|
|
26151
|
+
return loadRecorder(false);
|
|
26152
|
+
}
|
|
26153
|
+
return PromisePolyfill.resolve();
|
|
26154
|
+
}, this));
|
|
26155
|
+
}
|
|
26156
|
+
};
|
|
26157
|
+
|
|
26158
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26159
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26160
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26161
|
+
return false;
|
|
26162
|
+
}
|
|
26163
|
+
try {
|
|
26164
|
+
return this._recorder['isRecording']();
|
|
26165
|
+
} catch (e) {
|
|
26166
|
+
this.reportError('Error checking if recording is active', e);
|
|
26167
|
+
return false;
|
|
26168
|
+
}
|
|
26169
|
+
};
|
|
26170
|
+
|
|
26171
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26172
|
+
var isRecording = this.isRecording();
|
|
26173
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26174
|
+
|
|
26175
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26176
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26177
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26178
|
+
var newRate = trigger['percentage'];
|
|
26179
|
+
var propertyFilters = trigger['property_filters'];
|
|
26180
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26181
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26182
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26183
|
+
.then(function(targeting) {
|
|
26184
|
+
try {
|
|
26185
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26186
|
+
event_name,
|
|
26187
|
+
properties,
|
|
26188
|
+
{
|
|
26189
|
+
'event_name': event_name,
|
|
26190
|
+
'property_filters': propertyFilters
|
|
26191
|
+
}
|
|
26192
|
+
);
|
|
26193
|
+
if (result['matches']) {
|
|
26194
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26195
|
+
}
|
|
26196
|
+
} catch (err) {
|
|
26197
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26198
|
+
}
|
|
26199
|
+
}.bind(this)).catch(function(err) {
|
|
26200
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26201
|
+
});
|
|
26202
|
+
} else {
|
|
26203
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26204
|
+
}
|
|
26205
|
+
}
|
|
26206
|
+
}
|
|
26207
|
+
};
|
|
26208
|
+
|
|
26209
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26210
|
+
if (this._recorder) {
|
|
26211
|
+
return this._recorder['stopRecording']();
|
|
26212
|
+
}
|
|
26213
|
+
return PromisePolyfill.resolve();
|
|
26214
|
+
};
|
|
26215
|
+
|
|
26216
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26217
|
+
if (this._recorder) {
|
|
26218
|
+
return this._recorder['pauseRecording']();
|
|
26219
|
+
}
|
|
26220
|
+
return PromisePolyfill.resolve();
|
|
26221
|
+
};
|
|
26222
|
+
|
|
26223
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26224
|
+
if (this._recorder) {
|
|
26225
|
+
return this._recorder['resumeRecording']();
|
|
26226
|
+
}
|
|
26227
|
+
return PromisePolyfill.resolve();
|
|
26228
|
+
};
|
|
26229
|
+
|
|
26230
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26231
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
26232
|
+
};
|
|
26233
|
+
|
|
26234
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26235
|
+
var props = {};
|
|
26236
|
+
var replay_id = this.getSessionReplayId();
|
|
26237
|
+
if (replay_id) {
|
|
26238
|
+
props['$mp_replay_id'] = replay_id;
|
|
26239
|
+
}
|
|
26240
|
+
return props;
|
|
26241
|
+
};
|
|
26242
|
+
|
|
26243
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26244
|
+
var replay_url = null;
|
|
26245
|
+
var replay_id = this.getSessionReplayId();
|
|
26246
|
+
if (replay_id) {
|
|
26247
|
+
var query_params = _.HTTPBuildQuery({
|
|
26248
|
+
'replay_id': replay_id,
|
|
26249
|
+
'distinct_id': this.getDistinctId(),
|
|
26250
|
+
'token': this.getMpConfig('token')
|
|
26251
|
+
});
|
|
26252
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26253
|
+
}
|
|
26254
|
+
return replay_url;
|
|
26255
|
+
};
|
|
26256
|
+
|
|
26257
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26258
|
+
var replay_id = null;
|
|
26259
|
+
if (this._recorder) {
|
|
26260
|
+
replay_id = this._recorder['replayId'];
|
|
26261
|
+
}
|
|
26262
|
+
return replay_id || null;
|
|
26263
|
+
};
|
|
26264
|
+
|
|
26265
|
+
// "private" public method to reach into the recorder in test cases
|
|
26266
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26267
|
+
return this._recorder;
|
|
26268
|
+
};
|
|
26269
|
+
|
|
26270
|
+
safewrapClass(RecorderManager);
|
|
26271
|
+
|
|
26272
|
+
/* eslint camelcase: "off" */
|
|
26273
|
+
|
|
26274
|
+
|
|
25403
26275
|
/**
|
|
25404
26276
|
* DomTracker Object
|
|
25405
26277
|
* @constructor
|
|
@@ -26860,13 +27732,17 @@
|
|
|
26860
27732
|
'record_collect_fonts': false,
|
|
26861
27733
|
'record_console': true,
|
|
26862
27734
|
'record_heatmap_data': false,
|
|
27735
|
+
'recording_event_triggers': {},
|
|
26863
27736
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
26864
27737
|
'record_mask_inputs': true,
|
|
26865
27738
|
'record_max_ms': MAX_RECORDING_MS,
|
|
26866
27739
|
'record_min_ms': 0,
|
|
27740
|
+
'record_network': false,
|
|
27741
|
+
'record_network_options': {},
|
|
26867
27742
|
'record_sessions_percent': 0,
|
|
26868
|
-
'recorder_src':
|
|
26869
|
-
'targeting_src':
|
|
27743
|
+
'recorder_src': null,
|
|
27744
|
+
'targeting_src': null,
|
|
27745
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
26870
27746
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
26871
27747
|
};
|
|
26872
27748
|
|
|
@@ -27020,6 +27896,19 @@
|
|
|
27020
27896
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27021
27897
|
}));
|
|
27022
27898
|
|
|
27899
|
+
this.recorderManager = new RecorderManager({
|
|
27900
|
+
mixpanelInstance: this,
|
|
27901
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
27902
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
27903
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
27904
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
27905
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
27906
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
27907
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
27908
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
27909
|
+
loadExtraBundle: load_extra_bundle
|
|
27910
|
+
});
|
|
27911
|
+
|
|
27023
27912
|
this['_jsc'] = NOOP_FUNC;
|
|
27024
27913
|
|
|
27025
27914
|
this.__dom_loaded_queue = [];
|
|
@@ -27098,7 +27987,7 @@
|
|
|
27098
27987
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27099
27988
|
trackingFunc: _.bind(this.track, this),
|
|
27100
27989
|
loadExtraBundle: load_extra_bundle,
|
|
27101
|
-
targetingSrc: this.get_config('targeting_src')
|
|
27990
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27102
27991
|
});
|
|
27103
27992
|
this.flags.init();
|
|
27104
27993
|
this['flags'] = this.flags;
|
|
@@ -27111,11 +28000,11 @@
|
|
|
27111
28000
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27112
28001
|
var mode = this.get_config('remote_settings_mode');
|
|
27113
28002
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27114
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27115
|
-
this._check_and_start_session_recording();
|
|
28003
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28004
|
+
return this._check_and_start_session_recording();
|
|
27116
28005
|
}, this));
|
|
27117
28006
|
} else {
|
|
27118
|
-
this._check_and_start_session_recording();
|
|
28007
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27119
28008
|
}
|
|
27120
28009
|
};
|
|
27121
28010
|
|
|
@@ -27159,132 +28048,50 @@
|
|
|
27159
28048
|
return this.tab_id || null;
|
|
27160
28049
|
};
|
|
27161
28050
|
|
|
27162
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27163
|
-
if (this.get_config('disable_persistence')) {
|
|
27164
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27165
|
-
return Promise.resolve(false);
|
|
27166
|
-
}
|
|
27167
|
-
|
|
27168
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27169
|
-
var tab_id = this.get_tab_id();
|
|
27170
|
-
return recording_registry_idb.init()
|
|
27171
|
-
.then(function () {
|
|
27172
|
-
return recording_registry_idb.getAll();
|
|
27173
|
-
})
|
|
27174
|
-
.then(function (recordings) {
|
|
27175
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27176
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27177
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27178
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27179
|
-
return true;
|
|
27180
|
-
}
|
|
27181
|
-
}
|
|
27182
|
-
return false;
|
|
27183
|
-
})
|
|
27184
|
-
.catch(_.bind(function (err) {
|
|
27185
|
-
this.report_error('Error checking recording registry', err);
|
|
27186
|
-
}, this));
|
|
27187
|
-
};
|
|
27188
|
-
|
|
27189
28051
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27190
|
-
|
|
27191
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27192
|
-
return;
|
|
27193
|
-
}
|
|
27194
|
-
|
|
27195
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27196
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27197
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27198
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27199
|
-
}, this);
|
|
27200
|
-
|
|
27201
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27202
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27203
|
-
} else {
|
|
27204
|
-
handleLoadedRecorder();
|
|
27205
|
-
}
|
|
27206
|
-
}, this);
|
|
27207
|
-
|
|
27208
|
-
/**
|
|
27209
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27210
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
27211
|
-
*/
|
|
27212
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27213
|
-
if (force_start || is_sampled) {
|
|
27214
|
-
loadRecorder(true);
|
|
27215
|
-
} else {
|
|
27216
|
-
this._should_load_recorder()
|
|
27217
|
-
.then(function (shouldLoad) {
|
|
27218
|
-
if (shouldLoad) {
|
|
27219
|
-
loadRecorder(false);
|
|
27220
|
-
}
|
|
27221
|
-
});
|
|
27222
|
-
}
|
|
28052
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27223
28053
|
});
|
|
27224
28054
|
|
|
28055
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28056
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28057
|
+
};
|
|
28058
|
+
|
|
27225
28059
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27226
|
-
this._check_and_start_session_recording(true);
|
|
28060
|
+
return this._check_and_start_session_recording(true);
|
|
27227
28061
|
};
|
|
27228
28062
|
|
|
27229
28063
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27230
|
-
|
|
27231
|
-
return this._recorder['stopRecording']();
|
|
27232
|
-
}
|
|
27233
|
-
return Promise.resolve();
|
|
28064
|
+
return this.recorderManager.stopSessionRecording();
|
|
27234
28065
|
};
|
|
27235
28066
|
|
|
27236
28067
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27237
|
-
|
|
27238
|
-
return this._recorder['pauseRecording']();
|
|
27239
|
-
}
|
|
27240
|
-
return Promise.resolve();
|
|
28068
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27241
28069
|
};
|
|
27242
28070
|
|
|
27243
28071
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27244
|
-
|
|
27245
|
-
return this._recorder['resumeRecording']();
|
|
27246
|
-
}
|
|
27247
|
-
return Promise.resolve();
|
|
28072
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27248
28073
|
};
|
|
27249
28074
|
|
|
27250
28075
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27251
|
-
return this.
|
|
28076
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27252
28077
|
};
|
|
27253
28078
|
|
|
27254
28079
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27255
|
-
|
|
27256
|
-
var replay_id = this._get_session_replay_id();
|
|
27257
|
-
if (replay_id) {
|
|
27258
|
-
props['$mp_replay_id'] = replay_id;
|
|
27259
|
-
}
|
|
27260
|
-
return props;
|
|
28080
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27261
28081
|
};
|
|
27262
28082
|
|
|
27263
28083
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27264
|
-
|
|
27265
|
-
var replay_id = this._get_session_replay_id();
|
|
27266
|
-
if (replay_id) {
|
|
27267
|
-
var query_params = _.HTTPBuildQuery({
|
|
27268
|
-
'replay_id': replay_id,
|
|
27269
|
-
'distinct_id': this.get_distinct_id(),
|
|
27270
|
-
'token': this.get_config('token')
|
|
27271
|
-
});
|
|
27272
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27273
|
-
}
|
|
27274
|
-
return replay_url;
|
|
27275
|
-
};
|
|
27276
|
-
|
|
27277
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27278
|
-
var replay_id = null;
|
|
27279
|
-
if (this._recorder) {
|
|
27280
|
-
replay_id = this._recorder['replayId'];
|
|
27281
|
-
}
|
|
27282
|
-
return replay_id || null;
|
|
28084
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27283
28085
|
};
|
|
27284
28086
|
|
|
27285
28087
|
// "private" public method to reach into the recorder in test cases
|
|
27286
28088
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27287
|
-
return this.
|
|
28089
|
+
return this.recorderManager.getRecorder();
|
|
28090
|
+
};
|
|
28091
|
+
|
|
28092
|
+
// "private" public method to get session recording init promise in test cases
|
|
28093
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28094
|
+
return this.__session_recording_init_promise;
|
|
27288
28095
|
};
|
|
27289
28096
|
|
|
27290
28097
|
// Private methods
|
|
@@ -27542,6 +28349,7 @@
|
|
|
27542
28349
|
};
|
|
27543
28350
|
|
|
27544
28351
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28352
|
+
var self = this;
|
|
27545
28353
|
var disableRecordingIfStrict = function() {
|
|
27546
28354
|
if (mode === 'strict') {
|
|
27547
28355
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -27562,7 +28370,6 @@
|
|
|
27562
28370
|
};
|
|
27563
28371
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
27564
28372
|
var full_url = settings_endpoint + '?' + query_string;
|
|
27565
|
-
var self = this;
|
|
27566
28373
|
|
|
27567
28374
|
var abortController = new AbortController();
|
|
27568
28375
|
var timeout_id = setTimeout(function() {
|
|
@@ -27754,6 +28561,34 @@
|
|
|
27754
28561
|
this._execute_array([item]);
|
|
27755
28562
|
};
|
|
27756
28563
|
|
|
28564
|
+
/**
|
|
28565
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
28566
|
+
* this function enable tracking of all events. If passed an
|
|
28567
|
+
* array of event names, those events will be enabled, but other
|
|
28568
|
+
* existing disabled events will continue to be not tracked.
|
|
28569
|
+
*
|
|
28570
|
+
* @param {Array} [events] An array of event names to enable
|
|
28571
|
+
*/
|
|
28572
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
28573
|
+
var keys, new_disabled_events, i, j;
|
|
28574
|
+
|
|
28575
|
+
if (typeof(events) === 'undefined') {
|
|
28576
|
+
this._flags.disable_all_events = false;
|
|
28577
|
+
} else {
|
|
28578
|
+
keys = {};
|
|
28579
|
+
new_disabled_events = [];
|
|
28580
|
+
for (i = 0; i < events.length; i++) {
|
|
28581
|
+
keys[events[i]] = true;
|
|
28582
|
+
}
|
|
28583
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
28584
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
28585
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
28586
|
+
}
|
|
28587
|
+
}
|
|
28588
|
+
this.__disabled_events = new_disabled_events;
|
|
28589
|
+
}
|
|
28590
|
+
};
|
|
28591
|
+
|
|
27757
28592
|
/**
|
|
27758
28593
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
27759
28594
|
* this function disables tracking of any event. If passed an
|
|
@@ -27927,6 +28762,8 @@
|
|
|
27927
28762
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
27928
28763
|
}
|
|
27929
28764
|
|
|
28765
|
+
this._start_recording_on_event(event_name, properties);
|
|
28766
|
+
|
|
27930
28767
|
var data = {
|
|
27931
28768
|
'event': event_name,
|
|
27932
28769
|
'properties': properties
|
|
@@ -29135,6 +29972,7 @@
|
|
|
29135
29972
|
// MixpanelLib Exports
|
|
29136
29973
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29137
29974
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
29975
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29138
29976
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29139
29977
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29140
29978
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29178,6 +30016,7 @@
|
|
|
29178
30016
|
|
|
29179
30017
|
// Exports intended only for testing
|
|
29180
30018
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30019
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29181
30020
|
|
|
29182
30021
|
// MixpanelPersistence Exports
|
|
29183
30022
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|