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
package/dist/mixpanel.module.js
CHANGED
|
@@ -23,16 +23,19 @@ if (typeof(window) === 'undefined') {
|
|
|
23
23
|
win = window;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
var Config = {
|
|
27
|
+
DEBUG: false,
|
|
28
|
+
LIB_VERSION: '2.76.0'
|
|
29
|
+
};
|
|
29
30
|
|
|
30
|
-
//
|
|
31
|
+
// Window global names for async modules
|
|
31
32
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
32
|
-
|
|
33
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
34
33
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
35
34
|
|
|
35
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
36
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
37
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
38
|
+
|
|
36
39
|
function _array_like_to_array(arr, len) {
|
|
37
40
|
if (len == null || len > arr.length) len = arr.length;
|
|
38
41
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -18132,7 +18135,7 @@ var __defNormalProp = function(obj, key, value) {
|
|
|
18132
18135
|
var __publicField = function(obj, key, value) {
|
|
18133
18136
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18134
18137
|
};
|
|
18135
|
-
function patch(source, name, replacement) {
|
|
18138
|
+
function patch$3(source, name, replacement) {
|
|
18136
18139
|
try {
|
|
18137
18140
|
if (!(name in source)) {
|
|
18138
18141
|
return function() {};
|
|
@@ -18549,7 +18552,7 @@ function initLogObserver(cb, win, options) {
|
|
|
18549
18552
|
if (!_logger[level]) {
|
|
18550
18553
|
return function() {};
|
|
18551
18554
|
}
|
|
18552
|
-
return patch(_logger, level, function(original) {
|
|
18555
|
+
return patch$3(_logger, level, function(original) {
|
|
18553
18556
|
var _this1 = _this;
|
|
18554
18557
|
return function() {
|
|
18555
18558
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18970,11 +18973,6 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
|
|
|
18970
18973
|
PromisePolyfill = NpoPromise;
|
|
18971
18974
|
}
|
|
18972
18975
|
|
|
18973
|
-
var Config = {
|
|
18974
|
-
DEBUG: false,
|
|
18975
|
-
LIB_VERSION: '2.75.0'
|
|
18976
|
-
};
|
|
18977
|
-
|
|
18978
18976
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18979
18977
|
|
|
18980
18978
|
// Maximum allowed session recording length
|
|
@@ -20706,6 +20704,17 @@ var isOnline = function() {
|
|
|
20706
20704
|
|
|
20707
20705
|
var NOOP_FUNC = function () {};
|
|
20708
20706
|
|
|
20707
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20708
|
+
var matches = false;
|
|
20709
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20710
|
+
if (url.match(regexList[i])) {
|
|
20711
|
+
matches = true;
|
|
20712
|
+
break;
|
|
20713
|
+
}
|
|
20714
|
+
}
|
|
20715
|
+
return matches;
|
|
20716
|
+
};
|
|
20717
|
+
|
|
20709
20718
|
var JSONStringify = null, JSONParse = null;
|
|
20710
20719
|
if (typeof JSON !== 'undefined') {
|
|
20711
20720
|
JSONStringify = JSON.stringify;
|
|
@@ -21177,7 +21186,7 @@ function _addOptOutCheck(method, getConfigValue) {
|
|
|
21177
21186
|
};
|
|
21178
21187
|
}
|
|
21179
21188
|
|
|
21180
|
-
var logger$
|
|
21189
|
+
var logger$7 = console_with_prefix('lock');
|
|
21181
21190
|
|
|
21182
21191
|
/**
|
|
21183
21192
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21229,7 +21238,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
|
21229
21238
|
|
|
21230
21239
|
var delay = function(cb) {
|
|
21231
21240
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21232
|
-
logger$
|
|
21241
|
+
logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21233
21242
|
storage.removeItem(keyZ);
|
|
21234
21243
|
storage.removeItem(keyY);
|
|
21235
21244
|
loop();
|
|
@@ -21376,7 +21385,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
|
|
|
21376
21385
|
}, this));
|
|
21377
21386
|
};
|
|
21378
21387
|
|
|
21379
|
-
var logger$
|
|
21388
|
+
var logger$6 = console_with_prefix('batch');
|
|
21380
21389
|
|
|
21381
21390
|
/**
|
|
21382
21391
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21405,7 +21414,7 @@ var RequestQueue = function (storageKey, options) {
|
|
|
21405
21414
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21406
21415
|
});
|
|
21407
21416
|
}
|
|
21408
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21417
|
+
this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
|
|
21409
21418
|
|
|
21410
21419
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21411
21420
|
|
|
@@ -21738,7 +21747,7 @@ RequestQueue.prototype.clear = function () {
|
|
|
21738
21747
|
// maximum interval between request retries after exponential backoff
|
|
21739
21748
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21740
21749
|
|
|
21741
|
-
var logger$
|
|
21750
|
+
var logger$5 = console_with_prefix('batch');
|
|
21742
21751
|
|
|
21743
21752
|
/**
|
|
21744
21753
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21866,7 +21875,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
|
|
|
21866
21875
|
*/
|
|
21867
21876
|
RequestBatcher.prototype.flush = function(options) {
|
|
21868
21877
|
if (this.requestInProgress) {
|
|
21869
|
-
logger$
|
|
21878
|
+
logger$5.log('Flush: Request already in progress');
|
|
21870
21879
|
return PromisePolyfill.resolve();
|
|
21871
21880
|
}
|
|
21872
21881
|
|
|
@@ -22043,7 +22052,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
22043
22052
|
if (options.unloading) {
|
|
22044
22053
|
requestOptions.transport = 'sendBeacon';
|
|
22045
22054
|
}
|
|
22046
|
-
logger$
|
|
22055
|
+
logger$5.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22047
22056
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22048
22057
|
}, this))
|
|
22049
22058
|
.catch(_.bind(function(err) {
|
|
@@ -22056,7 +22065,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
22056
22065
|
* Log error to global logger and optional user-defined logger.
|
|
22057
22066
|
*/
|
|
22058
22067
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22059
|
-
logger$
|
|
22068
|
+
logger$5.error.apply(logger$5.error, arguments);
|
|
22060
22069
|
if (this.errorReporter) {
|
|
22061
22070
|
try {
|
|
22062
22071
|
if (!(err instanceof Error)) {
|
|
@@ -22064,7 +22073,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
|
|
|
22064
22073
|
}
|
|
22065
22074
|
this.errorReporter(msg, err);
|
|
22066
22075
|
} catch(err) {
|
|
22067
|
-
logger$
|
|
22076
|
+
logger$5.error(err);
|
|
22068
22077
|
}
|
|
22069
22078
|
}
|
|
22070
22079
|
};
|
|
@@ -22186,7 +22195,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
|
|
|
22186
22195
|
|
|
22187
22196
|
var MAX_DEPTH = 5;
|
|
22188
22197
|
|
|
22189
|
-
var logger$
|
|
22198
|
+
var logger$4 = console_with_prefix('autocapture');
|
|
22190
22199
|
|
|
22191
22200
|
|
|
22192
22201
|
function getClasses(el) {
|
|
@@ -22450,7 +22459,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22450
22459
|
return false;
|
|
22451
22460
|
}
|
|
22452
22461
|
} catch (err) {
|
|
22453
|
-
logger$
|
|
22462
|
+
logger$4.critical('Error while checking element in allowElementCallback', err);
|
|
22454
22463
|
return false;
|
|
22455
22464
|
}
|
|
22456
22465
|
}
|
|
@@ -22467,7 +22476,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22467
22476
|
return true;
|
|
22468
22477
|
}
|
|
22469
22478
|
} catch (err) {
|
|
22470
|
-
logger$
|
|
22479
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22471
22480
|
}
|
|
22472
22481
|
}
|
|
22473
22482
|
return false;
|
|
@@ -22482,7 +22491,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22482
22491
|
return true;
|
|
22483
22492
|
}
|
|
22484
22493
|
} catch (err) {
|
|
22485
|
-
logger$
|
|
22494
|
+
logger$4.critical('Error while checking element in blockElementCallback', err);
|
|
22486
22495
|
return true;
|
|
22487
22496
|
}
|
|
22488
22497
|
}
|
|
@@ -22496,7 +22505,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22496
22505
|
return true;
|
|
22497
22506
|
}
|
|
22498
22507
|
} catch (err) {
|
|
22499
|
-
logger$
|
|
22508
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22500
22509
|
}
|
|
22501
22510
|
}
|
|
22502
22511
|
}
|
|
@@ -23043,6 +23052,655 @@ function shouldMaskText(element, privacyConfig) {
|
|
|
23043
23052
|
}
|
|
23044
23053
|
}
|
|
23045
23054
|
|
|
23055
|
+
/**
|
|
23056
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23057
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23058
|
+
*
|
|
23059
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23060
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23061
|
+
*
|
|
23062
|
+
*/
|
|
23063
|
+
|
|
23064
|
+
var logger$3 = console_with_prefix('network-plugin');
|
|
23065
|
+
|
|
23066
|
+
/**
|
|
23067
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23068
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23069
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23070
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23071
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23072
|
+
* @param {Window} win
|
|
23073
|
+
* @returns {number}
|
|
23074
|
+
*/
|
|
23075
|
+
function getTimeOrigin(win) {
|
|
23076
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23077
|
+
}
|
|
23078
|
+
|
|
23079
|
+
/**
|
|
23080
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23081
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23082
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23083
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23084
|
+
*/
|
|
23085
|
+
|
|
23086
|
+
/**
|
|
23087
|
+
* @typedef {Record<string, string>} Headers
|
|
23088
|
+
*/
|
|
23089
|
+
|
|
23090
|
+
/**
|
|
23091
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23092
|
+
*/
|
|
23093
|
+
|
|
23094
|
+
/**
|
|
23095
|
+
* @callback networkCallback
|
|
23096
|
+
* @param {NetworkData} data
|
|
23097
|
+
* @returns {void}
|
|
23098
|
+
*/
|
|
23099
|
+
|
|
23100
|
+
/**
|
|
23101
|
+
* @callback listenerHandler
|
|
23102
|
+
* @returns {void}
|
|
23103
|
+
*/
|
|
23104
|
+
|
|
23105
|
+
/**
|
|
23106
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23107
|
+
*/
|
|
23108
|
+
|
|
23109
|
+
/**
|
|
23110
|
+
* @typedef {Object} RecordPlugin
|
|
23111
|
+
* @property {string} name
|
|
23112
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23113
|
+
* @property {NetworkRecordOptions} [options]
|
|
23114
|
+
*/
|
|
23115
|
+
|
|
23116
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23117
|
+
var defaultNetworkOptions = {
|
|
23118
|
+
initiatorTypes: [
|
|
23119
|
+
'audio',
|
|
23120
|
+
'beacon',
|
|
23121
|
+
'body',
|
|
23122
|
+
'css',
|
|
23123
|
+
'early-hint',
|
|
23124
|
+
'embed',
|
|
23125
|
+
'fetch',
|
|
23126
|
+
'frame',
|
|
23127
|
+
'iframe',
|
|
23128
|
+
'icon',
|
|
23129
|
+
'image',
|
|
23130
|
+
'img',
|
|
23131
|
+
'input',
|
|
23132
|
+
'link',
|
|
23133
|
+
'navigation',
|
|
23134
|
+
'object',
|
|
23135
|
+
'ping',
|
|
23136
|
+
'script',
|
|
23137
|
+
'track',
|
|
23138
|
+
'video',
|
|
23139
|
+
'xmlhttprequest',
|
|
23140
|
+
],
|
|
23141
|
+
ignoreRequestFn: function() { return false; },
|
|
23142
|
+
recordHeaders: {
|
|
23143
|
+
request: [],
|
|
23144
|
+
response: [],
|
|
23145
|
+
},
|
|
23146
|
+
recordBodyUrls: {
|
|
23147
|
+
request: [],
|
|
23148
|
+
response: [],
|
|
23149
|
+
},
|
|
23150
|
+
recordInitialRequests: false,
|
|
23151
|
+
};
|
|
23152
|
+
|
|
23153
|
+
/**
|
|
23154
|
+
* @param {PerformanceEntry} entry
|
|
23155
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23156
|
+
*/
|
|
23157
|
+
function isNavigationTiming(entry) {
|
|
23158
|
+
return entry.entryType === 'navigation';
|
|
23159
|
+
}
|
|
23160
|
+
|
|
23161
|
+
/**
|
|
23162
|
+
* @param {PerformanceEntry} entry
|
|
23163
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23164
|
+
*/
|
|
23165
|
+
function isResourceTiming (entry) {
|
|
23166
|
+
return entry.entryType === 'resource';
|
|
23167
|
+
}
|
|
23168
|
+
|
|
23169
|
+
function findLast(array, predicate) {
|
|
23170
|
+
var length = array.length;
|
|
23171
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23172
|
+
if (predicate(array[i])) {
|
|
23173
|
+
return array[i];
|
|
23174
|
+
}
|
|
23175
|
+
}
|
|
23176
|
+
}
|
|
23177
|
+
|
|
23178
|
+
/**
|
|
23179
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23180
|
+
* Adapted from Sentry's `fill` utility:
|
|
23181
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23182
|
+
*
|
|
23183
|
+
* @param {object} source - The object containing the method to patch
|
|
23184
|
+
* @param {string} name - The method name to patch
|
|
23185
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23186
|
+
* @returns {function} A function that restores the original method
|
|
23187
|
+
*/
|
|
23188
|
+
function patch(source, name, replacementFactory) {
|
|
23189
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23190
|
+
return function() {};
|
|
23191
|
+
}
|
|
23192
|
+
var original = source[name];
|
|
23193
|
+
var wrapped = replacementFactory(original);
|
|
23194
|
+
source[name] = wrapped;
|
|
23195
|
+
return function() {
|
|
23196
|
+
source[name] = original;
|
|
23197
|
+
};
|
|
23198
|
+
}
|
|
23199
|
+
|
|
23200
|
+
|
|
23201
|
+
/**
|
|
23202
|
+
* Maximum body size to record (1MB)
|
|
23203
|
+
*/
|
|
23204
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23205
|
+
|
|
23206
|
+
/**
|
|
23207
|
+
* Truncate string if it exceeds max size
|
|
23208
|
+
* @param {string} str
|
|
23209
|
+
* @returns {string}
|
|
23210
|
+
*/
|
|
23211
|
+
function truncateBody(str) {
|
|
23212
|
+
if (!str || typeof str !== 'string') {
|
|
23213
|
+
return str;
|
|
23214
|
+
}
|
|
23215
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23216
|
+
logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23217
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23218
|
+
}
|
|
23219
|
+
return str;
|
|
23220
|
+
}
|
|
23221
|
+
|
|
23222
|
+
/**
|
|
23223
|
+
* @param {networkCallback} cb
|
|
23224
|
+
* @param {Window} win
|
|
23225
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23226
|
+
* @returns {listenerHandler}
|
|
23227
|
+
*/
|
|
23228
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23229
|
+
if (!win.PerformanceObserver) {
|
|
23230
|
+
logger$3.error('PerformanceObserver not supported');
|
|
23231
|
+
return function() {
|
|
23232
|
+
//
|
|
23233
|
+
};
|
|
23234
|
+
}
|
|
23235
|
+
if (options.recordInitialRequests) {
|
|
23236
|
+
var initialPerformanceEntries = win.performance
|
|
23237
|
+
.getEntries()
|
|
23238
|
+
.filter(function(entry) {
|
|
23239
|
+
return isNavigationTiming(entry) ||
|
|
23240
|
+
(isResourceTiming(entry) &&
|
|
23241
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23242
|
+
});
|
|
23243
|
+
cb({
|
|
23244
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23245
|
+
return {
|
|
23246
|
+
url: entry.name,
|
|
23247
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23248
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23249
|
+
startTime: Math.round(entry.startTime),
|
|
23250
|
+
endTime: Math.round(entry.responseEnd),
|
|
23251
|
+
timeOrigin: getTimeOrigin(win),
|
|
23252
|
+
};
|
|
23253
|
+
}),
|
|
23254
|
+
isInitial: true,
|
|
23255
|
+
});
|
|
23256
|
+
}
|
|
23257
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23258
|
+
var performanceEntries = entries
|
|
23259
|
+
.getEntries()
|
|
23260
|
+
.filter(function(entry) {
|
|
23261
|
+
return isResourceTiming(entry) &&
|
|
23262
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23263
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23264
|
+
entry.initiatorType !== 'fetch';
|
|
23265
|
+
});
|
|
23266
|
+
cb({
|
|
23267
|
+
requests: performanceEntries.map(function(entry) {
|
|
23268
|
+
return {
|
|
23269
|
+
url: entry.name,
|
|
23270
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23271
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23272
|
+
startTime: Math.round(entry.startTime),
|
|
23273
|
+
endTime: Math.round(entry.responseEnd),
|
|
23274
|
+
timeOrigin: getTimeOrigin(win),
|
|
23275
|
+
};
|
|
23276
|
+
}),
|
|
23277
|
+
});
|
|
23278
|
+
});
|
|
23279
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23280
|
+
return function() {
|
|
23281
|
+
observer.disconnect();
|
|
23282
|
+
};
|
|
23283
|
+
}
|
|
23284
|
+
|
|
23285
|
+
/**
|
|
23286
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23287
|
+
* @param {'request' | 'response'} type
|
|
23288
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23289
|
+
* @param {string} headerName
|
|
23290
|
+
* @returns {boolean}
|
|
23291
|
+
*/
|
|
23292
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23293
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23294
|
+
return false;
|
|
23295
|
+
}
|
|
23296
|
+
|
|
23297
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23298
|
+
}
|
|
23299
|
+
|
|
23300
|
+
/**
|
|
23301
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23302
|
+
* @param {'request' | 'response'} type
|
|
23303
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23304
|
+
* @param {string} url
|
|
23305
|
+
* @returns {boolean}
|
|
23306
|
+
*/
|
|
23307
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23308
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23309
|
+
return false;
|
|
23310
|
+
}
|
|
23311
|
+
|
|
23312
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23313
|
+
}
|
|
23314
|
+
|
|
23315
|
+
function tryReadXHRBody(body) {
|
|
23316
|
+
if (body === null || body === undefined) {
|
|
23317
|
+
return null;
|
|
23318
|
+
}
|
|
23319
|
+
|
|
23320
|
+
var result;
|
|
23321
|
+
if (typeof body === 'string') {
|
|
23322
|
+
result = body;
|
|
23323
|
+
} else if (body instanceof Document) {
|
|
23324
|
+
result = body.textContent;
|
|
23325
|
+
} else if (body instanceof FormData) {
|
|
23326
|
+
result = _.HTTPBuildQuery(body);
|
|
23327
|
+
} else if (_.isObject(body)) {
|
|
23328
|
+
try {
|
|
23329
|
+
result = JSON.stringify(body);
|
|
23330
|
+
} catch (e) {
|
|
23331
|
+
return 'Failed to stringify response object';
|
|
23332
|
+
}
|
|
23333
|
+
} else {
|
|
23334
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23335
|
+
}
|
|
23336
|
+
|
|
23337
|
+
return truncateBody(result);
|
|
23338
|
+
}
|
|
23339
|
+
|
|
23340
|
+
/**
|
|
23341
|
+
* @param {Request | Response} r
|
|
23342
|
+
* @returns {Promise<string>}
|
|
23343
|
+
*/
|
|
23344
|
+
function tryReadFetchBody(r) {
|
|
23345
|
+
return new Promise(function(resolve) {
|
|
23346
|
+
var timeout = setTimeout(function() {
|
|
23347
|
+
resolve('Timeout while trying to read body');
|
|
23348
|
+
}, 500);
|
|
23349
|
+
try {
|
|
23350
|
+
r.clone()
|
|
23351
|
+
.text()
|
|
23352
|
+
.then(
|
|
23353
|
+
function(txt) {
|
|
23354
|
+
clearTimeout(timeout);
|
|
23355
|
+
resolve(truncateBody(txt));
|
|
23356
|
+
},
|
|
23357
|
+
function(reason) {
|
|
23358
|
+
clearTimeout(timeout);
|
|
23359
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23360
|
+
}
|
|
23361
|
+
);
|
|
23362
|
+
} catch (e) {
|
|
23363
|
+
clearTimeout(timeout);
|
|
23364
|
+
resolve('Failed to read body: ' + String(e));
|
|
23365
|
+
}
|
|
23366
|
+
});
|
|
23367
|
+
}
|
|
23368
|
+
|
|
23369
|
+
/**
|
|
23370
|
+
* @param {Window} win
|
|
23371
|
+
* @param {string} initiatorType
|
|
23372
|
+
* @param {string} url
|
|
23373
|
+
* @param {number} [after]
|
|
23374
|
+
* @param {number} [before]
|
|
23375
|
+
* @param {number} [attempt]
|
|
23376
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23377
|
+
*/
|
|
23378
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23379
|
+
if (attempt === undefined) {
|
|
23380
|
+
attempt = 0;
|
|
23381
|
+
}
|
|
23382
|
+
if (attempt > 10) {
|
|
23383
|
+
logger$3.error('Cannot find performance entry');
|
|
23384
|
+
return Promise.resolve(null);
|
|
23385
|
+
}
|
|
23386
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23387
|
+
win.performance.getEntriesByName(url)
|
|
23388
|
+
);
|
|
23389
|
+
var performanceEntry = findLast(
|
|
23390
|
+
urlPerformanceEntries,
|
|
23391
|
+
function(entry) {
|
|
23392
|
+
return isResourceTiming(entry) &&
|
|
23393
|
+
entry.initiatorType === initiatorType &&
|
|
23394
|
+
(!after || entry.startTime >= after) &&
|
|
23395
|
+
(!before || entry.startTime <= before);
|
|
23396
|
+
}
|
|
23397
|
+
);
|
|
23398
|
+
if (!performanceEntry) {
|
|
23399
|
+
return new Promise(function(resolve) {
|
|
23400
|
+
setTimeout(resolve, 50 * attempt);
|
|
23401
|
+
}).then(function() {
|
|
23402
|
+
return getRequestPerformanceEntry(
|
|
23403
|
+
win,
|
|
23404
|
+
initiatorType,
|
|
23405
|
+
url,
|
|
23406
|
+
after,
|
|
23407
|
+
before,
|
|
23408
|
+
attempt + 1
|
|
23409
|
+
);
|
|
23410
|
+
});
|
|
23411
|
+
}
|
|
23412
|
+
return Promise.resolve(performanceEntry);
|
|
23413
|
+
}
|
|
23414
|
+
|
|
23415
|
+
/**
|
|
23416
|
+
* @param {networkCallback} cb
|
|
23417
|
+
* @param {Window} win
|
|
23418
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23419
|
+
* @returns {listenerHandler}
|
|
23420
|
+
*/
|
|
23421
|
+
function initXhrObserver(cb, win, options) {
|
|
23422
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23423
|
+
return function() {
|
|
23424
|
+
//
|
|
23425
|
+
};
|
|
23426
|
+
}
|
|
23427
|
+
var restorePatch = patch(
|
|
23428
|
+
win.XMLHttpRequest.prototype,
|
|
23429
|
+
'open',
|
|
23430
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23431
|
+
return function(
|
|
23432
|
+
/** @type {string} */ method,
|
|
23433
|
+
/** @type {string | URL} */ url,
|
|
23434
|
+
/** @type {boolean} */ async,
|
|
23435
|
+
username, password
|
|
23436
|
+
) {
|
|
23437
|
+
if (async === undefined) {
|
|
23438
|
+
async = true;
|
|
23439
|
+
}
|
|
23440
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23441
|
+
var req = new Request(url, { method: method });
|
|
23442
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23443
|
+
var networkRequest = {};
|
|
23444
|
+
/** @type {number | undefined} */
|
|
23445
|
+
var after;
|
|
23446
|
+
/** @type {number | undefined} */
|
|
23447
|
+
var before;
|
|
23448
|
+
|
|
23449
|
+
/** @type {Headers} */
|
|
23450
|
+
var requestHeaders = {};
|
|
23451
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23452
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23453
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23454
|
+
requestHeaders[header] = value;
|
|
23455
|
+
}
|
|
23456
|
+
return originalSetRequestHeader(header, value);
|
|
23457
|
+
};
|
|
23458
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23459
|
+
|
|
23460
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23461
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23462
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23463
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23464
|
+
}
|
|
23465
|
+
after = win.performance.now();
|
|
23466
|
+
return originalSend(body);
|
|
23467
|
+
};
|
|
23468
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23469
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23470
|
+
return;
|
|
23471
|
+
}
|
|
23472
|
+
before = win.performance.now();
|
|
23473
|
+
/** @type {Headers} */
|
|
23474
|
+
var responseHeaders = {};
|
|
23475
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23476
|
+
if (rawHeaders) {
|
|
23477
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23478
|
+
headers.forEach(function(line) {
|
|
23479
|
+
if (!line) return;
|
|
23480
|
+
var colonIndex = line.indexOf(': ');
|
|
23481
|
+
if (colonIndex === -1) return;
|
|
23482
|
+
var header = line.substring(0, colonIndex);
|
|
23483
|
+
var value = line.substring(colonIndex + 2);
|
|
23484
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23485
|
+
responseHeaders[header] = value;
|
|
23486
|
+
}
|
|
23487
|
+
});
|
|
23488
|
+
}
|
|
23489
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23490
|
+
if (
|
|
23491
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23492
|
+
) {
|
|
23493
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23494
|
+
}
|
|
23495
|
+
getRequestPerformanceEntry(
|
|
23496
|
+
win,
|
|
23497
|
+
'xmlhttprequest',
|
|
23498
|
+
req.url,
|
|
23499
|
+
after,
|
|
23500
|
+
before
|
|
23501
|
+
)
|
|
23502
|
+
.then(function(entry) {
|
|
23503
|
+
if (!entry) {
|
|
23504
|
+
logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23505
|
+
return;
|
|
23506
|
+
}
|
|
23507
|
+
/** @type {NetworkRequest} */
|
|
23508
|
+
var request = {
|
|
23509
|
+
url: entry.name,
|
|
23510
|
+
method: req.method,
|
|
23511
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23512
|
+
status: xhr.status,
|
|
23513
|
+
startTime: Math.round(entry.startTime),
|
|
23514
|
+
endTime: Math.round(entry.responseEnd),
|
|
23515
|
+
timeOrigin: getTimeOrigin(win),
|
|
23516
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23517
|
+
requestBody: networkRequest.requestBody,
|
|
23518
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23519
|
+
responseBody: networkRequest.responseBody,
|
|
23520
|
+
};
|
|
23521
|
+
cb({ requests: [request] });
|
|
23522
|
+
})
|
|
23523
|
+
.catch(function(e) {
|
|
23524
|
+
logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23525
|
+
});
|
|
23526
|
+
});
|
|
23527
|
+
|
|
23528
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23529
|
+
};
|
|
23530
|
+
}
|
|
23531
|
+
);
|
|
23532
|
+
return function() {
|
|
23533
|
+
restorePatch();
|
|
23534
|
+
};
|
|
23535
|
+
}
|
|
23536
|
+
|
|
23537
|
+
/**
|
|
23538
|
+
* @param {networkCallback} cb
|
|
23539
|
+
* @param {Window} win
|
|
23540
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23541
|
+
* @returns {listenerHandler}
|
|
23542
|
+
*/
|
|
23543
|
+
function initFetchObserver(cb, win, options) {
|
|
23544
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23545
|
+
return function() {
|
|
23546
|
+
//
|
|
23547
|
+
};
|
|
23548
|
+
}
|
|
23549
|
+
|
|
23550
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23551
|
+
return function() {
|
|
23552
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23553
|
+
/** @type {Response | undefined} */
|
|
23554
|
+
var res;
|
|
23555
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23556
|
+
var networkRequest = {};
|
|
23557
|
+
/** @type {number | undefined} */
|
|
23558
|
+
var after;
|
|
23559
|
+
/** @type {number | undefined} */
|
|
23560
|
+
var before;
|
|
23561
|
+
|
|
23562
|
+
var originalFetchPromise;
|
|
23563
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23564
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23565
|
+
try {
|
|
23566
|
+
/** @type {Headers} */
|
|
23567
|
+
var requestHeaders = {};
|
|
23568
|
+
req.headers.forEach(function(value, header) {
|
|
23569
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23570
|
+
requestHeaders[header] = value;
|
|
23571
|
+
}
|
|
23572
|
+
});
|
|
23573
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23574
|
+
|
|
23575
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23576
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23577
|
+
.then(function(body) {
|
|
23578
|
+
networkRequest.requestBody = body;
|
|
23579
|
+
});
|
|
23580
|
+
}
|
|
23581
|
+
|
|
23582
|
+
after = win.performance.now();
|
|
23583
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23584
|
+
res = response;
|
|
23585
|
+
before = win.performance.now();
|
|
23586
|
+
|
|
23587
|
+
/** @type {Headers} */
|
|
23588
|
+
var responseHeaders = {};
|
|
23589
|
+
res.headers.forEach(function(value, header) {
|
|
23590
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23591
|
+
responseHeaders[header] = value;
|
|
23592
|
+
}
|
|
23593
|
+
});
|
|
23594
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23595
|
+
|
|
23596
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23597
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23598
|
+
.then(function(body) {
|
|
23599
|
+
networkRequest.responseBody = body;
|
|
23600
|
+
});
|
|
23601
|
+
}
|
|
23602
|
+
|
|
23603
|
+
return res;
|
|
23604
|
+
});
|
|
23605
|
+
} catch (e) {
|
|
23606
|
+
originalFetchPromise = Promise.reject(e);
|
|
23607
|
+
}
|
|
23608
|
+
|
|
23609
|
+
// await concurrently so we don't delay the fetch response
|
|
23610
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23611
|
+
.then(function () {
|
|
23612
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23613
|
+
})
|
|
23614
|
+
.then(function(entry) {
|
|
23615
|
+
if (!entry) {
|
|
23616
|
+
logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23617
|
+
return;
|
|
23618
|
+
}
|
|
23619
|
+
/** @type {NetworkRequest} */
|
|
23620
|
+
var request = {
|
|
23621
|
+
url: entry.name,
|
|
23622
|
+
method: req.method,
|
|
23623
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23624
|
+
status: res ? res.status : undefined,
|
|
23625
|
+
startTime: Math.round(entry.startTime),
|
|
23626
|
+
endTime: Math.round(entry.responseEnd),
|
|
23627
|
+
timeOrigin: getTimeOrigin(win),
|
|
23628
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23629
|
+
requestBody: networkRequest.requestBody,
|
|
23630
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23631
|
+
responseBody: networkRequest.responseBody,
|
|
23632
|
+
};
|
|
23633
|
+
cb({ requests: [request] });
|
|
23634
|
+
})
|
|
23635
|
+
.catch(function (e) {
|
|
23636
|
+
logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23637
|
+
});
|
|
23638
|
+
|
|
23639
|
+
return originalFetchPromise;
|
|
23640
|
+
};
|
|
23641
|
+
});
|
|
23642
|
+
return function() {
|
|
23643
|
+
restorePatch();
|
|
23644
|
+
};
|
|
23645
|
+
}
|
|
23646
|
+
|
|
23647
|
+
/**
|
|
23648
|
+
* @param {networkCallback} callback
|
|
23649
|
+
* @param {Window} win
|
|
23650
|
+
* @param {NetworkRecordOptions} options
|
|
23651
|
+
* @returns {listenerHandler}
|
|
23652
|
+
*/
|
|
23653
|
+
function initNetworkObserver(callback, win, options) {
|
|
23654
|
+
if (!('performance' in win)) {
|
|
23655
|
+
return function() {
|
|
23656
|
+
//
|
|
23657
|
+
};
|
|
23658
|
+
}
|
|
23659
|
+
|
|
23660
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23661
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23662
|
+
options = Object.assign({}, options, {
|
|
23663
|
+
recordHeaders: recordHeaders,
|
|
23664
|
+
recordBodyUrls: recordBodyUrls,
|
|
23665
|
+
});
|
|
23666
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23667
|
+
|
|
23668
|
+
/** @type {networkCallback} */
|
|
23669
|
+
var cb = function(data) {
|
|
23670
|
+
var requests = data.requests.filter(function(request) {
|
|
23671
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23672
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23673
|
+
});
|
|
23674
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23675
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23676
|
+
}
|
|
23677
|
+
};
|
|
23678
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23679
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23680
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23681
|
+
return function() {
|
|
23682
|
+
performanceObserver();
|
|
23683
|
+
xhrObserver();
|
|
23684
|
+
fetchObserver();
|
|
23685
|
+
};
|
|
23686
|
+
}
|
|
23687
|
+
|
|
23688
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23689
|
+
// a changed format in the mixpanel product.
|
|
23690
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23691
|
+
|
|
23692
|
+
/**
|
|
23693
|
+
* @param {NetworkRecordOptions} [options]
|
|
23694
|
+
* @returns {RecordPlugin}
|
|
23695
|
+
*/
|
|
23696
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23697
|
+
return {
|
|
23698
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23699
|
+
observer: initNetworkObserver,
|
|
23700
|
+
options: options,
|
|
23701
|
+
};
|
|
23702
|
+
};
|
|
23703
|
+
|
|
23046
23704
|
/**
|
|
23047
23705
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23048
23706
|
*/
|
|
@@ -23276,12 +23934,35 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23276
23934
|
|
|
23277
23935
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23278
23936
|
|
|
23279
|
-
|
|
23280
|
-
|
|
23281
|
-
|
|
23282
|
-
|
|
23283
|
-
|
|
23284
|
-
|
|
23937
|
+
var plugins = [];
|
|
23938
|
+
if (this.getConfig('record_network')) {
|
|
23939
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23940
|
+
// don't track requests to Mixpanel /record API
|
|
23941
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23942
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23943
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23944
|
+
|
|
23945
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23946
|
+
}
|
|
23947
|
+
|
|
23948
|
+
if (this.getConfig('record_console')) {
|
|
23949
|
+
plugins.push(
|
|
23950
|
+
getRecordConsolePlugin({
|
|
23951
|
+
stringifyOptions: {
|
|
23952
|
+
stringLengthLimit: 1000,
|
|
23953
|
+
numOfKeysLimit: 50,
|
|
23954
|
+
depthOfLimit: 2
|
|
23955
|
+
}
|
|
23956
|
+
})
|
|
23957
|
+
);
|
|
23958
|
+
}
|
|
23959
|
+
|
|
23960
|
+
try {
|
|
23961
|
+
this._stopRecording = this._rrwebRecord({
|
|
23962
|
+
'emit': function (ev) {
|
|
23963
|
+
if (this.idleExpires && this.idleExpires < ev.timestamp) {
|
|
23964
|
+
this._onIdleTimeout();
|
|
23965
|
+
return;
|
|
23285
23966
|
}
|
|
23286
23967
|
if (isUserEvent(ev)) {
|
|
23287
23968
|
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
@@ -23314,15 +23995,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23314
23995
|
'sampling': {
|
|
23315
23996
|
'canvas': 15
|
|
23316
23997
|
},
|
|
23317
|
-
'plugins':
|
|
23318
|
-
getRecordConsolePlugin({
|
|
23319
|
-
stringifyOptions: {
|
|
23320
|
-
stringLengthLimit: 1000,
|
|
23321
|
-
numOfKeysLimit: 50,
|
|
23322
|
-
depthOfLimit: 2
|
|
23323
|
-
}
|
|
23324
|
-
})
|
|
23325
|
-
] : []
|
|
23998
|
+
'plugins': plugins,
|
|
23326
23999
|
});
|
|
23327
24000
|
} catch (err) {
|
|
23328
24001
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23437,6 +24110,10 @@ SessionRecording.deserialize = function (serializedRecording, options) {
|
|
|
23437
24110
|
return recording;
|
|
23438
24111
|
};
|
|
23439
24112
|
|
|
24113
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24114
|
+
return this.getConfig('api_routes')['record'];
|
|
24115
|
+
};
|
|
24116
|
+
|
|
23440
24117
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23441
24118
|
var onSuccess = function (response, responseBody) {
|
|
23442
24119
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23456,7 +24133,7 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
|
|
|
23456
24133
|
});
|
|
23457
24134
|
}.bind(this);
|
|
23458
24135
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23459
|
-
win['fetch'](apiHost + '/' + this.
|
|
24136
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23460
24137
|
'method': 'POST',
|
|
23461
24138
|
'headers': {
|
|
23462
24139
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23857,8 +24534,12 @@ MixpanelRecorder.prototype.resetRecording = function () {
|
|
|
23857
24534
|
this.startRecording({shouldStopBatcher: true});
|
|
23858
24535
|
};
|
|
23859
24536
|
|
|
24537
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24538
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24539
|
+
};
|
|
24540
|
+
|
|
23860
24541
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23861
|
-
if (this.
|
|
24542
|
+
if (this.isRecording()) {
|
|
23862
24543
|
return this.activeRecording.replayId;
|
|
23863
24544
|
} else {
|
|
23864
24545
|
return null;
|
|
@@ -24362,53 +25043,6 @@ function requireLogic () {
|
|
|
24362
25043
|
var logicExports = requireLogic();
|
|
24363
25044
|
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24364
25045
|
|
|
24365
|
-
/**
|
|
24366
|
-
* Shared helper to recursively lowercase strings in nested structures
|
|
24367
|
-
* @param {*} obj - Value to process
|
|
24368
|
-
* @param {boolean} lowercaseKeys - Whether to lowercase object keys
|
|
24369
|
-
* @returns {*} Processed value with lowercased strings
|
|
24370
|
-
*/
|
|
24371
|
-
var lowercaseJson = function(obj, lowercaseKeys) {
|
|
24372
|
-
if (obj === null || obj === undefined) {
|
|
24373
|
-
return obj;
|
|
24374
|
-
} else if (typeof obj === 'string') {
|
|
24375
|
-
return obj.toLowerCase();
|
|
24376
|
-
} else if (Array.isArray(obj)) {
|
|
24377
|
-
return obj.map(function(item) {
|
|
24378
|
-
return lowercaseJson(item, lowercaseKeys);
|
|
24379
|
-
});
|
|
24380
|
-
} else if (obj === Object(obj)) {
|
|
24381
|
-
var result = {};
|
|
24382
|
-
for (var key in obj) {
|
|
24383
|
-
if (obj.hasOwnProperty(key)) {
|
|
24384
|
-
var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
|
|
24385
|
-
result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
|
|
24386
|
-
}
|
|
24387
|
-
}
|
|
24388
|
-
return result;
|
|
24389
|
-
} else {
|
|
24390
|
-
return obj;
|
|
24391
|
-
}
|
|
24392
|
-
};
|
|
24393
|
-
|
|
24394
|
-
/**
|
|
24395
|
-
* Lowercase all string keys and values in a nested structure
|
|
24396
|
-
* @param {*} val - Value to process
|
|
24397
|
-
* @returns {*} Processed value with lowercased strings
|
|
24398
|
-
*/
|
|
24399
|
-
var lowercaseKeysAndValues = function(val) {
|
|
24400
|
-
return lowercaseJson(val, true);
|
|
24401
|
-
};
|
|
24402
|
-
|
|
24403
|
-
/**
|
|
24404
|
-
* Lowercase only leaf node string values in a nested structure (keys unchanged)
|
|
24405
|
-
* @param {*} val - Value to process
|
|
24406
|
-
* @returns {*} Processed value with lowercased leaf strings
|
|
24407
|
-
*/
|
|
24408
|
-
var lowercaseOnlyLeafNodes = function(val) {
|
|
24409
|
-
return lowercaseJson(val, false);
|
|
24410
|
-
};
|
|
24411
|
-
|
|
24412
25046
|
/**
|
|
24413
25047
|
* Check if an event matches the given criteria
|
|
24414
25048
|
* @param {string} eventName - The name of the event being checked
|
|
@@ -24432,13 +25066,8 @@ var eventMatchesCriteria = function(eventName, properties, criteria) {
|
|
|
24432
25066
|
|
|
24433
25067
|
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24434
25068
|
try {
|
|
24435
|
-
//
|
|
24436
|
-
|
|
24437
|
-
|
|
24438
|
-
// Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
|
|
24439
|
-
var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
|
|
24440
|
-
|
|
24441
|
-
filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
|
|
25069
|
+
// Use properties as-is for case-sensitive matching
|
|
25070
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
24442
25071
|
} catch (error) {
|
|
24443
25072
|
return {
|
|
24444
25073
|
matches: false,
|
|
@@ -24558,7 +25187,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
|
|
|
24558
25187
|
observer.observe(shadowRoot, this.observerConfig);
|
|
24559
25188
|
this.shadowObservers.push(observer);
|
|
24560
25189
|
} catch (e) {
|
|
24561
|
-
logger$
|
|
25190
|
+
logger$4.critical('Error while observing shadow root', e);
|
|
24562
25191
|
}
|
|
24563
25192
|
};
|
|
24564
25193
|
|
|
@@ -24569,7 +25198,7 @@ ShadowDOMObserver.prototype.start = function() {
|
|
|
24569
25198
|
}
|
|
24570
25199
|
|
|
24571
25200
|
if (!weakSetSupported()) {
|
|
24572
|
-
logger$
|
|
25201
|
+
logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
24573
25202
|
return;
|
|
24574
25203
|
}
|
|
24575
25204
|
|
|
@@ -24585,7 +25214,7 @@ ShadowDOMObserver.prototype.stop = function() {
|
|
|
24585
25214
|
try {
|
|
24586
25215
|
this.shadowObservers[i].disconnect();
|
|
24587
25216
|
} catch (e) {
|
|
24588
|
-
logger$
|
|
25217
|
+
logger$4.critical('Error while disconnecting shadow DOM observer', e);
|
|
24589
25218
|
}
|
|
24590
25219
|
}
|
|
24591
25220
|
this.shadowObservers = [];
|
|
@@ -24773,7 +25402,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24773
25402
|
|
|
24774
25403
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24775
25404
|
} catch (e) {
|
|
24776
|
-
logger$
|
|
25405
|
+
logger$4.critical('Error while setting up mutation observer', e);
|
|
24777
25406
|
}
|
|
24778
25407
|
}
|
|
24779
25408
|
|
|
@@ -24788,7 +25417,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24788
25417
|
);
|
|
24789
25418
|
this.shadowDOMObserver.start();
|
|
24790
25419
|
} catch (e) {
|
|
24791
|
-
logger$
|
|
25420
|
+
logger$4.critical('Error while setting up shadow DOM observer', e);
|
|
24792
25421
|
this.shadowDOMObserver = null;
|
|
24793
25422
|
}
|
|
24794
25423
|
}
|
|
@@ -24815,7 +25444,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24815
25444
|
try {
|
|
24816
25445
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24817
25446
|
} catch (e) {
|
|
24818
|
-
logger$
|
|
25447
|
+
logger$4.critical('Error while removing event listener', e);
|
|
24819
25448
|
}
|
|
24820
25449
|
}
|
|
24821
25450
|
this.eventListeners = [];
|
|
@@ -24824,7 +25453,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24824
25453
|
try {
|
|
24825
25454
|
this.mutationObserver.disconnect();
|
|
24826
25455
|
} catch (e) {
|
|
24827
|
-
logger$
|
|
25456
|
+
logger$4.critical('Error while disconnecting mutation observer', e);
|
|
24828
25457
|
}
|
|
24829
25458
|
this.mutationObserver = null;
|
|
24830
25459
|
}
|
|
@@ -24833,7 +25462,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24833
25462
|
try {
|
|
24834
25463
|
this.shadowDOMObserver.stop();
|
|
24835
25464
|
} catch (e) {
|
|
24836
|
-
logger$
|
|
25465
|
+
logger$4.critical('Error while stopping shadow DOM observer', e);
|
|
24837
25466
|
}
|
|
24838
25467
|
this.shadowDOMObserver = null;
|
|
24839
25468
|
}
|
|
@@ -24911,7 +25540,7 @@ var Autocapture = function(mp) {
|
|
|
24911
25540
|
|
|
24912
25541
|
Autocapture.prototype.init = function() {
|
|
24913
25542
|
if (!minDOMApisSupported()) {
|
|
24914
|
-
logger$
|
|
25543
|
+
logger$4.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24915
25544
|
return;
|
|
24916
25545
|
}
|
|
24917
25546
|
this.initPageListeners();
|
|
@@ -24943,27 +25572,15 @@ Autocapture.prototype.getConfig = function(key) {
|
|
|
24943
25572
|
};
|
|
24944
25573
|
|
|
24945
25574
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24946
|
-
var i;
|
|
24947
25575
|
var currentUrl = _.info.currentUrl();
|
|
24948
25576
|
|
|
24949
25577
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24950
25578
|
if (allowUrlRegexes.length) {
|
|
24951
25579
|
// we're using an allowlist, only track if current URL matches
|
|
24952
|
-
|
|
24953
|
-
|
|
24954
|
-
|
|
24955
|
-
|
|
24956
|
-
if (currentUrl.match(allowRegex)) {
|
|
24957
|
-
allowed = true;
|
|
24958
|
-
break;
|
|
24959
|
-
}
|
|
24960
|
-
} catch (err) {
|
|
24961
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24962
|
-
return true;
|
|
24963
|
-
}
|
|
24964
|
-
}
|
|
24965
|
-
if (!allowed) {
|
|
24966
|
-
// wasn't allowed by any regex
|
|
25580
|
+
try {
|
|
25581
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25582
|
+
} catch (err) {
|
|
25583
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
24967
25584
|
return true;
|
|
24968
25585
|
}
|
|
24969
25586
|
}
|
|
@@ -24973,17 +25590,12 @@ Autocapture.prototype.currentUrlBlocked = function() {
|
|
|
24973
25590
|
return false;
|
|
24974
25591
|
}
|
|
24975
25592
|
|
|
24976
|
-
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
24981
|
-
} catch (err) {
|
|
24982
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24983
|
-
return true;
|
|
24984
|
-
}
|
|
25593
|
+
try {
|
|
25594
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25595
|
+
} catch (err) {
|
|
25596
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
25597
|
+
return true;
|
|
24985
25598
|
}
|
|
24986
|
-
return false;
|
|
24987
25599
|
};
|
|
24988
25600
|
|
|
24989
25601
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -25119,7 +25731,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
|
|
|
25119
25731
|
return;
|
|
25120
25732
|
}
|
|
25121
25733
|
|
|
25122
|
-
logger$
|
|
25734
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
25123
25735
|
|
|
25124
25736
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
25125
25737
|
|
|
@@ -25145,7 +25757,7 @@ Autocapture.prototype.initClickTracking = function() {
|
|
|
25145
25757
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
25146
25758
|
return;
|
|
25147
25759
|
}
|
|
25148
|
-
logger$
|
|
25760
|
+
logger$4.log('Initializing click tracking');
|
|
25149
25761
|
|
|
25150
25762
|
this.listenerClick = function(ev) {
|
|
25151
25763
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -25164,7 +25776,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
|
|
|
25164
25776
|
return;
|
|
25165
25777
|
}
|
|
25166
25778
|
|
|
25167
|
-
logger$
|
|
25779
|
+
logger$4.log('Initializing dead click tracking');
|
|
25168
25780
|
if (!this._deadClickTracker) {
|
|
25169
25781
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
25170
25782
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -25198,7 +25810,7 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
25198
25810
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
25199
25811
|
return;
|
|
25200
25812
|
}
|
|
25201
|
-
logger$
|
|
25813
|
+
logger$4.log('Initializing input tracking');
|
|
25202
25814
|
|
|
25203
25815
|
this.listenerChange = function(ev) {
|
|
25204
25816
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -25215,7 +25827,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
25215
25827
|
if (!this.pageviewTrackingConfig()) {
|
|
25216
25828
|
return;
|
|
25217
25829
|
}
|
|
25218
|
-
logger$
|
|
25830
|
+
logger$4.log('Initializing pageview tracking');
|
|
25219
25831
|
|
|
25220
25832
|
var previousTrackedUrl = '';
|
|
25221
25833
|
var tracked = false;
|
|
@@ -25250,7 +25862,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
25250
25862
|
}
|
|
25251
25863
|
if (didPathChange) {
|
|
25252
25864
|
this.lastScrollCheckpoint = 0;
|
|
25253
|
-
logger$
|
|
25865
|
+
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
25254
25866
|
}
|
|
25255
25867
|
}
|
|
25256
25868
|
}.bind(this));
|
|
@@ -25265,7 +25877,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
25265
25877
|
return;
|
|
25266
25878
|
}
|
|
25267
25879
|
|
|
25268
|
-
logger$
|
|
25880
|
+
logger$4.log('Initializing rage click tracking');
|
|
25269
25881
|
if (!this._rageClickTracker) {
|
|
25270
25882
|
this._rageClickTracker = new RageClickTracker();
|
|
25271
25883
|
}
|
|
@@ -25295,7 +25907,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
25295
25907
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
25296
25908
|
return;
|
|
25297
25909
|
}
|
|
25298
|
-
logger$
|
|
25910
|
+
logger$4.log('Initializing scroll tracking');
|
|
25299
25911
|
this.lastScrollCheckpoint = 0;
|
|
25300
25912
|
|
|
25301
25913
|
var scrollTrackFunction = function() {
|
|
@@ -25332,7 +25944,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
25332
25944
|
}
|
|
25333
25945
|
}
|
|
25334
25946
|
} catch (err) {
|
|
25335
|
-
logger$
|
|
25947
|
+
logger$4.critical('Error while calculating scroll percentage', err);
|
|
25336
25948
|
}
|
|
25337
25949
|
if (shouldTrack) {
|
|
25338
25950
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -25350,7 +25962,7 @@ Autocapture.prototype.initSubmitTracking = function() {
|
|
|
25350
25962
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
25351
25963
|
return;
|
|
25352
25964
|
}
|
|
25353
|
-
logger$
|
|
25965
|
+
logger$4.log('Initializing submit tracking');
|
|
25354
25966
|
|
|
25355
25967
|
this.listenerSubmit = function(ev) {
|
|
25356
25968
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -25372,7 +25984,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
|
|
|
25372
25984
|
return;
|
|
25373
25985
|
}
|
|
25374
25986
|
|
|
25375
|
-
logger$
|
|
25987
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
25376
25988
|
this._initScrollDepthTracking();
|
|
25377
25989
|
var previousTrackedUrl = _.info.currentUrl();
|
|
25378
25990
|
|
|
@@ -25980,6 +26592,214 @@ FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getT
|
|
|
25980
26592
|
/* eslint camelcase: "off" */
|
|
25981
26593
|
|
|
25982
26594
|
|
|
26595
|
+
/**
|
|
26596
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26597
|
+
* @constructor
|
|
26598
|
+
*/
|
|
26599
|
+
var RecorderManager = function(initOptions) {
|
|
26600
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26601
|
+
// but ideally we should be able to remove this dependency.
|
|
26602
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26603
|
+
|
|
26604
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26605
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26606
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26607
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26608
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26609
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26610
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26611
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26612
|
+
|
|
26613
|
+
this._recorder = null;
|
|
26614
|
+
};
|
|
26615
|
+
|
|
26616
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26617
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26618
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26619
|
+
return PromisePolyfill.resolve(false);
|
|
26620
|
+
}
|
|
26621
|
+
|
|
26622
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26623
|
+
var tab_id = this.getTabId();
|
|
26624
|
+
return recording_registry_idb.init()
|
|
26625
|
+
.then(function () {
|
|
26626
|
+
return recording_registry_idb.getAll();
|
|
26627
|
+
})
|
|
26628
|
+
.then(function (recordings) {
|
|
26629
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26630
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26631
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26632
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26633
|
+
return true;
|
|
26634
|
+
}
|
|
26635
|
+
}
|
|
26636
|
+
return false;
|
|
26637
|
+
})
|
|
26638
|
+
.catch(_.bind(function (err) {
|
|
26639
|
+
this.reportError('Error checking recording registry', err);
|
|
26640
|
+
return false;
|
|
26641
|
+
}, this));
|
|
26642
|
+
};
|
|
26643
|
+
|
|
26644
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26645
|
+
if (!win['MutationObserver']) {
|
|
26646
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26647
|
+
return PromisePolyfill.resolve();
|
|
26648
|
+
}
|
|
26649
|
+
|
|
26650
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26651
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26652
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26653
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26654
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26655
|
+
resolve();
|
|
26656
|
+
}, this));
|
|
26657
|
+
|
|
26658
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26659
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26660
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26661
|
+
} else {
|
|
26662
|
+
handleLoadedRecorder();
|
|
26663
|
+
}
|
|
26664
|
+
}, this));
|
|
26665
|
+
}, this);
|
|
26666
|
+
|
|
26667
|
+
/**
|
|
26668
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26669
|
+
* 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.
|
|
26670
|
+
*/
|
|
26671
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26672
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26673
|
+
if (force_start || is_sampled) {
|
|
26674
|
+
return loadRecorder(true);
|
|
26675
|
+
} else {
|
|
26676
|
+
return this.shouldLoadRecorder()
|
|
26677
|
+
.then(_.bind(function (shouldLoad) {
|
|
26678
|
+
if (shouldLoad) {
|
|
26679
|
+
return loadRecorder(false);
|
|
26680
|
+
}
|
|
26681
|
+
return PromisePolyfill.resolve();
|
|
26682
|
+
}, this));
|
|
26683
|
+
}
|
|
26684
|
+
};
|
|
26685
|
+
|
|
26686
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26687
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26688
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26689
|
+
return false;
|
|
26690
|
+
}
|
|
26691
|
+
try {
|
|
26692
|
+
return this._recorder['isRecording']();
|
|
26693
|
+
} catch (e) {
|
|
26694
|
+
this.reportError('Error checking if recording is active', e);
|
|
26695
|
+
return false;
|
|
26696
|
+
}
|
|
26697
|
+
};
|
|
26698
|
+
|
|
26699
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26700
|
+
var isRecording = this.isRecording();
|
|
26701
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26702
|
+
|
|
26703
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26704
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26705
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26706
|
+
var newRate = trigger['percentage'];
|
|
26707
|
+
var propertyFilters = trigger['property_filters'];
|
|
26708
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26709
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26710
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26711
|
+
.then(function(targeting) {
|
|
26712
|
+
try {
|
|
26713
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26714
|
+
event_name,
|
|
26715
|
+
properties,
|
|
26716
|
+
{
|
|
26717
|
+
'event_name': event_name,
|
|
26718
|
+
'property_filters': propertyFilters
|
|
26719
|
+
}
|
|
26720
|
+
);
|
|
26721
|
+
if (result['matches']) {
|
|
26722
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26723
|
+
}
|
|
26724
|
+
} catch (err) {
|
|
26725
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26726
|
+
}
|
|
26727
|
+
}.bind(this)).catch(function(err) {
|
|
26728
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26729
|
+
});
|
|
26730
|
+
} else {
|
|
26731
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26732
|
+
}
|
|
26733
|
+
}
|
|
26734
|
+
}
|
|
26735
|
+
};
|
|
26736
|
+
|
|
26737
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26738
|
+
if (this._recorder) {
|
|
26739
|
+
return this._recorder['stopRecording']();
|
|
26740
|
+
}
|
|
26741
|
+
return PromisePolyfill.resolve();
|
|
26742
|
+
};
|
|
26743
|
+
|
|
26744
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26745
|
+
if (this._recorder) {
|
|
26746
|
+
return this._recorder['pauseRecording']();
|
|
26747
|
+
}
|
|
26748
|
+
return PromisePolyfill.resolve();
|
|
26749
|
+
};
|
|
26750
|
+
|
|
26751
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26752
|
+
if (this._recorder) {
|
|
26753
|
+
return this._recorder['resumeRecording']();
|
|
26754
|
+
}
|
|
26755
|
+
return PromisePolyfill.resolve();
|
|
26756
|
+
};
|
|
26757
|
+
|
|
26758
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26759
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
26760
|
+
};
|
|
26761
|
+
|
|
26762
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26763
|
+
var props = {};
|
|
26764
|
+
var replay_id = this.getSessionReplayId();
|
|
26765
|
+
if (replay_id) {
|
|
26766
|
+
props['$mp_replay_id'] = replay_id;
|
|
26767
|
+
}
|
|
26768
|
+
return props;
|
|
26769
|
+
};
|
|
26770
|
+
|
|
26771
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26772
|
+
var replay_url = null;
|
|
26773
|
+
var replay_id = this.getSessionReplayId();
|
|
26774
|
+
if (replay_id) {
|
|
26775
|
+
var query_params = _.HTTPBuildQuery({
|
|
26776
|
+
'replay_id': replay_id,
|
|
26777
|
+
'distinct_id': this.getDistinctId(),
|
|
26778
|
+
'token': this.getMpConfig('token')
|
|
26779
|
+
});
|
|
26780
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26781
|
+
}
|
|
26782
|
+
return replay_url;
|
|
26783
|
+
};
|
|
26784
|
+
|
|
26785
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26786
|
+
var replay_id = null;
|
|
26787
|
+
if (this._recorder) {
|
|
26788
|
+
replay_id = this._recorder['replayId'];
|
|
26789
|
+
}
|
|
26790
|
+
return replay_id || null;
|
|
26791
|
+
};
|
|
26792
|
+
|
|
26793
|
+
// "private" public method to reach into the recorder in test cases
|
|
26794
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26795
|
+
return this._recorder;
|
|
26796
|
+
};
|
|
26797
|
+
|
|
26798
|
+
safewrapClass(RecorderManager);
|
|
26799
|
+
|
|
26800
|
+
/* eslint camelcase: "off" */
|
|
26801
|
+
|
|
26802
|
+
|
|
25983
26803
|
/**
|
|
25984
26804
|
* DomTracker Object
|
|
25985
26805
|
* @constructor
|
|
@@ -27440,13 +28260,17 @@ var DEFAULT_CONFIG = {
|
|
|
27440
28260
|
'record_collect_fonts': false,
|
|
27441
28261
|
'record_console': true,
|
|
27442
28262
|
'record_heatmap_data': false,
|
|
28263
|
+
'recording_event_triggers': {},
|
|
27443
28264
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
27444
28265
|
'record_mask_inputs': true,
|
|
27445
28266
|
'record_max_ms': MAX_RECORDING_MS,
|
|
27446
28267
|
'record_min_ms': 0,
|
|
28268
|
+
'record_network': false,
|
|
28269
|
+
'record_network_options': {},
|
|
27447
28270
|
'record_sessions_percent': 0,
|
|
27448
|
-
'recorder_src':
|
|
27449
|
-
'targeting_src':
|
|
28271
|
+
'recorder_src': null,
|
|
28272
|
+
'targeting_src': null,
|
|
28273
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
27450
28274
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
27451
28275
|
};
|
|
27452
28276
|
|
|
@@ -27600,6 +28424,19 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27600
28424
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27601
28425
|
}));
|
|
27602
28426
|
|
|
28427
|
+
this.recorderManager = new RecorderManager({
|
|
28428
|
+
mixpanelInstance: this,
|
|
28429
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28430
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28431
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28432
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28433
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28434
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28435
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28436
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28437
|
+
loadExtraBundle: load_extra_bundle
|
|
28438
|
+
});
|
|
28439
|
+
|
|
27603
28440
|
this['_jsc'] = NOOP_FUNC;
|
|
27604
28441
|
|
|
27605
28442
|
this.__dom_loaded_queue = [];
|
|
@@ -27678,7 +28515,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27678
28515
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27679
28516
|
trackingFunc: _.bind(this.track, this),
|
|
27680
28517
|
loadExtraBundle: load_extra_bundle,
|
|
27681
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28518
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27682
28519
|
});
|
|
27683
28520
|
this.flags.init();
|
|
27684
28521
|
this['flags'] = this.flags;
|
|
@@ -27691,11 +28528,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27691
28528
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27692
28529
|
var mode = this.get_config('remote_settings_mode');
|
|
27693
28530
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27694
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27695
|
-
this._check_and_start_session_recording();
|
|
28531
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28532
|
+
return this._check_and_start_session_recording();
|
|
27696
28533
|
}, this));
|
|
27697
28534
|
} else {
|
|
27698
|
-
this._check_and_start_session_recording();
|
|
28535
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27699
28536
|
}
|
|
27700
28537
|
};
|
|
27701
28538
|
|
|
@@ -27739,132 +28576,50 @@ MixpanelLib.prototype.get_tab_id = function () {
|
|
|
27739
28576
|
return this.tab_id || null;
|
|
27740
28577
|
};
|
|
27741
28578
|
|
|
27742
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27743
|
-
if (this.get_config('disable_persistence')) {
|
|
27744
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27745
|
-
return Promise.resolve(false);
|
|
27746
|
-
}
|
|
27747
|
-
|
|
27748
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27749
|
-
var tab_id = this.get_tab_id();
|
|
27750
|
-
return recording_registry_idb.init()
|
|
27751
|
-
.then(function () {
|
|
27752
|
-
return recording_registry_idb.getAll();
|
|
27753
|
-
})
|
|
27754
|
-
.then(function (recordings) {
|
|
27755
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27756
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27757
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27758
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27759
|
-
return true;
|
|
27760
|
-
}
|
|
27761
|
-
}
|
|
27762
|
-
return false;
|
|
27763
|
-
})
|
|
27764
|
-
.catch(_.bind(function (err) {
|
|
27765
|
-
this.report_error('Error checking recording registry', err);
|
|
27766
|
-
}, this));
|
|
27767
|
-
};
|
|
27768
|
-
|
|
27769
28579
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27770
|
-
|
|
27771
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27772
|
-
return;
|
|
27773
|
-
}
|
|
27774
|
-
|
|
27775
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27776
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27777
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27778
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27779
|
-
}, this);
|
|
27780
|
-
|
|
27781
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27782
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27783
|
-
} else {
|
|
27784
|
-
handleLoadedRecorder();
|
|
27785
|
-
}
|
|
27786
|
-
}, this);
|
|
27787
|
-
|
|
27788
|
-
/**
|
|
27789
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27790
|
-
* 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.
|
|
27791
|
-
*/
|
|
27792
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27793
|
-
if (force_start || is_sampled) {
|
|
27794
|
-
loadRecorder(true);
|
|
27795
|
-
} else {
|
|
27796
|
-
this._should_load_recorder()
|
|
27797
|
-
.then(function (shouldLoad) {
|
|
27798
|
-
if (shouldLoad) {
|
|
27799
|
-
loadRecorder(false);
|
|
27800
|
-
}
|
|
27801
|
-
});
|
|
27802
|
-
}
|
|
28580
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27803
28581
|
});
|
|
27804
28582
|
|
|
28583
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28584
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28585
|
+
};
|
|
28586
|
+
|
|
27805
28587
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27806
|
-
this._check_and_start_session_recording(true);
|
|
28588
|
+
return this._check_and_start_session_recording(true);
|
|
27807
28589
|
};
|
|
27808
28590
|
|
|
27809
28591
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27810
|
-
|
|
27811
|
-
return this._recorder['stopRecording']();
|
|
27812
|
-
}
|
|
27813
|
-
return Promise.resolve();
|
|
28592
|
+
return this.recorderManager.stopSessionRecording();
|
|
27814
28593
|
};
|
|
27815
28594
|
|
|
27816
28595
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27817
|
-
|
|
27818
|
-
return this._recorder['pauseRecording']();
|
|
27819
|
-
}
|
|
27820
|
-
return Promise.resolve();
|
|
28596
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27821
28597
|
};
|
|
27822
28598
|
|
|
27823
28599
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27824
|
-
|
|
27825
|
-
return this._recorder['resumeRecording']();
|
|
27826
|
-
}
|
|
27827
|
-
return Promise.resolve();
|
|
28600
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27828
28601
|
};
|
|
27829
28602
|
|
|
27830
28603
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27831
|
-
return this.
|
|
28604
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27832
28605
|
};
|
|
27833
28606
|
|
|
27834
28607
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27835
|
-
|
|
27836
|
-
var replay_id = this._get_session_replay_id();
|
|
27837
|
-
if (replay_id) {
|
|
27838
|
-
props['$mp_replay_id'] = replay_id;
|
|
27839
|
-
}
|
|
27840
|
-
return props;
|
|
28608
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27841
28609
|
};
|
|
27842
28610
|
|
|
27843
28611
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27844
|
-
|
|
27845
|
-
var replay_id = this._get_session_replay_id();
|
|
27846
|
-
if (replay_id) {
|
|
27847
|
-
var query_params = _.HTTPBuildQuery({
|
|
27848
|
-
'replay_id': replay_id,
|
|
27849
|
-
'distinct_id': this.get_distinct_id(),
|
|
27850
|
-
'token': this.get_config('token')
|
|
27851
|
-
});
|
|
27852
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27853
|
-
}
|
|
27854
|
-
return replay_url;
|
|
27855
|
-
};
|
|
27856
|
-
|
|
27857
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27858
|
-
var replay_id = null;
|
|
27859
|
-
if (this._recorder) {
|
|
27860
|
-
replay_id = this._recorder['replayId'];
|
|
27861
|
-
}
|
|
27862
|
-
return replay_id || null;
|
|
28612
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27863
28613
|
};
|
|
27864
28614
|
|
|
27865
28615
|
// "private" public method to reach into the recorder in test cases
|
|
27866
28616
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27867
|
-
return this.
|
|
28617
|
+
return this.recorderManager.getRecorder();
|
|
28618
|
+
};
|
|
28619
|
+
|
|
28620
|
+
// "private" public method to get session recording init promise in test cases
|
|
28621
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28622
|
+
return this.__session_recording_init_promise;
|
|
27868
28623
|
};
|
|
27869
28624
|
|
|
27870
28625
|
// Private methods
|
|
@@ -28122,6 +28877,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
28122
28877
|
};
|
|
28123
28878
|
|
|
28124
28879
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28880
|
+
var self = this;
|
|
28125
28881
|
var disableRecordingIfStrict = function() {
|
|
28126
28882
|
if (mode === 'strict') {
|
|
28127
28883
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -28142,7 +28898,6 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
|
28142
28898
|
};
|
|
28143
28899
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
28144
28900
|
var full_url = settings_endpoint + '?' + query_string;
|
|
28145
|
-
var self = this;
|
|
28146
28901
|
|
|
28147
28902
|
var abortController = new AbortController();
|
|
28148
28903
|
var timeout_id = setTimeout(function() {
|
|
@@ -28334,6 +29089,34 @@ MixpanelLib.prototype.push = function(item) {
|
|
|
28334
29089
|
this._execute_array([item]);
|
|
28335
29090
|
};
|
|
28336
29091
|
|
|
29092
|
+
/**
|
|
29093
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29094
|
+
* this function enable tracking of all events. If passed an
|
|
29095
|
+
* array of event names, those events will be enabled, but other
|
|
29096
|
+
* existing disabled events will continue to be not tracked.
|
|
29097
|
+
*
|
|
29098
|
+
* @param {Array} [events] An array of event names to enable
|
|
29099
|
+
*/
|
|
29100
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29101
|
+
var keys, new_disabled_events, i, j;
|
|
29102
|
+
|
|
29103
|
+
if (typeof(events) === 'undefined') {
|
|
29104
|
+
this._flags.disable_all_events = false;
|
|
29105
|
+
} else {
|
|
29106
|
+
keys = {};
|
|
29107
|
+
new_disabled_events = [];
|
|
29108
|
+
for (i = 0; i < events.length; i++) {
|
|
29109
|
+
keys[events[i]] = true;
|
|
29110
|
+
}
|
|
29111
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29112
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29113
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29114
|
+
}
|
|
29115
|
+
}
|
|
29116
|
+
this.__disabled_events = new_disabled_events;
|
|
29117
|
+
}
|
|
29118
|
+
};
|
|
29119
|
+
|
|
28337
29120
|
/**
|
|
28338
29121
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
28339
29122
|
* this function disables tracking of any event. If passed an
|
|
@@ -28507,6 +29290,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
28507
29290
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
28508
29291
|
}
|
|
28509
29292
|
|
|
29293
|
+
this._start_recording_on_event(event_name, properties);
|
|
29294
|
+
|
|
28510
29295
|
var data = {
|
|
28511
29296
|
'event': event_name,
|
|
28512
29297
|
'properties': properties
|
|
@@ -29715,6 +30500,7 @@ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
|
29715
30500
|
// MixpanelLib Exports
|
|
29716
30501
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29717
30502
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30503
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29718
30504
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29719
30505
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29720
30506
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29758,6 +30544,7 @@ MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES
|
|
|
29758
30544
|
|
|
29759
30545
|
// Exports intended only for testing
|
|
29760
30546
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30547
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29761
30548
|
|
|
29762
30549
|
// MixpanelPersistence Exports
|
|
29763
30550
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|