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.umd.js
CHANGED
|
@@ -29,16 +29,19 @@
|
|
|
29
29
|
win = window;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
var Config = {
|
|
33
|
+
DEBUG: false,
|
|
34
|
+
LIB_VERSION: '2.76.0'
|
|
35
|
+
};
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
+
// Window global names for async modules
|
|
37
38
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
38
|
-
|
|
39
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
40
39
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
41
40
|
|
|
41
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
42
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
43
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
44
|
+
|
|
42
45
|
function _array_like_to_array(arr, len) {
|
|
43
46
|
if (len == null || len > arr.length) len = arr.length;
|
|
44
47
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -18138,7 +18141,7 @@
|
|
|
18138
18141
|
var __publicField = function(obj, key, value) {
|
|
18139
18142
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18140
18143
|
};
|
|
18141
|
-
function patch(source, name, replacement) {
|
|
18144
|
+
function patch$3(source, name, replacement) {
|
|
18142
18145
|
try {
|
|
18143
18146
|
if (!(name in source)) {
|
|
18144
18147
|
return function() {};
|
|
@@ -18555,7 +18558,7 @@
|
|
|
18555
18558
|
if (!_logger[level]) {
|
|
18556
18559
|
return function() {};
|
|
18557
18560
|
}
|
|
18558
|
-
return patch(_logger, level, function(original) {
|
|
18561
|
+
return patch$3(_logger, level, function(original) {
|
|
18559
18562
|
var _this1 = _this;
|
|
18560
18563
|
return function() {
|
|
18561
18564
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18976,11 +18979,6 @@
|
|
|
18976
18979
|
PromisePolyfill = NpoPromise;
|
|
18977
18980
|
}
|
|
18978
18981
|
|
|
18979
|
-
var Config = {
|
|
18980
|
-
DEBUG: false,
|
|
18981
|
-
LIB_VERSION: '2.75.0'
|
|
18982
|
-
};
|
|
18983
|
-
|
|
18984
18982
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18985
18983
|
|
|
18986
18984
|
// Maximum allowed session recording length
|
|
@@ -20712,6 +20710,17 @@
|
|
|
20712
20710
|
|
|
20713
20711
|
var NOOP_FUNC = function () {};
|
|
20714
20712
|
|
|
20713
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20714
|
+
var matches = false;
|
|
20715
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20716
|
+
if (url.match(regexList[i])) {
|
|
20717
|
+
matches = true;
|
|
20718
|
+
break;
|
|
20719
|
+
}
|
|
20720
|
+
}
|
|
20721
|
+
return matches;
|
|
20722
|
+
};
|
|
20723
|
+
|
|
20715
20724
|
var JSONStringify = null, JSONParse = null;
|
|
20716
20725
|
if (typeof JSON !== 'undefined') {
|
|
20717
20726
|
JSONStringify = JSON.stringify;
|
|
@@ -21183,7 +21192,7 @@
|
|
|
21183
21192
|
};
|
|
21184
21193
|
}
|
|
21185
21194
|
|
|
21186
|
-
var logger$
|
|
21195
|
+
var logger$7 = console_with_prefix('lock');
|
|
21187
21196
|
|
|
21188
21197
|
/**
|
|
21189
21198
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21235,7 +21244,7 @@
|
|
|
21235
21244
|
|
|
21236
21245
|
var delay = function(cb) {
|
|
21237
21246
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21238
|
-
logger$
|
|
21247
|
+
logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21239
21248
|
storage.removeItem(keyZ);
|
|
21240
21249
|
storage.removeItem(keyY);
|
|
21241
21250
|
loop();
|
|
@@ -21382,7 +21391,7 @@
|
|
|
21382
21391
|
}, this));
|
|
21383
21392
|
};
|
|
21384
21393
|
|
|
21385
|
-
var logger$
|
|
21394
|
+
var logger$6 = console_with_prefix('batch');
|
|
21386
21395
|
|
|
21387
21396
|
/**
|
|
21388
21397
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21411,7 +21420,7 @@
|
|
|
21411
21420
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21412
21421
|
});
|
|
21413
21422
|
}
|
|
21414
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21423
|
+
this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
|
|
21415
21424
|
|
|
21416
21425
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21417
21426
|
|
|
@@ -21744,7 +21753,7 @@
|
|
|
21744
21753
|
// maximum interval between request retries after exponential backoff
|
|
21745
21754
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21746
21755
|
|
|
21747
|
-
var logger$
|
|
21756
|
+
var logger$5 = console_with_prefix('batch');
|
|
21748
21757
|
|
|
21749
21758
|
/**
|
|
21750
21759
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21872,7 +21881,7 @@
|
|
|
21872
21881
|
*/
|
|
21873
21882
|
RequestBatcher.prototype.flush = function(options) {
|
|
21874
21883
|
if (this.requestInProgress) {
|
|
21875
|
-
logger$
|
|
21884
|
+
logger$5.log('Flush: Request already in progress');
|
|
21876
21885
|
return PromisePolyfill.resolve();
|
|
21877
21886
|
}
|
|
21878
21887
|
|
|
@@ -22049,7 +22058,7 @@
|
|
|
22049
22058
|
if (options.unloading) {
|
|
22050
22059
|
requestOptions.transport = 'sendBeacon';
|
|
22051
22060
|
}
|
|
22052
|
-
logger$
|
|
22061
|
+
logger$5.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22053
22062
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22054
22063
|
}, this))
|
|
22055
22064
|
.catch(_.bind(function(err) {
|
|
@@ -22062,7 +22071,7 @@
|
|
|
22062
22071
|
* Log error to global logger and optional user-defined logger.
|
|
22063
22072
|
*/
|
|
22064
22073
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22065
|
-
logger$
|
|
22074
|
+
logger$5.error.apply(logger$5.error, arguments);
|
|
22066
22075
|
if (this.errorReporter) {
|
|
22067
22076
|
try {
|
|
22068
22077
|
if (!(err instanceof Error)) {
|
|
@@ -22070,7 +22079,7 @@
|
|
|
22070
22079
|
}
|
|
22071
22080
|
this.errorReporter(msg, err);
|
|
22072
22081
|
} catch(err) {
|
|
22073
|
-
logger$
|
|
22082
|
+
logger$5.error(err);
|
|
22074
22083
|
}
|
|
22075
22084
|
}
|
|
22076
22085
|
};
|
|
@@ -22192,7 +22201,7 @@
|
|
|
22192
22201
|
|
|
22193
22202
|
var MAX_DEPTH = 5;
|
|
22194
22203
|
|
|
22195
|
-
var logger$
|
|
22204
|
+
var logger$4 = console_with_prefix('autocapture');
|
|
22196
22205
|
|
|
22197
22206
|
|
|
22198
22207
|
function getClasses(el) {
|
|
@@ -22456,7 +22465,7 @@
|
|
|
22456
22465
|
return false;
|
|
22457
22466
|
}
|
|
22458
22467
|
} catch (err) {
|
|
22459
|
-
logger$
|
|
22468
|
+
logger$4.critical('Error while checking element in allowElementCallback', err);
|
|
22460
22469
|
return false;
|
|
22461
22470
|
}
|
|
22462
22471
|
}
|
|
@@ -22473,7 +22482,7 @@
|
|
|
22473
22482
|
return true;
|
|
22474
22483
|
}
|
|
22475
22484
|
} catch (err) {
|
|
22476
|
-
logger$
|
|
22485
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22477
22486
|
}
|
|
22478
22487
|
}
|
|
22479
22488
|
return false;
|
|
@@ -22488,7 +22497,7 @@
|
|
|
22488
22497
|
return true;
|
|
22489
22498
|
}
|
|
22490
22499
|
} catch (err) {
|
|
22491
|
-
logger$
|
|
22500
|
+
logger$4.critical('Error while checking element in blockElementCallback', err);
|
|
22492
22501
|
return true;
|
|
22493
22502
|
}
|
|
22494
22503
|
}
|
|
@@ -22502,7 +22511,7 @@
|
|
|
22502
22511
|
return true;
|
|
22503
22512
|
}
|
|
22504
22513
|
} catch (err) {
|
|
22505
|
-
logger$
|
|
22514
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22506
22515
|
}
|
|
22507
22516
|
}
|
|
22508
22517
|
}
|
|
@@ -23049,6 +23058,655 @@
|
|
|
23049
23058
|
}
|
|
23050
23059
|
}
|
|
23051
23060
|
|
|
23061
|
+
/**
|
|
23062
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23063
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23064
|
+
*
|
|
23065
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23066
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23067
|
+
*
|
|
23068
|
+
*/
|
|
23069
|
+
|
|
23070
|
+
var logger$3 = console_with_prefix('network-plugin');
|
|
23071
|
+
|
|
23072
|
+
/**
|
|
23073
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23074
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23075
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23076
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23077
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23078
|
+
* @param {Window} win
|
|
23079
|
+
* @returns {number}
|
|
23080
|
+
*/
|
|
23081
|
+
function getTimeOrigin(win) {
|
|
23082
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23083
|
+
}
|
|
23084
|
+
|
|
23085
|
+
/**
|
|
23086
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23087
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23088
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23089
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23090
|
+
*/
|
|
23091
|
+
|
|
23092
|
+
/**
|
|
23093
|
+
* @typedef {Record<string, string>} Headers
|
|
23094
|
+
*/
|
|
23095
|
+
|
|
23096
|
+
/**
|
|
23097
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23098
|
+
*/
|
|
23099
|
+
|
|
23100
|
+
/**
|
|
23101
|
+
* @callback networkCallback
|
|
23102
|
+
* @param {NetworkData} data
|
|
23103
|
+
* @returns {void}
|
|
23104
|
+
*/
|
|
23105
|
+
|
|
23106
|
+
/**
|
|
23107
|
+
* @callback listenerHandler
|
|
23108
|
+
* @returns {void}
|
|
23109
|
+
*/
|
|
23110
|
+
|
|
23111
|
+
/**
|
|
23112
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23113
|
+
*/
|
|
23114
|
+
|
|
23115
|
+
/**
|
|
23116
|
+
* @typedef {Object} RecordPlugin
|
|
23117
|
+
* @property {string} name
|
|
23118
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23119
|
+
* @property {NetworkRecordOptions} [options]
|
|
23120
|
+
*/
|
|
23121
|
+
|
|
23122
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23123
|
+
var defaultNetworkOptions = {
|
|
23124
|
+
initiatorTypes: [
|
|
23125
|
+
'audio',
|
|
23126
|
+
'beacon',
|
|
23127
|
+
'body',
|
|
23128
|
+
'css',
|
|
23129
|
+
'early-hint',
|
|
23130
|
+
'embed',
|
|
23131
|
+
'fetch',
|
|
23132
|
+
'frame',
|
|
23133
|
+
'iframe',
|
|
23134
|
+
'icon',
|
|
23135
|
+
'image',
|
|
23136
|
+
'img',
|
|
23137
|
+
'input',
|
|
23138
|
+
'link',
|
|
23139
|
+
'navigation',
|
|
23140
|
+
'object',
|
|
23141
|
+
'ping',
|
|
23142
|
+
'script',
|
|
23143
|
+
'track',
|
|
23144
|
+
'video',
|
|
23145
|
+
'xmlhttprequest',
|
|
23146
|
+
],
|
|
23147
|
+
ignoreRequestFn: function() { return false; },
|
|
23148
|
+
recordHeaders: {
|
|
23149
|
+
request: [],
|
|
23150
|
+
response: [],
|
|
23151
|
+
},
|
|
23152
|
+
recordBodyUrls: {
|
|
23153
|
+
request: [],
|
|
23154
|
+
response: [],
|
|
23155
|
+
},
|
|
23156
|
+
recordInitialRequests: false,
|
|
23157
|
+
};
|
|
23158
|
+
|
|
23159
|
+
/**
|
|
23160
|
+
* @param {PerformanceEntry} entry
|
|
23161
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23162
|
+
*/
|
|
23163
|
+
function isNavigationTiming(entry) {
|
|
23164
|
+
return entry.entryType === 'navigation';
|
|
23165
|
+
}
|
|
23166
|
+
|
|
23167
|
+
/**
|
|
23168
|
+
* @param {PerformanceEntry} entry
|
|
23169
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23170
|
+
*/
|
|
23171
|
+
function isResourceTiming (entry) {
|
|
23172
|
+
return entry.entryType === 'resource';
|
|
23173
|
+
}
|
|
23174
|
+
|
|
23175
|
+
function findLast(array, predicate) {
|
|
23176
|
+
var length = array.length;
|
|
23177
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23178
|
+
if (predicate(array[i])) {
|
|
23179
|
+
return array[i];
|
|
23180
|
+
}
|
|
23181
|
+
}
|
|
23182
|
+
}
|
|
23183
|
+
|
|
23184
|
+
/**
|
|
23185
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23186
|
+
* Adapted from Sentry's `fill` utility:
|
|
23187
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23188
|
+
*
|
|
23189
|
+
* @param {object} source - The object containing the method to patch
|
|
23190
|
+
* @param {string} name - The method name to patch
|
|
23191
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23192
|
+
* @returns {function} A function that restores the original method
|
|
23193
|
+
*/
|
|
23194
|
+
function patch(source, name, replacementFactory) {
|
|
23195
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23196
|
+
return function() {};
|
|
23197
|
+
}
|
|
23198
|
+
var original = source[name];
|
|
23199
|
+
var wrapped = replacementFactory(original);
|
|
23200
|
+
source[name] = wrapped;
|
|
23201
|
+
return function() {
|
|
23202
|
+
source[name] = original;
|
|
23203
|
+
};
|
|
23204
|
+
}
|
|
23205
|
+
|
|
23206
|
+
|
|
23207
|
+
/**
|
|
23208
|
+
* Maximum body size to record (1MB)
|
|
23209
|
+
*/
|
|
23210
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23211
|
+
|
|
23212
|
+
/**
|
|
23213
|
+
* Truncate string if it exceeds max size
|
|
23214
|
+
* @param {string} str
|
|
23215
|
+
* @returns {string}
|
|
23216
|
+
*/
|
|
23217
|
+
function truncateBody(str) {
|
|
23218
|
+
if (!str || typeof str !== 'string') {
|
|
23219
|
+
return str;
|
|
23220
|
+
}
|
|
23221
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23222
|
+
logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23223
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23224
|
+
}
|
|
23225
|
+
return str;
|
|
23226
|
+
}
|
|
23227
|
+
|
|
23228
|
+
/**
|
|
23229
|
+
* @param {networkCallback} cb
|
|
23230
|
+
* @param {Window} win
|
|
23231
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23232
|
+
* @returns {listenerHandler}
|
|
23233
|
+
*/
|
|
23234
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23235
|
+
if (!win.PerformanceObserver) {
|
|
23236
|
+
logger$3.error('PerformanceObserver not supported');
|
|
23237
|
+
return function() {
|
|
23238
|
+
//
|
|
23239
|
+
};
|
|
23240
|
+
}
|
|
23241
|
+
if (options.recordInitialRequests) {
|
|
23242
|
+
var initialPerformanceEntries = win.performance
|
|
23243
|
+
.getEntries()
|
|
23244
|
+
.filter(function(entry) {
|
|
23245
|
+
return isNavigationTiming(entry) ||
|
|
23246
|
+
(isResourceTiming(entry) &&
|
|
23247
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23248
|
+
});
|
|
23249
|
+
cb({
|
|
23250
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23251
|
+
return {
|
|
23252
|
+
url: entry.name,
|
|
23253
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23254
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23255
|
+
startTime: Math.round(entry.startTime),
|
|
23256
|
+
endTime: Math.round(entry.responseEnd),
|
|
23257
|
+
timeOrigin: getTimeOrigin(win),
|
|
23258
|
+
};
|
|
23259
|
+
}),
|
|
23260
|
+
isInitial: true,
|
|
23261
|
+
});
|
|
23262
|
+
}
|
|
23263
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23264
|
+
var performanceEntries = entries
|
|
23265
|
+
.getEntries()
|
|
23266
|
+
.filter(function(entry) {
|
|
23267
|
+
return isResourceTiming(entry) &&
|
|
23268
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23269
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23270
|
+
entry.initiatorType !== 'fetch';
|
|
23271
|
+
});
|
|
23272
|
+
cb({
|
|
23273
|
+
requests: performanceEntries.map(function(entry) {
|
|
23274
|
+
return {
|
|
23275
|
+
url: entry.name,
|
|
23276
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23277
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23278
|
+
startTime: Math.round(entry.startTime),
|
|
23279
|
+
endTime: Math.round(entry.responseEnd),
|
|
23280
|
+
timeOrigin: getTimeOrigin(win),
|
|
23281
|
+
};
|
|
23282
|
+
}),
|
|
23283
|
+
});
|
|
23284
|
+
});
|
|
23285
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23286
|
+
return function() {
|
|
23287
|
+
observer.disconnect();
|
|
23288
|
+
};
|
|
23289
|
+
}
|
|
23290
|
+
|
|
23291
|
+
/**
|
|
23292
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23293
|
+
* @param {'request' | 'response'} type
|
|
23294
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23295
|
+
* @param {string} headerName
|
|
23296
|
+
* @returns {boolean}
|
|
23297
|
+
*/
|
|
23298
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23299
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23300
|
+
return false;
|
|
23301
|
+
}
|
|
23302
|
+
|
|
23303
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23304
|
+
}
|
|
23305
|
+
|
|
23306
|
+
/**
|
|
23307
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23308
|
+
* @param {'request' | 'response'} type
|
|
23309
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23310
|
+
* @param {string} url
|
|
23311
|
+
* @returns {boolean}
|
|
23312
|
+
*/
|
|
23313
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23314
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23315
|
+
return false;
|
|
23316
|
+
}
|
|
23317
|
+
|
|
23318
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23319
|
+
}
|
|
23320
|
+
|
|
23321
|
+
function tryReadXHRBody(body) {
|
|
23322
|
+
if (body === null || body === undefined) {
|
|
23323
|
+
return null;
|
|
23324
|
+
}
|
|
23325
|
+
|
|
23326
|
+
var result;
|
|
23327
|
+
if (typeof body === 'string') {
|
|
23328
|
+
result = body;
|
|
23329
|
+
} else if (body instanceof Document) {
|
|
23330
|
+
result = body.textContent;
|
|
23331
|
+
} else if (body instanceof FormData) {
|
|
23332
|
+
result = _.HTTPBuildQuery(body);
|
|
23333
|
+
} else if (_.isObject(body)) {
|
|
23334
|
+
try {
|
|
23335
|
+
result = JSON.stringify(body);
|
|
23336
|
+
} catch (e) {
|
|
23337
|
+
return 'Failed to stringify response object';
|
|
23338
|
+
}
|
|
23339
|
+
} else {
|
|
23340
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23341
|
+
}
|
|
23342
|
+
|
|
23343
|
+
return truncateBody(result);
|
|
23344
|
+
}
|
|
23345
|
+
|
|
23346
|
+
/**
|
|
23347
|
+
* @param {Request | Response} r
|
|
23348
|
+
* @returns {Promise<string>}
|
|
23349
|
+
*/
|
|
23350
|
+
function tryReadFetchBody(r) {
|
|
23351
|
+
return new Promise(function(resolve) {
|
|
23352
|
+
var timeout = setTimeout(function() {
|
|
23353
|
+
resolve('Timeout while trying to read body');
|
|
23354
|
+
}, 500);
|
|
23355
|
+
try {
|
|
23356
|
+
r.clone()
|
|
23357
|
+
.text()
|
|
23358
|
+
.then(
|
|
23359
|
+
function(txt) {
|
|
23360
|
+
clearTimeout(timeout);
|
|
23361
|
+
resolve(truncateBody(txt));
|
|
23362
|
+
},
|
|
23363
|
+
function(reason) {
|
|
23364
|
+
clearTimeout(timeout);
|
|
23365
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23366
|
+
}
|
|
23367
|
+
);
|
|
23368
|
+
} catch (e) {
|
|
23369
|
+
clearTimeout(timeout);
|
|
23370
|
+
resolve('Failed to read body: ' + String(e));
|
|
23371
|
+
}
|
|
23372
|
+
});
|
|
23373
|
+
}
|
|
23374
|
+
|
|
23375
|
+
/**
|
|
23376
|
+
* @param {Window} win
|
|
23377
|
+
* @param {string} initiatorType
|
|
23378
|
+
* @param {string} url
|
|
23379
|
+
* @param {number} [after]
|
|
23380
|
+
* @param {number} [before]
|
|
23381
|
+
* @param {number} [attempt]
|
|
23382
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23383
|
+
*/
|
|
23384
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23385
|
+
if (attempt === undefined) {
|
|
23386
|
+
attempt = 0;
|
|
23387
|
+
}
|
|
23388
|
+
if (attempt > 10) {
|
|
23389
|
+
logger$3.error('Cannot find performance entry');
|
|
23390
|
+
return Promise.resolve(null);
|
|
23391
|
+
}
|
|
23392
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23393
|
+
win.performance.getEntriesByName(url)
|
|
23394
|
+
);
|
|
23395
|
+
var performanceEntry = findLast(
|
|
23396
|
+
urlPerformanceEntries,
|
|
23397
|
+
function(entry) {
|
|
23398
|
+
return isResourceTiming(entry) &&
|
|
23399
|
+
entry.initiatorType === initiatorType &&
|
|
23400
|
+
(!after || entry.startTime >= after) &&
|
|
23401
|
+
(!before || entry.startTime <= before);
|
|
23402
|
+
}
|
|
23403
|
+
);
|
|
23404
|
+
if (!performanceEntry) {
|
|
23405
|
+
return new Promise(function(resolve) {
|
|
23406
|
+
setTimeout(resolve, 50 * attempt);
|
|
23407
|
+
}).then(function() {
|
|
23408
|
+
return getRequestPerformanceEntry(
|
|
23409
|
+
win,
|
|
23410
|
+
initiatorType,
|
|
23411
|
+
url,
|
|
23412
|
+
after,
|
|
23413
|
+
before,
|
|
23414
|
+
attempt + 1
|
|
23415
|
+
);
|
|
23416
|
+
});
|
|
23417
|
+
}
|
|
23418
|
+
return Promise.resolve(performanceEntry);
|
|
23419
|
+
}
|
|
23420
|
+
|
|
23421
|
+
/**
|
|
23422
|
+
* @param {networkCallback} cb
|
|
23423
|
+
* @param {Window} win
|
|
23424
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23425
|
+
* @returns {listenerHandler}
|
|
23426
|
+
*/
|
|
23427
|
+
function initXhrObserver(cb, win, options) {
|
|
23428
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23429
|
+
return function() {
|
|
23430
|
+
//
|
|
23431
|
+
};
|
|
23432
|
+
}
|
|
23433
|
+
var restorePatch = patch(
|
|
23434
|
+
win.XMLHttpRequest.prototype,
|
|
23435
|
+
'open',
|
|
23436
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23437
|
+
return function(
|
|
23438
|
+
/** @type {string} */ method,
|
|
23439
|
+
/** @type {string | URL} */ url,
|
|
23440
|
+
/** @type {boolean} */ async,
|
|
23441
|
+
username, password
|
|
23442
|
+
) {
|
|
23443
|
+
if (async === undefined) {
|
|
23444
|
+
async = true;
|
|
23445
|
+
}
|
|
23446
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23447
|
+
var req = new Request(url, { method: method });
|
|
23448
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23449
|
+
var networkRequest = {};
|
|
23450
|
+
/** @type {number | undefined} */
|
|
23451
|
+
var after;
|
|
23452
|
+
/** @type {number | undefined} */
|
|
23453
|
+
var before;
|
|
23454
|
+
|
|
23455
|
+
/** @type {Headers} */
|
|
23456
|
+
var requestHeaders = {};
|
|
23457
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23458
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23459
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23460
|
+
requestHeaders[header] = value;
|
|
23461
|
+
}
|
|
23462
|
+
return originalSetRequestHeader(header, value);
|
|
23463
|
+
};
|
|
23464
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23465
|
+
|
|
23466
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23467
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23468
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23469
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23470
|
+
}
|
|
23471
|
+
after = win.performance.now();
|
|
23472
|
+
return originalSend(body);
|
|
23473
|
+
};
|
|
23474
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23475
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23476
|
+
return;
|
|
23477
|
+
}
|
|
23478
|
+
before = win.performance.now();
|
|
23479
|
+
/** @type {Headers} */
|
|
23480
|
+
var responseHeaders = {};
|
|
23481
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23482
|
+
if (rawHeaders) {
|
|
23483
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23484
|
+
headers.forEach(function(line) {
|
|
23485
|
+
if (!line) return;
|
|
23486
|
+
var colonIndex = line.indexOf(': ');
|
|
23487
|
+
if (colonIndex === -1) return;
|
|
23488
|
+
var header = line.substring(0, colonIndex);
|
|
23489
|
+
var value = line.substring(colonIndex + 2);
|
|
23490
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23491
|
+
responseHeaders[header] = value;
|
|
23492
|
+
}
|
|
23493
|
+
});
|
|
23494
|
+
}
|
|
23495
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23496
|
+
if (
|
|
23497
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23498
|
+
) {
|
|
23499
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23500
|
+
}
|
|
23501
|
+
getRequestPerformanceEntry(
|
|
23502
|
+
win,
|
|
23503
|
+
'xmlhttprequest',
|
|
23504
|
+
req.url,
|
|
23505
|
+
after,
|
|
23506
|
+
before
|
|
23507
|
+
)
|
|
23508
|
+
.then(function(entry) {
|
|
23509
|
+
if (!entry) {
|
|
23510
|
+
logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23511
|
+
return;
|
|
23512
|
+
}
|
|
23513
|
+
/** @type {NetworkRequest} */
|
|
23514
|
+
var request = {
|
|
23515
|
+
url: entry.name,
|
|
23516
|
+
method: req.method,
|
|
23517
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23518
|
+
status: xhr.status,
|
|
23519
|
+
startTime: Math.round(entry.startTime),
|
|
23520
|
+
endTime: Math.round(entry.responseEnd),
|
|
23521
|
+
timeOrigin: getTimeOrigin(win),
|
|
23522
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23523
|
+
requestBody: networkRequest.requestBody,
|
|
23524
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23525
|
+
responseBody: networkRequest.responseBody,
|
|
23526
|
+
};
|
|
23527
|
+
cb({ requests: [request] });
|
|
23528
|
+
})
|
|
23529
|
+
.catch(function(e) {
|
|
23530
|
+
logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23531
|
+
});
|
|
23532
|
+
});
|
|
23533
|
+
|
|
23534
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23535
|
+
};
|
|
23536
|
+
}
|
|
23537
|
+
);
|
|
23538
|
+
return function() {
|
|
23539
|
+
restorePatch();
|
|
23540
|
+
};
|
|
23541
|
+
}
|
|
23542
|
+
|
|
23543
|
+
/**
|
|
23544
|
+
* @param {networkCallback} cb
|
|
23545
|
+
* @param {Window} win
|
|
23546
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23547
|
+
* @returns {listenerHandler}
|
|
23548
|
+
*/
|
|
23549
|
+
function initFetchObserver(cb, win, options) {
|
|
23550
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23551
|
+
return function() {
|
|
23552
|
+
//
|
|
23553
|
+
};
|
|
23554
|
+
}
|
|
23555
|
+
|
|
23556
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23557
|
+
return function() {
|
|
23558
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23559
|
+
/** @type {Response | undefined} */
|
|
23560
|
+
var res;
|
|
23561
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23562
|
+
var networkRequest = {};
|
|
23563
|
+
/** @type {number | undefined} */
|
|
23564
|
+
var after;
|
|
23565
|
+
/** @type {number | undefined} */
|
|
23566
|
+
var before;
|
|
23567
|
+
|
|
23568
|
+
var originalFetchPromise;
|
|
23569
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23570
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23571
|
+
try {
|
|
23572
|
+
/** @type {Headers} */
|
|
23573
|
+
var requestHeaders = {};
|
|
23574
|
+
req.headers.forEach(function(value, header) {
|
|
23575
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23576
|
+
requestHeaders[header] = value;
|
|
23577
|
+
}
|
|
23578
|
+
});
|
|
23579
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23580
|
+
|
|
23581
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23582
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23583
|
+
.then(function(body) {
|
|
23584
|
+
networkRequest.requestBody = body;
|
|
23585
|
+
});
|
|
23586
|
+
}
|
|
23587
|
+
|
|
23588
|
+
after = win.performance.now();
|
|
23589
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23590
|
+
res = response;
|
|
23591
|
+
before = win.performance.now();
|
|
23592
|
+
|
|
23593
|
+
/** @type {Headers} */
|
|
23594
|
+
var responseHeaders = {};
|
|
23595
|
+
res.headers.forEach(function(value, header) {
|
|
23596
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23597
|
+
responseHeaders[header] = value;
|
|
23598
|
+
}
|
|
23599
|
+
});
|
|
23600
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23601
|
+
|
|
23602
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23603
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23604
|
+
.then(function(body) {
|
|
23605
|
+
networkRequest.responseBody = body;
|
|
23606
|
+
});
|
|
23607
|
+
}
|
|
23608
|
+
|
|
23609
|
+
return res;
|
|
23610
|
+
});
|
|
23611
|
+
} catch (e) {
|
|
23612
|
+
originalFetchPromise = Promise.reject(e);
|
|
23613
|
+
}
|
|
23614
|
+
|
|
23615
|
+
// await concurrently so we don't delay the fetch response
|
|
23616
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23617
|
+
.then(function () {
|
|
23618
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23619
|
+
})
|
|
23620
|
+
.then(function(entry) {
|
|
23621
|
+
if (!entry) {
|
|
23622
|
+
logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23623
|
+
return;
|
|
23624
|
+
}
|
|
23625
|
+
/** @type {NetworkRequest} */
|
|
23626
|
+
var request = {
|
|
23627
|
+
url: entry.name,
|
|
23628
|
+
method: req.method,
|
|
23629
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23630
|
+
status: res ? res.status : undefined,
|
|
23631
|
+
startTime: Math.round(entry.startTime),
|
|
23632
|
+
endTime: Math.round(entry.responseEnd),
|
|
23633
|
+
timeOrigin: getTimeOrigin(win),
|
|
23634
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23635
|
+
requestBody: networkRequest.requestBody,
|
|
23636
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23637
|
+
responseBody: networkRequest.responseBody,
|
|
23638
|
+
};
|
|
23639
|
+
cb({ requests: [request] });
|
|
23640
|
+
})
|
|
23641
|
+
.catch(function (e) {
|
|
23642
|
+
logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23643
|
+
});
|
|
23644
|
+
|
|
23645
|
+
return originalFetchPromise;
|
|
23646
|
+
};
|
|
23647
|
+
});
|
|
23648
|
+
return function() {
|
|
23649
|
+
restorePatch();
|
|
23650
|
+
};
|
|
23651
|
+
}
|
|
23652
|
+
|
|
23653
|
+
/**
|
|
23654
|
+
* @param {networkCallback} callback
|
|
23655
|
+
* @param {Window} win
|
|
23656
|
+
* @param {NetworkRecordOptions} options
|
|
23657
|
+
* @returns {listenerHandler}
|
|
23658
|
+
*/
|
|
23659
|
+
function initNetworkObserver(callback, win, options) {
|
|
23660
|
+
if (!('performance' in win)) {
|
|
23661
|
+
return function() {
|
|
23662
|
+
//
|
|
23663
|
+
};
|
|
23664
|
+
}
|
|
23665
|
+
|
|
23666
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23667
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23668
|
+
options = Object.assign({}, options, {
|
|
23669
|
+
recordHeaders: recordHeaders,
|
|
23670
|
+
recordBodyUrls: recordBodyUrls,
|
|
23671
|
+
});
|
|
23672
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23673
|
+
|
|
23674
|
+
/** @type {networkCallback} */
|
|
23675
|
+
var cb = function(data) {
|
|
23676
|
+
var requests = data.requests.filter(function(request) {
|
|
23677
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23678
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23679
|
+
});
|
|
23680
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23681
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23682
|
+
}
|
|
23683
|
+
};
|
|
23684
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23685
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23686
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23687
|
+
return function() {
|
|
23688
|
+
performanceObserver();
|
|
23689
|
+
xhrObserver();
|
|
23690
|
+
fetchObserver();
|
|
23691
|
+
};
|
|
23692
|
+
}
|
|
23693
|
+
|
|
23694
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23695
|
+
// a changed format in the mixpanel product.
|
|
23696
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23697
|
+
|
|
23698
|
+
/**
|
|
23699
|
+
* @param {NetworkRecordOptions} [options]
|
|
23700
|
+
* @returns {RecordPlugin}
|
|
23701
|
+
*/
|
|
23702
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23703
|
+
return {
|
|
23704
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23705
|
+
observer: initNetworkObserver,
|
|
23706
|
+
options: options,
|
|
23707
|
+
};
|
|
23708
|
+
};
|
|
23709
|
+
|
|
23052
23710
|
/**
|
|
23053
23711
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23054
23712
|
*/
|
|
@@ -23282,12 +23940,35 @@
|
|
|
23282
23940
|
|
|
23283
23941
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23284
23942
|
|
|
23285
|
-
|
|
23286
|
-
|
|
23287
|
-
|
|
23288
|
-
|
|
23289
|
-
|
|
23290
|
-
|
|
23943
|
+
var plugins = [];
|
|
23944
|
+
if (this.getConfig('record_network')) {
|
|
23945
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23946
|
+
// don't track requests to Mixpanel /record API
|
|
23947
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23948
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23949
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23950
|
+
|
|
23951
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23952
|
+
}
|
|
23953
|
+
|
|
23954
|
+
if (this.getConfig('record_console')) {
|
|
23955
|
+
plugins.push(
|
|
23956
|
+
getRecordConsolePlugin({
|
|
23957
|
+
stringifyOptions: {
|
|
23958
|
+
stringLengthLimit: 1000,
|
|
23959
|
+
numOfKeysLimit: 50,
|
|
23960
|
+
depthOfLimit: 2
|
|
23961
|
+
}
|
|
23962
|
+
})
|
|
23963
|
+
);
|
|
23964
|
+
}
|
|
23965
|
+
|
|
23966
|
+
try {
|
|
23967
|
+
this._stopRecording = this._rrwebRecord({
|
|
23968
|
+
'emit': function (ev) {
|
|
23969
|
+
if (this.idleExpires && this.idleExpires < ev.timestamp) {
|
|
23970
|
+
this._onIdleTimeout();
|
|
23971
|
+
return;
|
|
23291
23972
|
}
|
|
23292
23973
|
if (isUserEvent(ev)) {
|
|
23293
23974
|
if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
|
|
@@ -23320,15 +24001,7 @@
|
|
|
23320
24001
|
'sampling': {
|
|
23321
24002
|
'canvas': 15
|
|
23322
24003
|
},
|
|
23323
|
-
'plugins':
|
|
23324
|
-
getRecordConsolePlugin({
|
|
23325
|
-
stringifyOptions: {
|
|
23326
|
-
stringLengthLimit: 1000,
|
|
23327
|
-
numOfKeysLimit: 50,
|
|
23328
|
-
depthOfLimit: 2
|
|
23329
|
-
}
|
|
23330
|
-
})
|
|
23331
|
-
] : []
|
|
24004
|
+
'plugins': plugins,
|
|
23332
24005
|
});
|
|
23333
24006
|
} catch (err) {
|
|
23334
24007
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23443,6 +24116,10 @@
|
|
|
23443
24116
|
return recording;
|
|
23444
24117
|
};
|
|
23445
24118
|
|
|
24119
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24120
|
+
return this.getConfig('api_routes')['record'];
|
|
24121
|
+
};
|
|
24122
|
+
|
|
23446
24123
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23447
24124
|
var onSuccess = function (response, responseBody) {
|
|
23448
24125
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23462,7 +24139,7 @@
|
|
|
23462
24139
|
});
|
|
23463
24140
|
}.bind(this);
|
|
23464
24141
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23465
|
-
win['fetch'](apiHost + '/' + this.
|
|
24142
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23466
24143
|
'method': 'POST',
|
|
23467
24144
|
'headers': {
|
|
23468
24145
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23863,8 +24540,12 @@
|
|
|
23863
24540
|
this.startRecording({shouldStopBatcher: true});
|
|
23864
24541
|
};
|
|
23865
24542
|
|
|
24543
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24544
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24545
|
+
};
|
|
24546
|
+
|
|
23866
24547
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23867
|
-
if (this.
|
|
24548
|
+
if (this.isRecording()) {
|
|
23868
24549
|
return this.activeRecording.replayId;
|
|
23869
24550
|
} else {
|
|
23870
24551
|
return null;
|
|
@@ -24368,53 +25049,6 @@
|
|
|
24368
25049
|
var logicExports = requireLogic();
|
|
24369
25050
|
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24370
25051
|
|
|
24371
|
-
/**
|
|
24372
|
-
* Shared helper to recursively lowercase strings in nested structures
|
|
24373
|
-
* @param {*} obj - Value to process
|
|
24374
|
-
* @param {boolean} lowercaseKeys - Whether to lowercase object keys
|
|
24375
|
-
* @returns {*} Processed value with lowercased strings
|
|
24376
|
-
*/
|
|
24377
|
-
var lowercaseJson = function(obj, lowercaseKeys) {
|
|
24378
|
-
if (obj === null || obj === undefined) {
|
|
24379
|
-
return obj;
|
|
24380
|
-
} else if (typeof obj === 'string') {
|
|
24381
|
-
return obj.toLowerCase();
|
|
24382
|
-
} else if (Array.isArray(obj)) {
|
|
24383
|
-
return obj.map(function(item) {
|
|
24384
|
-
return lowercaseJson(item, lowercaseKeys);
|
|
24385
|
-
});
|
|
24386
|
-
} else if (obj === Object(obj)) {
|
|
24387
|
-
var result = {};
|
|
24388
|
-
for (var key in obj) {
|
|
24389
|
-
if (obj.hasOwnProperty(key)) {
|
|
24390
|
-
var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
|
|
24391
|
-
result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
|
|
24392
|
-
}
|
|
24393
|
-
}
|
|
24394
|
-
return result;
|
|
24395
|
-
} else {
|
|
24396
|
-
return obj;
|
|
24397
|
-
}
|
|
24398
|
-
};
|
|
24399
|
-
|
|
24400
|
-
/**
|
|
24401
|
-
* Lowercase all string keys and values in a nested structure
|
|
24402
|
-
* @param {*} val - Value to process
|
|
24403
|
-
* @returns {*} Processed value with lowercased strings
|
|
24404
|
-
*/
|
|
24405
|
-
var lowercaseKeysAndValues = function(val) {
|
|
24406
|
-
return lowercaseJson(val, true);
|
|
24407
|
-
};
|
|
24408
|
-
|
|
24409
|
-
/**
|
|
24410
|
-
* Lowercase only leaf node string values in a nested structure (keys unchanged)
|
|
24411
|
-
* @param {*} val - Value to process
|
|
24412
|
-
* @returns {*} Processed value with lowercased leaf strings
|
|
24413
|
-
*/
|
|
24414
|
-
var lowercaseOnlyLeafNodes = function(val) {
|
|
24415
|
-
return lowercaseJson(val, false);
|
|
24416
|
-
};
|
|
24417
|
-
|
|
24418
25052
|
/**
|
|
24419
25053
|
* Check if an event matches the given criteria
|
|
24420
25054
|
* @param {string} eventName - The name of the event being checked
|
|
@@ -24438,13 +25072,8 @@
|
|
|
24438
25072
|
|
|
24439
25073
|
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24440
25074
|
try {
|
|
24441
|
-
//
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
// Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
|
|
24445
|
-
var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
|
|
24446
|
-
|
|
24447
|
-
filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
|
|
25075
|
+
// Use properties as-is for case-sensitive matching
|
|
25076
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
24448
25077
|
} catch (error) {
|
|
24449
25078
|
return {
|
|
24450
25079
|
matches: false,
|
|
@@ -24564,7 +25193,7 @@
|
|
|
24564
25193
|
observer.observe(shadowRoot, this.observerConfig);
|
|
24565
25194
|
this.shadowObservers.push(observer);
|
|
24566
25195
|
} catch (e) {
|
|
24567
|
-
logger$
|
|
25196
|
+
logger$4.critical('Error while observing shadow root', e);
|
|
24568
25197
|
}
|
|
24569
25198
|
};
|
|
24570
25199
|
|
|
@@ -24575,7 +25204,7 @@
|
|
|
24575
25204
|
}
|
|
24576
25205
|
|
|
24577
25206
|
if (!weakSetSupported()) {
|
|
24578
|
-
logger$
|
|
25207
|
+
logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
24579
25208
|
return;
|
|
24580
25209
|
}
|
|
24581
25210
|
|
|
@@ -24591,7 +25220,7 @@
|
|
|
24591
25220
|
try {
|
|
24592
25221
|
this.shadowObservers[i].disconnect();
|
|
24593
25222
|
} catch (e) {
|
|
24594
|
-
logger$
|
|
25223
|
+
logger$4.critical('Error while disconnecting shadow DOM observer', e);
|
|
24595
25224
|
}
|
|
24596
25225
|
}
|
|
24597
25226
|
this.shadowObservers = [];
|
|
@@ -24779,7 +25408,7 @@
|
|
|
24779
25408
|
|
|
24780
25409
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24781
25410
|
} catch (e) {
|
|
24782
|
-
logger$
|
|
25411
|
+
logger$4.critical('Error while setting up mutation observer', e);
|
|
24783
25412
|
}
|
|
24784
25413
|
}
|
|
24785
25414
|
|
|
@@ -24794,7 +25423,7 @@
|
|
|
24794
25423
|
);
|
|
24795
25424
|
this.shadowDOMObserver.start();
|
|
24796
25425
|
} catch (e) {
|
|
24797
|
-
logger$
|
|
25426
|
+
logger$4.critical('Error while setting up shadow DOM observer', e);
|
|
24798
25427
|
this.shadowDOMObserver = null;
|
|
24799
25428
|
}
|
|
24800
25429
|
}
|
|
@@ -24821,7 +25450,7 @@
|
|
|
24821
25450
|
try {
|
|
24822
25451
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24823
25452
|
} catch (e) {
|
|
24824
|
-
logger$
|
|
25453
|
+
logger$4.critical('Error while removing event listener', e);
|
|
24825
25454
|
}
|
|
24826
25455
|
}
|
|
24827
25456
|
this.eventListeners = [];
|
|
@@ -24830,7 +25459,7 @@
|
|
|
24830
25459
|
try {
|
|
24831
25460
|
this.mutationObserver.disconnect();
|
|
24832
25461
|
} catch (e) {
|
|
24833
|
-
logger$
|
|
25462
|
+
logger$4.critical('Error while disconnecting mutation observer', e);
|
|
24834
25463
|
}
|
|
24835
25464
|
this.mutationObserver = null;
|
|
24836
25465
|
}
|
|
@@ -24839,7 +25468,7 @@
|
|
|
24839
25468
|
try {
|
|
24840
25469
|
this.shadowDOMObserver.stop();
|
|
24841
25470
|
} catch (e) {
|
|
24842
|
-
logger$
|
|
25471
|
+
logger$4.critical('Error while stopping shadow DOM observer', e);
|
|
24843
25472
|
}
|
|
24844
25473
|
this.shadowDOMObserver = null;
|
|
24845
25474
|
}
|
|
@@ -24917,7 +25546,7 @@
|
|
|
24917
25546
|
|
|
24918
25547
|
Autocapture.prototype.init = function() {
|
|
24919
25548
|
if (!minDOMApisSupported()) {
|
|
24920
|
-
logger$
|
|
25549
|
+
logger$4.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24921
25550
|
return;
|
|
24922
25551
|
}
|
|
24923
25552
|
this.initPageListeners();
|
|
@@ -24949,27 +25578,15 @@
|
|
|
24949
25578
|
};
|
|
24950
25579
|
|
|
24951
25580
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24952
|
-
var i;
|
|
24953
25581
|
var currentUrl = _.info.currentUrl();
|
|
24954
25582
|
|
|
24955
25583
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24956
25584
|
if (allowUrlRegexes.length) {
|
|
24957
25585
|
// we're using an allowlist, only track if current URL matches
|
|
24958
|
-
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
24962
|
-
if (currentUrl.match(allowRegex)) {
|
|
24963
|
-
allowed = true;
|
|
24964
|
-
break;
|
|
24965
|
-
}
|
|
24966
|
-
} catch (err) {
|
|
24967
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24968
|
-
return true;
|
|
24969
|
-
}
|
|
24970
|
-
}
|
|
24971
|
-
if (!allowed) {
|
|
24972
|
-
// wasn't allowed by any regex
|
|
25586
|
+
try {
|
|
25587
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25588
|
+
} catch (err) {
|
|
25589
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
24973
25590
|
return true;
|
|
24974
25591
|
}
|
|
24975
25592
|
}
|
|
@@ -24979,17 +25596,12 @@
|
|
|
24979
25596
|
return false;
|
|
24980
25597
|
}
|
|
24981
25598
|
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
|
|
24985
|
-
|
|
24986
|
-
|
|
24987
|
-
} catch (err) {
|
|
24988
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24989
|
-
return true;
|
|
24990
|
-
}
|
|
25599
|
+
try {
|
|
25600
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25601
|
+
} catch (err) {
|
|
25602
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
25603
|
+
return true;
|
|
24991
25604
|
}
|
|
24992
|
-
return false;
|
|
24993
25605
|
};
|
|
24994
25606
|
|
|
24995
25607
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -25125,7 +25737,7 @@
|
|
|
25125
25737
|
return;
|
|
25126
25738
|
}
|
|
25127
25739
|
|
|
25128
|
-
logger$
|
|
25740
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
25129
25741
|
|
|
25130
25742
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
25131
25743
|
|
|
@@ -25151,7 +25763,7 @@
|
|
|
25151
25763
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
25152
25764
|
return;
|
|
25153
25765
|
}
|
|
25154
|
-
logger$
|
|
25766
|
+
logger$4.log('Initializing click tracking');
|
|
25155
25767
|
|
|
25156
25768
|
this.listenerClick = function(ev) {
|
|
25157
25769
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -25170,7 +25782,7 @@
|
|
|
25170
25782
|
return;
|
|
25171
25783
|
}
|
|
25172
25784
|
|
|
25173
|
-
logger$
|
|
25785
|
+
logger$4.log('Initializing dead click tracking');
|
|
25174
25786
|
if (!this._deadClickTracker) {
|
|
25175
25787
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
25176
25788
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -25204,7 +25816,7 @@
|
|
|
25204
25816
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
25205
25817
|
return;
|
|
25206
25818
|
}
|
|
25207
|
-
logger$
|
|
25819
|
+
logger$4.log('Initializing input tracking');
|
|
25208
25820
|
|
|
25209
25821
|
this.listenerChange = function(ev) {
|
|
25210
25822
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -25221,7 +25833,7 @@
|
|
|
25221
25833
|
if (!this.pageviewTrackingConfig()) {
|
|
25222
25834
|
return;
|
|
25223
25835
|
}
|
|
25224
|
-
logger$
|
|
25836
|
+
logger$4.log('Initializing pageview tracking');
|
|
25225
25837
|
|
|
25226
25838
|
var previousTrackedUrl = '';
|
|
25227
25839
|
var tracked = false;
|
|
@@ -25256,7 +25868,7 @@
|
|
|
25256
25868
|
}
|
|
25257
25869
|
if (didPathChange) {
|
|
25258
25870
|
this.lastScrollCheckpoint = 0;
|
|
25259
|
-
logger$
|
|
25871
|
+
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
25260
25872
|
}
|
|
25261
25873
|
}
|
|
25262
25874
|
}.bind(this));
|
|
@@ -25271,7 +25883,7 @@
|
|
|
25271
25883
|
return;
|
|
25272
25884
|
}
|
|
25273
25885
|
|
|
25274
|
-
logger$
|
|
25886
|
+
logger$4.log('Initializing rage click tracking');
|
|
25275
25887
|
if (!this._rageClickTracker) {
|
|
25276
25888
|
this._rageClickTracker = new RageClickTracker();
|
|
25277
25889
|
}
|
|
@@ -25301,7 +25913,7 @@
|
|
|
25301
25913
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
25302
25914
|
return;
|
|
25303
25915
|
}
|
|
25304
|
-
logger$
|
|
25916
|
+
logger$4.log('Initializing scroll tracking');
|
|
25305
25917
|
this.lastScrollCheckpoint = 0;
|
|
25306
25918
|
|
|
25307
25919
|
var scrollTrackFunction = function() {
|
|
@@ -25338,7 +25950,7 @@
|
|
|
25338
25950
|
}
|
|
25339
25951
|
}
|
|
25340
25952
|
} catch (err) {
|
|
25341
|
-
logger$
|
|
25953
|
+
logger$4.critical('Error while calculating scroll percentage', err);
|
|
25342
25954
|
}
|
|
25343
25955
|
if (shouldTrack) {
|
|
25344
25956
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -25356,7 +25968,7 @@
|
|
|
25356
25968
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
25357
25969
|
return;
|
|
25358
25970
|
}
|
|
25359
|
-
logger$
|
|
25971
|
+
logger$4.log('Initializing submit tracking');
|
|
25360
25972
|
|
|
25361
25973
|
this.listenerSubmit = function(ev) {
|
|
25362
25974
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -25378,7 +25990,7 @@
|
|
|
25378
25990
|
return;
|
|
25379
25991
|
}
|
|
25380
25992
|
|
|
25381
|
-
logger$
|
|
25993
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
25382
25994
|
this._initScrollDepthTracking();
|
|
25383
25995
|
var previousTrackedUrl = _.info.currentUrl();
|
|
25384
25996
|
|
|
@@ -25986,6 +26598,214 @@
|
|
|
25986
26598
|
/* eslint camelcase: "off" */
|
|
25987
26599
|
|
|
25988
26600
|
|
|
26601
|
+
/**
|
|
26602
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26603
|
+
* @constructor
|
|
26604
|
+
*/
|
|
26605
|
+
var RecorderManager = function(initOptions) {
|
|
26606
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26607
|
+
// but ideally we should be able to remove this dependency.
|
|
26608
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26609
|
+
|
|
26610
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26611
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26612
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26613
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26614
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26615
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26616
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26617
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26618
|
+
|
|
26619
|
+
this._recorder = null;
|
|
26620
|
+
};
|
|
26621
|
+
|
|
26622
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26623
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26624
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26625
|
+
return PromisePolyfill.resolve(false);
|
|
26626
|
+
}
|
|
26627
|
+
|
|
26628
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26629
|
+
var tab_id = this.getTabId();
|
|
26630
|
+
return recording_registry_idb.init()
|
|
26631
|
+
.then(function () {
|
|
26632
|
+
return recording_registry_idb.getAll();
|
|
26633
|
+
})
|
|
26634
|
+
.then(function (recordings) {
|
|
26635
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26636
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26637
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26638
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26639
|
+
return true;
|
|
26640
|
+
}
|
|
26641
|
+
}
|
|
26642
|
+
return false;
|
|
26643
|
+
})
|
|
26644
|
+
.catch(_.bind(function (err) {
|
|
26645
|
+
this.reportError('Error checking recording registry', err);
|
|
26646
|
+
return false;
|
|
26647
|
+
}, this));
|
|
26648
|
+
};
|
|
26649
|
+
|
|
26650
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26651
|
+
if (!win['MutationObserver']) {
|
|
26652
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26653
|
+
return PromisePolyfill.resolve();
|
|
26654
|
+
}
|
|
26655
|
+
|
|
26656
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26657
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26658
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26659
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26660
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26661
|
+
resolve();
|
|
26662
|
+
}, this));
|
|
26663
|
+
|
|
26664
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26665
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26666
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26667
|
+
} else {
|
|
26668
|
+
handleLoadedRecorder();
|
|
26669
|
+
}
|
|
26670
|
+
}, this));
|
|
26671
|
+
}, this);
|
|
26672
|
+
|
|
26673
|
+
/**
|
|
26674
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26675
|
+
* 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.
|
|
26676
|
+
*/
|
|
26677
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26678
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26679
|
+
if (force_start || is_sampled) {
|
|
26680
|
+
return loadRecorder(true);
|
|
26681
|
+
} else {
|
|
26682
|
+
return this.shouldLoadRecorder()
|
|
26683
|
+
.then(_.bind(function (shouldLoad) {
|
|
26684
|
+
if (shouldLoad) {
|
|
26685
|
+
return loadRecorder(false);
|
|
26686
|
+
}
|
|
26687
|
+
return PromisePolyfill.resolve();
|
|
26688
|
+
}, this));
|
|
26689
|
+
}
|
|
26690
|
+
};
|
|
26691
|
+
|
|
26692
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26693
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26694
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26695
|
+
return false;
|
|
26696
|
+
}
|
|
26697
|
+
try {
|
|
26698
|
+
return this._recorder['isRecording']();
|
|
26699
|
+
} catch (e) {
|
|
26700
|
+
this.reportError('Error checking if recording is active', e);
|
|
26701
|
+
return false;
|
|
26702
|
+
}
|
|
26703
|
+
};
|
|
26704
|
+
|
|
26705
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26706
|
+
var isRecording = this.isRecording();
|
|
26707
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26708
|
+
|
|
26709
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26710
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26711
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26712
|
+
var newRate = trigger['percentage'];
|
|
26713
|
+
var propertyFilters = trigger['property_filters'];
|
|
26714
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26715
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26716
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26717
|
+
.then(function(targeting) {
|
|
26718
|
+
try {
|
|
26719
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26720
|
+
event_name,
|
|
26721
|
+
properties,
|
|
26722
|
+
{
|
|
26723
|
+
'event_name': event_name,
|
|
26724
|
+
'property_filters': propertyFilters
|
|
26725
|
+
}
|
|
26726
|
+
);
|
|
26727
|
+
if (result['matches']) {
|
|
26728
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26729
|
+
}
|
|
26730
|
+
} catch (err) {
|
|
26731
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26732
|
+
}
|
|
26733
|
+
}.bind(this)).catch(function(err) {
|
|
26734
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26735
|
+
});
|
|
26736
|
+
} else {
|
|
26737
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26738
|
+
}
|
|
26739
|
+
}
|
|
26740
|
+
}
|
|
26741
|
+
};
|
|
26742
|
+
|
|
26743
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26744
|
+
if (this._recorder) {
|
|
26745
|
+
return this._recorder['stopRecording']();
|
|
26746
|
+
}
|
|
26747
|
+
return PromisePolyfill.resolve();
|
|
26748
|
+
};
|
|
26749
|
+
|
|
26750
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26751
|
+
if (this._recorder) {
|
|
26752
|
+
return this._recorder['pauseRecording']();
|
|
26753
|
+
}
|
|
26754
|
+
return PromisePolyfill.resolve();
|
|
26755
|
+
};
|
|
26756
|
+
|
|
26757
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26758
|
+
if (this._recorder) {
|
|
26759
|
+
return this._recorder['resumeRecording']();
|
|
26760
|
+
}
|
|
26761
|
+
return PromisePolyfill.resolve();
|
|
26762
|
+
};
|
|
26763
|
+
|
|
26764
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26765
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
26766
|
+
};
|
|
26767
|
+
|
|
26768
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26769
|
+
var props = {};
|
|
26770
|
+
var replay_id = this.getSessionReplayId();
|
|
26771
|
+
if (replay_id) {
|
|
26772
|
+
props['$mp_replay_id'] = replay_id;
|
|
26773
|
+
}
|
|
26774
|
+
return props;
|
|
26775
|
+
};
|
|
26776
|
+
|
|
26777
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26778
|
+
var replay_url = null;
|
|
26779
|
+
var replay_id = this.getSessionReplayId();
|
|
26780
|
+
if (replay_id) {
|
|
26781
|
+
var query_params = _.HTTPBuildQuery({
|
|
26782
|
+
'replay_id': replay_id,
|
|
26783
|
+
'distinct_id': this.getDistinctId(),
|
|
26784
|
+
'token': this.getMpConfig('token')
|
|
26785
|
+
});
|
|
26786
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26787
|
+
}
|
|
26788
|
+
return replay_url;
|
|
26789
|
+
};
|
|
26790
|
+
|
|
26791
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26792
|
+
var replay_id = null;
|
|
26793
|
+
if (this._recorder) {
|
|
26794
|
+
replay_id = this._recorder['replayId'];
|
|
26795
|
+
}
|
|
26796
|
+
return replay_id || null;
|
|
26797
|
+
};
|
|
26798
|
+
|
|
26799
|
+
// "private" public method to reach into the recorder in test cases
|
|
26800
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26801
|
+
return this._recorder;
|
|
26802
|
+
};
|
|
26803
|
+
|
|
26804
|
+
safewrapClass(RecorderManager);
|
|
26805
|
+
|
|
26806
|
+
/* eslint camelcase: "off" */
|
|
26807
|
+
|
|
26808
|
+
|
|
25989
26809
|
/**
|
|
25990
26810
|
* DomTracker Object
|
|
25991
26811
|
* @constructor
|
|
@@ -27446,13 +28266,17 @@
|
|
|
27446
28266
|
'record_collect_fonts': false,
|
|
27447
28267
|
'record_console': true,
|
|
27448
28268
|
'record_heatmap_data': false,
|
|
28269
|
+
'recording_event_triggers': {},
|
|
27449
28270
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
27450
28271
|
'record_mask_inputs': true,
|
|
27451
28272
|
'record_max_ms': MAX_RECORDING_MS,
|
|
27452
28273
|
'record_min_ms': 0,
|
|
28274
|
+
'record_network': false,
|
|
28275
|
+
'record_network_options': {},
|
|
27453
28276
|
'record_sessions_percent': 0,
|
|
27454
|
-
'recorder_src':
|
|
27455
|
-
'targeting_src':
|
|
28277
|
+
'recorder_src': null,
|
|
28278
|
+
'targeting_src': null,
|
|
28279
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
27456
28280
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
27457
28281
|
};
|
|
27458
28282
|
|
|
@@ -27606,6 +28430,19 @@
|
|
|
27606
28430
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27607
28431
|
}));
|
|
27608
28432
|
|
|
28433
|
+
this.recorderManager = new RecorderManager({
|
|
28434
|
+
mixpanelInstance: this,
|
|
28435
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28436
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28437
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28438
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28439
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28440
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28441
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28442
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28443
|
+
loadExtraBundle: load_extra_bundle
|
|
28444
|
+
});
|
|
28445
|
+
|
|
27609
28446
|
this['_jsc'] = NOOP_FUNC;
|
|
27610
28447
|
|
|
27611
28448
|
this.__dom_loaded_queue = [];
|
|
@@ -27684,7 +28521,7 @@
|
|
|
27684
28521
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27685
28522
|
trackingFunc: _.bind(this.track, this),
|
|
27686
28523
|
loadExtraBundle: load_extra_bundle,
|
|
27687
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28524
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27688
28525
|
});
|
|
27689
28526
|
this.flags.init();
|
|
27690
28527
|
this['flags'] = this.flags;
|
|
@@ -27697,11 +28534,11 @@
|
|
|
27697
28534
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27698
28535
|
var mode = this.get_config('remote_settings_mode');
|
|
27699
28536
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27700
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27701
|
-
this._check_and_start_session_recording();
|
|
28537
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28538
|
+
return this._check_and_start_session_recording();
|
|
27702
28539
|
}, this));
|
|
27703
28540
|
} else {
|
|
27704
|
-
this._check_and_start_session_recording();
|
|
28541
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27705
28542
|
}
|
|
27706
28543
|
};
|
|
27707
28544
|
|
|
@@ -27745,132 +28582,50 @@
|
|
|
27745
28582
|
return this.tab_id || null;
|
|
27746
28583
|
};
|
|
27747
28584
|
|
|
27748
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27749
|
-
if (this.get_config('disable_persistence')) {
|
|
27750
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27751
|
-
return Promise.resolve(false);
|
|
27752
|
-
}
|
|
27753
|
-
|
|
27754
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27755
|
-
var tab_id = this.get_tab_id();
|
|
27756
|
-
return recording_registry_idb.init()
|
|
27757
|
-
.then(function () {
|
|
27758
|
-
return recording_registry_idb.getAll();
|
|
27759
|
-
})
|
|
27760
|
-
.then(function (recordings) {
|
|
27761
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27762
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27763
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27764
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27765
|
-
return true;
|
|
27766
|
-
}
|
|
27767
|
-
}
|
|
27768
|
-
return false;
|
|
27769
|
-
})
|
|
27770
|
-
.catch(_.bind(function (err) {
|
|
27771
|
-
this.report_error('Error checking recording registry', err);
|
|
27772
|
-
}, this));
|
|
27773
|
-
};
|
|
27774
|
-
|
|
27775
28585
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27776
|
-
|
|
27777
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27778
|
-
return;
|
|
27779
|
-
}
|
|
27780
|
-
|
|
27781
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27782
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27783
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27784
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27785
|
-
}, this);
|
|
27786
|
-
|
|
27787
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27788
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27789
|
-
} else {
|
|
27790
|
-
handleLoadedRecorder();
|
|
27791
|
-
}
|
|
27792
|
-
}, this);
|
|
27793
|
-
|
|
27794
|
-
/**
|
|
27795
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27796
|
-
* 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.
|
|
27797
|
-
*/
|
|
27798
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27799
|
-
if (force_start || is_sampled) {
|
|
27800
|
-
loadRecorder(true);
|
|
27801
|
-
} else {
|
|
27802
|
-
this._should_load_recorder()
|
|
27803
|
-
.then(function (shouldLoad) {
|
|
27804
|
-
if (shouldLoad) {
|
|
27805
|
-
loadRecorder(false);
|
|
27806
|
-
}
|
|
27807
|
-
});
|
|
27808
|
-
}
|
|
28586
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27809
28587
|
});
|
|
27810
28588
|
|
|
28589
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28590
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28591
|
+
};
|
|
28592
|
+
|
|
27811
28593
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27812
|
-
this._check_and_start_session_recording(true);
|
|
28594
|
+
return this._check_and_start_session_recording(true);
|
|
27813
28595
|
};
|
|
27814
28596
|
|
|
27815
28597
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27816
|
-
|
|
27817
|
-
return this._recorder['stopRecording']();
|
|
27818
|
-
}
|
|
27819
|
-
return Promise.resolve();
|
|
28598
|
+
return this.recorderManager.stopSessionRecording();
|
|
27820
28599
|
};
|
|
27821
28600
|
|
|
27822
28601
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27823
|
-
|
|
27824
|
-
return this._recorder['pauseRecording']();
|
|
27825
|
-
}
|
|
27826
|
-
return Promise.resolve();
|
|
28602
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27827
28603
|
};
|
|
27828
28604
|
|
|
27829
28605
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27830
|
-
|
|
27831
|
-
return this._recorder['resumeRecording']();
|
|
27832
|
-
}
|
|
27833
|
-
return Promise.resolve();
|
|
28606
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27834
28607
|
};
|
|
27835
28608
|
|
|
27836
28609
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27837
|
-
return this.
|
|
28610
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27838
28611
|
};
|
|
27839
28612
|
|
|
27840
28613
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27841
|
-
|
|
27842
|
-
var replay_id = this._get_session_replay_id();
|
|
27843
|
-
if (replay_id) {
|
|
27844
|
-
props['$mp_replay_id'] = replay_id;
|
|
27845
|
-
}
|
|
27846
|
-
return props;
|
|
28614
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27847
28615
|
};
|
|
27848
28616
|
|
|
27849
28617
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27850
|
-
|
|
27851
|
-
var replay_id = this._get_session_replay_id();
|
|
27852
|
-
if (replay_id) {
|
|
27853
|
-
var query_params = _.HTTPBuildQuery({
|
|
27854
|
-
'replay_id': replay_id,
|
|
27855
|
-
'distinct_id': this.get_distinct_id(),
|
|
27856
|
-
'token': this.get_config('token')
|
|
27857
|
-
});
|
|
27858
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27859
|
-
}
|
|
27860
|
-
return replay_url;
|
|
27861
|
-
};
|
|
27862
|
-
|
|
27863
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27864
|
-
var replay_id = null;
|
|
27865
|
-
if (this._recorder) {
|
|
27866
|
-
replay_id = this._recorder['replayId'];
|
|
27867
|
-
}
|
|
27868
|
-
return replay_id || null;
|
|
28618
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27869
28619
|
};
|
|
27870
28620
|
|
|
27871
28621
|
// "private" public method to reach into the recorder in test cases
|
|
27872
28622
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27873
|
-
return this.
|
|
28623
|
+
return this.recorderManager.getRecorder();
|
|
28624
|
+
};
|
|
28625
|
+
|
|
28626
|
+
// "private" public method to get session recording init promise in test cases
|
|
28627
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28628
|
+
return this.__session_recording_init_promise;
|
|
27874
28629
|
};
|
|
27875
28630
|
|
|
27876
28631
|
// Private methods
|
|
@@ -28128,6 +28883,7 @@
|
|
|
28128
28883
|
};
|
|
28129
28884
|
|
|
28130
28885
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28886
|
+
var self = this;
|
|
28131
28887
|
var disableRecordingIfStrict = function() {
|
|
28132
28888
|
if (mode === 'strict') {
|
|
28133
28889
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -28148,7 +28904,6 @@
|
|
|
28148
28904
|
};
|
|
28149
28905
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
28150
28906
|
var full_url = settings_endpoint + '?' + query_string;
|
|
28151
|
-
var self = this;
|
|
28152
28907
|
|
|
28153
28908
|
var abortController = new AbortController();
|
|
28154
28909
|
var timeout_id = setTimeout(function() {
|
|
@@ -28340,6 +29095,34 @@
|
|
|
28340
29095
|
this._execute_array([item]);
|
|
28341
29096
|
};
|
|
28342
29097
|
|
|
29098
|
+
/**
|
|
29099
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29100
|
+
* this function enable tracking of all events. If passed an
|
|
29101
|
+
* array of event names, those events will be enabled, but other
|
|
29102
|
+
* existing disabled events will continue to be not tracked.
|
|
29103
|
+
*
|
|
29104
|
+
* @param {Array} [events] An array of event names to enable
|
|
29105
|
+
*/
|
|
29106
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29107
|
+
var keys, new_disabled_events, i, j;
|
|
29108
|
+
|
|
29109
|
+
if (typeof(events) === 'undefined') {
|
|
29110
|
+
this._flags.disable_all_events = false;
|
|
29111
|
+
} else {
|
|
29112
|
+
keys = {};
|
|
29113
|
+
new_disabled_events = [];
|
|
29114
|
+
for (i = 0; i < events.length; i++) {
|
|
29115
|
+
keys[events[i]] = true;
|
|
29116
|
+
}
|
|
29117
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29118
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29119
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29120
|
+
}
|
|
29121
|
+
}
|
|
29122
|
+
this.__disabled_events = new_disabled_events;
|
|
29123
|
+
}
|
|
29124
|
+
};
|
|
29125
|
+
|
|
28343
29126
|
/**
|
|
28344
29127
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
28345
29128
|
* this function disables tracking of any event. If passed an
|
|
@@ -28513,6 +29296,8 @@
|
|
|
28513
29296
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
28514
29297
|
}
|
|
28515
29298
|
|
|
29299
|
+
this._start_recording_on_event(event_name, properties);
|
|
29300
|
+
|
|
28516
29301
|
var data = {
|
|
28517
29302
|
'event': event_name,
|
|
28518
29303
|
'properties': properties
|
|
@@ -29721,6 +30506,7 @@
|
|
|
29721
30506
|
// MixpanelLib Exports
|
|
29722
30507
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29723
30508
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30509
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29724
30510
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29725
30511
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29726
30512
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29764,6 +30550,7 @@
|
|
|
29764
30550
|
|
|
29765
30551
|
// Exports intended only for testing
|
|
29766
30552
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30553
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29767
30554
|
|
|
29768
30555
|
// MixpanelPersistence Exports
|
|
29769
30556
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|