mixpanel-browser 2.75.0 → 2.77.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/dependabot.yml +8 -0
- package/.github/workflows/integration-tests.yml +4 -4
- package/.github/workflows/unit-tests.yml +4 -4
- package/CHANGELOG.md +14 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-DLKbUIEE.js +23669 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CmVvUyFM.js +2520 -0
- package/dist/mixpanel-core.cjs.d.ts +70 -1
- package/dist/mixpanel-core.cjs.js +724 -426
- package/dist/mixpanel-recorder.js +791 -41
- 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 +70 -1
- package/dist/mixpanel-with-async-modules.cjs.js +724 -426
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +724 -426
- package/dist/mixpanel-with-recorder.d.ts +70 -1
- package/dist/mixpanel-with-recorder.js +1471 -450
- package/dist/mixpanel-with-recorder.min.d.ts +70 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +70 -1
- package/dist/mixpanel.amd.js +1473 -504
- package/dist/mixpanel.cjs.d.ts +70 -1
- package/dist/mixpanel.cjs.js +1473 -504
- package/dist/mixpanel.globals.js +724 -426
- package/dist/mixpanel.min.js +189 -182
- package/dist/mixpanel.module.d.ts +70 -1
- package/dist/mixpanel.module.js +1473 -504
- package/dist/mixpanel.umd.d.ts +70 -1
- package/dist/mixpanel.umd.js +1473 -504
- package/dist/rrweb-bundled.js +61 -9
- package/dist/rrweb-compiled.js +56 -9
- package/logo.svg +5 -0
- package/package.json +6 -4
- 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 +70 -1
- package/src/mixpanel-core.js +77 -112
- 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 +36 -12
- package/src/recorder/utils.js +27 -1
- package/src/recorder-manager.js +324 -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 +69 -1
- package/src/globals.js +0 -14
- /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
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.77.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];
|
|
@@ -10730,13 +10733,7 @@ var MutationBuffer = /*#__PURE__*/ function() {
|
|
|
10730
10733
|
};
|
|
10731
10734
|
while(_this.mapRemoves.length){
|
|
10732
10735
|
var removedNode = _this.mapRemoves.shift();
|
|
10733
|
-
|
|
10734
|
-
try {
|
|
10735
|
-
_this.iframeManager.removeIframe(removedNode);
|
|
10736
|
-
} catch (e2) {}
|
|
10737
|
-
} else {
|
|
10738
|
-
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10739
|
-
}
|
|
10736
|
+
_this.cleanupRemovedNode(removedNode);
|
|
10740
10737
|
_this.mirror.removeNodeFromMap(removedNode);
|
|
10741
10738
|
}
|
|
10742
10739
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
@@ -11056,6 +11053,20 @@ var MutationBuffer = /*#__PURE__*/ function() {
|
|
|
11056
11053
|
}
|
|
11057
11054
|
}
|
|
11058
11055
|
});
|
|
11056
|
+
__publicField$1(this, "cleanupRemovedNode", function(node2) {
|
|
11057
|
+
if (node2.nodeName === "IFRAME") {
|
|
11058
|
+
try {
|
|
11059
|
+
_this.iframeManager.removeIframe(node2);
|
|
11060
|
+
} catch (e2) {}
|
|
11061
|
+
} else {
|
|
11062
|
+
try {
|
|
11063
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(node2);
|
|
11064
|
+
} catch (e2) {}
|
|
11065
|
+
}
|
|
11066
|
+
node2.childNodes.forEach(function(child) {
|
|
11067
|
+
_this.cleanupRemovedNode(child);
|
|
11068
|
+
});
|
|
11069
|
+
});
|
|
11059
11070
|
}
|
|
11060
11071
|
var _proto = MutationBuffer.prototype;
|
|
11061
11072
|
_proto.init = function init(options) {
|
|
@@ -13283,6 +13294,31 @@ var ProcessedNodeManager = /*#__PURE__*/ function() {
|
|
|
13283
13294
|
_proto.destroy = function destroy() {};
|
|
13284
13295
|
return ProcessedNodeManager;
|
|
13285
13296
|
}();
|
|
13297
|
+
function toOrigin(url) {
|
|
13298
|
+
try {
|
|
13299
|
+
var origin = new URL(url).origin;
|
|
13300
|
+
return origin !== "null" ? origin : null;
|
|
13301
|
+
} catch (e) {
|
|
13302
|
+
return null;
|
|
13303
|
+
}
|
|
13304
|
+
}
|
|
13305
|
+
function buildAllowedOriginSet(origins) {
|
|
13306
|
+
if (!Array.isArray(origins) || origins.length === 0) {
|
|
13307
|
+
throw new Error("[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.");
|
|
13308
|
+
}
|
|
13309
|
+
var set = /* @__PURE__ */ new Set();
|
|
13310
|
+
for(var i2 = 0; i2 < origins.length; i2++){
|
|
13311
|
+
var entry = origins[i2];
|
|
13312
|
+
if (typeof entry !== "string") {
|
|
13313
|
+
throw new Error("[rrweb] allowedIframeOrigins[" + i2 + "] must be a string, got " + (typeof entry === "undefined" ? "undefined" : _type_of(entry)) + ".");
|
|
13314
|
+
}
|
|
13315
|
+
var origin = toOrigin(entry);
|
|
13316
|
+
if (origin) {
|
|
13317
|
+
set.add(origin);
|
|
13318
|
+
}
|
|
13319
|
+
}
|
|
13320
|
+
return Object.freeze(set);
|
|
13321
|
+
}
|
|
13286
13322
|
var wrappedEmit;
|
|
13287
13323
|
var takeFullSnapshot$1;
|
|
13288
13324
|
var canvasManager;
|
|
@@ -13304,10 +13340,17 @@ try {
|
|
|
13304
13340
|
var mirror = createMirror$2();
|
|
13305
13341
|
function record(options) {
|
|
13306
13342
|
if (options === void 0) options = {};
|
|
13307
|
-
var emit = options.emit, checkoutEveryNms = options.checkoutEveryNms, checkoutEveryNth = options.checkoutEveryNth, _options_blockClass = options.blockClass, blockClass = _options_blockClass === void 0 ? "rr-block" : _options_blockClass, _options_blockSelector = options.blockSelector, blockSelector = _options_blockSelector === void 0 ? null : _options_blockSelector, _options_ignoreClass = options.ignoreClass, ignoreClass = _options_ignoreClass === void 0 ? "rr-ignore" : _options_ignoreClass, _options_ignoreSelector = options.ignoreSelector, ignoreSelector = _options_ignoreSelector === void 0 ? null : _options_ignoreSelector, _options_maskTextClass = options.maskTextClass, maskTextClass = _options_maskTextClass === void 0 ? "rr-mask" : _options_maskTextClass, _options_maskTextSelector = options.maskTextSelector, maskTextSelector = _options_maskTextSelector === void 0 ? null : _options_maskTextSelector, _options_inlineStylesheet = options.inlineStylesheet, inlineStylesheet = _options_inlineStylesheet === void 0 ? true : _options_inlineStylesheet, maskAllInputs = options.maskAllInputs, _maskInputOptions = options.maskInputOptions, _slimDOMOptions = options.slimDOMOptions, maskInputFn = options.maskInputFn, maskTextFn = options.maskTextFn, hooks = options.hooks, packFn = options.packFn, _options_sampling = options.sampling, sampling = _options_sampling === void 0 ? {} : _options_sampling, _options_dataURLOptions = options.dataURLOptions, dataURLOptions = _options_dataURLOptions === void 0 ? {} : _options_dataURLOptions, mousemoveWait = options.mousemoveWait, _options_recordDOM = options.recordDOM, recordDOM = _options_recordDOM === void 0 ? true : _options_recordDOM, _options_recordCanvas = options.recordCanvas, recordCanvas = _options_recordCanvas === void 0 ? false : _options_recordCanvas, _options_recordCrossOriginIframes = options.recordCrossOriginIframes, recordCrossOriginIframes = _options_recordCrossOriginIframes === void 0 ? false : _options_recordCrossOriginIframes, _options_recordAfter = options.recordAfter, recordAfter = _options_recordAfter === void 0 ? options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load" : _options_recordAfter, _options_userTriggeredOnInput = options.userTriggeredOnInput, userTriggeredOnInput = _options_userTriggeredOnInput === void 0 ? false : _options_userTriggeredOnInput, _options_collectFonts = options.collectFonts, collectFonts = _options_collectFonts === void 0 ? false : _options_collectFonts, _options_inlineImages = options.inlineImages, inlineImages = _options_inlineImages === void 0 ? false : _options_inlineImages, plugins = options.plugins, _options_keepIframeSrcFn = options.keepIframeSrcFn, keepIframeSrcFn = _options_keepIframeSrcFn === void 0 ? function() {
|
|
13343
|
+
var emit = options.emit, checkoutEveryNms = options.checkoutEveryNms, checkoutEveryNth = options.checkoutEveryNth, _options_blockClass = options.blockClass, blockClass = _options_blockClass === void 0 ? "rr-block" : _options_blockClass, _options_blockSelector = options.blockSelector, blockSelector = _options_blockSelector === void 0 ? null : _options_blockSelector, _options_ignoreClass = options.ignoreClass, ignoreClass = _options_ignoreClass === void 0 ? "rr-ignore" : _options_ignoreClass, _options_ignoreSelector = options.ignoreSelector, ignoreSelector = _options_ignoreSelector === void 0 ? null : _options_ignoreSelector, _options_maskTextClass = options.maskTextClass, maskTextClass = _options_maskTextClass === void 0 ? "rr-mask" : _options_maskTextClass, _options_maskTextSelector = options.maskTextSelector, maskTextSelector = _options_maskTextSelector === void 0 ? null : _options_maskTextSelector, _options_inlineStylesheet = options.inlineStylesheet, inlineStylesheet = _options_inlineStylesheet === void 0 ? true : _options_inlineStylesheet, maskAllInputs = options.maskAllInputs, _maskInputOptions = options.maskInputOptions, _slimDOMOptions = options.slimDOMOptions, maskInputFn = options.maskInputFn, maskTextFn = options.maskTextFn, hooks = options.hooks, packFn = options.packFn, _options_sampling = options.sampling, sampling = _options_sampling === void 0 ? {} : _options_sampling, _options_dataURLOptions = options.dataURLOptions, dataURLOptions = _options_dataURLOptions === void 0 ? {} : _options_dataURLOptions, mousemoveWait = options.mousemoveWait, _options_recordDOM = options.recordDOM, recordDOM = _options_recordDOM === void 0 ? true : _options_recordDOM, _options_recordCanvas = options.recordCanvas, recordCanvas = _options_recordCanvas === void 0 ? false : _options_recordCanvas, _options_recordCrossOriginIframes = options.recordCrossOriginIframes, recordCrossOriginIframes = _options_recordCrossOriginIframes === void 0 ? false : _options_recordCrossOriginIframes, allowedIframeOrigins = options.allowedIframeOrigins, _options_recordAfter = options.recordAfter, recordAfter = _options_recordAfter === void 0 ? options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load" : _options_recordAfter, _options_userTriggeredOnInput = options.userTriggeredOnInput, userTriggeredOnInput = _options_userTriggeredOnInput === void 0 ? false : _options_userTriggeredOnInput, _options_collectFonts = options.collectFonts, collectFonts = _options_collectFonts === void 0 ? false : _options_collectFonts, _options_inlineImages = options.inlineImages, inlineImages = _options_inlineImages === void 0 ? false : _options_inlineImages, plugins = options.plugins, _options_keepIframeSrcFn = options.keepIframeSrcFn, keepIframeSrcFn = _options_keepIframeSrcFn === void 0 ? function() {
|
|
13308
13344
|
return false;
|
|
13309
13345
|
} : _options_keepIframeSrcFn, _options_ignoreCSSAttributes = options.ignoreCSSAttributes, ignoreCSSAttributes = _options_ignoreCSSAttributes === void 0 ? /* @__PURE__ */ new Set([]) : _options_ignoreCSSAttributes, errorHandler2 = options.errorHandler;
|
|
13310
13346
|
registerErrorHandler(errorHandler2);
|
|
13347
|
+
var validatedOrigins;
|
|
13348
|
+
if (recordCrossOriginIframes && allowedIframeOrigins && allowedIframeOrigins.length > 0) {
|
|
13349
|
+
validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
|
|
13350
|
+
if (validatedOrigins.size === 0) {
|
|
13351
|
+
validatedOrigins = void 0;
|
|
13352
|
+
}
|
|
13353
|
+
}
|
|
13311
13354
|
var inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
|
|
13312
13355
|
var passEmitsToParent = false;
|
|
13313
13356
|
if (!inEmittingFrame) {
|
|
@@ -13399,7 +13442,14 @@ function record(options) {
|
|
|
13399
13442
|
origin: window.location.origin,
|
|
13400
13443
|
isCheckout: isCheckout
|
|
13401
13444
|
};
|
|
13402
|
-
|
|
13445
|
+
if (validatedOrigins) {
|
|
13446
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(validatedOrigins), _step; !(_step = _iterator()).done;){
|
|
13447
|
+
var targetOrigin = _step.value;
|
|
13448
|
+
window.parent.postMessage(message, targetOrigin);
|
|
13449
|
+
}
|
|
13450
|
+
} else {
|
|
13451
|
+
window.parent.postMessage(message, "*");
|
|
13452
|
+
}
|
|
13403
13453
|
}
|
|
13404
13454
|
if (e2.type === EventType.FullSnapshot) {
|
|
13405
13455
|
lastFullSnapshotEvent = e2;
|
|
@@ -18132,7 +18182,7 @@ var __defNormalProp = function(obj, key, value) {
|
|
|
18132
18182
|
var __publicField = function(obj, key, value) {
|
|
18133
18183
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18134
18184
|
};
|
|
18135
|
-
function patch(source, name, replacement) {
|
|
18185
|
+
function patch$3(source, name, replacement) {
|
|
18136
18186
|
try {
|
|
18137
18187
|
if (!(name in source)) {
|
|
18138
18188
|
return function() {};
|
|
@@ -18549,7 +18599,7 @@ function initLogObserver(cb, win, options) {
|
|
|
18549
18599
|
if (!_logger[level]) {
|
|
18550
18600
|
return function() {};
|
|
18551
18601
|
}
|
|
18552
|
-
return patch(_logger, level, function(original) {
|
|
18602
|
+
return patch$3(_logger, level, function(original) {
|
|
18553
18603
|
var _this1 = _this;
|
|
18554
18604
|
return function() {
|
|
18555
18605
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18970,11 +19020,6 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
|
|
|
18970
19020
|
PromisePolyfill = NpoPromise;
|
|
18971
19021
|
}
|
|
18972
19022
|
|
|
18973
|
-
var Config = {
|
|
18974
|
-
DEBUG: false,
|
|
18975
|
-
LIB_VERSION: '2.75.0'
|
|
18976
|
-
};
|
|
18977
|
-
|
|
18978
19023
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18979
19024
|
|
|
18980
19025
|
// Maximum allowed session recording length
|
|
@@ -20706,6 +20751,17 @@ var isOnline = function() {
|
|
|
20706
20751
|
|
|
20707
20752
|
var NOOP_FUNC = function () {};
|
|
20708
20753
|
|
|
20754
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20755
|
+
var matches = false;
|
|
20756
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20757
|
+
if (url.match(regexList[i])) {
|
|
20758
|
+
matches = true;
|
|
20759
|
+
break;
|
|
20760
|
+
}
|
|
20761
|
+
}
|
|
20762
|
+
return matches;
|
|
20763
|
+
};
|
|
20764
|
+
|
|
20709
20765
|
var JSONStringify = null, JSONParse = null;
|
|
20710
20766
|
if (typeof JSON !== 'undefined') {
|
|
20711
20767
|
JSONStringify = JSON.stringify;
|
|
@@ -21177,7 +21233,7 @@ function _addOptOutCheck(method, getConfigValue) {
|
|
|
21177
21233
|
};
|
|
21178
21234
|
}
|
|
21179
21235
|
|
|
21180
|
-
var logger$
|
|
21236
|
+
var logger$8 = console_with_prefix('lock');
|
|
21181
21237
|
|
|
21182
21238
|
/**
|
|
21183
21239
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21229,7 +21285,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
|
21229
21285
|
|
|
21230
21286
|
var delay = function(cb) {
|
|
21231
21287
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21232
|
-
logger$
|
|
21288
|
+
logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21233
21289
|
storage.removeItem(keyZ);
|
|
21234
21290
|
storage.removeItem(keyY);
|
|
21235
21291
|
loop();
|
|
@@ -21376,7 +21432,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
|
|
|
21376
21432
|
}, this));
|
|
21377
21433
|
};
|
|
21378
21434
|
|
|
21379
|
-
var logger$
|
|
21435
|
+
var logger$7 = console_with_prefix('batch');
|
|
21380
21436
|
|
|
21381
21437
|
/**
|
|
21382
21438
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21405,7 +21461,7 @@ var RequestQueue = function (storageKey, options) {
|
|
|
21405
21461
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21406
21462
|
});
|
|
21407
21463
|
}
|
|
21408
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21464
|
+
this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
|
|
21409
21465
|
|
|
21410
21466
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21411
21467
|
|
|
@@ -21738,7 +21794,7 @@ RequestQueue.prototype.clear = function () {
|
|
|
21738
21794
|
// maximum interval between request retries after exponential backoff
|
|
21739
21795
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21740
21796
|
|
|
21741
|
-
var logger$
|
|
21797
|
+
var logger$6 = console_with_prefix('batch');
|
|
21742
21798
|
|
|
21743
21799
|
/**
|
|
21744
21800
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21866,7 +21922,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
|
|
|
21866
21922
|
*/
|
|
21867
21923
|
RequestBatcher.prototype.flush = function(options) {
|
|
21868
21924
|
if (this.requestInProgress) {
|
|
21869
|
-
logger$
|
|
21925
|
+
logger$6.log('Flush: Request already in progress');
|
|
21870
21926
|
return PromisePolyfill.resolve();
|
|
21871
21927
|
}
|
|
21872
21928
|
|
|
@@ -22043,7 +22099,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
22043
22099
|
if (options.unloading) {
|
|
22044
22100
|
requestOptions.transport = 'sendBeacon';
|
|
22045
22101
|
}
|
|
22046
|
-
logger$
|
|
22102
|
+
logger$6.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22047
22103
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22048
22104
|
}, this))
|
|
22049
22105
|
.catch(_.bind(function(err) {
|
|
@@ -22056,7 +22112,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
22056
22112
|
* Log error to global logger and optional user-defined logger.
|
|
22057
22113
|
*/
|
|
22058
22114
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22059
|
-
logger$
|
|
22115
|
+
logger$6.error.apply(logger$6.error, arguments);
|
|
22060
22116
|
if (this.errorReporter) {
|
|
22061
22117
|
try {
|
|
22062
22118
|
if (!(err instanceof Error)) {
|
|
@@ -22064,7 +22120,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
|
|
|
22064
22120
|
}
|
|
22065
22121
|
this.errorReporter(msg, err);
|
|
22066
22122
|
} catch(err) {
|
|
22067
|
-
logger$
|
|
22123
|
+
logger$6.error(err);
|
|
22068
22124
|
}
|
|
22069
22125
|
}
|
|
22070
22126
|
};
|
|
@@ -22081,6 +22137,29 @@ var isRecordingExpired = function(serializedRecording) {
|
|
|
22081
22137
|
|
|
22082
22138
|
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
22083
22139
|
|
|
22140
|
+
var validateAllowedOrigins = function(origins, logger) {
|
|
22141
|
+
if (!_.isArray(origins)) {
|
|
22142
|
+
if (origins) {
|
|
22143
|
+
logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
|
|
22144
|
+
}
|
|
22145
|
+
return [];
|
|
22146
|
+
}
|
|
22147
|
+
var valid = [];
|
|
22148
|
+
for (var i = 0; i < origins.length; i++) {
|
|
22149
|
+
try {
|
|
22150
|
+
var origin = new URL(origins[i]).origin;
|
|
22151
|
+
if (origin === 'null') {
|
|
22152
|
+
logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
|
|
22153
|
+
continue;
|
|
22154
|
+
}
|
|
22155
|
+
valid.push(origin);
|
|
22156
|
+
} catch (e) {
|
|
22157
|
+
logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
|
|
22158
|
+
}
|
|
22159
|
+
}
|
|
22160
|
+
return valid;
|
|
22161
|
+
};
|
|
22162
|
+
|
|
22084
22163
|
// stateless utils
|
|
22085
22164
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
22086
22165
|
|
|
@@ -22186,7 +22265,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
|
|
|
22186
22265
|
|
|
22187
22266
|
var MAX_DEPTH = 5;
|
|
22188
22267
|
|
|
22189
|
-
var logger$
|
|
22268
|
+
var logger$5 = console_with_prefix('autocapture');
|
|
22190
22269
|
|
|
22191
22270
|
|
|
22192
22271
|
function getClasses(el) {
|
|
@@ -22450,7 +22529,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22450
22529
|
return false;
|
|
22451
22530
|
}
|
|
22452
22531
|
} catch (err) {
|
|
22453
|
-
logger$
|
|
22532
|
+
logger$5.critical('Error while checking element in allowElementCallback', err);
|
|
22454
22533
|
return false;
|
|
22455
22534
|
}
|
|
22456
22535
|
}
|
|
@@ -22467,7 +22546,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22467
22546
|
return true;
|
|
22468
22547
|
}
|
|
22469
22548
|
} catch (err) {
|
|
22470
|
-
logger$
|
|
22549
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22471
22550
|
}
|
|
22472
22551
|
}
|
|
22473
22552
|
return false;
|
|
@@ -22482,7 +22561,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22482
22561
|
return true;
|
|
22483
22562
|
}
|
|
22484
22563
|
} catch (err) {
|
|
22485
|
-
logger$
|
|
22564
|
+
logger$5.critical('Error while checking element in blockElementCallback', err);
|
|
22486
22565
|
return true;
|
|
22487
22566
|
}
|
|
22488
22567
|
}
|
|
@@ -22496,7 +22575,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22496
22575
|
return true;
|
|
22497
22576
|
}
|
|
22498
22577
|
} catch (err) {
|
|
22499
|
-
logger$
|
|
22578
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22500
22579
|
}
|
|
22501
22580
|
}
|
|
22502
22581
|
}
|
|
@@ -23044,177 +23123,826 @@ function shouldMaskText(element, privacyConfig) {
|
|
|
23044
23123
|
}
|
|
23045
23124
|
|
|
23046
23125
|
/**
|
|
23047
|
-
*
|
|
23126
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23127
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23128
|
+
*
|
|
23129
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23130
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23131
|
+
*
|
|
23048
23132
|
*/
|
|
23049
23133
|
|
|
23134
|
+
var logger$4 = console_with_prefix('network-plugin');
|
|
23050
23135
|
|
|
23051
|
-
|
|
23052
|
-
|
|
23053
|
-
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
|
|
23058
|
-
|
|
23059
|
-
|
|
23136
|
+
/**
|
|
23137
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23138
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23139
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23140
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23141
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23142
|
+
* @param {Window} win
|
|
23143
|
+
* @returns {number}
|
|
23144
|
+
*/
|
|
23145
|
+
function getTimeOrigin(win) {
|
|
23146
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23147
|
+
}
|
|
23060
23148
|
|
|
23061
|
-
|
|
23062
|
-
|
|
23063
|
-
|
|
23064
|
-
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
IncrementalSource.TouchMove,
|
|
23068
|
-
IncrementalSource.MediaInteraction,
|
|
23069
|
-
IncrementalSource.Drag,
|
|
23070
|
-
IncrementalSource.Selection,
|
|
23071
|
-
]);
|
|
23149
|
+
/**
|
|
23150
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23151
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23152
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23153
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23154
|
+
*/
|
|
23072
23155
|
|
|
23073
|
-
|
|
23074
|
-
|
|
23075
|
-
|
|
23156
|
+
/**
|
|
23157
|
+
* @typedef {Record<string, string>} Headers
|
|
23158
|
+
*/
|
|
23076
23159
|
|
|
23077
23160
|
/**
|
|
23078
|
-
* @typedef {
|
|
23079
|
-
* @property {number} idleExpires
|
|
23080
|
-
* @property {number} maxExpires
|
|
23081
|
-
* @property {number} replayStartTime
|
|
23082
|
-
* @property {number} lastEventTimestamp
|
|
23083
|
-
* @property {number} seqNo
|
|
23084
|
-
* @property {string} batchStartUrl
|
|
23085
|
-
* @property {string} replayId
|
|
23086
|
-
* @property {string} tabId
|
|
23087
|
-
* @property {string} replayStartUrl
|
|
23161
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23088
23162
|
*/
|
|
23089
23163
|
|
|
23090
23164
|
/**
|
|
23091
|
-
* @
|
|
23092
|
-
* @
|
|
23093
|
-
* @
|
|
23094
|
-
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23095
|
-
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23096
|
-
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23097
|
-
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23098
|
-
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23099
|
-
* optional properties for deserialization:
|
|
23100
|
-
* @property {number} idleExpires
|
|
23101
|
-
* @property {number} maxExpires
|
|
23102
|
-
* @property {number} replayStartTime
|
|
23103
|
-
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23104
|
-
* @property {number} seqNo
|
|
23105
|
-
* @property {string} batchStartUrl
|
|
23106
|
-
* @property {string} replayStartUrl
|
|
23165
|
+
* @callback networkCallback
|
|
23166
|
+
* @param {NetworkData} data
|
|
23167
|
+
* @returns {void}
|
|
23107
23168
|
*/
|
|
23108
23169
|
|
|
23109
23170
|
/**
|
|
23110
|
-
* @
|
|
23111
|
-
* @
|
|
23112
|
-
* @property {string} user_id
|
|
23113
|
-
* @property {string} device_id
|
|
23171
|
+
* @callback listenerHandler
|
|
23172
|
+
* @returns {void}
|
|
23114
23173
|
*/
|
|
23115
23174
|
|
|
23175
|
+
/**
|
|
23176
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23177
|
+
*/
|
|
23116
23178
|
|
|
23117
23179
|
/**
|
|
23118
|
-
*
|
|
23119
|
-
* @
|
|
23180
|
+
* @typedef {Object} RecordPlugin
|
|
23181
|
+
* @property {string} name
|
|
23182
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23183
|
+
* @property {NetworkRecordOptions} [options]
|
|
23120
23184
|
*/
|
|
23121
|
-
var SessionRecording = function(options) {
|
|
23122
|
-
this._mixpanel = options.mixpanelInstance;
|
|
23123
|
-
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23124
|
-
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23125
|
-
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23126
|
-
this._rrwebRecord = options.rrwebRecord || null;
|
|
23127
23185
|
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23186
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23187
|
+
var defaultNetworkOptions = {
|
|
23188
|
+
initiatorTypes: [
|
|
23189
|
+
'audio',
|
|
23190
|
+
'beacon',
|
|
23191
|
+
'body',
|
|
23192
|
+
'css',
|
|
23193
|
+
'early-hint',
|
|
23194
|
+
'embed',
|
|
23195
|
+
'fetch',
|
|
23196
|
+
'frame',
|
|
23197
|
+
'iframe',
|
|
23198
|
+
'icon',
|
|
23199
|
+
'image',
|
|
23200
|
+
'img',
|
|
23201
|
+
'input',
|
|
23202
|
+
'link',
|
|
23203
|
+
'navigation',
|
|
23204
|
+
'object',
|
|
23205
|
+
'ping',
|
|
23206
|
+
'script',
|
|
23207
|
+
'track',
|
|
23208
|
+
'video',
|
|
23209
|
+
'xmlhttprequest',
|
|
23210
|
+
],
|
|
23211
|
+
ignoreRequestFn: function() { return false; },
|
|
23212
|
+
recordHeaders: {
|
|
23213
|
+
request: [],
|
|
23214
|
+
response: [],
|
|
23215
|
+
},
|
|
23216
|
+
recordBodyUrls: {
|
|
23217
|
+
request: [],
|
|
23218
|
+
response: [],
|
|
23219
|
+
},
|
|
23220
|
+
recordInitialRequests: false,
|
|
23221
|
+
};
|
|
23131
23222
|
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
|
|
23137
|
-
|
|
23138
|
-
|
|
23223
|
+
/**
|
|
23224
|
+
* @param {PerformanceEntry} entry
|
|
23225
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23226
|
+
*/
|
|
23227
|
+
function isNavigationTiming(entry) {
|
|
23228
|
+
return entry.entryType === 'navigation';
|
|
23229
|
+
}
|
|
23139
23230
|
|
|
23140
|
-
|
|
23141
|
-
|
|
23231
|
+
/**
|
|
23232
|
+
* @param {PerformanceEntry} entry
|
|
23233
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23234
|
+
*/
|
|
23235
|
+
function isResourceTiming (entry) {
|
|
23236
|
+
return entry.entryType === 'resource';
|
|
23237
|
+
}
|
|
23142
23238
|
|
|
23143
|
-
|
|
23144
|
-
|
|
23239
|
+
function findLast(array, predicate) {
|
|
23240
|
+
var length = array.length;
|
|
23241
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23242
|
+
if (predicate(array[i])) {
|
|
23243
|
+
return array[i];
|
|
23244
|
+
}
|
|
23245
|
+
}
|
|
23246
|
+
}
|
|
23145
23247
|
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23248
|
+
/**
|
|
23249
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23250
|
+
* Adapted from Sentry's `fill` utility:
|
|
23251
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23252
|
+
*
|
|
23253
|
+
* @param {object} source - The object containing the method to patch
|
|
23254
|
+
* @param {string} name - The method name to patch
|
|
23255
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23256
|
+
* @returns {function} A function that restores the original method
|
|
23257
|
+
*/
|
|
23258
|
+
function patch(source, name, replacementFactory) {
|
|
23259
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23260
|
+
return function() {};
|
|
23261
|
+
}
|
|
23262
|
+
var original = source[name];
|
|
23263
|
+
var wrapped = replacementFactory(original);
|
|
23264
|
+
source[name] = wrapped;
|
|
23265
|
+
return function() {
|
|
23266
|
+
source[name] = original;
|
|
23267
|
+
};
|
|
23268
|
+
}
|
|
23149
23269
|
|
|
23150
|
-
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23151
|
-
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23152
|
-
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23153
|
-
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23154
|
-
errorReporter: this.reportError.bind(this),
|
|
23155
|
-
flushOnlyOnInterval: true,
|
|
23156
|
-
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23157
|
-
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23158
|
-
queueStorage: this.queueStorage,
|
|
23159
|
-
sharedLockStorage: options.sharedLockStorage,
|
|
23160
|
-
usePersistence: usePersistence,
|
|
23161
|
-
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23162
23270
|
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
sharedLockTimeoutMS: 10 * 1000,
|
|
23168
|
-
});
|
|
23169
|
-
};
|
|
23271
|
+
/**
|
|
23272
|
+
* Maximum body size to record (1MB)
|
|
23273
|
+
*/
|
|
23274
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23170
23275
|
|
|
23171
23276
|
/**
|
|
23172
|
-
*
|
|
23277
|
+
* Truncate string if it exceeds max size
|
|
23278
|
+
* @param {string} str
|
|
23279
|
+
* @returns {string}
|
|
23173
23280
|
*/
|
|
23174
|
-
|
|
23175
|
-
if (
|
|
23176
|
-
return
|
|
23281
|
+
function truncateBody(str) {
|
|
23282
|
+
if (!str || typeof str !== 'string') {
|
|
23283
|
+
return str;
|
|
23284
|
+
}
|
|
23285
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23286
|
+
logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23287
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23177
23288
|
}
|
|
23289
|
+
return str;
|
|
23290
|
+
}
|
|
23178
23291
|
|
|
23179
|
-
|
|
23180
|
-
|
|
23292
|
+
/**
|
|
23293
|
+
* @param {networkCallback} cb
|
|
23294
|
+
* @param {Window} win
|
|
23295
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23296
|
+
* @returns {listenerHandler}
|
|
23297
|
+
*/
|
|
23298
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23299
|
+
if (!win.PerformanceObserver) {
|
|
23300
|
+
logger$4.error('PerformanceObserver not supported');
|
|
23301
|
+
return function() {
|
|
23302
|
+
//
|
|
23303
|
+
};
|
|
23304
|
+
}
|
|
23305
|
+
if (options.recordInitialRequests) {
|
|
23306
|
+
var initialPerformanceEntries = win.performance
|
|
23307
|
+
.getEntries()
|
|
23308
|
+
.filter(function(entry) {
|
|
23309
|
+
return isNavigationTiming(entry) ||
|
|
23310
|
+
(isResourceTiming(entry) &&
|
|
23311
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23312
|
+
});
|
|
23313
|
+
cb({
|
|
23314
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23315
|
+
return {
|
|
23316
|
+
url: entry.name,
|
|
23317
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23318
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23319
|
+
startTime: Math.round(entry.startTime),
|
|
23320
|
+
endTime: Math.round(entry.responseEnd),
|
|
23321
|
+
timeOrigin: getTimeOrigin(win),
|
|
23322
|
+
};
|
|
23323
|
+
}),
|
|
23324
|
+
isInitial: true,
|
|
23325
|
+
});
|
|
23326
|
+
}
|
|
23327
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23328
|
+
var performanceEntries = entries
|
|
23329
|
+
.getEntries()
|
|
23330
|
+
.filter(function(entry) {
|
|
23331
|
+
return isResourceTiming(entry) &&
|
|
23332
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23333
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23334
|
+
entry.initiatorType !== 'fetch';
|
|
23335
|
+
});
|
|
23336
|
+
cb({
|
|
23337
|
+
requests: performanceEntries.map(function(entry) {
|
|
23338
|
+
return {
|
|
23339
|
+
url: entry.name,
|
|
23340
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23341
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23342
|
+
startTime: Math.round(entry.startTime),
|
|
23343
|
+
endTime: Math.round(entry.responseEnd),
|
|
23344
|
+
timeOrigin: getTimeOrigin(win),
|
|
23345
|
+
};
|
|
23346
|
+
}),
|
|
23347
|
+
});
|
|
23348
|
+
});
|
|
23349
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23350
|
+
return function() {
|
|
23351
|
+
observer.disconnect();
|
|
23181
23352
|
};
|
|
23353
|
+
}
|
|
23182
23354
|
|
|
23183
|
-
|
|
23184
|
-
|
|
23185
|
-
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23355
|
+
/**
|
|
23356
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23357
|
+
* @param {'request' | 'response'} type
|
|
23358
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23359
|
+
* @param {string} headerName
|
|
23360
|
+
* @returns {boolean}
|
|
23361
|
+
*/
|
|
23362
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23363
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23364
|
+
return false;
|
|
23191
23365
|
}
|
|
23192
|
-
return userIdInfo;
|
|
23193
|
-
};
|
|
23194
23366
|
|
|
23195
|
-
|
|
23196
|
-
|
|
23367
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23368
|
+
}
|
|
23197
23369
|
|
|
23198
|
-
|
|
23199
|
-
|
|
23200
|
-
|
|
23201
|
-
|
|
23202
|
-
|
|
23203
|
-
|
|
23204
|
-
|
|
23370
|
+
/**
|
|
23371
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23372
|
+
* @param {'request' | 'response'} type
|
|
23373
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23374
|
+
* @param {string} url
|
|
23375
|
+
* @returns {boolean}
|
|
23376
|
+
*/
|
|
23377
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23378
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23379
|
+
return false;
|
|
23380
|
+
}
|
|
23205
23381
|
|
|
23206
|
-
|
|
23207
|
-
|
|
23208
|
-
return this.queueStorage.removeItem(this.batcherKey);
|
|
23209
|
-
}.bind(this));
|
|
23210
|
-
}.bind(this));
|
|
23211
|
-
};
|
|
23382
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23383
|
+
}
|
|
23212
23384
|
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23385
|
+
function tryReadXHRBody(body) {
|
|
23386
|
+
if (body === null || body === undefined) {
|
|
23387
|
+
return null;
|
|
23388
|
+
}
|
|
23216
23389
|
|
|
23217
|
-
|
|
23390
|
+
var result;
|
|
23391
|
+
if (typeof body === 'string') {
|
|
23392
|
+
result = body;
|
|
23393
|
+
} else if (body instanceof Document) {
|
|
23394
|
+
result = body.textContent;
|
|
23395
|
+
} else if (body instanceof FormData) {
|
|
23396
|
+
result = _.HTTPBuildQuery(body);
|
|
23397
|
+
} else if (_.isObject(body)) {
|
|
23398
|
+
try {
|
|
23399
|
+
result = JSON.stringify(body);
|
|
23400
|
+
} catch (e) {
|
|
23401
|
+
return 'Failed to stringify response object';
|
|
23402
|
+
}
|
|
23403
|
+
} else {
|
|
23404
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23405
|
+
}
|
|
23406
|
+
|
|
23407
|
+
return truncateBody(result);
|
|
23408
|
+
}
|
|
23409
|
+
|
|
23410
|
+
/**
|
|
23411
|
+
* @param {Request | Response} r
|
|
23412
|
+
* @returns {Promise<string>}
|
|
23413
|
+
*/
|
|
23414
|
+
function tryReadFetchBody(r) {
|
|
23415
|
+
return new Promise(function(resolve) {
|
|
23416
|
+
var timeout = setTimeout(function() {
|
|
23417
|
+
resolve('Timeout while trying to read body');
|
|
23418
|
+
}, 500);
|
|
23419
|
+
try {
|
|
23420
|
+
r.clone()
|
|
23421
|
+
.text()
|
|
23422
|
+
.then(
|
|
23423
|
+
function(txt) {
|
|
23424
|
+
clearTimeout(timeout);
|
|
23425
|
+
resolve(truncateBody(txt));
|
|
23426
|
+
},
|
|
23427
|
+
function(reason) {
|
|
23428
|
+
clearTimeout(timeout);
|
|
23429
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23430
|
+
}
|
|
23431
|
+
);
|
|
23432
|
+
} catch (e) {
|
|
23433
|
+
clearTimeout(timeout);
|
|
23434
|
+
resolve('Failed to read body: ' + String(e));
|
|
23435
|
+
}
|
|
23436
|
+
});
|
|
23437
|
+
}
|
|
23438
|
+
|
|
23439
|
+
/**
|
|
23440
|
+
* @param {Window} win
|
|
23441
|
+
* @param {string} initiatorType
|
|
23442
|
+
* @param {string} url
|
|
23443
|
+
* @param {number} [after]
|
|
23444
|
+
* @param {number} [before]
|
|
23445
|
+
* @param {number} [attempt]
|
|
23446
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23447
|
+
*/
|
|
23448
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23449
|
+
if (attempt === undefined) {
|
|
23450
|
+
attempt = 0;
|
|
23451
|
+
}
|
|
23452
|
+
if (attempt > 10) {
|
|
23453
|
+
logger$4.error('Cannot find performance entry');
|
|
23454
|
+
return Promise.resolve(null);
|
|
23455
|
+
}
|
|
23456
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23457
|
+
win.performance.getEntriesByName(url)
|
|
23458
|
+
);
|
|
23459
|
+
var performanceEntry = findLast(
|
|
23460
|
+
urlPerformanceEntries,
|
|
23461
|
+
function(entry) {
|
|
23462
|
+
return isResourceTiming(entry) &&
|
|
23463
|
+
entry.initiatorType === initiatorType &&
|
|
23464
|
+
(!after || entry.startTime >= after) &&
|
|
23465
|
+
(!before || entry.startTime <= before);
|
|
23466
|
+
}
|
|
23467
|
+
);
|
|
23468
|
+
if (!performanceEntry) {
|
|
23469
|
+
return new Promise(function(resolve) {
|
|
23470
|
+
setTimeout(resolve, 50 * attempt);
|
|
23471
|
+
}).then(function() {
|
|
23472
|
+
return getRequestPerformanceEntry(
|
|
23473
|
+
win,
|
|
23474
|
+
initiatorType,
|
|
23475
|
+
url,
|
|
23476
|
+
after,
|
|
23477
|
+
before,
|
|
23478
|
+
attempt + 1
|
|
23479
|
+
);
|
|
23480
|
+
});
|
|
23481
|
+
}
|
|
23482
|
+
return Promise.resolve(performanceEntry);
|
|
23483
|
+
}
|
|
23484
|
+
|
|
23485
|
+
/**
|
|
23486
|
+
* @param {networkCallback} cb
|
|
23487
|
+
* @param {Window} win
|
|
23488
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23489
|
+
* @returns {listenerHandler}
|
|
23490
|
+
*/
|
|
23491
|
+
function initXhrObserver(cb, win, options) {
|
|
23492
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23493
|
+
return function() {
|
|
23494
|
+
//
|
|
23495
|
+
};
|
|
23496
|
+
}
|
|
23497
|
+
var restorePatch = patch(
|
|
23498
|
+
win.XMLHttpRequest.prototype,
|
|
23499
|
+
'open',
|
|
23500
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23501
|
+
return function(
|
|
23502
|
+
/** @type {string} */ method,
|
|
23503
|
+
/** @type {string | URL} */ url,
|
|
23504
|
+
/** @type {boolean} */ async,
|
|
23505
|
+
username, password
|
|
23506
|
+
) {
|
|
23507
|
+
if (async === undefined) {
|
|
23508
|
+
async = true;
|
|
23509
|
+
}
|
|
23510
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23511
|
+
var req = new Request(url, { method: method });
|
|
23512
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23513
|
+
var networkRequest = {};
|
|
23514
|
+
/** @type {number | undefined} */
|
|
23515
|
+
var after;
|
|
23516
|
+
/** @type {number | undefined} */
|
|
23517
|
+
var before;
|
|
23518
|
+
|
|
23519
|
+
/** @type {Headers} */
|
|
23520
|
+
var requestHeaders = {};
|
|
23521
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23522
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23523
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23524
|
+
requestHeaders[header] = value;
|
|
23525
|
+
}
|
|
23526
|
+
return originalSetRequestHeader(header, value);
|
|
23527
|
+
};
|
|
23528
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23529
|
+
|
|
23530
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23531
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23532
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23533
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23534
|
+
}
|
|
23535
|
+
after = win.performance.now();
|
|
23536
|
+
return originalSend(body);
|
|
23537
|
+
};
|
|
23538
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23539
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23540
|
+
return;
|
|
23541
|
+
}
|
|
23542
|
+
before = win.performance.now();
|
|
23543
|
+
/** @type {Headers} */
|
|
23544
|
+
var responseHeaders = {};
|
|
23545
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23546
|
+
if (rawHeaders) {
|
|
23547
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23548
|
+
headers.forEach(function(line) {
|
|
23549
|
+
if (!line) return;
|
|
23550
|
+
var colonIndex = line.indexOf(': ');
|
|
23551
|
+
if (colonIndex === -1) return;
|
|
23552
|
+
var header = line.substring(0, colonIndex);
|
|
23553
|
+
var value = line.substring(colonIndex + 2);
|
|
23554
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23555
|
+
responseHeaders[header] = value;
|
|
23556
|
+
}
|
|
23557
|
+
});
|
|
23558
|
+
}
|
|
23559
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23560
|
+
if (
|
|
23561
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23562
|
+
) {
|
|
23563
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23564
|
+
}
|
|
23565
|
+
getRequestPerformanceEntry(
|
|
23566
|
+
win,
|
|
23567
|
+
'xmlhttprequest',
|
|
23568
|
+
req.url,
|
|
23569
|
+
after,
|
|
23570
|
+
before
|
|
23571
|
+
)
|
|
23572
|
+
.then(function(entry) {
|
|
23573
|
+
if (!entry) {
|
|
23574
|
+
logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23575
|
+
return;
|
|
23576
|
+
}
|
|
23577
|
+
/** @type {NetworkRequest} */
|
|
23578
|
+
var request = {
|
|
23579
|
+
url: entry.name,
|
|
23580
|
+
method: req.method,
|
|
23581
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23582
|
+
status: xhr.status,
|
|
23583
|
+
startTime: Math.round(entry.startTime),
|
|
23584
|
+
endTime: Math.round(entry.responseEnd),
|
|
23585
|
+
timeOrigin: getTimeOrigin(win),
|
|
23586
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23587
|
+
requestBody: networkRequest.requestBody,
|
|
23588
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23589
|
+
responseBody: networkRequest.responseBody,
|
|
23590
|
+
};
|
|
23591
|
+
cb({ requests: [request] });
|
|
23592
|
+
})
|
|
23593
|
+
.catch(function(e) {
|
|
23594
|
+
logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23595
|
+
});
|
|
23596
|
+
});
|
|
23597
|
+
|
|
23598
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23599
|
+
};
|
|
23600
|
+
}
|
|
23601
|
+
);
|
|
23602
|
+
return function() {
|
|
23603
|
+
restorePatch();
|
|
23604
|
+
};
|
|
23605
|
+
}
|
|
23606
|
+
|
|
23607
|
+
/**
|
|
23608
|
+
* @param {networkCallback} cb
|
|
23609
|
+
* @param {Window} win
|
|
23610
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23611
|
+
* @returns {listenerHandler}
|
|
23612
|
+
*/
|
|
23613
|
+
function initFetchObserver(cb, win, options) {
|
|
23614
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23615
|
+
return function() {
|
|
23616
|
+
//
|
|
23617
|
+
};
|
|
23618
|
+
}
|
|
23619
|
+
|
|
23620
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23621
|
+
return function() {
|
|
23622
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23623
|
+
/** @type {Response | undefined} */
|
|
23624
|
+
var res;
|
|
23625
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23626
|
+
var networkRequest = {};
|
|
23627
|
+
/** @type {number | undefined} */
|
|
23628
|
+
var after;
|
|
23629
|
+
/** @type {number | undefined} */
|
|
23630
|
+
var before;
|
|
23631
|
+
|
|
23632
|
+
var originalFetchPromise;
|
|
23633
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23634
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23635
|
+
try {
|
|
23636
|
+
/** @type {Headers} */
|
|
23637
|
+
var requestHeaders = {};
|
|
23638
|
+
req.headers.forEach(function(value, header) {
|
|
23639
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23640
|
+
requestHeaders[header] = value;
|
|
23641
|
+
}
|
|
23642
|
+
});
|
|
23643
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23644
|
+
|
|
23645
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23646
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23647
|
+
.then(function(body) {
|
|
23648
|
+
networkRequest.requestBody = body;
|
|
23649
|
+
});
|
|
23650
|
+
}
|
|
23651
|
+
|
|
23652
|
+
after = win.performance.now();
|
|
23653
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23654
|
+
res = response;
|
|
23655
|
+
before = win.performance.now();
|
|
23656
|
+
|
|
23657
|
+
/** @type {Headers} */
|
|
23658
|
+
var responseHeaders = {};
|
|
23659
|
+
res.headers.forEach(function(value, header) {
|
|
23660
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23661
|
+
responseHeaders[header] = value;
|
|
23662
|
+
}
|
|
23663
|
+
});
|
|
23664
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23665
|
+
|
|
23666
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23667
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23668
|
+
.then(function(body) {
|
|
23669
|
+
networkRequest.responseBody = body;
|
|
23670
|
+
});
|
|
23671
|
+
}
|
|
23672
|
+
|
|
23673
|
+
return res;
|
|
23674
|
+
});
|
|
23675
|
+
} catch (e) {
|
|
23676
|
+
originalFetchPromise = Promise.reject(e);
|
|
23677
|
+
}
|
|
23678
|
+
|
|
23679
|
+
// await concurrently so we don't delay the fetch response
|
|
23680
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23681
|
+
.then(function () {
|
|
23682
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23683
|
+
})
|
|
23684
|
+
.then(function(entry) {
|
|
23685
|
+
if (!entry) {
|
|
23686
|
+
logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23687
|
+
return;
|
|
23688
|
+
}
|
|
23689
|
+
/** @type {NetworkRequest} */
|
|
23690
|
+
var request = {
|
|
23691
|
+
url: entry.name,
|
|
23692
|
+
method: req.method,
|
|
23693
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23694
|
+
status: res ? res.status : undefined,
|
|
23695
|
+
startTime: Math.round(entry.startTime),
|
|
23696
|
+
endTime: Math.round(entry.responseEnd),
|
|
23697
|
+
timeOrigin: getTimeOrigin(win),
|
|
23698
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23699
|
+
requestBody: networkRequest.requestBody,
|
|
23700
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23701
|
+
responseBody: networkRequest.responseBody,
|
|
23702
|
+
};
|
|
23703
|
+
cb({ requests: [request] });
|
|
23704
|
+
})
|
|
23705
|
+
.catch(function (e) {
|
|
23706
|
+
logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23707
|
+
});
|
|
23708
|
+
|
|
23709
|
+
return originalFetchPromise;
|
|
23710
|
+
};
|
|
23711
|
+
});
|
|
23712
|
+
return function() {
|
|
23713
|
+
restorePatch();
|
|
23714
|
+
};
|
|
23715
|
+
}
|
|
23716
|
+
|
|
23717
|
+
/**
|
|
23718
|
+
* @param {networkCallback} callback
|
|
23719
|
+
* @param {Window} win
|
|
23720
|
+
* @param {NetworkRecordOptions} options
|
|
23721
|
+
* @returns {listenerHandler}
|
|
23722
|
+
*/
|
|
23723
|
+
function initNetworkObserver(callback, win, options) {
|
|
23724
|
+
if (!('performance' in win)) {
|
|
23725
|
+
return function() {
|
|
23726
|
+
//
|
|
23727
|
+
};
|
|
23728
|
+
}
|
|
23729
|
+
|
|
23730
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23731
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23732
|
+
options = Object.assign({}, options, {
|
|
23733
|
+
recordHeaders: recordHeaders,
|
|
23734
|
+
recordBodyUrls: recordBodyUrls,
|
|
23735
|
+
});
|
|
23736
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23737
|
+
|
|
23738
|
+
/** @type {networkCallback} */
|
|
23739
|
+
var cb = function(data) {
|
|
23740
|
+
var requests = data.requests.filter(function(request) {
|
|
23741
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23742
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23743
|
+
});
|
|
23744
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23745
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23746
|
+
}
|
|
23747
|
+
};
|
|
23748
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23749
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23750
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23751
|
+
return function() {
|
|
23752
|
+
performanceObserver();
|
|
23753
|
+
xhrObserver();
|
|
23754
|
+
fetchObserver();
|
|
23755
|
+
};
|
|
23756
|
+
}
|
|
23757
|
+
|
|
23758
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23759
|
+
// a changed format in the mixpanel product.
|
|
23760
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23761
|
+
|
|
23762
|
+
/**
|
|
23763
|
+
* @param {NetworkRecordOptions} [options]
|
|
23764
|
+
* @returns {RecordPlugin}
|
|
23765
|
+
*/
|
|
23766
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23767
|
+
return {
|
|
23768
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23769
|
+
observer: initNetworkObserver,
|
|
23770
|
+
options: options,
|
|
23771
|
+
};
|
|
23772
|
+
};
|
|
23773
|
+
|
|
23774
|
+
/**
|
|
23775
|
+
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23776
|
+
*/
|
|
23777
|
+
|
|
23778
|
+
|
|
23779
|
+
var logger$3 = console_with_prefix('recorder');
|
|
23780
|
+
var CompressionStream = win['CompressionStream'];
|
|
23781
|
+
|
|
23782
|
+
var RECORDER_BATCHER_LIB_CONFIG = {
|
|
23783
|
+
'batch_size': 1000,
|
|
23784
|
+
'batch_flush_interval_ms': 10 * 1000,
|
|
23785
|
+
'batch_request_timeout_ms': 90 * 1000,
|
|
23786
|
+
'batch_autostart': true
|
|
23787
|
+
};
|
|
23788
|
+
|
|
23789
|
+
var ACTIVE_SOURCES = new Set([
|
|
23790
|
+
IncrementalSource.MouseMove,
|
|
23791
|
+
IncrementalSource.MouseInteraction,
|
|
23792
|
+
IncrementalSource.Scroll,
|
|
23793
|
+
IncrementalSource.ViewportResize,
|
|
23794
|
+
IncrementalSource.Input,
|
|
23795
|
+
IncrementalSource.TouchMove,
|
|
23796
|
+
IncrementalSource.MediaInteraction,
|
|
23797
|
+
IncrementalSource.Drag,
|
|
23798
|
+
IncrementalSource.Selection,
|
|
23799
|
+
]);
|
|
23800
|
+
|
|
23801
|
+
function isUserEvent(ev) {
|
|
23802
|
+
return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
|
|
23803
|
+
}
|
|
23804
|
+
|
|
23805
|
+
/**
|
|
23806
|
+
* @typedef {Object} SerializedRecording
|
|
23807
|
+
* @property {number} idleExpires
|
|
23808
|
+
* @property {number} maxExpires
|
|
23809
|
+
* @property {number} replayStartTime
|
|
23810
|
+
* @property {number} lastEventTimestamp
|
|
23811
|
+
* @property {number} seqNo
|
|
23812
|
+
* @property {string} batchStartUrl
|
|
23813
|
+
* @property {string} replayId
|
|
23814
|
+
* @property {string} tabId
|
|
23815
|
+
* @property {string} replayStartUrl
|
|
23816
|
+
*/
|
|
23817
|
+
|
|
23818
|
+
/**
|
|
23819
|
+
* @typedef {Object} SessionRecordingOptions
|
|
23820
|
+
* @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
23821
|
+
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
23822
|
+
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23823
|
+
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23824
|
+
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23825
|
+
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23826
|
+
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23827
|
+
* optional properties for deserialization:
|
|
23828
|
+
* @property {number} idleExpires
|
|
23829
|
+
* @property {number} maxExpires
|
|
23830
|
+
* @property {number} replayStartTime
|
|
23831
|
+
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23832
|
+
* @property {number} seqNo
|
|
23833
|
+
* @property {string} batchStartUrl
|
|
23834
|
+
* @property {string} replayStartUrl
|
|
23835
|
+
*/
|
|
23836
|
+
|
|
23837
|
+
/**
|
|
23838
|
+
* @typedef {Object} UserIdInfo
|
|
23839
|
+
* @property {string} distinct_id
|
|
23840
|
+
* @property {string} user_id
|
|
23841
|
+
* @property {string} device_id
|
|
23842
|
+
*/
|
|
23843
|
+
|
|
23844
|
+
|
|
23845
|
+
/**
|
|
23846
|
+
* This class encapsulates a single session recording and its lifecycle.
|
|
23847
|
+
* @param {SessionRecordingOptions} options
|
|
23848
|
+
*/
|
|
23849
|
+
var SessionRecording = function(options) {
|
|
23850
|
+
this._mixpanel = options.mixpanelInstance;
|
|
23851
|
+
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23852
|
+
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23853
|
+
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23854
|
+
this._rrwebRecord = options.rrwebRecord || null;
|
|
23855
|
+
|
|
23856
|
+
// internal rrweb stopRecording function
|
|
23857
|
+
this._stopRecording = null;
|
|
23858
|
+
this.replayId = options.replayId;
|
|
23859
|
+
|
|
23860
|
+
this.batchStartUrl = options.batchStartUrl || null;
|
|
23861
|
+
this.replayStartUrl = options.replayStartUrl || null;
|
|
23862
|
+
this.idleExpires = options.idleExpires || null;
|
|
23863
|
+
this.maxExpires = options.maxExpires || null;
|
|
23864
|
+
this.replayStartTime = options.replayStartTime || null;
|
|
23865
|
+
this.lastEventTimestamp = options.lastEventTimestamp || null;
|
|
23866
|
+
this.seqNo = options.seqNo || 0;
|
|
23867
|
+
|
|
23868
|
+
this.idleTimeoutId = null;
|
|
23869
|
+
this.maxTimeoutId = null;
|
|
23870
|
+
|
|
23871
|
+
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23872
|
+
this.recordMinMs = 0;
|
|
23873
|
+
|
|
23874
|
+
// disable persistence if localStorage is not supported
|
|
23875
|
+
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
23876
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
|
|
23877
|
+
|
|
23878
|
+
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23879
|
+
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23880
|
+
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23881
|
+
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23882
|
+
errorReporter: this.reportError.bind(this),
|
|
23883
|
+
flushOnlyOnInterval: true,
|
|
23884
|
+
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23885
|
+
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23886
|
+
queueStorage: this.queueStorage,
|
|
23887
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
23888
|
+
usePersistence: usePersistence,
|
|
23889
|
+
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23890
|
+
|
|
23891
|
+
// increased throttle and shared lock timeout because recording events are very high frequency.
|
|
23892
|
+
// this will minimize the amount of lock contention between enqueued events.
|
|
23893
|
+
// for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
|
|
23894
|
+
enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
|
|
23895
|
+
sharedLockTimeoutMS: 10 * 1000,
|
|
23896
|
+
});
|
|
23897
|
+
};
|
|
23898
|
+
|
|
23899
|
+
/**
|
|
23900
|
+
* @returns {UserIdInfo}
|
|
23901
|
+
*/
|
|
23902
|
+
SessionRecording.prototype.getUserIdInfo = function () {
|
|
23903
|
+
if (this.finalFlushUserIdInfo) {
|
|
23904
|
+
return this.finalFlushUserIdInfo;
|
|
23905
|
+
}
|
|
23906
|
+
|
|
23907
|
+
var userIdInfo = {
|
|
23908
|
+
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
23909
|
+
};
|
|
23910
|
+
|
|
23911
|
+
// send ID management props if they exist
|
|
23912
|
+
var deviceId = this._mixpanel.get_property('$device_id');
|
|
23913
|
+
if (deviceId) {
|
|
23914
|
+
userIdInfo['$device_id'] = deviceId;
|
|
23915
|
+
}
|
|
23916
|
+
var userId = this._mixpanel.get_property('$user_id');
|
|
23917
|
+
if (userId) {
|
|
23918
|
+
userIdInfo['$user_id'] = userId;
|
|
23919
|
+
}
|
|
23920
|
+
return userIdInfo;
|
|
23921
|
+
};
|
|
23922
|
+
|
|
23923
|
+
SessionRecording.prototype.unloadPersistedData = function () {
|
|
23924
|
+
this.batcher.stop();
|
|
23925
|
+
|
|
23926
|
+
return this.queueStorage.init().catch(function () {
|
|
23927
|
+
this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
|
|
23928
|
+
}.bind(this)).then(function () {
|
|
23929
|
+
// if the recording is too short, just delete any stored events without flushing
|
|
23930
|
+
if (this.getDurationMs() < this._getRecordMinMs()) {
|
|
23931
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23932
|
+
}
|
|
23933
|
+
|
|
23934
|
+
return this.batcher.flush()
|
|
23935
|
+
.then(function () {
|
|
23936
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23937
|
+
}.bind(this));
|
|
23938
|
+
}.bind(this));
|
|
23939
|
+
};
|
|
23940
|
+
|
|
23941
|
+
SessionRecording.prototype.getConfig = function(configVar) {
|
|
23942
|
+
return this._mixpanel.get_config(configVar);
|
|
23943
|
+
};
|
|
23944
|
+
|
|
23945
|
+
// Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
|
|
23218
23946
|
// reaches into this class instance and expects the snake case version of the function.
|
|
23219
23947
|
// eslint-disable-next-line camelcase
|
|
23220
23948
|
SessionRecording.prototype.get_config = function(configVar) {
|
|
@@ -23228,14 +23956,14 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23228
23956
|
}
|
|
23229
23957
|
|
|
23230
23958
|
if (this._stopRecording !== null) {
|
|
23231
|
-
logger$
|
|
23959
|
+
logger$3.log('Recording already in progress, skipping startRecording.');
|
|
23232
23960
|
return;
|
|
23233
23961
|
}
|
|
23234
23962
|
|
|
23235
23963
|
this.recordMaxMs = this.getConfig('record_max_ms');
|
|
23236
23964
|
if (this.recordMaxMs > MAX_RECORDING_MS) {
|
|
23237
23965
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23238
|
-
logger$
|
|
23966
|
+
logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
23239
23967
|
}
|
|
23240
23968
|
|
|
23241
23969
|
if (!this.maxExpires) {
|
|
@@ -23276,6 +24004,31 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23276
24004
|
|
|
23277
24005
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23278
24006
|
|
|
24007
|
+
var plugins = [];
|
|
24008
|
+
if (this.getConfig('record_network')) {
|
|
24009
|
+
var options = this.getConfig('record_network_options') || {};
|
|
24010
|
+
// don't track requests to Mixpanel /record API
|
|
24011
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
24012
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
24013
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
24014
|
+
|
|
24015
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
24016
|
+
}
|
|
24017
|
+
|
|
24018
|
+
if (this.getConfig('record_console')) {
|
|
24019
|
+
plugins.push(
|
|
24020
|
+
getRecordConsolePlugin({
|
|
24021
|
+
stringifyOptions: {
|
|
24022
|
+
stringLengthLimit: 1000,
|
|
24023
|
+
numOfKeysLimit: 50,
|
|
24024
|
+
depthOfLimit: 2
|
|
24025
|
+
}
|
|
24026
|
+
})
|
|
24027
|
+
);
|
|
24028
|
+
}
|
|
24029
|
+
|
|
24030
|
+
var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
|
|
24031
|
+
|
|
23279
24032
|
try {
|
|
23280
24033
|
this._stopRecording = this._rrwebRecord({
|
|
23281
24034
|
'emit': function (ev) {
|
|
@@ -23310,19 +24063,13 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23310
24063
|
'maskTextSelector': '*',
|
|
23311
24064
|
'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
|
|
23312
24065
|
'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
|
|
24066
|
+
'recordCrossOriginIframes': validatedOrigins.length > 0,
|
|
24067
|
+
'allowedIframeOrigins': validatedOrigins,
|
|
23313
24068
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
23314
24069
|
'sampling': {
|
|
23315
24070
|
'canvas': 15
|
|
23316
24071
|
},
|
|
23317
|
-
'plugins':
|
|
23318
|
-
getRecordConsolePlugin({
|
|
23319
|
-
stringifyOptions: {
|
|
23320
|
-
stringLengthLimit: 1000,
|
|
23321
|
-
numOfKeysLimit: 50,
|
|
23322
|
-
depthOfLimit: 2
|
|
23323
|
-
}
|
|
23324
|
-
})
|
|
23325
|
-
] : []
|
|
24072
|
+
'plugins': plugins,
|
|
23326
24073
|
});
|
|
23327
24074
|
} catch (err) {
|
|
23328
24075
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23437,6 +24184,10 @@ SessionRecording.deserialize = function (serializedRecording, options) {
|
|
|
23437
24184
|
return recording;
|
|
23438
24185
|
};
|
|
23439
24186
|
|
|
24187
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24188
|
+
return this.getConfig('api_routes')['record'];
|
|
24189
|
+
};
|
|
24190
|
+
|
|
23440
24191
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23441
24192
|
var onSuccess = function (response, responseBody) {
|
|
23442
24193
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23456,7 +24207,7 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
|
|
|
23456
24207
|
});
|
|
23457
24208
|
}.bind(this);
|
|
23458
24209
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23459
|
-
win['fetch'](apiHost + '/' + this.
|
|
24210
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23460
24211
|
'method': 'POST',
|
|
23461
24212
|
'headers': {
|
|
23462
24213
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23538,14 +24289,14 @@ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
|
|
|
23538
24289
|
|
|
23539
24290
|
|
|
23540
24291
|
SessionRecording.prototype.reportError = function(msg, err) {
|
|
23541
|
-
logger$
|
|
24292
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
23542
24293
|
try {
|
|
23543
24294
|
if (!err && !(msg instanceof Error)) {
|
|
23544
24295
|
msg = new Error(msg);
|
|
23545
24296
|
}
|
|
23546
24297
|
this.getConfig('error_reporter')(msg, err);
|
|
23547
24298
|
} catch(err) {
|
|
23548
|
-
logger$
|
|
24299
|
+
logger$3.error(err);
|
|
23549
24300
|
}
|
|
23550
24301
|
};
|
|
23551
24302
|
|
|
@@ -23574,7 +24325,7 @@ SessionRecording.prototype._getRecordMinMs = function() {
|
|
|
23574
24325
|
var configValue = this.getConfig('record_min_ms');
|
|
23575
24326
|
|
|
23576
24327
|
if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
23577
|
-
logger$
|
|
24328
|
+
logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
23578
24329
|
return MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
23579
24330
|
}
|
|
23580
24331
|
|
|
@@ -23737,7 +24488,7 @@ RecordingRegistry.prototype.flushInactiveRecordings = function () {
|
|
|
23737
24488
|
.catch(this.handleError.bind(this));
|
|
23738
24489
|
};
|
|
23739
24490
|
|
|
23740
|
-
var logger$
|
|
24491
|
+
var logger$2 = console_with_prefix('recorder');
|
|
23741
24492
|
|
|
23742
24493
|
/**
|
|
23743
24494
|
* Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
|
|
@@ -23753,7 +24504,7 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
|
|
|
23753
24504
|
*/
|
|
23754
24505
|
this.recordingRegistry = new RecordingRegistry({
|
|
23755
24506
|
mixpanelInstance: this.mixpanelInstance,
|
|
23756
|
-
errorReporter: logger$
|
|
24507
|
+
errorReporter: logger$2.error,
|
|
23757
24508
|
sharedLockStorage: sharedLockStorage
|
|
23758
24509
|
});
|
|
23759
24510
|
this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
|
|
@@ -23765,17 +24516,17 @@ var MixpanelRecorder = function(mixpanelInstance, rrwebRecord, sharedLockStorage
|
|
|
23765
24516
|
MixpanelRecorder.prototype.startRecording = function(options) {
|
|
23766
24517
|
options = options || {};
|
|
23767
24518
|
if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
|
|
23768
|
-
logger$
|
|
24519
|
+
logger$2.log('Recording already in progress, skipping startRecording.');
|
|
23769
24520
|
return;
|
|
23770
24521
|
}
|
|
23771
24522
|
|
|
23772
24523
|
var onIdleTimeout = function () {
|
|
23773
|
-
logger$
|
|
24524
|
+
logger$2.log('Idle timeout reached, restarting recording.');
|
|
23774
24525
|
this.resetRecording();
|
|
23775
24526
|
}.bind(this);
|
|
23776
24527
|
|
|
23777
24528
|
var onMaxLengthReached = function () {
|
|
23778
|
-
logger$
|
|
24529
|
+
logger$2.log('Max recording length reached, stopping recording.');
|
|
23779
24530
|
this.resetRecording();
|
|
23780
24531
|
}.bind(this);
|
|
23781
24532
|
|
|
@@ -23845,7 +24596,7 @@ MixpanelRecorder.prototype.resumeRecording = function (startNewIfInactive) {
|
|
|
23845
24596
|
} else if (startNewIfInactive) {
|
|
23846
24597
|
return this.startRecording({shouldStopBatcher: false});
|
|
23847
24598
|
} else {
|
|
23848
|
-
logger$
|
|
24599
|
+
logger$2.log('No resumable recording found.');
|
|
23849
24600
|
return null;
|
|
23850
24601
|
}
|
|
23851
24602
|
}.bind(this));
|
|
@@ -23857,8 +24608,12 @@ MixpanelRecorder.prototype.resetRecording = function () {
|
|
|
23857
24608
|
this.startRecording({shouldStopBatcher: true});
|
|
23858
24609
|
};
|
|
23859
24610
|
|
|
24611
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24612
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24613
|
+
};
|
|
24614
|
+
|
|
23860
24615
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23861
|
-
if (this.
|
|
24616
|
+
if (this.isRecording()) {
|
|
23862
24617
|
return this.activeRecording.replayId;
|
|
23863
24618
|
} else {
|
|
23864
24619
|
return null;
|
|
@@ -24362,53 +25117,6 @@ function requireLogic () {
|
|
|
24362
25117
|
var logicExports = requireLogic();
|
|
24363
25118
|
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24364
25119
|
|
|
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
25120
|
/**
|
|
24413
25121
|
* Check if an event matches the given criteria
|
|
24414
25122
|
* @param {string} eventName - The name of the event being checked
|
|
@@ -24432,13 +25140,8 @@ var eventMatchesCriteria = function(eventName, properties, criteria) {
|
|
|
24432
25140
|
|
|
24433
25141
|
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24434
25142
|
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);
|
|
25143
|
+
// Use properties as-is for case-sensitive matching
|
|
25144
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
24442
25145
|
} catch (error) {
|
|
24443
25146
|
return {
|
|
24444
25147
|
matches: false,
|
|
@@ -24558,7 +25261,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
|
|
|
24558
25261
|
observer.observe(shadowRoot, this.observerConfig);
|
|
24559
25262
|
this.shadowObservers.push(observer);
|
|
24560
25263
|
} catch (e) {
|
|
24561
|
-
logger$
|
|
25264
|
+
logger$5.critical('Error while observing shadow root', e);
|
|
24562
25265
|
}
|
|
24563
25266
|
};
|
|
24564
25267
|
|
|
@@ -24569,7 +25272,7 @@ ShadowDOMObserver.prototype.start = function() {
|
|
|
24569
25272
|
}
|
|
24570
25273
|
|
|
24571
25274
|
if (!weakSetSupported()) {
|
|
24572
|
-
logger$
|
|
25275
|
+
logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
24573
25276
|
return;
|
|
24574
25277
|
}
|
|
24575
25278
|
|
|
@@ -24585,7 +25288,7 @@ ShadowDOMObserver.prototype.stop = function() {
|
|
|
24585
25288
|
try {
|
|
24586
25289
|
this.shadowObservers[i].disconnect();
|
|
24587
25290
|
} catch (e) {
|
|
24588
|
-
logger$
|
|
25291
|
+
logger$5.critical('Error while disconnecting shadow DOM observer', e);
|
|
24589
25292
|
}
|
|
24590
25293
|
}
|
|
24591
25294
|
this.shadowObservers = [];
|
|
@@ -24773,7 +25476,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24773
25476
|
|
|
24774
25477
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24775
25478
|
} catch (e) {
|
|
24776
|
-
logger$
|
|
25479
|
+
logger$5.critical('Error while setting up mutation observer', e);
|
|
24777
25480
|
}
|
|
24778
25481
|
}
|
|
24779
25482
|
|
|
@@ -24788,7 +25491,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24788
25491
|
);
|
|
24789
25492
|
this.shadowDOMObserver.start();
|
|
24790
25493
|
} catch (e) {
|
|
24791
|
-
logger$
|
|
25494
|
+
logger$5.critical('Error while setting up shadow DOM observer', e);
|
|
24792
25495
|
this.shadowDOMObserver = null;
|
|
24793
25496
|
}
|
|
24794
25497
|
}
|
|
@@ -24815,7 +25518,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24815
25518
|
try {
|
|
24816
25519
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24817
25520
|
} catch (e) {
|
|
24818
|
-
logger$
|
|
25521
|
+
logger$5.critical('Error while removing event listener', e);
|
|
24819
25522
|
}
|
|
24820
25523
|
}
|
|
24821
25524
|
this.eventListeners = [];
|
|
@@ -24824,7 +25527,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24824
25527
|
try {
|
|
24825
25528
|
this.mutationObserver.disconnect();
|
|
24826
25529
|
} catch (e) {
|
|
24827
|
-
logger$
|
|
25530
|
+
logger$5.critical('Error while disconnecting mutation observer', e);
|
|
24828
25531
|
}
|
|
24829
25532
|
this.mutationObserver = null;
|
|
24830
25533
|
}
|
|
@@ -24833,7 +25536,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24833
25536
|
try {
|
|
24834
25537
|
this.shadowDOMObserver.stop();
|
|
24835
25538
|
} catch (e) {
|
|
24836
|
-
logger$
|
|
25539
|
+
logger$5.critical('Error while stopping shadow DOM observer', e);
|
|
24837
25540
|
}
|
|
24838
25541
|
this.shadowDOMObserver = null;
|
|
24839
25542
|
}
|
|
@@ -24911,7 +25614,7 @@ var Autocapture = function(mp) {
|
|
|
24911
25614
|
|
|
24912
25615
|
Autocapture.prototype.init = function() {
|
|
24913
25616
|
if (!minDOMApisSupported()) {
|
|
24914
|
-
logger$
|
|
25617
|
+
logger$5.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24915
25618
|
return;
|
|
24916
25619
|
}
|
|
24917
25620
|
this.initPageListeners();
|
|
@@ -24943,27 +25646,15 @@ Autocapture.prototype.getConfig = function(key) {
|
|
|
24943
25646
|
};
|
|
24944
25647
|
|
|
24945
25648
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24946
|
-
var i;
|
|
24947
25649
|
var currentUrl = _.info.currentUrl();
|
|
24948
25650
|
|
|
24949
25651
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24950
25652
|
if (allowUrlRegexes.length) {
|
|
24951
25653
|
// 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
|
|
25654
|
+
try {
|
|
25655
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25656
|
+
} catch (err) {
|
|
25657
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
24967
25658
|
return true;
|
|
24968
25659
|
}
|
|
24969
25660
|
}
|
|
@@ -24973,17 +25664,12 @@ Autocapture.prototype.currentUrlBlocked = function() {
|
|
|
24973
25664
|
return false;
|
|
24974
25665
|
}
|
|
24975
25666
|
|
|
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
|
-
}
|
|
25667
|
+
try {
|
|
25668
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25669
|
+
} catch (err) {
|
|
25670
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
25671
|
+
return true;
|
|
24985
25672
|
}
|
|
24986
|
-
return false;
|
|
24987
25673
|
};
|
|
24988
25674
|
|
|
24989
25675
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -25119,7 +25805,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
|
|
|
25119
25805
|
return;
|
|
25120
25806
|
}
|
|
25121
25807
|
|
|
25122
|
-
logger$
|
|
25808
|
+
logger$5.log('Initializing scroll depth tracking');
|
|
25123
25809
|
|
|
25124
25810
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
25125
25811
|
|
|
@@ -25145,7 +25831,7 @@ Autocapture.prototype.initClickTracking = function() {
|
|
|
25145
25831
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
25146
25832
|
return;
|
|
25147
25833
|
}
|
|
25148
|
-
logger$
|
|
25834
|
+
logger$5.log('Initializing click tracking');
|
|
25149
25835
|
|
|
25150
25836
|
this.listenerClick = function(ev) {
|
|
25151
25837
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -25164,7 +25850,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
|
|
|
25164
25850
|
return;
|
|
25165
25851
|
}
|
|
25166
25852
|
|
|
25167
|
-
logger$
|
|
25853
|
+
logger$5.log('Initializing dead click tracking');
|
|
25168
25854
|
if (!this._deadClickTracker) {
|
|
25169
25855
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
25170
25856
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -25198,7 +25884,7 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
25198
25884
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
25199
25885
|
return;
|
|
25200
25886
|
}
|
|
25201
|
-
logger$
|
|
25887
|
+
logger$5.log('Initializing input tracking');
|
|
25202
25888
|
|
|
25203
25889
|
this.listenerChange = function(ev) {
|
|
25204
25890
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -25215,7 +25901,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
25215
25901
|
if (!this.pageviewTrackingConfig()) {
|
|
25216
25902
|
return;
|
|
25217
25903
|
}
|
|
25218
|
-
logger$
|
|
25904
|
+
logger$5.log('Initializing pageview tracking');
|
|
25219
25905
|
|
|
25220
25906
|
var previousTrackedUrl = '';
|
|
25221
25907
|
var tracked = false;
|
|
@@ -25250,7 +25936,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
25250
25936
|
}
|
|
25251
25937
|
if (didPathChange) {
|
|
25252
25938
|
this.lastScrollCheckpoint = 0;
|
|
25253
|
-
logger$
|
|
25939
|
+
logger$5.log('Path change: re-initializing scroll depth checkpoints');
|
|
25254
25940
|
}
|
|
25255
25941
|
}
|
|
25256
25942
|
}.bind(this));
|
|
@@ -25265,7 +25951,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
25265
25951
|
return;
|
|
25266
25952
|
}
|
|
25267
25953
|
|
|
25268
|
-
logger$
|
|
25954
|
+
logger$5.log('Initializing rage click tracking');
|
|
25269
25955
|
if (!this._rageClickTracker) {
|
|
25270
25956
|
this._rageClickTracker = new RageClickTracker();
|
|
25271
25957
|
}
|
|
@@ -25295,7 +25981,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
25295
25981
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
25296
25982
|
return;
|
|
25297
25983
|
}
|
|
25298
|
-
logger$
|
|
25984
|
+
logger$5.log('Initializing scroll tracking');
|
|
25299
25985
|
this.lastScrollCheckpoint = 0;
|
|
25300
25986
|
|
|
25301
25987
|
var scrollTrackFunction = function() {
|
|
@@ -25332,7 +26018,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
25332
26018
|
}
|
|
25333
26019
|
}
|
|
25334
26020
|
} catch (err) {
|
|
25335
|
-
logger$
|
|
26021
|
+
logger$5.critical('Error while calculating scroll percentage', err);
|
|
25336
26022
|
}
|
|
25337
26023
|
if (shouldTrack) {
|
|
25338
26024
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -25350,7 +26036,7 @@ Autocapture.prototype.initSubmitTracking = function() {
|
|
|
25350
26036
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
25351
26037
|
return;
|
|
25352
26038
|
}
|
|
25353
|
-
logger$
|
|
26039
|
+
logger$5.log('Initializing submit tracking');
|
|
25354
26040
|
|
|
25355
26041
|
this.listenerSubmit = function(ev) {
|
|
25356
26042
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -25372,7 +26058,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
|
|
|
25372
26058
|
return;
|
|
25373
26059
|
}
|
|
25374
26060
|
|
|
25375
|
-
logger$
|
|
26061
|
+
logger$5.log('Initializing page visibility tracking.');
|
|
25376
26062
|
this._initScrollDepthTracking();
|
|
25377
26063
|
var previousTrackedUrl = _.info.currentUrl();
|
|
25378
26064
|
|
|
@@ -25457,7 +26143,7 @@ var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
|
|
|
25457
26143
|
return win[TARGETING_GLOBAL_NAME];
|
|
25458
26144
|
};
|
|
25459
26145
|
|
|
25460
|
-
var logger = console_with_prefix('flags');
|
|
26146
|
+
var logger$1 = console_with_prefix('flags');
|
|
25461
26147
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
25462
26148
|
|
|
25463
26149
|
var CONFIG_CONTEXT = 'context';
|
|
@@ -25500,7 +26186,7 @@ var FeatureFlagManager = function(initOptions) {
|
|
|
25500
26186
|
|
|
25501
26187
|
FeatureFlagManager.prototype.init = function() {
|
|
25502
26188
|
if (!this.minApisSupported()) {
|
|
25503
|
-
logger.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
26189
|
+
logger$1.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
25504
26190
|
return;
|
|
25505
26191
|
}
|
|
25506
26192
|
|
|
@@ -25535,7 +26221,7 @@ FeatureFlagManager.prototype.isSystemEnabled = function() {
|
|
|
25535
26221
|
|
|
25536
26222
|
FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
25537
26223
|
if (!this.isSystemEnabled()) {
|
|
25538
|
-
logger.critical('Feature Flags not enabled, cannot update context');
|
|
26224
|
+
logger$1.critical('Feature Flags not enabled, cannot update context');
|
|
25539
26225
|
return Promise.resolve();
|
|
25540
26226
|
}
|
|
25541
26227
|
|
|
@@ -25552,7 +26238,7 @@ FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
|
25552
26238
|
|
|
25553
26239
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
25554
26240
|
if (!this.isSystemEnabled()) {
|
|
25555
|
-
logger.error('Feature Flags not enabled');
|
|
26241
|
+
logger$1.error('Feature Flags not enabled');
|
|
25556
26242
|
}
|
|
25557
26243
|
return !!this.flags;
|
|
25558
26244
|
};
|
|
@@ -25565,7 +26251,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
25565
26251
|
var distinctId = this.getMpProperty('distinct_id');
|
|
25566
26252
|
var deviceId = this.getMpProperty('$device_id');
|
|
25567
26253
|
var traceparent = generateTraceparent();
|
|
25568
|
-
logger.log('Fetching flags for distinct ID: ' + distinctId);
|
|
26254
|
+
logger$1.log('Fetching flags for distinct ID: ' + distinctId);
|
|
25569
26255
|
|
|
25570
26256
|
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
25571
26257
|
var searchParams = new URLSearchParams();
|
|
@@ -25664,11 +26350,11 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
25664
26350
|
this._loadTargetingIfNeeded();
|
|
25665
26351
|
}.bind(this)).catch(function(error) {
|
|
25666
26352
|
this.markFetchComplete();
|
|
25667
|
-
logger.error(error);
|
|
26353
|
+
logger$1.error(error);
|
|
25668
26354
|
}.bind(this));
|
|
25669
26355
|
}.bind(this)).catch(function(error) {
|
|
25670
26356
|
this.markFetchComplete();
|
|
25671
|
-
logger.error(error);
|
|
26357
|
+
logger$1.error(error);
|
|
25672
26358
|
}.bind(this));
|
|
25673
26359
|
|
|
25674
26360
|
return this.fetchPromise;
|
|
@@ -25676,7 +26362,7 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
25676
26362
|
|
|
25677
26363
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
25678
26364
|
if (!this._fetchInProgressStartTime) {
|
|
25679
|
-
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
26365
|
+
logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
25680
26366
|
return;
|
|
25681
26367
|
}
|
|
25682
26368
|
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
@@ -25698,7 +26384,7 @@ FeatureFlagManager.prototype._loadTargetingIfNeeded = function() {
|
|
|
25698
26384
|
|
|
25699
26385
|
if (hasPropertyFilters) {
|
|
25700
26386
|
this.getTargeting().then(function() {
|
|
25701
|
-
logger.log('targeting loaded for property filter evaluation');
|
|
26387
|
+
logger$1.log('targeting loaded for property filter evaluation');
|
|
25702
26388
|
});
|
|
25703
26389
|
}
|
|
25704
26390
|
};
|
|
@@ -25713,7 +26399,7 @@ FeatureFlagManager.prototype.getTargeting = function() {
|
|
|
25713
26399
|
this.loadExtraBundle.bind(this),
|
|
25714
26400
|
this.targetingSrc
|
|
25715
26401
|
).catch(function(error) {
|
|
25716
|
-
logger.error('Failed to load targeting: ' + error);
|
|
26402
|
+
logger$1.error('Failed to load targeting: ' + error);
|
|
25717
26403
|
}.bind(this));
|
|
25718
26404
|
};
|
|
25719
26405
|
|
|
@@ -25767,7 +26453,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
|
|
|
25767
26453
|
|
|
25768
26454
|
// If no targeting library and event has property filters, skip it
|
|
25769
26455
|
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
25770
|
-
logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
26456
|
+
logger$1.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
25771
26457
|
return;
|
|
25772
26458
|
}
|
|
25773
26459
|
|
|
@@ -25790,7 +26476,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
|
|
|
25790
26476
|
}
|
|
25791
26477
|
|
|
25792
26478
|
if (matchResult.error) {
|
|
25793
|
-
logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
26479
|
+
logger$1.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
25794
26480
|
return;
|
|
25795
26481
|
}
|
|
25796
26482
|
|
|
@@ -25798,7 +26484,7 @@ FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, p
|
|
|
25798
26484
|
return;
|
|
25799
26485
|
}
|
|
25800
26486
|
|
|
25801
|
-
logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
26487
|
+
logger$1.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
25802
26488
|
|
|
25803
26489
|
var newVariant = {
|
|
25804
26490
|
'key': pendingEvent['pending_variant']['variant_key'],
|
|
@@ -25839,7 +26525,7 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
|
|
|
25839
26525
|
'first_time_event_hash': firstTimeEventHash
|
|
25840
26526
|
};
|
|
25841
26527
|
|
|
25842
|
-
logger.log('Recording first-time event for flag: ' + flagId);
|
|
26528
|
+
logger$1.log('Recording first-time event for flag: ' + flagId);
|
|
25843
26529
|
|
|
25844
26530
|
// Fire-and-forget POST request
|
|
25845
26531
|
this.fetch.call(win, url, {
|
|
@@ -25852,130 +26538,446 @@ FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId,
|
|
|
25852
26538
|
'body': JSON.stringify(payload)
|
|
25853
26539
|
}).catch(function(error) {
|
|
25854
26540
|
// Silent failure - cohort sync will catch up
|
|
25855
|
-
logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26541
|
+
logger$1.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26542
|
+
});
|
|
26543
|
+
};
|
|
26544
|
+
|
|
26545
|
+
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
26546
|
+
if (!this.fetchPromise) {
|
|
26547
|
+
return new Promise(function(resolve) {
|
|
26548
|
+
logger$1.critical('Feature Flags not initialized');
|
|
26549
|
+
resolve(fallback);
|
|
26550
|
+
});
|
|
26551
|
+
}
|
|
26552
|
+
|
|
26553
|
+
return this.fetchPromise.then(function() {
|
|
26554
|
+
return this.getVariantSync(featureName, fallback);
|
|
26555
|
+
}.bind(this)).catch(function(error) {
|
|
26556
|
+
logger$1.error(error);
|
|
26557
|
+
return fallback;
|
|
26558
|
+
});
|
|
26559
|
+
};
|
|
26560
|
+
|
|
26561
|
+
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
26562
|
+
if (!this.areFlagsReady()) {
|
|
26563
|
+
logger$1.log('Flags not loaded yet');
|
|
26564
|
+
return fallback;
|
|
26565
|
+
}
|
|
26566
|
+
var feature = this.flags.get(featureName);
|
|
26567
|
+
if (!feature) {
|
|
26568
|
+
logger$1.log('No flag found: "' + featureName + '"');
|
|
26569
|
+
return fallback;
|
|
26570
|
+
}
|
|
26571
|
+
this.trackFeatureCheck(featureName, feature);
|
|
26572
|
+
return feature;
|
|
26573
|
+
};
|
|
26574
|
+
|
|
26575
|
+
FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
|
|
26576
|
+
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
26577
|
+
return feature['value'];
|
|
26578
|
+
}).catch(function(error) {
|
|
26579
|
+
logger$1.error(error);
|
|
26580
|
+
return fallbackValue;
|
|
26581
|
+
});
|
|
26582
|
+
};
|
|
26583
|
+
|
|
26584
|
+
// TODO remove deprecated method
|
|
26585
|
+
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
26586
|
+
logger$1.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
26587
|
+
return this.getVariantValue(featureName, fallbackValue);
|
|
26588
|
+
};
|
|
26589
|
+
|
|
26590
|
+
FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
|
|
26591
|
+
return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
|
|
26592
|
+
};
|
|
26593
|
+
|
|
26594
|
+
FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
|
|
26595
|
+
return this.getVariantValue(featureName).then(function() {
|
|
26596
|
+
return this.isEnabledSync(featureName, fallbackValue);
|
|
26597
|
+
}.bind(this)).catch(function(error) {
|
|
26598
|
+
logger$1.error(error);
|
|
26599
|
+
return fallbackValue;
|
|
25856
26600
|
});
|
|
25857
26601
|
};
|
|
25858
26602
|
|
|
25859
|
-
FeatureFlagManager.prototype.
|
|
25860
|
-
|
|
25861
|
-
|
|
25862
|
-
|
|
25863
|
-
|
|
25864
|
-
|
|
26603
|
+
FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
|
|
26604
|
+
fallbackValue = fallbackValue || false;
|
|
26605
|
+
var val = this.getVariantValueSync(featureName, fallbackValue);
|
|
26606
|
+
if (val !== true && val !== false) {
|
|
26607
|
+
logger$1.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
26608
|
+
val = fallbackValue;
|
|
26609
|
+
}
|
|
26610
|
+
return val;
|
|
26611
|
+
};
|
|
26612
|
+
|
|
26613
|
+
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
26614
|
+
if (this.trackedFeatures.has(featureName)) {
|
|
26615
|
+
return;
|
|
26616
|
+
}
|
|
26617
|
+
this.trackedFeatures.add(featureName);
|
|
26618
|
+
|
|
26619
|
+
var trackingProperties = {
|
|
26620
|
+
'Experiment name': featureName,
|
|
26621
|
+
'Variant name': feature['key'],
|
|
26622
|
+
'$experiment_type': 'feature_flag',
|
|
26623
|
+
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
26624
|
+
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
26625
|
+
'Variant fetch latency (ms)': this._fetchLatency,
|
|
26626
|
+
'Variant fetch traceparent': this._traceparent,
|
|
26627
|
+
};
|
|
26628
|
+
|
|
26629
|
+
if (feature['experiment_id'] !== 'undefined') {
|
|
26630
|
+
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
26631
|
+
}
|
|
26632
|
+
if (feature['is_experiment_active'] !== 'undefined') {
|
|
26633
|
+
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
26634
|
+
}
|
|
26635
|
+
if (feature['is_qa_tester'] !== 'undefined') {
|
|
26636
|
+
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
26637
|
+
}
|
|
26638
|
+
|
|
26639
|
+
this.track('$experiment_started', trackingProperties);
|
|
26640
|
+
};
|
|
26641
|
+
|
|
26642
|
+
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
26643
|
+
return !!this.fetch &&
|
|
26644
|
+
typeof Promise !== 'undefined' &&
|
|
26645
|
+
typeof Map !== 'undefined' &&
|
|
26646
|
+
typeof Set !== 'undefined';
|
|
26647
|
+
};
|
|
26648
|
+
|
|
26649
|
+
safewrapClass(FeatureFlagManager);
|
|
26650
|
+
|
|
26651
|
+
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
26652
|
+
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
26653
|
+
FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
|
|
26654
|
+
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
26655
|
+
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
26656
|
+
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
26657
|
+
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
26658
|
+
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
26659
|
+
|
|
26660
|
+
// Deprecated method
|
|
26661
|
+
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
26662
|
+
|
|
26663
|
+
// Exports intended only for testing
|
|
26664
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26665
|
+
|
|
26666
|
+
/* eslint camelcase: "off" */
|
|
26667
|
+
|
|
26668
|
+
|
|
26669
|
+
var logger = console_with_prefix('recorder');
|
|
26670
|
+
|
|
26671
|
+
var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
|
|
26672
|
+
var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
|
|
26673
|
+
|
|
26674
|
+
|
|
26675
|
+
/**
|
|
26676
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26677
|
+
* @constructor
|
|
26678
|
+
*/
|
|
26679
|
+
var RecorderManager = function(initOptions) {
|
|
26680
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26681
|
+
// but ideally we should be able to remove this dependency.
|
|
26682
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26683
|
+
|
|
26684
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26685
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26686
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26687
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26688
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26689
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26690
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26691
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26692
|
+
|
|
26693
|
+
this._recorder = null;
|
|
26694
|
+
this._parentReplayId = null;
|
|
26695
|
+
this._parentFrameRetryInterval = null;
|
|
26696
|
+
};
|
|
26697
|
+
|
|
26698
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26699
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26700
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26701
|
+
return PromisePolyfill.resolve(false);
|
|
26702
|
+
}
|
|
26703
|
+
|
|
26704
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26705
|
+
var tab_id = this.getTabId();
|
|
26706
|
+
return recording_registry_idb.init()
|
|
26707
|
+
.then(function () {
|
|
26708
|
+
return recording_registry_idb.getAll();
|
|
26709
|
+
})
|
|
26710
|
+
.then(function (recordings) {
|
|
26711
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26712
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26713
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26714
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26715
|
+
return true;
|
|
26716
|
+
}
|
|
26717
|
+
}
|
|
26718
|
+
return false;
|
|
26719
|
+
})
|
|
26720
|
+
.catch(_.bind(function (err) {
|
|
26721
|
+
this.reportError('Error checking recording registry', err);
|
|
26722
|
+
return false;
|
|
26723
|
+
}, this));
|
|
26724
|
+
};
|
|
26725
|
+
|
|
26726
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26727
|
+
if (!win['MutationObserver']) {
|
|
26728
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26729
|
+
return PromisePolyfill.resolve();
|
|
26730
|
+
}
|
|
26731
|
+
|
|
26732
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26733
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26734
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26735
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26736
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26737
|
+
resolve();
|
|
26738
|
+
}, this));
|
|
26739
|
+
|
|
26740
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26741
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26742
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26743
|
+
} else {
|
|
26744
|
+
handleLoadedRecorder();
|
|
26745
|
+
}
|
|
26746
|
+
}, this));
|
|
26747
|
+
}, this);
|
|
26748
|
+
|
|
26749
|
+
// Cross-origin iframe handling
|
|
26750
|
+
var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger);
|
|
26751
|
+
var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
|
|
26752
|
+
|
|
26753
|
+
if (isCrossOriginRecordingEnabled) {
|
|
26754
|
+
// listen for handshake requests from their own child iframes (including nested)
|
|
26755
|
+
this._setupParentFrameListener(allowedOrigins);
|
|
26756
|
+
|
|
26757
|
+
if (win.parent !== win) {
|
|
26758
|
+
// also wait for parent's replay ID
|
|
26759
|
+
this._setupChildFrameListener(allowedOrigins, loadRecorder);
|
|
26760
|
+
this._sendParentFrameRequestWithRetry(allowedOrigins);
|
|
26761
|
+
return PromisePolyfill.resolve();
|
|
26762
|
+
}
|
|
26763
|
+
}
|
|
26764
|
+
|
|
26765
|
+
/**
|
|
26766
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26767
|
+
* 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.
|
|
26768
|
+
*/
|
|
26769
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26770
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26771
|
+
if (force_start || is_sampled) {
|
|
26772
|
+
return loadRecorder(true);
|
|
26773
|
+
} else {
|
|
26774
|
+
return this.shouldLoadRecorder()
|
|
26775
|
+
.then(_.bind(function (shouldLoad) {
|
|
26776
|
+
if (shouldLoad) {
|
|
26777
|
+
return loadRecorder(false);
|
|
26778
|
+
}
|
|
26779
|
+
return PromisePolyfill.resolve();
|
|
26780
|
+
}, this));
|
|
26781
|
+
}
|
|
26782
|
+
};
|
|
26783
|
+
|
|
26784
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26785
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26786
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26787
|
+
return false;
|
|
26788
|
+
}
|
|
26789
|
+
try {
|
|
26790
|
+
return this._recorder['isRecording']();
|
|
26791
|
+
} catch (e) {
|
|
26792
|
+
this.reportError('Error checking if recording is active', e);
|
|
26793
|
+
return false;
|
|
25865
26794
|
}
|
|
26795
|
+
};
|
|
25866
26796
|
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
|
|
25871
|
-
|
|
25872
|
-
|
|
26797
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26798
|
+
var isRecording = this.isRecording();
|
|
26799
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26800
|
+
|
|
26801
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26802
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26803
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26804
|
+
var newRate = trigger['percentage'];
|
|
26805
|
+
var propertyFilters = trigger['property_filters'];
|
|
26806
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26807
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26808
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26809
|
+
.then(function(targeting) {
|
|
26810
|
+
try {
|
|
26811
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26812
|
+
event_name,
|
|
26813
|
+
properties,
|
|
26814
|
+
{
|
|
26815
|
+
'event_name': event_name,
|
|
26816
|
+
'property_filters': propertyFilters
|
|
26817
|
+
}
|
|
26818
|
+
);
|
|
26819
|
+
if (result['matches']) {
|
|
26820
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26821
|
+
}
|
|
26822
|
+
} catch (err) {
|
|
26823
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26824
|
+
}
|
|
26825
|
+
}.bind(this)).catch(function(err) {
|
|
26826
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26827
|
+
});
|
|
26828
|
+
} else {
|
|
26829
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26830
|
+
}
|
|
26831
|
+
}
|
|
26832
|
+
}
|
|
25873
26833
|
};
|
|
25874
26834
|
|
|
25875
|
-
|
|
25876
|
-
if (
|
|
25877
|
-
|
|
25878
|
-
return fallback;
|
|
26835
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26836
|
+
if (this._recorder) {
|
|
26837
|
+
return this._recorder['stopRecording']();
|
|
25879
26838
|
}
|
|
25880
|
-
|
|
25881
|
-
|
|
25882
|
-
|
|
25883
|
-
|
|
26839
|
+
return PromisePolyfill.resolve();
|
|
26840
|
+
};
|
|
26841
|
+
|
|
26842
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26843
|
+
if (this._recorder) {
|
|
26844
|
+
return this._recorder['pauseRecording']();
|
|
25884
26845
|
}
|
|
25885
|
-
|
|
25886
|
-
return feature;
|
|
26846
|
+
return PromisePolyfill.resolve();
|
|
25887
26847
|
};
|
|
25888
26848
|
|
|
25889
|
-
|
|
25890
|
-
|
|
25891
|
-
return
|
|
25892
|
-
}
|
|
25893
|
-
|
|
25894
|
-
return fallbackValue;
|
|
25895
|
-
});
|
|
26849
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26850
|
+
if (this._recorder) {
|
|
26851
|
+
return this._recorder['resumeRecording']();
|
|
26852
|
+
}
|
|
26853
|
+
return PromisePolyfill.resolve();
|
|
25896
26854
|
};
|
|
25897
26855
|
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
25901
|
-
return this.getVariantValue(featureName, fallbackValue);
|
|
26856
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26857
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
25902
26858
|
};
|
|
25903
26859
|
|
|
25904
|
-
|
|
25905
|
-
|
|
26860
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26861
|
+
var props = {};
|
|
26862
|
+
var replay_id = this.getSessionReplayId();
|
|
26863
|
+
if (replay_id) {
|
|
26864
|
+
props['$mp_replay_id'] = replay_id;
|
|
26865
|
+
}
|
|
26866
|
+
return props;
|
|
25906
26867
|
};
|
|
25907
26868
|
|
|
25908
|
-
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
|
|
25913
|
-
|
|
25914
|
-
|
|
26869
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26870
|
+
var replay_url = null;
|
|
26871
|
+
var replay_id = this.getSessionReplayId();
|
|
26872
|
+
if (replay_id) {
|
|
26873
|
+
var query_params = _.HTTPBuildQuery({
|
|
26874
|
+
'replay_id': replay_id,
|
|
26875
|
+
'distinct_id': this.getDistinctId(),
|
|
26876
|
+
'token': this.getMpConfig('token')
|
|
26877
|
+
});
|
|
26878
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26879
|
+
}
|
|
26880
|
+
return replay_url;
|
|
25915
26881
|
};
|
|
25916
26882
|
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
|
|
25921
|
-
logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
25922
|
-
val = fallbackValue;
|
|
26883
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26884
|
+
// Child iframe uses parent's replay ID
|
|
26885
|
+
if (this._parentReplayId) {
|
|
26886
|
+
return this._parentReplayId;
|
|
25923
26887
|
}
|
|
25924
|
-
|
|
26888
|
+
var replay_id = null;
|
|
26889
|
+
if (this._recorder) {
|
|
26890
|
+
replay_id = this._recorder['replayId'];
|
|
26891
|
+
}
|
|
26892
|
+
return replay_id || null;
|
|
25925
26893
|
};
|
|
25926
26894
|
|
|
25927
|
-
|
|
25928
|
-
|
|
26895
|
+
// "private" public method to reach into the recorder in test cases
|
|
26896
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26897
|
+
return this._recorder;
|
|
26898
|
+
};
|
|
26899
|
+
|
|
26900
|
+
RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
|
|
26901
|
+
if (this._childFrameMessageHandler) {
|
|
25929
26902
|
return;
|
|
25930
26903
|
}
|
|
25931
|
-
this
|
|
25932
|
-
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
'
|
|
25936
|
-
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
26904
|
+
var self = this;
|
|
26905
|
+
this._childFrameMessageHandler = function(event) {
|
|
26906
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26907
|
+
var data = event.data;
|
|
26908
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
|
|
26909
|
+
self._parentReplayId = data['replayId'];
|
|
26910
|
+
if (data['distinctId']) {
|
|
26911
|
+
self.mixpanelInstance['identify'](data['distinctId']);
|
|
26912
|
+
}
|
|
26913
|
+
self._parentFrameRetryActive = false;
|
|
26914
|
+
win.removeEventListener('message', self._childFrameMessageHandler);
|
|
26915
|
+
self._childFrameMessageHandler = null;
|
|
26916
|
+
loadRecorder(true);
|
|
26917
|
+
}
|
|
25941
26918
|
};
|
|
26919
|
+
win.addEventListener('message', this._childFrameMessageHandler);
|
|
26920
|
+
};
|
|
25942
26921
|
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
26922
|
+
RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
|
|
26923
|
+
var message = {};
|
|
26924
|
+
message['type'] = IFRAME_HANDSHAKE_REQUEST;
|
|
26925
|
+
message['token'] = this.getMpConfig('token');
|
|
26926
|
+
for (var i = 0; i < allowedOrigins.length; i++) {
|
|
26927
|
+
try {
|
|
26928
|
+
win.parent.postMessage(message, allowedOrigins[i]);
|
|
26929
|
+
} catch (e) {
|
|
26930
|
+
// origin mismatch - ignore
|
|
26931
|
+
}
|
|
25951
26932
|
}
|
|
25952
|
-
|
|
25953
|
-
this.track('$experiment_started', trackingProperties);
|
|
25954
26933
|
};
|
|
25955
26934
|
|
|
25956
|
-
|
|
25957
|
-
|
|
25958
|
-
|
|
25959
|
-
|
|
25960
|
-
|
|
25961
|
-
|
|
26935
|
+
RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
|
|
26936
|
+
var self = this;
|
|
26937
|
+
var maxRetries = 10;
|
|
26938
|
+
var retryCount = 0;
|
|
26939
|
+
var delay = 50;
|
|
26940
|
+
this._parentFrameRetryActive = true;
|
|
25962
26941
|
|
|
25963
|
-
|
|
26942
|
+
this._sendParentFrameRequest(allowedOrigins);
|
|
25964
26943
|
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
|
|
26944
|
+
function scheduleRetry() {
|
|
26945
|
+
setTimeout(function() {
|
|
26946
|
+
if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
|
|
26947
|
+
return;
|
|
26948
|
+
}
|
|
26949
|
+
self._sendParentFrameRequest(allowedOrigins);
|
|
26950
|
+
delay *= 2;
|
|
26951
|
+
scheduleRetry();
|
|
26952
|
+
}, delay);
|
|
26953
|
+
}
|
|
26954
|
+
scheduleRetry();
|
|
26955
|
+
};
|
|
25973
26956
|
|
|
25974
|
-
|
|
25975
|
-
|
|
26957
|
+
RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
|
|
26958
|
+
if (this._parentFrameMessageHandler) {
|
|
26959
|
+
return;
|
|
26960
|
+
}
|
|
26961
|
+
var self = this;
|
|
26962
|
+
this._parentFrameMessageHandler = function(event) {
|
|
26963
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26964
|
+
var data = event.data;
|
|
26965
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
|
|
26966
|
+
var replayId = self.getSessionReplayId();
|
|
26967
|
+
if (replayId) {
|
|
26968
|
+
var response = {};
|
|
26969
|
+
response['type'] = IFRAME_HANDSHAKE_RESPONSE;
|
|
26970
|
+
response['token'] = self.getMpConfig('token');
|
|
26971
|
+
response['replayId'] = replayId;
|
|
26972
|
+
response['distinctId'] = self.getDistinctId();
|
|
26973
|
+
event.source.postMessage(response, event.origin);
|
|
26974
|
+
}
|
|
26975
|
+
}
|
|
26976
|
+
};
|
|
26977
|
+
win.addEventListener('message', this._parentFrameMessageHandler);
|
|
26978
|
+
};
|
|
25976
26979
|
|
|
25977
|
-
|
|
25978
|
-
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26980
|
+
safewrapClass(RecorderManager);
|
|
25979
26981
|
|
|
25980
26982
|
/* eslint camelcase: "off" */
|
|
25981
26983
|
|
|
@@ -27349,7 +28351,6 @@ var INIT_SNIPPET = 1;
|
|
|
27349
28351
|
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
27350
28352
|
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
27351
28353
|
|
|
27352
|
-
|
|
27353
28354
|
/*
|
|
27354
28355
|
* Dynamic... constants? Is that an oxymoron?
|
|
27355
28356
|
*/
|
|
@@ -27434,19 +28435,24 @@ var DEFAULT_CONFIG = {
|
|
|
27434
28435
|
'batch_request_timeout_ms': 90000,
|
|
27435
28436
|
'batch_autostart': true,
|
|
27436
28437
|
'hooks': {},
|
|
28438
|
+
'record_allowed_iframe_origins': [],
|
|
27437
28439
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
27438
28440
|
'record_block_selector': 'img, video, audio',
|
|
27439
28441
|
'record_canvas': false,
|
|
27440
28442
|
'record_collect_fonts': false,
|
|
27441
28443
|
'record_console': true,
|
|
27442
28444
|
'record_heatmap_data': false,
|
|
28445
|
+
'recording_event_triggers': {},
|
|
27443
28446
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
27444
28447
|
'record_mask_inputs': true,
|
|
27445
28448
|
'record_max_ms': MAX_RECORDING_MS,
|
|
27446
28449
|
'record_min_ms': 0,
|
|
28450
|
+
'record_network': false,
|
|
28451
|
+
'record_network_options': {},
|
|
27447
28452
|
'record_sessions_percent': 0,
|
|
27448
|
-
'recorder_src':
|
|
27449
|
-
'targeting_src':
|
|
28453
|
+
'recorder_src': null,
|
|
28454
|
+
'targeting_src': null,
|
|
28455
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
27450
28456
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
27451
28457
|
};
|
|
27452
28458
|
|
|
@@ -27600,6 +28606,19 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27600
28606
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27601
28607
|
}));
|
|
27602
28608
|
|
|
28609
|
+
this.recorderManager = new RecorderManager({
|
|
28610
|
+
mixpanelInstance: this,
|
|
28611
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28612
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28613
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28614
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28615
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28616
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28617
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28618
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28619
|
+
loadExtraBundle: load_extra_bundle
|
|
28620
|
+
});
|
|
28621
|
+
|
|
27603
28622
|
this['_jsc'] = NOOP_FUNC;
|
|
27604
28623
|
|
|
27605
28624
|
this.__dom_loaded_queue = [];
|
|
@@ -27678,7 +28697,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27678
28697
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27679
28698
|
trackingFunc: _.bind(this.track, this),
|
|
27680
28699
|
loadExtraBundle: load_extra_bundle,
|
|
27681
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28700
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27682
28701
|
});
|
|
27683
28702
|
this.flags.init();
|
|
27684
28703
|
this['flags'] = this.flags;
|
|
@@ -27691,11 +28710,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
27691
28710
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27692
28711
|
var mode = this.get_config('remote_settings_mode');
|
|
27693
28712
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27694
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27695
|
-
this._check_and_start_session_recording();
|
|
28713
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28714
|
+
return this._check_and_start_session_recording();
|
|
27696
28715
|
}, this));
|
|
27697
28716
|
} else {
|
|
27698
|
-
this._check_and_start_session_recording();
|
|
28717
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27699
28718
|
}
|
|
27700
28719
|
};
|
|
27701
28720
|
|
|
@@ -27739,132 +28758,50 @@ MixpanelLib.prototype.get_tab_id = function () {
|
|
|
27739
28758
|
return this.tab_id || null;
|
|
27740
28759
|
};
|
|
27741
28760
|
|
|
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
28761
|
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
|
-
}
|
|
28762
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27803
28763
|
});
|
|
27804
28764
|
|
|
28765
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28766
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28767
|
+
};
|
|
28768
|
+
|
|
27805
28769
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27806
|
-
this._check_and_start_session_recording(true);
|
|
28770
|
+
return this._check_and_start_session_recording(true);
|
|
27807
28771
|
};
|
|
27808
28772
|
|
|
27809
28773
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27810
|
-
|
|
27811
|
-
return this._recorder['stopRecording']();
|
|
27812
|
-
}
|
|
27813
|
-
return Promise.resolve();
|
|
28774
|
+
return this.recorderManager.stopSessionRecording();
|
|
27814
28775
|
};
|
|
27815
28776
|
|
|
27816
28777
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27817
|
-
|
|
27818
|
-
return this._recorder['pauseRecording']();
|
|
27819
|
-
}
|
|
27820
|
-
return Promise.resolve();
|
|
28778
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27821
28779
|
};
|
|
27822
28780
|
|
|
27823
28781
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27824
|
-
|
|
27825
|
-
return this._recorder['resumeRecording']();
|
|
27826
|
-
}
|
|
27827
|
-
return Promise.resolve();
|
|
28782
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27828
28783
|
};
|
|
27829
28784
|
|
|
27830
28785
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27831
|
-
return this.
|
|
28786
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27832
28787
|
};
|
|
27833
28788
|
|
|
27834
28789
|
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;
|
|
28790
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27841
28791
|
};
|
|
27842
28792
|
|
|
27843
28793
|
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;
|
|
28794
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27863
28795
|
};
|
|
27864
28796
|
|
|
27865
28797
|
// "private" public method to reach into the recorder in test cases
|
|
27866
28798
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27867
|
-
return this.
|
|
28799
|
+
return this.recorderManager.getRecorder();
|
|
28800
|
+
};
|
|
28801
|
+
|
|
28802
|
+
// "private" public method to get session recording init promise in test cases
|
|
28803
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28804
|
+
return this.__session_recording_init_promise;
|
|
27868
28805
|
};
|
|
27869
28806
|
|
|
27870
28807
|
// Private methods
|
|
@@ -28122,6 +29059,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
28122
29059
|
};
|
|
28123
29060
|
|
|
28124
29061
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
29062
|
+
var self = this;
|
|
28125
29063
|
var disableRecordingIfStrict = function() {
|
|
28126
29064
|
if (mode === 'strict') {
|
|
28127
29065
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -28142,7 +29080,6 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
|
28142
29080
|
};
|
|
28143
29081
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
28144
29082
|
var full_url = settings_endpoint + '?' + query_string;
|
|
28145
|
-
var self = this;
|
|
28146
29083
|
|
|
28147
29084
|
var abortController = new AbortController();
|
|
28148
29085
|
var timeout_id = setTimeout(function() {
|
|
@@ -28334,6 +29271,34 @@ MixpanelLib.prototype.push = function(item) {
|
|
|
28334
29271
|
this._execute_array([item]);
|
|
28335
29272
|
};
|
|
28336
29273
|
|
|
29274
|
+
/**
|
|
29275
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29276
|
+
* this function enable tracking of all events. If passed an
|
|
29277
|
+
* array of event names, those events will be enabled, but other
|
|
29278
|
+
* existing disabled events will continue to be not tracked.
|
|
29279
|
+
*
|
|
29280
|
+
* @param {Array} [events] An array of event names to enable
|
|
29281
|
+
*/
|
|
29282
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29283
|
+
var keys, new_disabled_events, i, j;
|
|
29284
|
+
|
|
29285
|
+
if (typeof(events) === 'undefined') {
|
|
29286
|
+
this._flags.disable_all_events = false;
|
|
29287
|
+
} else {
|
|
29288
|
+
keys = {};
|
|
29289
|
+
new_disabled_events = [];
|
|
29290
|
+
for (i = 0; i < events.length; i++) {
|
|
29291
|
+
keys[events[i]] = true;
|
|
29292
|
+
}
|
|
29293
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29294
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29295
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29296
|
+
}
|
|
29297
|
+
}
|
|
29298
|
+
this.__disabled_events = new_disabled_events;
|
|
29299
|
+
}
|
|
29300
|
+
};
|
|
29301
|
+
|
|
28337
29302
|
/**
|
|
28338
29303
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
28339
29304
|
* this function disables tracking of any event. If passed an
|
|
@@ -28507,6 +29472,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
28507
29472
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
28508
29473
|
}
|
|
28509
29474
|
|
|
29475
|
+
this._start_recording_on_event(event_name, properties);
|
|
29476
|
+
|
|
28510
29477
|
var data = {
|
|
28511
29478
|
'event': event_name,
|
|
28512
29479
|
'properties': properties
|
|
@@ -29715,6 +30682,7 @@ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
|
29715
30682
|
// MixpanelLib Exports
|
|
29716
30683
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29717
30684
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30685
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29718
30686
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29719
30687
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29720
30688
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29758,6 +30726,7 @@ MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES
|
|
|
29758
30726
|
|
|
29759
30727
|
// Exports intended only for testing
|
|
29760
30728
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30729
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29761
30730
|
|
|
29762
30731
|
// MixpanelPersistence Exports
|
|
29763
30732
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|