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
|
@@ -26,16 +26,19 @@
|
|
|
26
26
|
win = window;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
var Config = {
|
|
30
|
+
DEBUG: false,
|
|
31
|
+
LIB_VERSION: '2.77.0'
|
|
32
|
+
};
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// Window global names for async modules
|
|
34
35
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
35
|
-
|
|
36
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
37
36
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
38
37
|
|
|
38
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
39
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
40
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
41
|
+
|
|
39
42
|
function _array_like_to_array(arr, len) {
|
|
40
43
|
if (len == null || len > arr.length) len = arr.length;
|
|
41
44
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -10733,13 +10736,7 @@
|
|
|
10733
10736
|
};
|
|
10734
10737
|
while(_this.mapRemoves.length){
|
|
10735
10738
|
var removedNode = _this.mapRemoves.shift();
|
|
10736
|
-
|
|
10737
|
-
try {
|
|
10738
|
-
_this.iframeManager.removeIframe(removedNode);
|
|
10739
|
-
} catch (e2) {}
|
|
10740
|
-
} else {
|
|
10741
|
-
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10742
|
-
}
|
|
10739
|
+
_this.cleanupRemovedNode(removedNode);
|
|
10743
10740
|
_this.mirror.removeNodeFromMap(removedNode);
|
|
10744
10741
|
}
|
|
10745
10742
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
@@ -11059,6 +11056,20 @@
|
|
|
11059
11056
|
}
|
|
11060
11057
|
}
|
|
11061
11058
|
});
|
|
11059
|
+
__publicField$1(this, "cleanupRemovedNode", function(node2) {
|
|
11060
|
+
if (node2.nodeName === "IFRAME") {
|
|
11061
|
+
try {
|
|
11062
|
+
_this.iframeManager.removeIframe(node2);
|
|
11063
|
+
} catch (e2) {}
|
|
11064
|
+
} else {
|
|
11065
|
+
try {
|
|
11066
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(node2);
|
|
11067
|
+
} catch (e2) {}
|
|
11068
|
+
}
|
|
11069
|
+
node2.childNodes.forEach(function(child) {
|
|
11070
|
+
_this.cleanupRemovedNode(child);
|
|
11071
|
+
});
|
|
11072
|
+
});
|
|
11062
11073
|
}
|
|
11063
11074
|
var _proto = MutationBuffer.prototype;
|
|
11064
11075
|
_proto.init = function init(options) {
|
|
@@ -13286,6 +13297,31 @@
|
|
|
13286
13297
|
_proto.destroy = function destroy() {};
|
|
13287
13298
|
return ProcessedNodeManager;
|
|
13288
13299
|
}();
|
|
13300
|
+
function toOrigin(url) {
|
|
13301
|
+
try {
|
|
13302
|
+
var origin = new URL(url).origin;
|
|
13303
|
+
return origin !== "null" ? origin : null;
|
|
13304
|
+
} catch (e) {
|
|
13305
|
+
return null;
|
|
13306
|
+
}
|
|
13307
|
+
}
|
|
13308
|
+
function buildAllowedOriginSet(origins) {
|
|
13309
|
+
if (!Array.isArray(origins) || origins.length === 0) {
|
|
13310
|
+
throw new Error("[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.");
|
|
13311
|
+
}
|
|
13312
|
+
var set = /* @__PURE__ */ new Set();
|
|
13313
|
+
for(var i2 = 0; i2 < origins.length; i2++){
|
|
13314
|
+
var entry = origins[i2];
|
|
13315
|
+
if (typeof entry !== "string") {
|
|
13316
|
+
throw new Error("[rrweb] allowedIframeOrigins[" + i2 + "] must be a string, got " + (typeof entry === "undefined" ? "undefined" : _type_of(entry)) + ".");
|
|
13317
|
+
}
|
|
13318
|
+
var origin = toOrigin(entry);
|
|
13319
|
+
if (origin) {
|
|
13320
|
+
set.add(origin);
|
|
13321
|
+
}
|
|
13322
|
+
}
|
|
13323
|
+
return Object.freeze(set);
|
|
13324
|
+
}
|
|
13289
13325
|
var wrappedEmit;
|
|
13290
13326
|
var takeFullSnapshot$1;
|
|
13291
13327
|
var canvasManager;
|
|
@@ -13307,10 +13343,17 @@
|
|
|
13307
13343
|
var mirror = createMirror$2();
|
|
13308
13344
|
function record(options) {
|
|
13309
13345
|
if (options === void 0) options = {};
|
|
13310
|
-
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() {
|
|
13346
|
+
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() {
|
|
13311
13347
|
return false;
|
|
13312
13348
|
} : _options_keepIframeSrcFn, _options_ignoreCSSAttributes = options.ignoreCSSAttributes, ignoreCSSAttributes = _options_ignoreCSSAttributes === void 0 ? /* @__PURE__ */ new Set([]) : _options_ignoreCSSAttributes, errorHandler2 = options.errorHandler;
|
|
13313
13349
|
registerErrorHandler(errorHandler2);
|
|
13350
|
+
var validatedOrigins;
|
|
13351
|
+
if (recordCrossOriginIframes && allowedIframeOrigins && allowedIframeOrigins.length > 0) {
|
|
13352
|
+
validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
|
|
13353
|
+
if (validatedOrigins.size === 0) {
|
|
13354
|
+
validatedOrigins = void 0;
|
|
13355
|
+
}
|
|
13356
|
+
}
|
|
13314
13357
|
var inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
|
|
13315
13358
|
var passEmitsToParent = false;
|
|
13316
13359
|
if (!inEmittingFrame) {
|
|
@@ -13402,7 +13445,14 @@
|
|
|
13402
13445
|
origin: window.location.origin,
|
|
13403
13446
|
isCheckout: isCheckout
|
|
13404
13447
|
};
|
|
13405
|
-
|
|
13448
|
+
if (validatedOrigins) {
|
|
13449
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(validatedOrigins), _step; !(_step = _iterator()).done;){
|
|
13450
|
+
var targetOrigin = _step.value;
|
|
13451
|
+
window.parent.postMessage(message, targetOrigin);
|
|
13452
|
+
}
|
|
13453
|
+
} else {
|
|
13454
|
+
window.parent.postMessage(message, "*");
|
|
13455
|
+
}
|
|
13406
13456
|
}
|
|
13407
13457
|
if (e2.type === EventType.FullSnapshot) {
|
|
13408
13458
|
lastFullSnapshotEvent = e2;
|
|
@@ -18135,7 +18185,7 @@
|
|
|
18135
18185
|
var __publicField = function(obj, key, value) {
|
|
18136
18186
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18137
18187
|
};
|
|
18138
|
-
function patch(source, name, replacement) {
|
|
18188
|
+
function patch$3(source, name, replacement) {
|
|
18139
18189
|
try {
|
|
18140
18190
|
if (!(name in source)) {
|
|
18141
18191
|
return function() {};
|
|
@@ -18552,7 +18602,7 @@
|
|
|
18552
18602
|
if (!_logger[level]) {
|
|
18553
18603
|
return function() {};
|
|
18554
18604
|
}
|
|
18555
|
-
return patch(_logger, level, function(original) {
|
|
18605
|
+
return patch$3(_logger, level, function(original) {
|
|
18556
18606
|
var _this1 = _this;
|
|
18557
18607
|
return function() {
|
|
18558
18608
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18973,11 +19023,6 @@
|
|
|
18973
19023
|
PromisePolyfill = NpoPromise;
|
|
18974
19024
|
}
|
|
18975
19025
|
|
|
18976
|
-
var Config = {
|
|
18977
|
-
DEBUG: false,
|
|
18978
|
-
LIB_VERSION: '2.75.0'
|
|
18979
|
-
};
|
|
18980
|
-
|
|
18981
19026
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18982
19027
|
|
|
18983
19028
|
// Maximum allowed session recording length
|
|
@@ -20709,6 +20754,17 @@
|
|
|
20709
20754
|
|
|
20710
20755
|
var NOOP_FUNC = function () {};
|
|
20711
20756
|
|
|
20757
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20758
|
+
var matches = false;
|
|
20759
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20760
|
+
if (url.match(regexList[i])) {
|
|
20761
|
+
matches = true;
|
|
20762
|
+
break;
|
|
20763
|
+
}
|
|
20764
|
+
}
|
|
20765
|
+
return matches;
|
|
20766
|
+
};
|
|
20767
|
+
|
|
20712
20768
|
var JSONStringify = null, JSONParse = null;
|
|
20713
20769
|
if (typeof JSON !== 'undefined') {
|
|
20714
20770
|
JSONStringify = JSON.stringify;
|
|
@@ -21180,7 +21236,7 @@
|
|
|
21180
21236
|
};
|
|
21181
21237
|
}
|
|
21182
21238
|
|
|
21183
|
-
var logger$
|
|
21239
|
+
var logger$8 = console_with_prefix('lock');
|
|
21184
21240
|
|
|
21185
21241
|
/**
|
|
21186
21242
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21232,7 +21288,7 @@
|
|
|
21232
21288
|
|
|
21233
21289
|
var delay = function(cb) {
|
|
21234
21290
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21235
|
-
logger$
|
|
21291
|
+
logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21236
21292
|
storage.removeItem(keyZ);
|
|
21237
21293
|
storage.removeItem(keyY);
|
|
21238
21294
|
loop();
|
|
@@ -21379,7 +21435,7 @@
|
|
|
21379
21435
|
}, this));
|
|
21380
21436
|
};
|
|
21381
21437
|
|
|
21382
|
-
var logger$
|
|
21438
|
+
var logger$7 = console_with_prefix('batch');
|
|
21383
21439
|
|
|
21384
21440
|
/**
|
|
21385
21441
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21408,7 +21464,7 @@
|
|
|
21408
21464
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21409
21465
|
});
|
|
21410
21466
|
}
|
|
21411
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21467
|
+
this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
|
|
21412
21468
|
|
|
21413
21469
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21414
21470
|
|
|
@@ -21741,7 +21797,7 @@
|
|
|
21741
21797
|
// maximum interval between request retries after exponential backoff
|
|
21742
21798
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21743
21799
|
|
|
21744
|
-
var logger$
|
|
21800
|
+
var logger$6 = console_with_prefix('batch');
|
|
21745
21801
|
|
|
21746
21802
|
/**
|
|
21747
21803
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21869,7 +21925,7 @@
|
|
|
21869
21925
|
*/
|
|
21870
21926
|
RequestBatcher.prototype.flush = function(options) {
|
|
21871
21927
|
if (this.requestInProgress) {
|
|
21872
|
-
logger$
|
|
21928
|
+
logger$6.log('Flush: Request already in progress');
|
|
21873
21929
|
return PromisePolyfill.resolve();
|
|
21874
21930
|
}
|
|
21875
21931
|
|
|
@@ -22046,7 +22102,7 @@
|
|
|
22046
22102
|
if (options.unloading) {
|
|
22047
22103
|
requestOptions.transport = 'sendBeacon';
|
|
22048
22104
|
}
|
|
22049
|
-
logger$
|
|
22105
|
+
logger$6.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22050
22106
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22051
22107
|
}, this))
|
|
22052
22108
|
.catch(_.bind(function(err) {
|
|
@@ -22059,7 +22115,7 @@
|
|
|
22059
22115
|
* Log error to global logger and optional user-defined logger.
|
|
22060
22116
|
*/
|
|
22061
22117
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22062
|
-
logger$
|
|
22118
|
+
logger$6.error.apply(logger$6.error, arguments);
|
|
22063
22119
|
if (this.errorReporter) {
|
|
22064
22120
|
try {
|
|
22065
22121
|
if (!(err instanceof Error)) {
|
|
@@ -22067,7 +22123,7 @@
|
|
|
22067
22123
|
}
|
|
22068
22124
|
this.errorReporter(msg, err);
|
|
22069
22125
|
} catch(err) {
|
|
22070
|
-
logger$
|
|
22126
|
+
logger$6.error(err);
|
|
22071
22127
|
}
|
|
22072
22128
|
}
|
|
22073
22129
|
};
|
|
@@ -22084,6 +22140,29 @@
|
|
|
22084
22140
|
|
|
22085
22141
|
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
22086
22142
|
|
|
22143
|
+
var validateAllowedOrigins = function(origins, logger) {
|
|
22144
|
+
if (!_.isArray(origins)) {
|
|
22145
|
+
if (origins) {
|
|
22146
|
+
logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
|
|
22147
|
+
}
|
|
22148
|
+
return [];
|
|
22149
|
+
}
|
|
22150
|
+
var valid = [];
|
|
22151
|
+
for (var i = 0; i < origins.length; i++) {
|
|
22152
|
+
try {
|
|
22153
|
+
var origin = new URL(origins[i]).origin;
|
|
22154
|
+
if (origin === 'null') {
|
|
22155
|
+
logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
|
|
22156
|
+
continue;
|
|
22157
|
+
}
|
|
22158
|
+
valid.push(origin);
|
|
22159
|
+
} catch (e) {
|
|
22160
|
+
logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
|
|
22161
|
+
}
|
|
22162
|
+
}
|
|
22163
|
+
return valid;
|
|
22164
|
+
};
|
|
22165
|
+
|
|
22087
22166
|
// stateless utils
|
|
22088
22167
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
22089
22168
|
|
|
@@ -22189,7 +22268,7 @@
|
|
|
22189
22268
|
|
|
22190
22269
|
var MAX_DEPTH = 5;
|
|
22191
22270
|
|
|
22192
|
-
var logger$
|
|
22271
|
+
var logger$5 = console_with_prefix('autocapture');
|
|
22193
22272
|
|
|
22194
22273
|
|
|
22195
22274
|
function getClasses(el) {
|
|
@@ -22453,7 +22532,7 @@
|
|
|
22453
22532
|
return false;
|
|
22454
22533
|
}
|
|
22455
22534
|
} catch (err) {
|
|
22456
|
-
logger$
|
|
22535
|
+
logger$5.critical('Error while checking element in allowElementCallback', err);
|
|
22457
22536
|
return false;
|
|
22458
22537
|
}
|
|
22459
22538
|
}
|
|
@@ -22470,7 +22549,7 @@
|
|
|
22470
22549
|
return true;
|
|
22471
22550
|
}
|
|
22472
22551
|
} catch (err) {
|
|
22473
|
-
logger$
|
|
22552
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22474
22553
|
}
|
|
22475
22554
|
}
|
|
22476
22555
|
return false;
|
|
@@ -22485,7 +22564,7 @@
|
|
|
22485
22564
|
return true;
|
|
22486
22565
|
}
|
|
22487
22566
|
} catch (err) {
|
|
22488
|
-
logger$
|
|
22567
|
+
logger$5.critical('Error while checking element in blockElementCallback', err);
|
|
22489
22568
|
return true;
|
|
22490
22569
|
}
|
|
22491
22570
|
}
|
|
@@ -22499,7 +22578,7 @@
|
|
|
22499
22578
|
return true;
|
|
22500
22579
|
}
|
|
22501
22580
|
} catch (err) {
|
|
22502
|
-
logger$
|
|
22581
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22503
22582
|
}
|
|
22504
22583
|
}
|
|
22505
22584
|
}
|
|
@@ -23047,177 +23126,826 @@
|
|
|
23047
23126
|
}
|
|
23048
23127
|
|
|
23049
23128
|
/**
|
|
23050
|
-
*
|
|
23129
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23130
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23131
|
+
*
|
|
23132
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23133
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23134
|
+
*
|
|
23051
23135
|
*/
|
|
23052
23136
|
|
|
23137
|
+
var logger$4 = console_with_prefix('network-plugin');
|
|
23053
23138
|
|
|
23054
|
-
|
|
23055
|
-
|
|
23056
|
-
|
|
23057
|
-
|
|
23058
|
-
|
|
23059
|
-
|
|
23060
|
-
|
|
23061
|
-
|
|
23062
|
-
|
|
23139
|
+
/**
|
|
23140
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23141
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23142
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23143
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23144
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23145
|
+
* @param {Window} win
|
|
23146
|
+
* @returns {number}
|
|
23147
|
+
*/
|
|
23148
|
+
function getTimeOrigin(win) {
|
|
23149
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23150
|
+
}
|
|
23063
23151
|
|
|
23064
|
-
|
|
23065
|
-
|
|
23066
|
-
|
|
23067
|
-
|
|
23068
|
-
|
|
23069
|
-
|
|
23070
|
-
IncrementalSource.TouchMove,
|
|
23071
|
-
IncrementalSource.MediaInteraction,
|
|
23072
|
-
IncrementalSource.Drag,
|
|
23073
|
-
IncrementalSource.Selection,
|
|
23074
|
-
]);
|
|
23152
|
+
/**
|
|
23153
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23154
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23155
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23156
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23157
|
+
*/
|
|
23075
23158
|
|
|
23076
|
-
|
|
23077
|
-
|
|
23078
|
-
|
|
23159
|
+
/**
|
|
23160
|
+
* @typedef {Record<string, string>} Headers
|
|
23161
|
+
*/
|
|
23079
23162
|
|
|
23080
23163
|
/**
|
|
23081
|
-
* @typedef {
|
|
23082
|
-
* @property {number} idleExpires
|
|
23083
|
-
* @property {number} maxExpires
|
|
23084
|
-
* @property {number} replayStartTime
|
|
23085
|
-
* @property {number} lastEventTimestamp
|
|
23086
|
-
* @property {number} seqNo
|
|
23087
|
-
* @property {string} batchStartUrl
|
|
23088
|
-
* @property {string} replayId
|
|
23089
|
-
* @property {string} tabId
|
|
23090
|
-
* @property {string} replayStartUrl
|
|
23164
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23091
23165
|
*/
|
|
23092
23166
|
|
|
23093
23167
|
/**
|
|
23094
|
-
* @
|
|
23095
|
-
* @
|
|
23096
|
-
* @
|
|
23097
|
-
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23098
|
-
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23099
|
-
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23100
|
-
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23101
|
-
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23102
|
-
* optional properties for deserialization:
|
|
23103
|
-
* @property {number} idleExpires
|
|
23104
|
-
* @property {number} maxExpires
|
|
23105
|
-
* @property {number} replayStartTime
|
|
23106
|
-
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23107
|
-
* @property {number} seqNo
|
|
23108
|
-
* @property {string} batchStartUrl
|
|
23109
|
-
* @property {string} replayStartUrl
|
|
23168
|
+
* @callback networkCallback
|
|
23169
|
+
* @param {NetworkData} data
|
|
23170
|
+
* @returns {void}
|
|
23110
23171
|
*/
|
|
23111
23172
|
|
|
23112
23173
|
/**
|
|
23113
|
-
* @
|
|
23114
|
-
* @
|
|
23115
|
-
* @property {string} user_id
|
|
23116
|
-
* @property {string} device_id
|
|
23174
|
+
* @callback listenerHandler
|
|
23175
|
+
* @returns {void}
|
|
23117
23176
|
*/
|
|
23118
23177
|
|
|
23178
|
+
/**
|
|
23179
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23180
|
+
*/
|
|
23119
23181
|
|
|
23120
23182
|
/**
|
|
23121
|
-
*
|
|
23122
|
-
* @
|
|
23183
|
+
* @typedef {Object} RecordPlugin
|
|
23184
|
+
* @property {string} name
|
|
23185
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23186
|
+
* @property {NetworkRecordOptions} [options]
|
|
23123
23187
|
*/
|
|
23124
|
-
var SessionRecording = function(options) {
|
|
23125
|
-
this._mixpanel = options.mixpanelInstance;
|
|
23126
|
-
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23127
|
-
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23128
|
-
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23129
|
-
this._rrwebRecord = options.rrwebRecord || null;
|
|
23130
23188
|
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23189
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23190
|
+
var defaultNetworkOptions = {
|
|
23191
|
+
initiatorTypes: [
|
|
23192
|
+
'audio',
|
|
23193
|
+
'beacon',
|
|
23194
|
+
'body',
|
|
23195
|
+
'css',
|
|
23196
|
+
'early-hint',
|
|
23197
|
+
'embed',
|
|
23198
|
+
'fetch',
|
|
23199
|
+
'frame',
|
|
23200
|
+
'iframe',
|
|
23201
|
+
'icon',
|
|
23202
|
+
'image',
|
|
23203
|
+
'img',
|
|
23204
|
+
'input',
|
|
23205
|
+
'link',
|
|
23206
|
+
'navigation',
|
|
23207
|
+
'object',
|
|
23208
|
+
'ping',
|
|
23209
|
+
'script',
|
|
23210
|
+
'track',
|
|
23211
|
+
'video',
|
|
23212
|
+
'xmlhttprequest',
|
|
23213
|
+
],
|
|
23214
|
+
ignoreRequestFn: function() { return false; },
|
|
23215
|
+
recordHeaders: {
|
|
23216
|
+
request: [],
|
|
23217
|
+
response: [],
|
|
23218
|
+
},
|
|
23219
|
+
recordBodyUrls: {
|
|
23220
|
+
request: [],
|
|
23221
|
+
response: [],
|
|
23222
|
+
},
|
|
23223
|
+
recordInitialRequests: false,
|
|
23224
|
+
};
|
|
23134
23225
|
|
|
23135
|
-
|
|
23136
|
-
|
|
23137
|
-
|
|
23138
|
-
|
|
23139
|
-
|
|
23140
|
-
|
|
23141
|
-
|
|
23226
|
+
/**
|
|
23227
|
+
* @param {PerformanceEntry} entry
|
|
23228
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23229
|
+
*/
|
|
23230
|
+
function isNavigationTiming(entry) {
|
|
23231
|
+
return entry.entryType === 'navigation';
|
|
23232
|
+
}
|
|
23142
23233
|
|
|
23143
|
-
|
|
23144
|
-
|
|
23234
|
+
/**
|
|
23235
|
+
* @param {PerformanceEntry} entry
|
|
23236
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23237
|
+
*/
|
|
23238
|
+
function isResourceTiming (entry) {
|
|
23239
|
+
return entry.entryType === 'resource';
|
|
23240
|
+
}
|
|
23145
23241
|
|
|
23146
|
-
|
|
23147
|
-
|
|
23242
|
+
function findLast(array, predicate) {
|
|
23243
|
+
var length = array.length;
|
|
23244
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23245
|
+
if (predicate(array[i])) {
|
|
23246
|
+
return array[i];
|
|
23247
|
+
}
|
|
23248
|
+
}
|
|
23249
|
+
}
|
|
23148
23250
|
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
|
|
23251
|
+
/**
|
|
23252
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23253
|
+
* Adapted from Sentry's `fill` utility:
|
|
23254
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23255
|
+
*
|
|
23256
|
+
* @param {object} source - The object containing the method to patch
|
|
23257
|
+
* @param {string} name - The method name to patch
|
|
23258
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23259
|
+
* @returns {function} A function that restores the original method
|
|
23260
|
+
*/
|
|
23261
|
+
function patch(source, name, replacementFactory) {
|
|
23262
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23263
|
+
return function() {};
|
|
23264
|
+
}
|
|
23265
|
+
var original = source[name];
|
|
23266
|
+
var wrapped = replacementFactory(original);
|
|
23267
|
+
source[name] = wrapped;
|
|
23268
|
+
return function() {
|
|
23269
|
+
source[name] = original;
|
|
23270
|
+
};
|
|
23271
|
+
}
|
|
23152
23272
|
|
|
23153
|
-
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23154
|
-
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23155
|
-
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23156
|
-
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23157
|
-
errorReporter: this.reportError.bind(this),
|
|
23158
|
-
flushOnlyOnInterval: true,
|
|
23159
|
-
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23160
|
-
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23161
|
-
queueStorage: this.queueStorage,
|
|
23162
|
-
sharedLockStorage: options.sharedLockStorage,
|
|
23163
|
-
usePersistence: usePersistence,
|
|
23164
|
-
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23165
23273
|
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
|
|
23169
|
-
|
|
23170
|
-
sharedLockTimeoutMS: 10 * 1000,
|
|
23171
|
-
});
|
|
23172
|
-
};
|
|
23274
|
+
/**
|
|
23275
|
+
* Maximum body size to record (1MB)
|
|
23276
|
+
*/
|
|
23277
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23173
23278
|
|
|
23174
23279
|
/**
|
|
23175
|
-
*
|
|
23280
|
+
* Truncate string if it exceeds max size
|
|
23281
|
+
* @param {string} str
|
|
23282
|
+
* @returns {string}
|
|
23176
23283
|
*/
|
|
23177
|
-
|
|
23178
|
-
if (
|
|
23179
|
-
return
|
|
23284
|
+
function truncateBody(str) {
|
|
23285
|
+
if (!str || typeof str !== 'string') {
|
|
23286
|
+
return str;
|
|
23287
|
+
}
|
|
23288
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23289
|
+
logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23290
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23180
23291
|
}
|
|
23292
|
+
return str;
|
|
23293
|
+
}
|
|
23181
23294
|
|
|
23182
|
-
|
|
23183
|
-
|
|
23295
|
+
/**
|
|
23296
|
+
* @param {networkCallback} cb
|
|
23297
|
+
* @param {Window} win
|
|
23298
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23299
|
+
* @returns {listenerHandler}
|
|
23300
|
+
*/
|
|
23301
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23302
|
+
if (!win.PerformanceObserver) {
|
|
23303
|
+
logger$4.error('PerformanceObserver not supported');
|
|
23304
|
+
return function() {
|
|
23305
|
+
//
|
|
23306
|
+
};
|
|
23307
|
+
}
|
|
23308
|
+
if (options.recordInitialRequests) {
|
|
23309
|
+
var initialPerformanceEntries = win.performance
|
|
23310
|
+
.getEntries()
|
|
23311
|
+
.filter(function(entry) {
|
|
23312
|
+
return isNavigationTiming(entry) ||
|
|
23313
|
+
(isResourceTiming(entry) &&
|
|
23314
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23315
|
+
});
|
|
23316
|
+
cb({
|
|
23317
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23318
|
+
return {
|
|
23319
|
+
url: entry.name,
|
|
23320
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23321
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23322
|
+
startTime: Math.round(entry.startTime),
|
|
23323
|
+
endTime: Math.round(entry.responseEnd),
|
|
23324
|
+
timeOrigin: getTimeOrigin(win),
|
|
23325
|
+
};
|
|
23326
|
+
}),
|
|
23327
|
+
isInitial: true,
|
|
23328
|
+
});
|
|
23329
|
+
}
|
|
23330
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23331
|
+
var performanceEntries = entries
|
|
23332
|
+
.getEntries()
|
|
23333
|
+
.filter(function(entry) {
|
|
23334
|
+
return isResourceTiming(entry) &&
|
|
23335
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23336
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23337
|
+
entry.initiatorType !== 'fetch';
|
|
23338
|
+
});
|
|
23339
|
+
cb({
|
|
23340
|
+
requests: performanceEntries.map(function(entry) {
|
|
23341
|
+
return {
|
|
23342
|
+
url: entry.name,
|
|
23343
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23344
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23345
|
+
startTime: Math.round(entry.startTime),
|
|
23346
|
+
endTime: Math.round(entry.responseEnd),
|
|
23347
|
+
timeOrigin: getTimeOrigin(win),
|
|
23348
|
+
};
|
|
23349
|
+
}),
|
|
23350
|
+
});
|
|
23351
|
+
});
|
|
23352
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23353
|
+
return function() {
|
|
23354
|
+
observer.disconnect();
|
|
23184
23355
|
};
|
|
23356
|
+
}
|
|
23185
23357
|
|
|
23186
|
-
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23358
|
+
/**
|
|
23359
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23360
|
+
* @param {'request' | 'response'} type
|
|
23361
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23362
|
+
* @param {string} headerName
|
|
23363
|
+
* @returns {boolean}
|
|
23364
|
+
*/
|
|
23365
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23366
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23367
|
+
return false;
|
|
23194
23368
|
}
|
|
23195
|
-
return userIdInfo;
|
|
23196
|
-
};
|
|
23197
23369
|
|
|
23198
|
-
|
|
23199
|
-
|
|
23370
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23371
|
+
}
|
|
23200
23372
|
|
|
23201
|
-
|
|
23202
|
-
|
|
23203
|
-
|
|
23204
|
-
|
|
23205
|
-
|
|
23206
|
-
|
|
23207
|
-
|
|
23373
|
+
/**
|
|
23374
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23375
|
+
* @param {'request' | 'response'} type
|
|
23376
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23377
|
+
* @param {string} url
|
|
23378
|
+
* @returns {boolean}
|
|
23379
|
+
*/
|
|
23380
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23381
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23382
|
+
return false;
|
|
23383
|
+
}
|
|
23208
23384
|
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
return this.queueStorage.removeItem(this.batcherKey);
|
|
23212
|
-
}.bind(this));
|
|
23213
|
-
}.bind(this));
|
|
23214
|
-
};
|
|
23385
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23386
|
+
}
|
|
23215
23387
|
|
|
23216
|
-
|
|
23217
|
-
|
|
23218
|
-
|
|
23388
|
+
function tryReadXHRBody(body) {
|
|
23389
|
+
if (body === null || body === undefined) {
|
|
23390
|
+
return null;
|
|
23391
|
+
}
|
|
23219
23392
|
|
|
23220
|
-
|
|
23393
|
+
var result;
|
|
23394
|
+
if (typeof body === 'string') {
|
|
23395
|
+
result = body;
|
|
23396
|
+
} else if (body instanceof Document) {
|
|
23397
|
+
result = body.textContent;
|
|
23398
|
+
} else if (body instanceof FormData) {
|
|
23399
|
+
result = _.HTTPBuildQuery(body);
|
|
23400
|
+
} else if (_.isObject(body)) {
|
|
23401
|
+
try {
|
|
23402
|
+
result = JSON.stringify(body);
|
|
23403
|
+
} catch (e) {
|
|
23404
|
+
return 'Failed to stringify response object';
|
|
23405
|
+
}
|
|
23406
|
+
} else {
|
|
23407
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23408
|
+
}
|
|
23409
|
+
|
|
23410
|
+
return truncateBody(result);
|
|
23411
|
+
}
|
|
23412
|
+
|
|
23413
|
+
/**
|
|
23414
|
+
* @param {Request | Response} r
|
|
23415
|
+
* @returns {Promise<string>}
|
|
23416
|
+
*/
|
|
23417
|
+
function tryReadFetchBody(r) {
|
|
23418
|
+
return new Promise(function(resolve) {
|
|
23419
|
+
var timeout = setTimeout(function() {
|
|
23420
|
+
resolve('Timeout while trying to read body');
|
|
23421
|
+
}, 500);
|
|
23422
|
+
try {
|
|
23423
|
+
r.clone()
|
|
23424
|
+
.text()
|
|
23425
|
+
.then(
|
|
23426
|
+
function(txt) {
|
|
23427
|
+
clearTimeout(timeout);
|
|
23428
|
+
resolve(truncateBody(txt));
|
|
23429
|
+
},
|
|
23430
|
+
function(reason) {
|
|
23431
|
+
clearTimeout(timeout);
|
|
23432
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23433
|
+
}
|
|
23434
|
+
);
|
|
23435
|
+
} catch (e) {
|
|
23436
|
+
clearTimeout(timeout);
|
|
23437
|
+
resolve('Failed to read body: ' + String(e));
|
|
23438
|
+
}
|
|
23439
|
+
});
|
|
23440
|
+
}
|
|
23441
|
+
|
|
23442
|
+
/**
|
|
23443
|
+
* @param {Window} win
|
|
23444
|
+
* @param {string} initiatorType
|
|
23445
|
+
* @param {string} url
|
|
23446
|
+
* @param {number} [after]
|
|
23447
|
+
* @param {number} [before]
|
|
23448
|
+
* @param {number} [attempt]
|
|
23449
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23450
|
+
*/
|
|
23451
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23452
|
+
if (attempt === undefined) {
|
|
23453
|
+
attempt = 0;
|
|
23454
|
+
}
|
|
23455
|
+
if (attempt > 10) {
|
|
23456
|
+
logger$4.error('Cannot find performance entry');
|
|
23457
|
+
return Promise.resolve(null);
|
|
23458
|
+
}
|
|
23459
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23460
|
+
win.performance.getEntriesByName(url)
|
|
23461
|
+
);
|
|
23462
|
+
var performanceEntry = findLast(
|
|
23463
|
+
urlPerformanceEntries,
|
|
23464
|
+
function(entry) {
|
|
23465
|
+
return isResourceTiming(entry) &&
|
|
23466
|
+
entry.initiatorType === initiatorType &&
|
|
23467
|
+
(!after || entry.startTime >= after) &&
|
|
23468
|
+
(!before || entry.startTime <= before);
|
|
23469
|
+
}
|
|
23470
|
+
);
|
|
23471
|
+
if (!performanceEntry) {
|
|
23472
|
+
return new Promise(function(resolve) {
|
|
23473
|
+
setTimeout(resolve, 50 * attempt);
|
|
23474
|
+
}).then(function() {
|
|
23475
|
+
return getRequestPerformanceEntry(
|
|
23476
|
+
win,
|
|
23477
|
+
initiatorType,
|
|
23478
|
+
url,
|
|
23479
|
+
after,
|
|
23480
|
+
before,
|
|
23481
|
+
attempt + 1
|
|
23482
|
+
);
|
|
23483
|
+
});
|
|
23484
|
+
}
|
|
23485
|
+
return Promise.resolve(performanceEntry);
|
|
23486
|
+
}
|
|
23487
|
+
|
|
23488
|
+
/**
|
|
23489
|
+
* @param {networkCallback} cb
|
|
23490
|
+
* @param {Window} win
|
|
23491
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23492
|
+
* @returns {listenerHandler}
|
|
23493
|
+
*/
|
|
23494
|
+
function initXhrObserver(cb, win, options) {
|
|
23495
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23496
|
+
return function() {
|
|
23497
|
+
//
|
|
23498
|
+
};
|
|
23499
|
+
}
|
|
23500
|
+
var restorePatch = patch(
|
|
23501
|
+
win.XMLHttpRequest.prototype,
|
|
23502
|
+
'open',
|
|
23503
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23504
|
+
return function(
|
|
23505
|
+
/** @type {string} */ method,
|
|
23506
|
+
/** @type {string | URL} */ url,
|
|
23507
|
+
/** @type {boolean} */ async,
|
|
23508
|
+
username, password
|
|
23509
|
+
) {
|
|
23510
|
+
if (async === undefined) {
|
|
23511
|
+
async = true;
|
|
23512
|
+
}
|
|
23513
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23514
|
+
var req = new Request(url, { method: method });
|
|
23515
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23516
|
+
var networkRequest = {};
|
|
23517
|
+
/** @type {number | undefined} */
|
|
23518
|
+
var after;
|
|
23519
|
+
/** @type {number | undefined} */
|
|
23520
|
+
var before;
|
|
23521
|
+
|
|
23522
|
+
/** @type {Headers} */
|
|
23523
|
+
var requestHeaders = {};
|
|
23524
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23525
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23526
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23527
|
+
requestHeaders[header] = value;
|
|
23528
|
+
}
|
|
23529
|
+
return originalSetRequestHeader(header, value);
|
|
23530
|
+
};
|
|
23531
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23532
|
+
|
|
23533
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23534
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23535
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23536
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23537
|
+
}
|
|
23538
|
+
after = win.performance.now();
|
|
23539
|
+
return originalSend(body);
|
|
23540
|
+
};
|
|
23541
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23542
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23543
|
+
return;
|
|
23544
|
+
}
|
|
23545
|
+
before = win.performance.now();
|
|
23546
|
+
/** @type {Headers} */
|
|
23547
|
+
var responseHeaders = {};
|
|
23548
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23549
|
+
if (rawHeaders) {
|
|
23550
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23551
|
+
headers.forEach(function(line) {
|
|
23552
|
+
if (!line) return;
|
|
23553
|
+
var colonIndex = line.indexOf(': ');
|
|
23554
|
+
if (colonIndex === -1) return;
|
|
23555
|
+
var header = line.substring(0, colonIndex);
|
|
23556
|
+
var value = line.substring(colonIndex + 2);
|
|
23557
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23558
|
+
responseHeaders[header] = value;
|
|
23559
|
+
}
|
|
23560
|
+
});
|
|
23561
|
+
}
|
|
23562
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23563
|
+
if (
|
|
23564
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23565
|
+
) {
|
|
23566
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23567
|
+
}
|
|
23568
|
+
getRequestPerformanceEntry(
|
|
23569
|
+
win,
|
|
23570
|
+
'xmlhttprequest',
|
|
23571
|
+
req.url,
|
|
23572
|
+
after,
|
|
23573
|
+
before
|
|
23574
|
+
)
|
|
23575
|
+
.then(function(entry) {
|
|
23576
|
+
if (!entry) {
|
|
23577
|
+
logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23578
|
+
return;
|
|
23579
|
+
}
|
|
23580
|
+
/** @type {NetworkRequest} */
|
|
23581
|
+
var request = {
|
|
23582
|
+
url: entry.name,
|
|
23583
|
+
method: req.method,
|
|
23584
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23585
|
+
status: xhr.status,
|
|
23586
|
+
startTime: Math.round(entry.startTime),
|
|
23587
|
+
endTime: Math.round(entry.responseEnd),
|
|
23588
|
+
timeOrigin: getTimeOrigin(win),
|
|
23589
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23590
|
+
requestBody: networkRequest.requestBody,
|
|
23591
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23592
|
+
responseBody: networkRequest.responseBody,
|
|
23593
|
+
};
|
|
23594
|
+
cb({ requests: [request] });
|
|
23595
|
+
})
|
|
23596
|
+
.catch(function(e) {
|
|
23597
|
+
logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23598
|
+
});
|
|
23599
|
+
});
|
|
23600
|
+
|
|
23601
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23602
|
+
};
|
|
23603
|
+
}
|
|
23604
|
+
);
|
|
23605
|
+
return function() {
|
|
23606
|
+
restorePatch();
|
|
23607
|
+
};
|
|
23608
|
+
}
|
|
23609
|
+
|
|
23610
|
+
/**
|
|
23611
|
+
* @param {networkCallback} cb
|
|
23612
|
+
* @param {Window} win
|
|
23613
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23614
|
+
* @returns {listenerHandler}
|
|
23615
|
+
*/
|
|
23616
|
+
function initFetchObserver(cb, win, options) {
|
|
23617
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23618
|
+
return function() {
|
|
23619
|
+
//
|
|
23620
|
+
};
|
|
23621
|
+
}
|
|
23622
|
+
|
|
23623
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23624
|
+
return function() {
|
|
23625
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23626
|
+
/** @type {Response | undefined} */
|
|
23627
|
+
var res;
|
|
23628
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23629
|
+
var networkRequest = {};
|
|
23630
|
+
/** @type {number | undefined} */
|
|
23631
|
+
var after;
|
|
23632
|
+
/** @type {number | undefined} */
|
|
23633
|
+
var before;
|
|
23634
|
+
|
|
23635
|
+
var originalFetchPromise;
|
|
23636
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23637
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23638
|
+
try {
|
|
23639
|
+
/** @type {Headers} */
|
|
23640
|
+
var requestHeaders = {};
|
|
23641
|
+
req.headers.forEach(function(value, header) {
|
|
23642
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23643
|
+
requestHeaders[header] = value;
|
|
23644
|
+
}
|
|
23645
|
+
});
|
|
23646
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23647
|
+
|
|
23648
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23649
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23650
|
+
.then(function(body) {
|
|
23651
|
+
networkRequest.requestBody = body;
|
|
23652
|
+
});
|
|
23653
|
+
}
|
|
23654
|
+
|
|
23655
|
+
after = win.performance.now();
|
|
23656
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23657
|
+
res = response;
|
|
23658
|
+
before = win.performance.now();
|
|
23659
|
+
|
|
23660
|
+
/** @type {Headers} */
|
|
23661
|
+
var responseHeaders = {};
|
|
23662
|
+
res.headers.forEach(function(value, header) {
|
|
23663
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23664
|
+
responseHeaders[header] = value;
|
|
23665
|
+
}
|
|
23666
|
+
});
|
|
23667
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23668
|
+
|
|
23669
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23670
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23671
|
+
.then(function(body) {
|
|
23672
|
+
networkRequest.responseBody = body;
|
|
23673
|
+
});
|
|
23674
|
+
}
|
|
23675
|
+
|
|
23676
|
+
return res;
|
|
23677
|
+
});
|
|
23678
|
+
} catch (e) {
|
|
23679
|
+
originalFetchPromise = Promise.reject(e);
|
|
23680
|
+
}
|
|
23681
|
+
|
|
23682
|
+
// await concurrently so we don't delay the fetch response
|
|
23683
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23684
|
+
.then(function () {
|
|
23685
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23686
|
+
})
|
|
23687
|
+
.then(function(entry) {
|
|
23688
|
+
if (!entry) {
|
|
23689
|
+
logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23690
|
+
return;
|
|
23691
|
+
}
|
|
23692
|
+
/** @type {NetworkRequest} */
|
|
23693
|
+
var request = {
|
|
23694
|
+
url: entry.name,
|
|
23695
|
+
method: req.method,
|
|
23696
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23697
|
+
status: res ? res.status : undefined,
|
|
23698
|
+
startTime: Math.round(entry.startTime),
|
|
23699
|
+
endTime: Math.round(entry.responseEnd),
|
|
23700
|
+
timeOrigin: getTimeOrigin(win),
|
|
23701
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23702
|
+
requestBody: networkRequest.requestBody,
|
|
23703
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23704
|
+
responseBody: networkRequest.responseBody,
|
|
23705
|
+
};
|
|
23706
|
+
cb({ requests: [request] });
|
|
23707
|
+
})
|
|
23708
|
+
.catch(function (e) {
|
|
23709
|
+
logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23710
|
+
});
|
|
23711
|
+
|
|
23712
|
+
return originalFetchPromise;
|
|
23713
|
+
};
|
|
23714
|
+
});
|
|
23715
|
+
return function() {
|
|
23716
|
+
restorePatch();
|
|
23717
|
+
};
|
|
23718
|
+
}
|
|
23719
|
+
|
|
23720
|
+
/**
|
|
23721
|
+
* @param {networkCallback} callback
|
|
23722
|
+
* @param {Window} win
|
|
23723
|
+
* @param {NetworkRecordOptions} options
|
|
23724
|
+
* @returns {listenerHandler}
|
|
23725
|
+
*/
|
|
23726
|
+
function initNetworkObserver(callback, win, options) {
|
|
23727
|
+
if (!('performance' in win)) {
|
|
23728
|
+
return function() {
|
|
23729
|
+
//
|
|
23730
|
+
};
|
|
23731
|
+
}
|
|
23732
|
+
|
|
23733
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23734
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23735
|
+
options = Object.assign({}, options, {
|
|
23736
|
+
recordHeaders: recordHeaders,
|
|
23737
|
+
recordBodyUrls: recordBodyUrls,
|
|
23738
|
+
});
|
|
23739
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23740
|
+
|
|
23741
|
+
/** @type {networkCallback} */
|
|
23742
|
+
var cb = function(data) {
|
|
23743
|
+
var requests = data.requests.filter(function(request) {
|
|
23744
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23745
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23746
|
+
});
|
|
23747
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23748
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23749
|
+
}
|
|
23750
|
+
};
|
|
23751
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23752
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23753
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23754
|
+
return function() {
|
|
23755
|
+
performanceObserver();
|
|
23756
|
+
xhrObserver();
|
|
23757
|
+
fetchObserver();
|
|
23758
|
+
};
|
|
23759
|
+
}
|
|
23760
|
+
|
|
23761
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23762
|
+
// a changed format in the mixpanel product.
|
|
23763
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23764
|
+
|
|
23765
|
+
/**
|
|
23766
|
+
* @param {NetworkRecordOptions} [options]
|
|
23767
|
+
* @returns {RecordPlugin}
|
|
23768
|
+
*/
|
|
23769
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23770
|
+
return {
|
|
23771
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23772
|
+
observer: initNetworkObserver,
|
|
23773
|
+
options: options,
|
|
23774
|
+
};
|
|
23775
|
+
};
|
|
23776
|
+
|
|
23777
|
+
/**
|
|
23778
|
+
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23779
|
+
*/
|
|
23780
|
+
|
|
23781
|
+
|
|
23782
|
+
var logger$3 = console_with_prefix('recorder');
|
|
23783
|
+
var CompressionStream = win['CompressionStream'];
|
|
23784
|
+
|
|
23785
|
+
var RECORDER_BATCHER_LIB_CONFIG = {
|
|
23786
|
+
'batch_size': 1000,
|
|
23787
|
+
'batch_flush_interval_ms': 10 * 1000,
|
|
23788
|
+
'batch_request_timeout_ms': 90 * 1000,
|
|
23789
|
+
'batch_autostart': true
|
|
23790
|
+
};
|
|
23791
|
+
|
|
23792
|
+
var ACTIVE_SOURCES = new Set([
|
|
23793
|
+
IncrementalSource.MouseMove,
|
|
23794
|
+
IncrementalSource.MouseInteraction,
|
|
23795
|
+
IncrementalSource.Scroll,
|
|
23796
|
+
IncrementalSource.ViewportResize,
|
|
23797
|
+
IncrementalSource.Input,
|
|
23798
|
+
IncrementalSource.TouchMove,
|
|
23799
|
+
IncrementalSource.MediaInteraction,
|
|
23800
|
+
IncrementalSource.Drag,
|
|
23801
|
+
IncrementalSource.Selection,
|
|
23802
|
+
]);
|
|
23803
|
+
|
|
23804
|
+
function isUserEvent(ev) {
|
|
23805
|
+
return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
|
|
23806
|
+
}
|
|
23807
|
+
|
|
23808
|
+
/**
|
|
23809
|
+
* @typedef {Object} SerializedRecording
|
|
23810
|
+
* @property {number} idleExpires
|
|
23811
|
+
* @property {number} maxExpires
|
|
23812
|
+
* @property {number} replayStartTime
|
|
23813
|
+
* @property {number} lastEventTimestamp
|
|
23814
|
+
* @property {number} seqNo
|
|
23815
|
+
* @property {string} batchStartUrl
|
|
23816
|
+
* @property {string} replayId
|
|
23817
|
+
* @property {string} tabId
|
|
23818
|
+
* @property {string} replayStartUrl
|
|
23819
|
+
*/
|
|
23820
|
+
|
|
23821
|
+
/**
|
|
23822
|
+
* @typedef {Object} SessionRecordingOptions
|
|
23823
|
+
* @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
23824
|
+
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
23825
|
+
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23826
|
+
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23827
|
+
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23828
|
+
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23829
|
+
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23830
|
+
* optional properties for deserialization:
|
|
23831
|
+
* @property {number} idleExpires
|
|
23832
|
+
* @property {number} maxExpires
|
|
23833
|
+
* @property {number} replayStartTime
|
|
23834
|
+
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23835
|
+
* @property {number} seqNo
|
|
23836
|
+
* @property {string} batchStartUrl
|
|
23837
|
+
* @property {string} replayStartUrl
|
|
23838
|
+
*/
|
|
23839
|
+
|
|
23840
|
+
/**
|
|
23841
|
+
* @typedef {Object} UserIdInfo
|
|
23842
|
+
* @property {string} distinct_id
|
|
23843
|
+
* @property {string} user_id
|
|
23844
|
+
* @property {string} device_id
|
|
23845
|
+
*/
|
|
23846
|
+
|
|
23847
|
+
|
|
23848
|
+
/**
|
|
23849
|
+
* This class encapsulates a single session recording and its lifecycle.
|
|
23850
|
+
* @param {SessionRecordingOptions} options
|
|
23851
|
+
*/
|
|
23852
|
+
var SessionRecording = function(options) {
|
|
23853
|
+
this._mixpanel = options.mixpanelInstance;
|
|
23854
|
+
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23855
|
+
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23856
|
+
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23857
|
+
this._rrwebRecord = options.rrwebRecord || null;
|
|
23858
|
+
|
|
23859
|
+
// internal rrweb stopRecording function
|
|
23860
|
+
this._stopRecording = null;
|
|
23861
|
+
this.replayId = options.replayId;
|
|
23862
|
+
|
|
23863
|
+
this.batchStartUrl = options.batchStartUrl || null;
|
|
23864
|
+
this.replayStartUrl = options.replayStartUrl || null;
|
|
23865
|
+
this.idleExpires = options.idleExpires || null;
|
|
23866
|
+
this.maxExpires = options.maxExpires || null;
|
|
23867
|
+
this.replayStartTime = options.replayStartTime || null;
|
|
23868
|
+
this.lastEventTimestamp = options.lastEventTimestamp || null;
|
|
23869
|
+
this.seqNo = options.seqNo || 0;
|
|
23870
|
+
|
|
23871
|
+
this.idleTimeoutId = null;
|
|
23872
|
+
this.maxTimeoutId = null;
|
|
23873
|
+
|
|
23874
|
+
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23875
|
+
this.recordMinMs = 0;
|
|
23876
|
+
|
|
23877
|
+
// disable persistence if localStorage is not supported
|
|
23878
|
+
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
23879
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
|
|
23880
|
+
|
|
23881
|
+
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23882
|
+
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23883
|
+
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23884
|
+
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23885
|
+
errorReporter: this.reportError.bind(this),
|
|
23886
|
+
flushOnlyOnInterval: true,
|
|
23887
|
+
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23888
|
+
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23889
|
+
queueStorage: this.queueStorage,
|
|
23890
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
23891
|
+
usePersistence: usePersistence,
|
|
23892
|
+
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23893
|
+
|
|
23894
|
+
// increased throttle and shared lock timeout because recording events are very high frequency.
|
|
23895
|
+
// this will minimize the amount of lock contention between enqueued events.
|
|
23896
|
+
// for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
|
|
23897
|
+
enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
|
|
23898
|
+
sharedLockTimeoutMS: 10 * 1000,
|
|
23899
|
+
});
|
|
23900
|
+
};
|
|
23901
|
+
|
|
23902
|
+
/**
|
|
23903
|
+
* @returns {UserIdInfo}
|
|
23904
|
+
*/
|
|
23905
|
+
SessionRecording.prototype.getUserIdInfo = function () {
|
|
23906
|
+
if (this.finalFlushUserIdInfo) {
|
|
23907
|
+
return this.finalFlushUserIdInfo;
|
|
23908
|
+
}
|
|
23909
|
+
|
|
23910
|
+
var userIdInfo = {
|
|
23911
|
+
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
23912
|
+
};
|
|
23913
|
+
|
|
23914
|
+
// send ID management props if they exist
|
|
23915
|
+
var deviceId = this._mixpanel.get_property('$device_id');
|
|
23916
|
+
if (deviceId) {
|
|
23917
|
+
userIdInfo['$device_id'] = deviceId;
|
|
23918
|
+
}
|
|
23919
|
+
var userId = this._mixpanel.get_property('$user_id');
|
|
23920
|
+
if (userId) {
|
|
23921
|
+
userIdInfo['$user_id'] = userId;
|
|
23922
|
+
}
|
|
23923
|
+
return userIdInfo;
|
|
23924
|
+
};
|
|
23925
|
+
|
|
23926
|
+
SessionRecording.prototype.unloadPersistedData = function () {
|
|
23927
|
+
this.batcher.stop();
|
|
23928
|
+
|
|
23929
|
+
return this.queueStorage.init().catch(function () {
|
|
23930
|
+
this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
|
|
23931
|
+
}.bind(this)).then(function () {
|
|
23932
|
+
// if the recording is too short, just delete any stored events without flushing
|
|
23933
|
+
if (this.getDurationMs() < this._getRecordMinMs()) {
|
|
23934
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23935
|
+
}
|
|
23936
|
+
|
|
23937
|
+
return this.batcher.flush()
|
|
23938
|
+
.then(function () {
|
|
23939
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23940
|
+
}.bind(this));
|
|
23941
|
+
}.bind(this));
|
|
23942
|
+
};
|
|
23943
|
+
|
|
23944
|
+
SessionRecording.prototype.getConfig = function(configVar) {
|
|
23945
|
+
return this._mixpanel.get_config(configVar);
|
|
23946
|
+
};
|
|
23947
|
+
|
|
23948
|
+
// Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
|
|
23221
23949
|
// reaches into this class instance and expects the snake case version of the function.
|
|
23222
23950
|
// eslint-disable-next-line camelcase
|
|
23223
23951
|
SessionRecording.prototype.get_config = function(configVar) {
|
|
@@ -23231,14 +23959,14 @@
|
|
|
23231
23959
|
}
|
|
23232
23960
|
|
|
23233
23961
|
if (this._stopRecording !== null) {
|
|
23234
|
-
logger$
|
|
23962
|
+
logger$3.log('Recording already in progress, skipping startRecording.');
|
|
23235
23963
|
return;
|
|
23236
23964
|
}
|
|
23237
23965
|
|
|
23238
23966
|
this.recordMaxMs = this.getConfig('record_max_ms');
|
|
23239
23967
|
if (this.recordMaxMs > MAX_RECORDING_MS) {
|
|
23240
23968
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23241
|
-
logger$
|
|
23969
|
+
logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
23242
23970
|
}
|
|
23243
23971
|
|
|
23244
23972
|
if (!this.maxExpires) {
|
|
@@ -23279,6 +24007,31 @@
|
|
|
23279
24007
|
|
|
23280
24008
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23281
24009
|
|
|
24010
|
+
var plugins = [];
|
|
24011
|
+
if (this.getConfig('record_network')) {
|
|
24012
|
+
var options = this.getConfig('record_network_options') || {};
|
|
24013
|
+
// don't track requests to Mixpanel /record API
|
|
24014
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
24015
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
24016
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
24017
|
+
|
|
24018
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
24019
|
+
}
|
|
24020
|
+
|
|
24021
|
+
if (this.getConfig('record_console')) {
|
|
24022
|
+
plugins.push(
|
|
24023
|
+
getRecordConsolePlugin({
|
|
24024
|
+
stringifyOptions: {
|
|
24025
|
+
stringLengthLimit: 1000,
|
|
24026
|
+
numOfKeysLimit: 50,
|
|
24027
|
+
depthOfLimit: 2
|
|
24028
|
+
}
|
|
24029
|
+
})
|
|
24030
|
+
);
|
|
24031
|
+
}
|
|
24032
|
+
|
|
24033
|
+
var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
|
|
24034
|
+
|
|
23282
24035
|
try {
|
|
23283
24036
|
this._stopRecording = this._rrwebRecord({
|
|
23284
24037
|
'emit': function (ev) {
|
|
@@ -23313,19 +24066,13 @@
|
|
|
23313
24066
|
'maskTextSelector': '*',
|
|
23314
24067
|
'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
|
|
23315
24068
|
'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
|
|
24069
|
+
'recordCrossOriginIframes': validatedOrigins.length > 0,
|
|
24070
|
+
'allowedIframeOrigins': validatedOrigins,
|
|
23316
24071
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
23317
24072
|
'sampling': {
|
|
23318
24073
|
'canvas': 15
|
|
23319
24074
|
},
|
|
23320
|
-
'plugins':
|
|
23321
|
-
getRecordConsolePlugin({
|
|
23322
|
-
stringifyOptions: {
|
|
23323
|
-
stringLengthLimit: 1000,
|
|
23324
|
-
numOfKeysLimit: 50,
|
|
23325
|
-
depthOfLimit: 2
|
|
23326
|
-
}
|
|
23327
|
-
})
|
|
23328
|
-
] : []
|
|
24075
|
+
'plugins': plugins,
|
|
23329
24076
|
});
|
|
23330
24077
|
} catch (err) {
|
|
23331
24078
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23440,6 +24187,10 @@
|
|
|
23440
24187
|
return recording;
|
|
23441
24188
|
};
|
|
23442
24189
|
|
|
24190
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24191
|
+
return this.getConfig('api_routes')['record'];
|
|
24192
|
+
};
|
|
24193
|
+
|
|
23443
24194
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23444
24195
|
var onSuccess = function (response, responseBody) {
|
|
23445
24196
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23459,7 +24210,7 @@
|
|
|
23459
24210
|
});
|
|
23460
24211
|
}.bind(this);
|
|
23461
24212
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23462
|
-
win['fetch'](apiHost + '/' + this.
|
|
24213
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23463
24214
|
'method': 'POST',
|
|
23464
24215
|
'headers': {
|
|
23465
24216
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23541,14 +24292,14 @@
|
|
|
23541
24292
|
|
|
23542
24293
|
|
|
23543
24294
|
SessionRecording.prototype.reportError = function(msg, err) {
|
|
23544
|
-
logger$
|
|
24295
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
23545
24296
|
try {
|
|
23546
24297
|
if (!err && !(msg instanceof Error)) {
|
|
23547
24298
|
msg = new Error(msg);
|
|
23548
24299
|
}
|
|
23549
24300
|
this.getConfig('error_reporter')(msg, err);
|
|
23550
24301
|
} catch(err) {
|
|
23551
|
-
logger$
|
|
24302
|
+
logger$3.error(err);
|
|
23552
24303
|
}
|
|
23553
24304
|
};
|
|
23554
24305
|
|
|
@@ -23577,7 +24328,7 @@
|
|
|
23577
24328
|
var configValue = this.getConfig('record_min_ms');
|
|
23578
24329
|
|
|
23579
24330
|
if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
23580
|
-
logger$
|
|
24331
|
+
logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
23581
24332
|
return MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
23582
24333
|
}
|
|
23583
24334
|
|
|
@@ -23740,7 +24491,7 @@
|
|
|
23740
24491
|
.catch(this.handleError.bind(this));
|
|
23741
24492
|
};
|
|
23742
24493
|
|
|
23743
|
-
var logger$
|
|
24494
|
+
var logger$2 = console_with_prefix('recorder');
|
|
23744
24495
|
|
|
23745
24496
|
/**
|
|
23746
24497
|
* Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
|
|
@@ -23756,7 +24507,7 @@
|
|
|
23756
24507
|
*/
|
|
23757
24508
|
this.recordingRegistry = new RecordingRegistry({
|
|
23758
24509
|
mixpanelInstance: this.mixpanelInstance,
|
|
23759
|
-
errorReporter: logger$
|
|
24510
|
+
errorReporter: logger$2.error,
|
|
23760
24511
|
sharedLockStorage: sharedLockStorage
|
|
23761
24512
|
});
|
|
23762
24513
|
this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
|
|
@@ -23768,17 +24519,17 @@
|
|
|
23768
24519
|
MixpanelRecorder.prototype.startRecording = function(options) {
|
|
23769
24520
|
options = options || {};
|
|
23770
24521
|
if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
|
|
23771
|
-
logger$
|
|
24522
|
+
logger$2.log('Recording already in progress, skipping startRecording.');
|
|
23772
24523
|
return;
|
|
23773
24524
|
}
|
|
23774
24525
|
|
|
23775
24526
|
var onIdleTimeout = function () {
|
|
23776
|
-
logger$
|
|
24527
|
+
logger$2.log('Idle timeout reached, restarting recording.');
|
|
23777
24528
|
this.resetRecording();
|
|
23778
24529
|
}.bind(this);
|
|
23779
24530
|
|
|
23780
24531
|
var onMaxLengthReached = function () {
|
|
23781
|
-
logger$
|
|
24532
|
+
logger$2.log('Max recording length reached, stopping recording.');
|
|
23782
24533
|
this.resetRecording();
|
|
23783
24534
|
}.bind(this);
|
|
23784
24535
|
|
|
@@ -23848,7 +24599,7 @@
|
|
|
23848
24599
|
} else if (startNewIfInactive) {
|
|
23849
24600
|
return this.startRecording({shouldStopBatcher: false});
|
|
23850
24601
|
} else {
|
|
23851
|
-
logger$
|
|
24602
|
+
logger$2.log('No resumable recording found.');
|
|
23852
24603
|
return null;
|
|
23853
24604
|
}
|
|
23854
24605
|
}.bind(this));
|
|
@@ -23860,8 +24611,12 @@
|
|
|
23860
24611
|
this.startRecording({shouldStopBatcher: true});
|
|
23861
24612
|
};
|
|
23862
24613
|
|
|
24614
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24615
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24616
|
+
};
|
|
24617
|
+
|
|
23863
24618
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23864
|
-
if (this.
|
|
24619
|
+
if (this.isRecording()) {
|
|
23865
24620
|
return this.activeRecording.replayId;
|
|
23866
24621
|
} else {
|
|
23867
24622
|
return null;
|
|
@@ -23978,7 +24733,7 @@
|
|
|
23978
24733
|
observer.observe(shadowRoot, this.observerConfig);
|
|
23979
24734
|
this.shadowObservers.push(observer);
|
|
23980
24735
|
} catch (e) {
|
|
23981
|
-
logger$
|
|
24736
|
+
logger$5.critical('Error while observing shadow root', e);
|
|
23982
24737
|
}
|
|
23983
24738
|
};
|
|
23984
24739
|
|
|
@@ -23989,7 +24744,7 @@
|
|
|
23989
24744
|
}
|
|
23990
24745
|
|
|
23991
24746
|
if (!weakSetSupported()) {
|
|
23992
|
-
logger$
|
|
24747
|
+
logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
23993
24748
|
return;
|
|
23994
24749
|
}
|
|
23995
24750
|
|
|
@@ -24005,7 +24760,7 @@
|
|
|
24005
24760
|
try {
|
|
24006
24761
|
this.shadowObservers[i].disconnect();
|
|
24007
24762
|
} catch (e) {
|
|
24008
|
-
logger$
|
|
24763
|
+
logger$5.critical('Error while disconnecting shadow DOM observer', e);
|
|
24009
24764
|
}
|
|
24010
24765
|
}
|
|
24011
24766
|
this.shadowObservers = [];
|
|
@@ -24193,7 +24948,7 @@
|
|
|
24193
24948
|
|
|
24194
24949
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24195
24950
|
} catch (e) {
|
|
24196
|
-
logger$
|
|
24951
|
+
logger$5.critical('Error while setting up mutation observer', e);
|
|
24197
24952
|
}
|
|
24198
24953
|
}
|
|
24199
24954
|
|
|
@@ -24208,7 +24963,7 @@
|
|
|
24208
24963
|
);
|
|
24209
24964
|
this.shadowDOMObserver.start();
|
|
24210
24965
|
} catch (e) {
|
|
24211
|
-
logger$
|
|
24966
|
+
logger$5.critical('Error while setting up shadow DOM observer', e);
|
|
24212
24967
|
this.shadowDOMObserver = null;
|
|
24213
24968
|
}
|
|
24214
24969
|
}
|
|
@@ -24235,7 +24990,7 @@
|
|
|
24235
24990
|
try {
|
|
24236
24991
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24237
24992
|
} catch (e) {
|
|
24238
|
-
logger$
|
|
24993
|
+
logger$5.critical('Error while removing event listener', e);
|
|
24239
24994
|
}
|
|
24240
24995
|
}
|
|
24241
24996
|
this.eventListeners = [];
|
|
@@ -24244,7 +24999,7 @@
|
|
|
24244
24999
|
try {
|
|
24245
25000
|
this.mutationObserver.disconnect();
|
|
24246
25001
|
} catch (e) {
|
|
24247
|
-
logger$
|
|
25002
|
+
logger$5.critical('Error while disconnecting mutation observer', e);
|
|
24248
25003
|
}
|
|
24249
25004
|
this.mutationObserver = null;
|
|
24250
25005
|
}
|
|
@@ -24253,7 +25008,7 @@
|
|
|
24253
25008
|
try {
|
|
24254
25009
|
this.shadowDOMObserver.stop();
|
|
24255
25010
|
} catch (e) {
|
|
24256
|
-
logger$
|
|
25011
|
+
logger$5.critical('Error while stopping shadow DOM observer', e);
|
|
24257
25012
|
}
|
|
24258
25013
|
this.shadowDOMObserver = null;
|
|
24259
25014
|
}
|
|
@@ -24331,7 +25086,7 @@
|
|
|
24331
25086
|
|
|
24332
25087
|
Autocapture.prototype.init = function() {
|
|
24333
25088
|
if (!minDOMApisSupported()) {
|
|
24334
|
-
logger$
|
|
25089
|
+
logger$5.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24335
25090
|
return;
|
|
24336
25091
|
}
|
|
24337
25092
|
this.initPageListeners();
|
|
@@ -24363,27 +25118,15 @@
|
|
|
24363
25118
|
};
|
|
24364
25119
|
|
|
24365
25120
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24366
|
-
var i;
|
|
24367
25121
|
var currentUrl = _.info.currentUrl();
|
|
24368
25122
|
|
|
24369
25123
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24370
25124
|
if (allowUrlRegexes.length) {
|
|
24371
25125
|
// we're using an allowlist, only track if current URL matches
|
|
24372
|
-
|
|
24373
|
-
|
|
24374
|
-
|
|
24375
|
-
|
|
24376
|
-
if (currentUrl.match(allowRegex)) {
|
|
24377
|
-
allowed = true;
|
|
24378
|
-
break;
|
|
24379
|
-
}
|
|
24380
|
-
} catch (err) {
|
|
24381
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24382
|
-
return true;
|
|
24383
|
-
}
|
|
24384
|
-
}
|
|
24385
|
-
if (!allowed) {
|
|
24386
|
-
// wasn't allowed by any regex
|
|
25126
|
+
try {
|
|
25127
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25128
|
+
} catch (err) {
|
|
25129
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
24387
25130
|
return true;
|
|
24388
25131
|
}
|
|
24389
25132
|
}
|
|
@@ -24393,17 +25136,12 @@
|
|
|
24393
25136
|
return false;
|
|
24394
25137
|
}
|
|
24395
25138
|
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24399
|
-
|
|
24400
|
-
|
|
24401
|
-
} catch (err) {
|
|
24402
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24403
|
-
return true;
|
|
24404
|
-
}
|
|
25139
|
+
try {
|
|
25140
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25141
|
+
} catch (err) {
|
|
25142
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
25143
|
+
return true;
|
|
24405
25144
|
}
|
|
24406
|
-
return false;
|
|
24407
25145
|
};
|
|
24408
25146
|
|
|
24409
25147
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -24539,7 +25277,7 @@
|
|
|
24539
25277
|
return;
|
|
24540
25278
|
}
|
|
24541
25279
|
|
|
24542
|
-
logger$
|
|
25280
|
+
logger$5.log('Initializing scroll depth tracking');
|
|
24543
25281
|
|
|
24544
25282
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
24545
25283
|
|
|
@@ -24565,7 +25303,7 @@
|
|
|
24565
25303
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
24566
25304
|
return;
|
|
24567
25305
|
}
|
|
24568
|
-
logger$
|
|
25306
|
+
logger$5.log('Initializing click tracking');
|
|
24569
25307
|
|
|
24570
25308
|
this.listenerClick = function(ev) {
|
|
24571
25309
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -24584,7 +25322,7 @@
|
|
|
24584
25322
|
return;
|
|
24585
25323
|
}
|
|
24586
25324
|
|
|
24587
|
-
logger$
|
|
25325
|
+
logger$5.log('Initializing dead click tracking');
|
|
24588
25326
|
if (!this._deadClickTracker) {
|
|
24589
25327
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
24590
25328
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -24618,7 +25356,7 @@
|
|
|
24618
25356
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
24619
25357
|
return;
|
|
24620
25358
|
}
|
|
24621
|
-
logger$
|
|
25359
|
+
logger$5.log('Initializing input tracking');
|
|
24622
25360
|
|
|
24623
25361
|
this.listenerChange = function(ev) {
|
|
24624
25362
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -24635,7 +25373,7 @@
|
|
|
24635
25373
|
if (!this.pageviewTrackingConfig()) {
|
|
24636
25374
|
return;
|
|
24637
25375
|
}
|
|
24638
|
-
logger$
|
|
25376
|
+
logger$5.log('Initializing pageview tracking');
|
|
24639
25377
|
|
|
24640
25378
|
var previousTrackedUrl = '';
|
|
24641
25379
|
var tracked = false;
|
|
@@ -24670,7 +25408,7 @@
|
|
|
24670
25408
|
}
|
|
24671
25409
|
if (didPathChange) {
|
|
24672
25410
|
this.lastScrollCheckpoint = 0;
|
|
24673
|
-
logger$
|
|
25411
|
+
logger$5.log('Path change: re-initializing scroll depth checkpoints');
|
|
24674
25412
|
}
|
|
24675
25413
|
}
|
|
24676
25414
|
}.bind(this));
|
|
@@ -24685,7 +25423,7 @@
|
|
|
24685
25423
|
return;
|
|
24686
25424
|
}
|
|
24687
25425
|
|
|
24688
|
-
logger$
|
|
25426
|
+
logger$5.log('Initializing rage click tracking');
|
|
24689
25427
|
if (!this._rageClickTracker) {
|
|
24690
25428
|
this._rageClickTracker = new RageClickTracker();
|
|
24691
25429
|
}
|
|
@@ -24715,7 +25453,7 @@
|
|
|
24715
25453
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
24716
25454
|
return;
|
|
24717
25455
|
}
|
|
24718
|
-
logger$
|
|
25456
|
+
logger$5.log('Initializing scroll tracking');
|
|
24719
25457
|
this.lastScrollCheckpoint = 0;
|
|
24720
25458
|
|
|
24721
25459
|
var scrollTrackFunction = function() {
|
|
@@ -24752,7 +25490,7 @@
|
|
|
24752
25490
|
}
|
|
24753
25491
|
}
|
|
24754
25492
|
} catch (err) {
|
|
24755
|
-
logger$
|
|
25493
|
+
logger$5.critical('Error while calculating scroll percentage', err);
|
|
24756
25494
|
}
|
|
24757
25495
|
if (shouldTrack) {
|
|
24758
25496
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -24770,7 +25508,7 @@
|
|
|
24770
25508
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
24771
25509
|
return;
|
|
24772
25510
|
}
|
|
24773
|
-
logger$
|
|
25511
|
+
logger$5.log('Initializing submit tracking');
|
|
24774
25512
|
|
|
24775
25513
|
this.listenerSubmit = function(ev) {
|
|
24776
25514
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -24792,7 +25530,7 @@
|
|
|
24792
25530
|
return;
|
|
24793
25531
|
}
|
|
24794
25532
|
|
|
24795
|
-
logger$
|
|
25533
|
+
logger$5.log('Initializing page visibility tracking.');
|
|
24796
25534
|
this._initScrollDepthTracking();
|
|
24797
25535
|
var previousTrackedUrl = _.info.currentUrl();
|
|
24798
25536
|
|
|
@@ -24877,7 +25615,7 @@
|
|
|
24877
25615
|
return win[TARGETING_GLOBAL_NAME];
|
|
24878
25616
|
};
|
|
24879
25617
|
|
|
24880
|
-
var logger = console_with_prefix('flags');
|
|
25618
|
+
var logger$1 = console_with_prefix('flags');
|
|
24881
25619
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
24882
25620
|
|
|
24883
25621
|
var CONFIG_CONTEXT = 'context';
|
|
@@ -24920,7 +25658,7 @@
|
|
|
24920
25658
|
|
|
24921
25659
|
FeatureFlagManager.prototype.init = function() {
|
|
24922
25660
|
if (!this.minApisSupported()) {
|
|
24923
|
-
logger.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
25661
|
+
logger$1.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
24924
25662
|
return;
|
|
24925
25663
|
}
|
|
24926
25664
|
|
|
@@ -24955,7 +25693,7 @@
|
|
|
24955
25693
|
|
|
24956
25694
|
FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
24957
25695
|
if (!this.isSystemEnabled()) {
|
|
24958
|
-
logger.critical('Feature Flags not enabled, cannot update context');
|
|
25696
|
+
logger$1.critical('Feature Flags not enabled, cannot update context');
|
|
24959
25697
|
return Promise.resolve();
|
|
24960
25698
|
}
|
|
24961
25699
|
|
|
@@ -24972,7 +25710,7 @@
|
|
|
24972
25710
|
|
|
24973
25711
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
24974
25712
|
if (!this.isSystemEnabled()) {
|
|
24975
|
-
logger.error('Feature Flags not enabled');
|
|
25713
|
+
logger$1.error('Feature Flags not enabled');
|
|
24976
25714
|
}
|
|
24977
25715
|
return !!this.flags;
|
|
24978
25716
|
};
|
|
@@ -24985,7 +25723,7 @@
|
|
|
24985
25723
|
var distinctId = this.getMpProperty('distinct_id');
|
|
24986
25724
|
var deviceId = this.getMpProperty('$device_id');
|
|
24987
25725
|
var traceparent = generateTraceparent();
|
|
24988
|
-
logger.log('Fetching flags for distinct ID: ' + distinctId);
|
|
25726
|
+
logger$1.log('Fetching flags for distinct ID: ' + distinctId);
|
|
24989
25727
|
|
|
24990
25728
|
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
24991
25729
|
var searchParams = new URLSearchParams();
|
|
@@ -25084,11 +25822,11 @@
|
|
|
25084
25822
|
this._loadTargetingIfNeeded();
|
|
25085
25823
|
}.bind(this)).catch(function(error) {
|
|
25086
25824
|
this.markFetchComplete();
|
|
25087
|
-
logger.error(error);
|
|
25825
|
+
logger$1.error(error);
|
|
25088
25826
|
}.bind(this));
|
|
25089
25827
|
}.bind(this)).catch(function(error) {
|
|
25090
25828
|
this.markFetchComplete();
|
|
25091
|
-
logger.error(error);
|
|
25829
|
+
logger$1.error(error);
|
|
25092
25830
|
}.bind(this));
|
|
25093
25831
|
|
|
25094
25832
|
return this.fetchPromise;
|
|
@@ -25096,7 +25834,7 @@
|
|
|
25096
25834
|
|
|
25097
25835
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
25098
25836
|
if (!this._fetchInProgressStartTime) {
|
|
25099
|
-
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
25837
|
+
logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
25100
25838
|
return;
|
|
25101
25839
|
}
|
|
25102
25840
|
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
@@ -25118,7 +25856,7 @@
|
|
|
25118
25856
|
|
|
25119
25857
|
if (hasPropertyFilters) {
|
|
25120
25858
|
this.getTargeting().then(function() {
|
|
25121
|
-
logger.log('targeting loaded for property filter evaluation');
|
|
25859
|
+
logger$1.log('targeting loaded for property filter evaluation');
|
|
25122
25860
|
});
|
|
25123
25861
|
}
|
|
25124
25862
|
};
|
|
@@ -25133,7 +25871,7 @@
|
|
|
25133
25871
|
this.loadExtraBundle.bind(this),
|
|
25134
25872
|
this.targetingSrc
|
|
25135
25873
|
).catch(function(error) {
|
|
25136
|
-
logger.error('Failed to load targeting: ' + error);
|
|
25874
|
+
logger$1.error('Failed to load targeting: ' + error);
|
|
25137
25875
|
}.bind(this));
|
|
25138
25876
|
};
|
|
25139
25877
|
|
|
@@ -25187,7 +25925,7 @@
|
|
|
25187
25925
|
|
|
25188
25926
|
// If no targeting library and event has property filters, skip it
|
|
25189
25927
|
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
25190
|
-
logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
25928
|
+
logger$1.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
25191
25929
|
return;
|
|
25192
25930
|
}
|
|
25193
25931
|
|
|
@@ -25210,7 +25948,7 @@
|
|
|
25210
25948
|
}
|
|
25211
25949
|
|
|
25212
25950
|
if (matchResult.error) {
|
|
25213
|
-
logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
25951
|
+
logger$1.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
25214
25952
|
return;
|
|
25215
25953
|
}
|
|
25216
25954
|
|
|
@@ -25218,7 +25956,7 @@
|
|
|
25218
25956
|
return;
|
|
25219
25957
|
}
|
|
25220
25958
|
|
|
25221
|
-
logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
25959
|
+
logger$1.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
25222
25960
|
|
|
25223
25961
|
var newVariant = {
|
|
25224
25962
|
'key': pendingEvent['pending_variant']['variant_key'],
|
|
@@ -25259,7 +25997,7 @@
|
|
|
25259
25997
|
'first_time_event_hash': firstTimeEventHash
|
|
25260
25998
|
};
|
|
25261
25999
|
|
|
25262
|
-
logger.log('Recording first-time event for flag: ' + flagId);
|
|
26000
|
+
logger$1.log('Recording first-time event for flag: ' + flagId);
|
|
25263
26001
|
|
|
25264
26002
|
// Fire-and-forget POST request
|
|
25265
26003
|
this.fetch.call(win, url, {
|
|
@@ -25272,130 +26010,446 @@
|
|
|
25272
26010
|
'body': JSON.stringify(payload)
|
|
25273
26011
|
}).catch(function(error) {
|
|
25274
26012
|
// Silent failure - cohort sync will catch up
|
|
25275
|
-
logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26013
|
+
logger$1.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26014
|
+
});
|
|
26015
|
+
};
|
|
26016
|
+
|
|
26017
|
+
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
26018
|
+
if (!this.fetchPromise) {
|
|
26019
|
+
return new Promise(function(resolve) {
|
|
26020
|
+
logger$1.critical('Feature Flags not initialized');
|
|
26021
|
+
resolve(fallback);
|
|
26022
|
+
});
|
|
26023
|
+
}
|
|
26024
|
+
|
|
26025
|
+
return this.fetchPromise.then(function() {
|
|
26026
|
+
return this.getVariantSync(featureName, fallback);
|
|
26027
|
+
}.bind(this)).catch(function(error) {
|
|
26028
|
+
logger$1.error(error);
|
|
26029
|
+
return fallback;
|
|
26030
|
+
});
|
|
26031
|
+
};
|
|
26032
|
+
|
|
26033
|
+
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
26034
|
+
if (!this.areFlagsReady()) {
|
|
26035
|
+
logger$1.log('Flags not loaded yet');
|
|
26036
|
+
return fallback;
|
|
26037
|
+
}
|
|
26038
|
+
var feature = this.flags.get(featureName);
|
|
26039
|
+
if (!feature) {
|
|
26040
|
+
logger$1.log('No flag found: "' + featureName + '"');
|
|
26041
|
+
return fallback;
|
|
26042
|
+
}
|
|
26043
|
+
this.trackFeatureCheck(featureName, feature);
|
|
26044
|
+
return feature;
|
|
26045
|
+
};
|
|
26046
|
+
|
|
26047
|
+
FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
|
|
26048
|
+
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
26049
|
+
return feature['value'];
|
|
26050
|
+
}).catch(function(error) {
|
|
26051
|
+
logger$1.error(error);
|
|
26052
|
+
return fallbackValue;
|
|
26053
|
+
});
|
|
26054
|
+
};
|
|
26055
|
+
|
|
26056
|
+
// TODO remove deprecated method
|
|
26057
|
+
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
26058
|
+
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.');
|
|
26059
|
+
return this.getVariantValue(featureName, fallbackValue);
|
|
26060
|
+
};
|
|
26061
|
+
|
|
26062
|
+
FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
|
|
26063
|
+
return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
|
|
26064
|
+
};
|
|
26065
|
+
|
|
26066
|
+
FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
|
|
26067
|
+
return this.getVariantValue(featureName).then(function() {
|
|
26068
|
+
return this.isEnabledSync(featureName, fallbackValue);
|
|
26069
|
+
}.bind(this)).catch(function(error) {
|
|
26070
|
+
logger$1.error(error);
|
|
26071
|
+
return fallbackValue;
|
|
25276
26072
|
});
|
|
25277
26073
|
};
|
|
25278
26074
|
|
|
25279
|
-
FeatureFlagManager.prototype.
|
|
25280
|
-
|
|
25281
|
-
|
|
25282
|
-
|
|
25283
|
-
|
|
25284
|
-
|
|
26075
|
+
FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
|
|
26076
|
+
fallbackValue = fallbackValue || false;
|
|
26077
|
+
var val = this.getVariantValueSync(featureName, fallbackValue);
|
|
26078
|
+
if (val !== true && val !== false) {
|
|
26079
|
+
logger$1.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
26080
|
+
val = fallbackValue;
|
|
26081
|
+
}
|
|
26082
|
+
return val;
|
|
26083
|
+
};
|
|
26084
|
+
|
|
26085
|
+
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
26086
|
+
if (this.trackedFeatures.has(featureName)) {
|
|
26087
|
+
return;
|
|
26088
|
+
}
|
|
26089
|
+
this.trackedFeatures.add(featureName);
|
|
26090
|
+
|
|
26091
|
+
var trackingProperties = {
|
|
26092
|
+
'Experiment name': featureName,
|
|
26093
|
+
'Variant name': feature['key'],
|
|
26094
|
+
'$experiment_type': 'feature_flag',
|
|
26095
|
+
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
26096
|
+
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
26097
|
+
'Variant fetch latency (ms)': this._fetchLatency,
|
|
26098
|
+
'Variant fetch traceparent': this._traceparent,
|
|
26099
|
+
};
|
|
26100
|
+
|
|
26101
|
+
if (feature['experiment_id'] !== 'undefined') {
|
|
26102
|
+
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
26103
|
+
}
|
|
26104
|
+
if (feature['is_experiment_active'] !== 'undefined') {
|
|
26105
|
+
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
26106
|
+
}
|
|
26107
|
+
if (feature['is_qa_tester'] !== 'undefined') {
|
|
26108
|
+
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
26109
|
+
}
|
|
26110
|
+
|
|
26111
|
+
this.track('$experiment_started', trackingProperties);
|
|
26112
|
+
};
|
|
26113
|
+
|
|
26114
|
+
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
26115
|
+
return !!this.fetch &&
|
|
26116
|
+
typeof Promise !== 'undefined' &&
|
|
26117
|
+
typeof Map !== 'undefined' &&
|
|
26118
|
+
typeof Set !== 'undefined';
|
|
26119
|
+
};
|
|
26120
|
+
|
|
26121
|
+
safewrapClass(FeatureFlagManager);
|
|
26122
|
+
|
|
26123
|
+
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
26124
|
+
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
26125
|
+
FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
|
|
26126
|
+
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
26127
|
+
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
26128
|
+
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
26129
|
+
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
26130
|
+
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
26131
|
+
|
|
26132
|
+
// Deprecated method
|
|
26133
|
+
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
26134
|
+
|
|
26135
|
+
// Exports intended only for testing
|
|
26136
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26137
|
+
|
|
26138
|
+
/* eslint camelcase: "off" */
|
|
26139
|
+
|
|
26140
|
+
|
|
26141
|
+
var logger = console_with_prefix('recorder');
|
|
26142
|
+
|
|
26143
|
+
var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
|
|
26144
|
+
var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
|
|
26145
|
+
|
|
26146
|
+
|
|
26147
|
+
/**
|
|
26148
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26149
|
+
* @constructor
|
|
26150
|
+
*/
|
|
26151
|
+
var RecorderManager = function(initOptions) {
|
|
26152
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26153
|
+
// but ideally we should be able to remove this dependency.
|
|
26154
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26155
|
+
|
|
26156
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26157
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26158
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26159
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26160
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26161
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26162
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26163
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26164
|
+
|
|
26165
|
+
this._recorder = null;
|
|
26166
|
+
this._parentReplayId = null;
|
|
26167
|
+
this._parentFrameRetryInterval = null;
|
|
26168
|
+
};
|
|
26169
|
+
|
|
26170
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26171
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26172
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26173
|
+
return PromisePolyfill.resolve(false);
|
|
26174
|
+
}
|
|
26175
|
+
|
|
26176
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26177
|
+
var tab_id = this.getTabId();
|
|
26178
|
+
return recording_registry_idb.init()
|
|
26179
|
+
.then(function () {
|
|
26180
|
+
return recording_registry_idb.getAll();
|
|
26181
|
+
})
|
|
26182
|
+
.then(function (recordings) {
|
|
26183
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26184
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26185
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26186
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26187
|
+
return true;
|
|
26188
|
+
}
|
|
26189
|
+
}
|
|
26190
|
+
return false;
|
|
26191
|
+
})
|
|
26192
|
+
.catch(_.bind(function (err) {
|
|
26193
|
+
this.reportError('Error checking recording registry', err);
|
|
26194
|
+
return false;
|
|
26195
|
+
}, this));
|
|
26196
|
+
};
|
|
26197
|
+
|
|
26198
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26199
|
+
if (!win['MutationObserver']) {
|
|
26200
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26201
|
+
return PromisePolyfill.resolve();
|
|
26202
|
+
}
|
|
26203
|
+
|
|
26204
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26205
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26206
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26207
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26208
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26209
|
+
resolve();
|
|
26210
|
+
}, this));
|
|
26211
|
+
|
|
26212
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26213
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26214
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26215
|
+
} else {
|
|
26216
|
+
handleLoadedRecorder();
|
|
26217
|
+
}
|
|
26218
|
+
}, this));
|
|
26219
|
+
}, this);
|
|
26220
|
+
|
|
26221
|
+
// Cross-origin iframe handling
|
|
26222
|
+
var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger);
|
|
26223
|
+
var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
|
|
26224
|
+
|
|
26225
|
+
if (isCrossOriginRecordingEnabled) {
|
|
26226
|
+
// listen for handshake requests from their own child iframes (including nested)
|
|
26227
|
+
this._setupParentFrameListener(allowedOrigins);
|
|
26228
|
+
|
|
26229
|
+
if (win.parent !== win) {
|
|
26230
|
+
// also wait for parent's replay ID
|
|
26231
|
+
this._setupChildFrameListener(allowedOrigins, loadRecorder);
|
|
26232
|
+
this._sendParentFrameRequestWithRetry(allowedOrigins);
|
|
26233
|
+
return PromisePolyfill.resolve();
|
|
26234
|
+
}
|
|
26235
|
+
}
|
|
26236
|
+
|
|
26237
|
+
/**
|
|
26238
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26239
|
+
* 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.
|
|
26240
|
+
*/
|
|
26241
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26242
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26243
|
+
if (force_start || is_sampled) {
|
|
26244
|
+
return loadRecorder(true);
|
|
26245
|
+
} else {
|
|
26246
|
+
return this.shouldLoadRecorder()
|
|
26247
|
+
.then(_.bind(function (shouldLoad) {
|
|
26248
|
+
if (shouldLoad) {
|
|
26249
|
+
return loadRecorder(false);
|
|
26250
|
+
}
|
|
26251
|
+
return PromisePolyfill.resolve();
|
|
26252
|
+
}, this));
|
|
26253
|
+
}
|
|
26254
|
+
};
|
|
26255
|
+
|
|
26256
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26257
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26258
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26259
|
+
return false;
|
|
26260
|
+
}
|
|
26261
|
+
try {
|
|
26262
|
+
return this._recorder['isRecording']();
|
|
26263
|
+
} catch (e) {
|
|
26264
|
+
this.reportError('Error checking if recording is active', e);
|
|
26265
|
+
return false;
|
|
25285
26266
|
}
|
|
26267
|
+
};
|
|
25286
26268
|
|
|
25287
|
-
|
|
25288
|
-
|
|
25289
|
-
|
|
25290
|
-
|
|
25291
|
-
|
|
25292
|
-
|
|
26269
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26270
|
+
var isRecording = this.isRecording();
|
|
26271
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26272
|
+
|
|
26273
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26274
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26275
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26276
|
+
var newRate = trigger['percentage'];
|
|
26277
|
+
var propertyFilters = trigger['property_filters'];
|
|
26278
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26279
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26280
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26281
|
+
.then(function(targeting) {
|
|
26282
|
+
try {
|
|
26283
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26284
|
+
event_name,
|
|
26285
|
+
properties,
|
|
26286
|
+
{
|
|
26287
|
+
'event_name': event_name,
|
|
26288
|
+
'property_filters': propertyFilters
|
|
26289
|
+
}
|
|
26290
|
+
);
|
|
26291
|
+
if (result['matches']) {
|
|
26292
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26293
|
+
}
|
|
26294
|
+
} catch (err) {
|
|
26295
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26296
|
+
}
|
|
26297
|
+
}.bind(this)).catch(function(err) {
|
|
26298
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26299
|
+
});
|
|
26300
|
+
} else {
|
|
26301
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26302
|
+
}
|
|
26303
|
+
}
|
|
26304
|
+
}
|
|
25293
26305
|
};
|
|
25294
26306
|
|
|
25295
|
-
|
|
25296
|
-
if (
|
|
25297
|
-
|
|
25298
|
-
return fallback;
|
|
26307
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26308
|
+
if (this._recorder) {
|
|
26309
|
+
return this._recorder['stopRecording']();
|
|
25299
26310
|
}
|
|
25300
|
-
|
|
25301
|
-
|
|
25302
|
-
|
|
25303
|
-
|
|
26311
|
+
return PromisePolyfill.resolve();
|
|
26312
|
+
};
|
|
26313
|
+
|
|
26314
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26315
|
+
if (this._recorder) {
|
|
26316
|
+
return this._recorder['pauseRecording']();
|
|
25304
26317
|
}
|
|
25305
|
-
|
|
25306
|
-
return feature;
|
|
26318
|
+
return PromisePolyfill.resolve();
|
|
25307
26319
|
};
|
|
25308
26320
|
|
|
25309
|
-
|
|
25310
|
-
|
|
25311
|
-
return
|
|
25312
|
-
}
|
|
25313
|
-
|
|
25314
|
-
return fallbackValue;
|
|
25315
|
-
});
|
|
26321
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26322
|
+
if (this._recorder) {
|
|
26323
|
+
return this._recorder['resumeRecording']();
|
|
26324
|
+
}
|
|
26325
|
+
return PromisePolyfill.resolve();
|
|
25316
26326
|
};
|
|
25317
26327
|
|
|
25318
|
-
|
|
25319
|
-
|
|
25320
|
-
logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
25321
|
-
return this.getVariantValue(featureName, fallbackValue);
|
|
26328
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26329
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
25322
26330
|
};
|
|
25323
26331
|
|
|
25324
|
-
|
|
25325
|
-
|
|
26332
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26333
|
+
var props = {};
|
|
26334
|
+
var replay_id = this.getSessionReplayId();
|
|
26335
|
+
if (replay_id) {
|
|
26336
|
+
props['$mp_replay_id'] = replay_id;
|
|
26337
|
+
}
|
|
26338
|
+
return props;
|
|
25326
26339
|
};
|
|
25327
26340
|
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
26341
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26342
|
+
var replay_url = null;
|
|
26343
|
+
var replay_id = this.getSessionReplayId();
|
|
26344
|
+
if (replay_id) {
|
|
26345
|
+
var query_params = _.HTTPBuildQuery({
|
|
26346
|
+
'replay_id': replay_id,
|
|
26347
|
+
'distinct_id': this.getDistinctId(),
|
|
26348
|
+
'token': this.getMpConfig('token')
|
|
26349
|
+
});
|
|
26350
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26351
|
+
}
|
|
26352
|
+
return replay_url;
|
|
25335
26353
|
};
|
|
25336
26354
|
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
|
|
25340
|
-
|
|
25341
|
-
logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
25342
|
-
val = fallbackValue;
|
|
26355
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26356
|
+
// Child iframe uses parent's replay ID
|
|
26357
|
+
if (this._parentReplayId) {
|
|
26358
|
+
return this._parentReplayId;
|
|
25343
26359
|
}
|
|
25344
|
-
|
|
26360
|
+
var replay_id = null;
|
|
26361
|
+
if (this._recorder) {
|
|
26362
|
+
replay_id = this._recorder['replayId'];
|
|
26363
|
+
}
|
|
26364
|
+
return replay_id || null;
|
|
25345
26365
|
};
|
|
25346
26366
|
|
|
25347
|
-
|
|
25348
|
-
|
|
26367
|
+
// "private" public method to reach into the recorder in test cases
|
|
26368
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26369
|
+
return this._recorder;
|
|
26370
|
+
};
|
|
26371
|
+
|
|
26372
|
+
RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
|
|
26373
|
+
if (this._childFrameMessageHandler) {
|
|
25349
26374
|
return;
|
|
25350
26375
|
}
|
|
25351
|
-
this
|
|
25352
|
-
|
|
25353
|
-
|
|
25354
|
-
|
|
25355
|
-
'
|
|
25356
|
-
|
|
25357
|
-
|
|
25358
|
-
|
|
25359
|
-
|
|
25360
|
-
|
|
26376
|
+
var self = this;
|
|
26377
|
+
this._childFrameMessageHandler = function(event) {
|
|
26378
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26379
|
+
var data = event.data;
|
|
26380
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
|
|
26381
|
+
self._parentReplayId = data['replayId'];
|
|
26382
|
+
if (data['distinctId']) {
|
|
26383
|
+
self.mixpanelInstance['identify'](data['distinctId']);
|
|
26384
|
+
}
|
|
26385
|
+
self._parentFrameRetryActive = false;
|
|
26386
|
+
win.removeEventListener('message', self._childFrameMessageHandler);
|
|
26387
|
+
self._childFrameMessageHandler = null;
|
|
26388
|
+
loadRecorder(true);
|
|
26389
|
+
}
|
|
25361
26390
|
};
|
|
26391
|
+
win.addEventListener('message', this._childFrameMessageHandler);
|
|
26392
|
+
};
|
|
25362
26393
|
|
|
25363
|
-
|
|
25364
|
-
|
|
25365
|
-
|
|
25366
|
-
|
|
25367
|
-
|
|
25368
|
-
|
|
25369
|
-
|
|
25370
|
-
|
|
26394
|
+
RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
|
|
26395
|
+
var message = {};
|
|
26396
|
+
message['type'] = IFRAME_HANDSHAKE_REQUEST;
|
|
26397
|
+
message['token'] = this.getMpConfig('token');
|
|
26398
|
+
for (var i = 0; i < allowedOrigins.length; i++) {
|
|
26399
|
+
try {
|
|
26400
|
+
win.parent.postMessage(message, allowedOrigins[i]);
|
|
26401
|
+
} catch (e) {
|
|
26402
|
+
// origin mismatch - ignore
|
|
26403
|
+
}
|
|
25371
26404
|
}
|
|
25372
|
-
|
|
25373
|
-
this.track('$experiment_started', trackingProperties);
|
|
25374
26405
|
};
|
|
25375
26406
|
|
|
25376
|
-
|
|
25377
|
-
|
|
25378
|
-
|
|
25379
|
-
|
|
25380
|
-
|
|
25381
|
-
|
|
26407
|
+
RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
|
|
26408
|
+
var self = this;
|
|
26409
|
+
var maxRetries = 10;
|
|
26410
|
+
var retryCount = 0;
|
|
26411
|
+
var delay = 50;
|
|
26412
|
+
this._parentFrameRetryActive = true;
|
|
25382
26413
|
|
|
25383
|
-
|
|
26414
|
+
this._sendParentFrameRequest(allowedOrigins);
|
|
25384
26415
|
|
|
25385
|
-
|
|
25386
|
-
|
|
25387
|
-
|
|
25388
|
-
|
|
25389
|
-
|
|
25390
|
-
|
|
25391
|
-
|
|
25392
|
-
|
|
26416
|
+
function scheduleRetry() {
|
|
26417
|
+
setTimeout(function() {
|
|
26418
|
+
if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
|
|
26419
|
+
return;
|
|
26420
|
+
}
|
|
26421
|
+
self._sendParentFrameRequest(allowedOrigins);
|
|
26422
|
+
delay *= 2;
|
|
26423
|
+
scheduleRetry();
|
|
26424
|
+
}, delay);
|
|
26425
|
+
}
|
|
26426
|
+
scheduleRetry();
|
|
26427
|
+
};
|
|
25393
26428
|
|
|
25394
|
-
|
|
25395
|
-
|
|
26429
|
+
RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
|
|
26430
|
+
if (this._parentFrameMessageHandler) {
|
|
26431
|
+
return;
|
|
26432
|
+
}
|
|
26433
|
+
var self = this;
|
|
26434
|
+
this._parentFrameMessageHandler = function(event) {
|
|
26435
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26436
|
+
var data = event.data;
|
|
26437
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
|
|
26438
|
+
var replayId = self.getSessionReplayId();
|
|
26439
|
+
if (replayId) {
|
|
26440
|
+
var response = {};
|
|
26441
|
+
response['type'] = IFRAME_HANDSHAKE_RESPONSE;
|
|
26442
|
+
response['token'] = self.getMpConfig('token');
|
|
26443
|
+
response['replayId'] = replayId;
|
|
26444
|
+
response['distinctId'] = self.getDistinctId();
|
|
26445
|
+
event.source.postMessage(response, event.origin);
|
|
26446
|
+
}
|
|
26447
|
+
}
|
|
26448
|
+
};
|
|
26449
|
+
win.addEventListener('message', this._parentFrameMessageHandler);
|
|
26450
|
+
};
|
|
25396
26451
|
|
|
25397
|
-
|
|
25398
|
-
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26452
|
+
safewrapClass(RecorderManager);
|
|
25399
26453
|
|
|
25400
26454
|
/* eslint camelcase: "off" */
|
|
25401
26455
|
|
|
@@ -26769,7 +27823,6 @@
|
|
|
26769
27823
|
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
26770
27824
|
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
26771
27825
|
|
|
26772
|
-
|
|
26773
27826
|
/*
|
|
26774
27827
|
* Dynamic... constants? Is that an oxymoron?
|
|
26775
27828
|
*/
|
|
@@ -26854,19 +27907,24 @@
|
|
|
26854
27907
|
'batch_request_timeout_ms': 90000,
|
|
26855
27908
|
'batch_autostart': true,
|
|
26856
27909
|
'hooks': {},
|
|
27910
|
+
'record_allowed_iframe_origins': [],
|
|
26857
27911
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
26858
27912
|
'record_block_selector': 'img, video, audio',
|
|
26859
27913
|
'record_canvas': false,
|
|
26860
27914
|
'record_collect_fonts': false,
|
|
26861
27915
|
'record_console': true,
|
|
26862
27916
|
'record_heatmap_data': false,
|
|
27917
|
+
'recording_event_triggers': {},
|
|
26863
27918
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
26864
27919
|
'record_mask_inputs': true,
|
|
26865
27920
|
'record_max_ms': MAX_RECORDING_MS,
|
|
26866
27921
|
'record_min_ms': 0,
|
|
27922
|
+
'record_network': false,
|
|
27923
|
+
'record_network_options': {},
|
|
26867
27924
|
'record_sessions_percent': 0,
|
|
26868
|
-
'recorder_src':
|
|
26869
|
-
'targeting_src':
|
|
27925
|
+
'recorder_src': null,
|
|
27926
|
+
'targeting_src': null,
|
|
27927
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
26870
27928
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
26871
27929
|
};
|
|
26872
27930
|
|
|
@@ -27020,6 +28078,19 @@
|
|
|
27020
28078
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27021
28079
|
}));
|
|
27022
28080
|
|
|
28081
|
+
this.recorderManager = new RecorderManager({
|
|
28082
|
+
mixpanelInstance: this,
|
|
28083
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28084
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28085
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28086
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28087
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28088
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28089
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28090
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28091
|
+
loadExtraBundle: load_extra_bundle
|
|
28092
|
+
});
|
|
28093
|
+
|
|
27023
28094
|
this['_jsc'] = NOOP_FUNC;
|
|
27024
28095
|
|
|
27025
28096
|
this.__dom_loaded_queue = [];
|
|
@@ -27098,7 +28169,7 @@
|
|
|
27098
28169
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27099
28170
|
trackingFunc: _.bind(this.track, this),
|
|
27100
28171
|
loadExtraBundle: load_extra_bundle,
|
|
27101
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28172
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27102
28173
|
});
|
|
27103
28174
|
this.flags.init();
|
|
27104
28175
|
this['flags'] = this.flags;
|
|
@@ -27111,11 +28182,11 @@
|
|
|
27111
28182
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27112
28183
|
var mode = this.get_config('remote_settings_mode');
|
|
27113
28184
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27114
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27115
|
-
this._check_and_start_session_recording();
|
|
28185
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28186
|
+
return this._check_and_start_session_recording();
|
|
27116
28187
|
}, this));
|
|
27117
28188
|
} else {
|
|
27118
|
-
this._check_and_start_session_recording();
|
|
28189
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27119
28190
|
}
|
|
27120
28191
|
};
|
|
27121
28192
|
|
|
@@ -27159,132 +28230,50 @@
|
|
|
27159
28230
|
return this.tab_id || null;
|
|
27160
28231
|
};
|
|
27161
28232
|
|
|
27162
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27163
|
-
if (this.get_config('disable_persistence')) {
|
|
27164
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27165
|
-
return Promise.resolve(false);
|
|
27166
|
-
}
|
|
27167
|
-
|
|
27168
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27169
|
-
var tab_id = this.get_tab_id();
|
|
27170
|
-
return recording_registry_idb.init()
|
|
27171
|
-
.then(function () {
|
|
27172
|
-
return recording_registry_idb.getAll();
|
|
27173
|
-
})
|
|
27174
|
-
.then(function (recordings) {
|
|
27175
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27176
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27177
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27178
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27179
|
-
return true;
|
|
27180
|
-
}
|
|
27181
|
-
}
|
|
27182
|
-
return false;
|
|
27183
|
-
})
|
|
27184
|
-
.catch(_.bind(function (err) {
|
|
27185
|
-
this.report_error('Error checking recording registry', err);
|
|
27186
|
-
}, this));
|
|
27187
|
-
};
|
|
27188
|
-
|
|
27189
28233
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27190
|
-
|
|
27191
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27192
|
-
return;
|
|
27193
|
-
}
|
|
27194
|
-
|
|
27195
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27196
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27197
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27198
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27199
|
-
}, this);
|
|
27200
|
-
|
|
27201
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27202
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27203
|
-
} else {
|
|
27204
|
-
handleLoadedRecorder();
|
|
27205
|
-
}
|
|
27206
|
-
}, this);
|
|
27207
|
-
|
|
27208
|
-
/**
|
|
27209
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27210
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
27211
|
-
*/
|
|
27212
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27213
|
-
if (force_start || is_sampled) {
|
|
27214
|
-
loadRecorder(true);
|
|
27215
|
-
} else {
|
|
27216
|
-
this._should_load_recorder()
|
|
27217
|
-
.then(function (shouldLoad) {
|
|
27218
|
-
if (shouldLoad) {
|
|
27219
|
-
loadRecorder(false);
|
|
27220
|
-
}
|
|
27221
|
-
});
|
|
27222
|
-
}
|
|
28234
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27223
28235
|
});
|
|
27224
28236
|
|
|
28237
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28238
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28239
|
+
};
|
|
28240
|
+
|
|
27225
28241
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27226
|
-
this._check_and_start_session_recording(true);
|
|
28242
|
+
return this._check_and_start_session_recording(true);
|
|
27227
28243
|
};
|
|
27228
28244
|
|
|
27229
28245
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27230
|
-
|
|
27231
|
-
return this._recorder['stopRecording']();
|
|
27232
|
-
}
|
|
27233
|
-
return Promise.resolve();
|
|
28246
|
+
return this.recorderManager.stopSessionRecording();
|
|
27234
28247
|
};
|
|
27235
28248
|
|
|
27236
28249
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27237
|
-
|
|
27238
|
-
return this._recorder['pauseRecording']();
|
|
27239
|
-
}
|
|
27240
|
-
return Promise.resolve();
|
|
28250
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27241
28251
|
};
|
|
27242
28252
|
|
|
27243
28253
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27244
|
-
|
|
27245
|
-
return this._recorder['resumeRecording']();
|
|
27246
|
-
}
|
|
27247
|
-
return Promise.resolve();
|
|
28254
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27248
28255
|
};
|
|
27249
28256
|
|
|
27250
28257
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27251
|
-
return this.
|
|
28258
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27252
28259
|
};
|
|
27253
28260
|
|
|
27254
28261
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27255
|
-
|
|
27256
|
-
var replay_id = this._get_session_replay_id();
|
|
27257
|
-
if (replay_id) {
|
|
27258
|
-
props['$mp_replay_id'] = replay_id;
|
|
27259
|
-
}
|
|
27260
|
-
return props;
|
|
28262
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27261
28263
|
};
|
|
27262
28264
|
|
|
27263
28265
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27264
|
-
|
|
27265
|
-
var replay_id = this._get_session_replay_id();
|
|
27266
|
-
if (replay_id) {
|
|
27267
|
-
var query_params = _.HTTPBuildQuery({
|
|
27268
|
-
'replay_id': replay_id,
|
|
27269
|
-
'distinct_id': this.get_distinct_id(),
|
|
27270
|
-
'token': this.get_config('token')
|
|
27271
|
-
});
|
|
27272
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27273
|
-
}
|
|
27274
|
-
return replay_url;
|
|
27275
|
-
};
|
|
27276
|
-
|
|
27277
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27278
|
-
var replay_id = null;
|
|
27279
|
-
if (this._recorder) {
|
|
27280
|
-
replay_id = this._recorder['replayId'];
|
|
27281
|
-
}
|
|
27282
|
-
return replay_id || null;
|
|
28266
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27283
28267
|
};
|
|
27284
28268
|
|
|
27285
28269
|
// "private" public method to reach into the recorder in test cases
|
|
27286
28270
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27287
|
-
return this.
|
|
28271
|
+
return this.recorderManager.getRecorder();
|
|
28272
|
+
};
|
|
28273
|
+
|
|
28274
|
+
// "private" public method to get session recording init promise in test cases
|
|
28275
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28276
|
+
return this.__session_recording_init_promise;
|
|
27288
28277
|
};
|
|
27289
28278
|
|
|
27290
28279
|
// Private methods
|
|
@@ -27542,6 +28531,7 @@
|
|
|
27542
28531
|
};
|
|
27543
28532
|
|
|
27544
28533
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28534
|
+
var self = this;
|
|
27545
28535
|
var disableRecordingIfStrict = function() {
|
|
27546
28536
|
if (mode === 'strict') {
|
|
27547
28537
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -27562,7 +28552,6 @@
|
|
|
27562
28552
|
};
|
|
27563
28553
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
27564
28554
|
var full_url = settings_endpoint + '?' + query_string;
|
|
27565
|
-
var self = this;
|
|
27566
28555
|
|
|
27567
28556
|
var abortController = new AbortController();
|
|
27568
28557
|
var timeout_id = setTimeout(function() {
|
|
@@ -27754,6 +28743,34 @@
|
|
|
27754
28743
|
this._execute_array([item]);
|
|
27755
28744
|
};
|
|
27756
28745
|
|
|
28746
|
+
/**
|
|
28747
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
28748
|
+
* this function enable tracking of all events. If passed an
|
|
28749
|
+
* array of event names, those events will be enabled, but other
|
|
28750
|
+
* existing disabled events will continue to be not tracked.
|
|
28751
|
+
*
|
|
28752
|
+
* @param {Array} [events] An array of event names to enable
|
|
28753
|
+
*/
|
|
28754
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
28755
|
+
var keys, new_disabled_events, i, j;
|
|
28756
|
+
|
|
28757
|
+
if (typeof(events) === 'undefined') {
|
|
28758
|
+
this._flags.disable_all_events = false;
|
|
28759
|
+
} else {
|
|
28760
|
+
keys = {};
|
|
28761
|
+
new_disabled_events = [];
|
|
28762
|
+
for (i = 0; i < events.length; i++) {
|
|
28763
|
+
keys[events[i]] = true;
|
|
28764
|
+
}
|
|
28765
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
28766
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
28767
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
28768
|
+
}
|
|
28769
|
+
}
|
|
28770
|
+
this.__disabled_events = new_disabled_events;
|
|
28771
|
+
}
|
|
28772
|
+
};
|
|
28773
|
+
|
|
27757
28774
|
/**
|
|
27758
28775
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
27759
28776
|
* this function disables tracking of any event. If passed an
|
|
@@ -27927,6 +28944,8 @@
|
|
|
27927
28944
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
27928
28945
|
}
|
|
27929
28946
|
|
|
28947
|
+
this._start_recording_on_event(event_name, properties);
|
|
28948
|
+
|
|
27930
28949
|
var data = {
|
|
27931
28950
|
'event': event_name,
|
|
27932
28951
|
'properties': properties
|
|
@@ -29135,6 +30154,7 @@
|
|
|
29135
30154
|
// MixpanelLib Exports
|
|
29136
30155
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29137
30156
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30157
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29138
30158
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29139
30159
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29140
30160
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29178,6 +30198,7 @@
|
|
|
29178
30198
|
|
|
29179
30199
|
// Exports intended only for testing
|
|
29180
30200
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30201
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29181
30202
|
|
|
29182
30203
|
// MixpanelPersistence Exports
|
|
29183
30204
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|