mixpanel-browser 2.75.0 → 2.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +14 -0
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/integration-tests.yml +4 -4
- package/.github/workflows/unit-tests.yml +4 -4
- package/CHANGELOG.md +14 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-DLKbUIEE.js +23669 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CmVvUyFM.js +2520 -0
- package/dist/mixpanel-core.cjs.d.ts +70 -1
- package/dist/mixpanel-core.cjs.js +724 -426
- package/dist/mixpanel-recorder.js +791 -41
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +6 -62
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-modules.cjs.js +724 -426
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +724 -426
- package/dist/mixpanel-with-recorder.d.ts +70 -1
- package/dist/mixpanel-with-recorder.js +1471 -450
- package/dist/mixpanel-with-recorder.min.d.ts +70 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +70 -1
- package/dist/mixpanel.amd.js +1473 -504
- package/dist/mixpanel.cjs.d.ts +70 -1
- package/dist/mixpanel.cjs.js +1473 -504
- package/dist/mixpanel.globals.js +724 -426
- package/dist/mixpanel.min.js +189 -182
- package/dist/mixpanel.module.d.ts +70 -1
- package/dist/mixpanel.module.js +1473 -504
- package/dist/mixpanel.umd.d.ts +70 -1
- package/dist/mixpanel.umd.js +1473 -504
- package/dist/rrweb-bundled.js +61 -9
- package/dist/rrweb-compiled.js +56 -9
- package/logo.svg +5 -0
- package/package.json +6 -4
- package/rollup.config.mjs +163 -46
- package/src/autocapture/index.js +10 -27
- package/src/config.js +9 -3
- package/src/flags/index.js +1 -2
- package/src/index.d.ts +70 -1
- package/src/mixpanel-core.js +77 -112
- package/src/recorder/index.js +1 -1
- package/src/recorder/recorder.js +5 -1
- package/src/recorder/rrweb-network-plugin.js +649 -0
- package/src/recorder/session-recording.js +36 -12
- package/src/recorder/utils.js +27 -1
- package/src/recorder-manager.js +324 -0
- package/src/request-batcher.js +1 -1
- package/src/targeting/event-matcher.js +2 -57
- package/src/targeting/index.js +1 -1
- package/src/targeting/loader.js +1 -1
- package/src/utils.js +13 -1
- package/testServer.js +69 -1
- package/src/globals.js +0 -14
- /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
package/dist/mixpanel.umd.js
CHANGED
|
@@ -29,16 +29,19 @@
|
|
|
29
29
|
win = window;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
var Config = {
|
|
33
|
+
DEBUG: false,
|
|
34
|
+
LIB_VERSION: '2.77.0'
|
|
35
|
+
};
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
+
// Window global names for async modules
|
|
37
38
|
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
38
|
-
|
|
39
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
40
39
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
41
40
|
|
|
41
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
42
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
43
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
44
|
+
|
|
42
45
|
function _array_like_to_array(arr, len) {
|
|
43
46
|
if (len == null || len > arr.length) len = arr.length;
|
|
44
47
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -10736,13 +10739,7 @@
|
|
|
10736
10739
|
};
|
|
10737
10740
|
while(_this.mapRemoves.length){
|
|
10738
10741
|
var removedNode = _this.mapRemoves.shift();
|
|
10739
|
-
|
|
10740
|
-
try {
|
|
10741
|
-
_this.iframeManager.removeIframe(removedNode);
|
|
10742
|
-
} catch (e2) {}
|
|
10743
|
-
} else {
|
|
10744
|
-
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10745
|
-
}
|
|
10742
|
+
_this.cleanupRemovedNode(removedNode);
|
|
10746
10743
|
_this.mirror.removeNodeFromMap(removedNode);
|
|
10747
10744
|
}
|
|
10748
10745
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
@@ -11062,6 +11059,20 @@
|
|
|
11062
11059
|
}
|
|
11063
11060
|
}
|
|
11064
11061
|
});
|
|
11062
|
+
__publicField$1(this, "cleanupRemovedNode", function(node2) {
|
|
11063
|
+
if (node2.nodeName === "IFRAME") {
|
|
11064
|
+
try {
|
|
11065
|
+
_this.iframeManager.removeIframe(node2);
|
|
11066
|
+
} catch (e2) {}
|
|
11067
|
+
} else {
|
|
11068
|
+
try {
|
|
11069
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(node2);
|
|
11070
|
+
} catch (e2) {}
|
|
11071
|
+
}
|
|
11072
|
+
node2.childNodes.forEach(function(child) {
|
|
11073
|
+
_this.cleanupRemovedNode(child);
|
|
11074
|
+
});
|
|
11075
|
+
});
|
|
11065
11076
|
}
|
|
11066
11077
|
var _proto = MutationBuffer.prototype;
|
|
11067
11078
|
_proto.init = function init(options) {
|
|
@@ -13289,6 +13300,31 @@
|
|
|
13289
13300
|
_proto.destroy = function destroy() {};
|
|
13290
13301
|
return ProcessedNodeManager;
|
|
13291
13302
|
}();
|
|
13303
|
+
function toOrigin(url) {
|
|
13304
|
+
try {
|
|
13305
|
+
var origin = new URL(url).origin;
|
|
13306
|
+
return origin !== "null" ? origin : null;
|
|
13307
|
+
} catch (e) {
|
|
13308
|
+
return null;
|
|
13309
|
+
}
|
|
13310
|
+
}
|
|
13311
|
+
function buildAllowedOriginSet(origins) {
|
|
13312
|
+
if (!Array.isArray(origins) || origins.length === 0) {
|
|
13313
|
+
throw new Error("[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.");
|
|
13314
|
+
}
|
|
13315
|
+
var set = /* @__PURE__ */ new Set();
|
|
13316
|
+
for(var i2 = 0; i2 < origins.length; i2++){
|
|
13317
|
+
var entry = origins[i2];
|
|
13318
|
+
if (typeof entry !== "string") {
|
|
13319
|
+
throw new Error("[rrweb] allowedIframeOrigins[" + i2 + "] must be a string, got " + (typeof entry === "undefined" ? "undefined" : _type_of(entry)) + ".");
|
|
13320
|
+
}
|
|
13321
|
+
var origin = toOrigin(entry);
|
|
13322
|
+
if (origin) {
|
|
13323
|
+
set.add(origin);
|
|
13324
|
+
}
|
|
13325
|
+
}
|
|
13326
|
+
return Object.freeze(set);
|
|
13327
|
+
}
|
|
13292
13328
|
var wrappedEmit;
|
|
13293
13329
|
var takeFullSnapshot$1;
|
|
13294
13330
|
var canvasManager;
|
|
@@ -13310,10 +13346,17 @@
|
|
|
13310
13346
|
var mirror = createMirror$2();
|
|
13311
13347
|
function record(options) {
|
|
13312
13348
|
if (options === void 0) options = {};
|
|
13313
|
-
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() {
|
|
13349
|
+
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() {
|
|
13314
13350
|
return false;
|
|
13315
13351
|
} : _options_keepIframeSrcFn, _options_ignoreCSSAttributes = options.ignoreCSSAttributes, ignoreCSSAttributes = _options_ignoreCSSAttributes === void 0 ? /* @__PURE__ */ new Set([]) : _options_ignoreCSSAttributes, errorHandler2 = options.errorHandler;
|
|
13316
13352
|
registerErrorHandler(errorHandler2);
|
|
13353
|
+
var validatedOrigins;
|
|
13354
|
+
if (recordCrossOriginIframes && allowedIframeOrigins && allowedIframeOrigins.length > 0) {
|
|
13355
|
+
validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
|
|
13356
|
+
if (validatedOrigins.size === 0) {
|
|
13357
|
+
validatedOrigins = void 0;
|
|
13358
|
+
}
|
|
13359
|
+
}
|
|
13317
13360
|
var inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
|
|
13318
13361
|
var passEmitsToParent = false;
|
|
13319
13362
|
if (!inEmittingFrame) {
|
|
@@ -13405,7 +13448,14 @@
|
|
|
13405
13448
|
origin: window.location.origin,
|
|
13406
13449
|
isCheckout: isCheckout
|
|
13407
13450
|
};
|
|
13408
|
-
|
|
13451
|
+
if (validatedOrigins) {
|
|
13452
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(validatedOrigins), _step; !(_step = _iterator()).done;){
|
|
13453
|
+
var targetOrigin = _step.value;
|
|
13454
|
+
window.parent.postMessage(message, targetOrigin);
|
|
13455
|
+
}
|
|
13456
|
+
} else {
|
|
13457
|
+
window.parent.postMessage(message, "*");
|
|
13458
|
+
}
|
|
13409
13459
|
}
|
|
13410
13460
|
if (e2.type === EventType.FullSnapshot) {
|
|
13411
13461
|
lastFullSnapshotEvent = e2;
|
|
@@ -18138,7 +18188,7 @@
|
|
|
18138
18188
|
var __publicField = function(obj, key, value) {
|
|
18139
18189
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18140
18190
|
};
|
|
18141
|
-
function patch(source, name, replacement) {
|
|
18191
|
+
function patch$3(source, name, replacement) {
|
|
18142
18192
|
try {
|
|
18143
18193
|
if (!(name in source)) {
|
|
18144
18194
|
return function() {};
|
|
@@ -18555,7 +18605,7 @@
|
|
|
18555
18605
|
if (!_logger[level]) {
|
|
18556
18606
|
return function() {};
|
|
18557
18607
|
}
|
|
18558
|
-
return patch(_logger, level, function(original) {
|
|
18608
|
+
return patch$3(_logger, level, function(original) {
|
|
18559
18609
|
var _this1 = _this;
|
|
18560
18610
|
return function() {
|
|
18561
18611
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18976,11 +19026,6 @@
|
|
|
18976
19026
|
PromisePolyfill = NpoPromise;
|
|
18977
19027
|
}
|
|
18978
19028
|
|
|
18979
|
-
var Config = {
|
|
18980
|
-
DEBUG: false,
|
|
18981
|
-
LIB_VERSION: '2.75.0'
|
|
18982
|
-
};
|
|
18983
|
-
|
|
18984
19029
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18985
19030
|
|
|
18986
19031
|
// Maximum allowed session recording length
|
|
@@ -20712,6 +20757,17 @@
|
|
|
20712
20757
|
|
|
20713
20758
|
var NOOP_FUNC = function () {};
|
|
20714
20759
|
|
|
20760
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20761
|
+
var matches = false;
|
|
20762
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20763
|
+
if (url.match(regexList[i])) {
|
|
20764
|
+
matches = true;
|
|
20765
|
+
break;
|
|
20766
|
+
}
|
|
20767
|
+
}
|
|
20768
|
+
return matches;
|
|
20769
|
+
};
|
|
20770
|
+
|
|
20715
20771
|
var JSONStringify = null, JSONParse = null;
|
|
20716
20772
|
if (typeof JSON !== 'undefined') {
|
|
20717
20773
|
JSONStringify = JSON.stringify;
|
|
@@ -21183,7 +21239,7 @@
|
|
|
21183
21239
|
};
|
|
21184
21240
|
}
|
|
21185
21241
|
|
|
21186
|
-
var logger$
|
|
21242
|
+
var logger$8 = console_with_prefix('lock');
|
|
21187
21243
|
|
|
21188
21244
|
/**
|
|
21189
21245
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21235,7 +21291,7 @@
|
|
|
21235
21291
|
|
|
21236
21292
|
var delay = function(cb) {
|
|
21237
21293
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21238
|
-
logger$
|
|
21294
|
+
logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21239
21295
|
storage.removeItem(keyZ);
|
|
21240
21296
|
storage.removeItem(keyY);
|
|
21241
21297
|
loop();
|
|
@@ -21382,7 +21438,7 @@
|
|
|
21382
21438
|
}, this));
|
|
21383
21439
|
};
|
|
21384
21440
|
|
|
21385
|
-
var logger$
|
|
21441
|
+
var logger$7 = console_with_prefix('batch');
|
|
21386
21442
|
|
|
21387
21443
|
/**
|
|
21388
21444
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21411,7 +21467,7 @@
|
|
|
21411
21467
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21412
21468
|
});
|
|
21413
21469
|
}
|
|
21414
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21470
|
+
this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
|
|
21415
21471
|
|
|
21416
21472
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21417
21473
|
|
|
@@ -21744,7 +21800,7 @@
|
|
|
21744
21800
|
// maximum interval between request retries after exponential backoff
|
|
21745
21801
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21746
21802
|
|
|
21747
|
-
var logger$
|
|
21803
|
+
var logger$6 = console_with_prefix('batch');
|
|
21748
21804
|
|
|
21749
21805
|
/**
|
|
21750
21806
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21872,7 +21928,7 @@
|
|
|
21872
21928
|
*/
|
|
21873
21929
|
RequestBatcher.prototype.flush = function(options) {
|
|
21874
21930
|
if (this.requestInProgress) {
|
|
21875
|
-
logger$
|
|
21931
|
+
logger$6.log('Flush: Request already in progress');
|
|
21876
21932
|
return PromisePolyfill.resolve();
|
|
21877
21933
|
}
|
|
21878
21934
|
|
|
@@ -22049,7 +22105,7 @@
|
|
|
22049
22105
|
if (options.unloading) {
|
|
22050
22106
|
requestOptions.transport = 'sendBeacon';
|
|
22051
22107
|
}
|
|
22052
|
-
logger$
|
|
22108
|
+
logger$6.log('MIXPANEL REQUEST:', dataForRequest);
|
|
22053
22109
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
22054
22110
|
}, this))
|
|
22055
22111
|
.catch(_.bind(function(err) {
|
|
@@ -22062,7 +22118,7 @@
|
|
|
22062
22118
|
* Log error to global logger and optional user-defined logger.
|
|
22063
22119
|
*/
|
|
22064
22120
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
22065
|
-
logger$
|
|
22121
|
+
logger$6.error.apply(logger$6.error, arguments);
|
|
22066
22122
|
if (this.errorReporter) {
|
|
22067
22123
|
try {
|
|
22068
22124
|
if (!(err instanceof Error)) {
|
|
@@ -22070,7 +22126,7 @@
|
|
|
22070
22126
|
}
|
|
22071
22127
|
this.errorReporter(msg, err);
|
|
22072
22128
|
} catch(err) {
|
|
22073
|
-
logger$
|
|
22129
|
+
logger$6.error(err);
|
|
22074
22130
|
}
|
|
22075
22131
|
}
|
|
22076
22132
|
};
|
|
@@ -22087,6 +22143,29 @@
|
|
|
22087
22143
|
|
|
22088
22144
|
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
22089
22145
|
|
|
22146
|
+
var validateAllowedOrigins = function(origins, logger) {
|
|
22147
|
+
if (!_.isArray(origins)) {
|
|
22148
|
+
if (origins) {
|
|
22149
|
+
logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
|
|
22150
|
+
}
|
|
22151
|
+
return [];
|
|
22152
|
+
}
|
|
22153
|
+
var valid = [];
|
|
22154
|
+
for (var i = 0; i < origins.length; i++) {
|
|
22155
|
+
try {
|
|
22156
|
+
var origin = new URL(origins[i]).origin;
|
|
22157
|
+
if (origin === 'null') {
|
|
22158
|
+
logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
|
|
22159
|
+
continue;
|
|
22160
|
+
}
|
|
22161
|
+
valid.push(origin);
|
|
22162
|
+
} catch (e) {
|
|
22163
|
+
logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
|
|
22164
|
+
}
|
|
22165
|
+
}
|
|
22166
|
+
return valid;
|
|
22167
|
+
};
|
|
22168
|
+
|
|
22090
22169
|
// stateless utils
|
|
22091
22170
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
22092
22171
|
|
|
@@ -22192,7 +22271,7 @@
|
|
|
22192
22271
|
|
|
22193
22272
|
var MAX_DEPTH = 5;
|
|
22194
22273
|
|
|
22195
|
-
var logger$
|
|
22274
|
+
var logger$5 = console_with_prefix('autocapture');
|
|
22196
22275
|
|
|
22197
22276
|
|
|
22198
22277
|
function getClasses(el) {
|
|
@@ -22456,7 +22535,7 @@
|
|
|
22456
22535
|
return false;
|
|
22457
22536
|
}
|
|
22458
22537
|
} catch (err) {
|
|
22459
|
-
logger$
|
|
22538
|
+
logger$5.critical('Error while checking element in allowElementCallback', err);
|
|
22460
22539
|
return false;
|
|
22461
22540
|
}
|
|
22462
22541
|
}
|
|
@@ -22473,7 +22552,7 @@
|
|
|
22473
22552
|
return true;
|
|
22474
22553
|
}
|
|
22475
22554
|
} catch (err) {
|
|
22476
|
-
logger$
|
|
22555
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22477
22556
|
}
|
|
22478
22557
|
}
|
|
22479
22558
|
return false;
|
|
@@ -22488,7 +22567,7 @@
|
|
|
22488
22567
|
return true;
|
|
22489
22568
|
}
|
|
22490
22569
|
} catch (err) {
|
|
22491
|
-
logger$
|
|
22570
|
+
logger$5.critical('Error while checking element in blockElementCallback', err);
|
|
22492
22571
|
return true;
|
|
22493
22572
|
}
|
|
22494
22573
|
}
|
|
@@ -22502,7 +22581,7 @@
|
|
|
22502
22581
|
return true;
|
|
22503
22582
|
}
|
|
22504
22583
|
} catch (err) {
|
|
22505
|
-
logger$
|
|
22584
|
+
logger$5.critical('Error while checking selector: ' + sel, err);
|
|
22506
22585
|
}
|
|
22507
22586
|
}
|
|
22508
22587
|
}
|
|
@@ -23050,177 +23129,826 @@
|
|
|
23050
23129
|
}
|
|
23051
23130
|
|
|
23052
23131
|
/**
|
|
23053
|
-
*
|
|
23132
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23133
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23134
|
+
*
|
|
23135
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23136
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23137
|
+
*
|
|
23054
23138
|
*/
|
|
23055
23139
|
|
|
23140
|
+
var logger$4 = console_with_prefix('network-plugin');
|
|
23056
23141
|
|
|
23057
|
-
|
|
23058
|
-
|
|
23059
|
-
|
|
23060
|
-
|
|
23061
|
-
|
|
23062
|
-
|
|
23063
|
-
|
|
23064
|
-
|
|
23065
|
-
|
|
23142
|
+
/**
|
|
23143
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23144
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23145
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23146
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23147
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23148
|
+
* @param {Window} win
|
|
23149
|
+
* @returns {number}
|
|
23150
|
+
*/
|
|
23151
|
+
function getTimeOrigin(win) {
|
|
23152
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23153
|
+
}
|
|
23066
23154
|
|
|
23067
|
-
|
|
23068
|
-
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
|
|
23073
|
-
IncrementalSource.TouchMove,
|
|
23074
|
-
IncrementalSource.MediaInteraction,
|
|
23075
|
-
IncrementalSource.Drag,
|
|
23076
|
-
IncrementalSource.Selection,
|
|
23077
|
-
]);
|
|
23155
|
+
/**
|
|
23156
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23157
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23158
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23159
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23160
|
+
*/
|
|
23078
23161
|
|
|
23079
|
-
|
|
23080
|
-
|
|
23081
|
-
|
|
23162
|
+
/**
|
|
23163
|
+
* @typedef {Record<string, string>} Headers
|
|
23164
|
+
*/
|
|
23082
23165
|
|
|
23083
23166
|
/**
|
|
23084
|
-
* @typedef {
|
|
23085
|
-
* @property {number} idleExpires
|
|
23086
|
-
* @property {number} maxExpires
|
|
23087
|
-
* @property {number} replayStartTime
|
|
23088
|
-
* @property {number} lastEventTimestamp
|
|
23089
|
-
* @property {number} seqNo
|
|
23090
|
-
* @property {string} batchStartUrl
|
|
23091
|
-
* @property {string} replayId
|
|
23092
|
-
* @property {string} tabId
|
|
23093
|
-
* @property {string} replayStartUrl
|
|
23167
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
23094
23168
|
*/
|
|
23095
23169
|
|
|
23096
23170
|
/**
|
|
23097
|
-
* @
|
|
23098
|
-
* @
|
|
23099
|
-
* @
|
|
23100
|
-
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23101
|
-
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23102
|
-
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23103
|
-
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23104
|
-
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23105
|
-
* optional properties for deserialization:
|
|
23106
|
-
* @property {number} idleExpires
|
|
23107
|
-
* @property {number} maxExpires
|
|
23108
|
-
* @property {number} replayStartTime
|
|
23109
|
-
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23110
|
-
* @property {number} seqNo
|
|
23111
|
-
* @property {string} batchStartUrl
|
|
23112
|
-
* @property {string} replayStartUrl
|
|
23171
|
+
* @callback networkCallback
|
|
23172
|
+
* @param {NetworkData} data
|
|
23173
|
+
* @returns {void}
|
|
23113
23174
|
*/
|
|
23114
23175
|
|
|
23115
23176
|
/**
|
|
23116
|
-
* @
|
|
23117
|
-
* @
|
|
23118
|
-
* @property {string} user_id
|
|
23119
|
-
* @property {string} device_id
|
|
23177
|
+
* @callback listenerHandler
|
|
23178
|
+
* @returns {void}
|
|
23120
23179
|
*/
|
|
23121
23180
|
|
|
23181
|
+
/**
|
|
23182
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23183
|
+
*/
|
|
23122
23184
|
|
|
23123
23185
|
/**
|
|
23124
|
-
*
|
|
23125
|
-
* @
|
|
23186
|
+
* @typedef {Object} RecordPlugin
|
|
23187
|
+
* @property {string} name
|
|
23188
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23189
|
+
* @property {NetworkRecordOptions} [options]
|
|
23126
23190
|
*/
|
|
23127
|
-
var SessionRecording = function(options) {
|
|
23128
|
-
this._mixpanel = options.mixpanelInstance;
|
|
23129
|
-
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23130
|
-
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23131
|
-
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23132
|
-
this._rrwebRecord = options.rrwebRecord || null;
|
|
23133
23191
|
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
|
|
23192
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23193
|
+
var defaultNetworkOptions = {
|
|
23194
|
+
initiatorTypes: [
|
|
23195
|
+
'audio',
|
|
23196
|
+
'beacon',
|
|
23197
|
+
'body',
|
|
23198
|
+
'css',
|
|
23199
|
+
'early-hint',
|
|
23200
|
+
'embed',
|
|
23201
|
+
'fetch',
|
|
23202
|
+
'frame',
|
|
23203
|
+
'iframe',
|
|
23204
|
+
'icon',
|
|
23205
|
+
'image',
|
|
23206
|
+
'img',
|
|
23207
|
+
'input',
|
|
23208
|
+
'link',
|
|
23209
|
+
'navigation',
|
|
23210
|
+
'object',
|
|
23211
|
+
'ping',
|
|
23212
|
+
'script',
|
|
23213
|
+
'track',
|
|
23214
|
+
'video',
|
|
23215
|
+
'xmlhttprequest',
|
|
23216
|
+
],
|
|
23217
|
+
ignoreRequestFn: function() { return false; },
|
|
23218
|
+
recordHeaders: {
|
|
23219
|
+
request: [],
|
|
23220
|
+
response: [],
|
|
23221
|
+
},
|
|
23222
|
+
recordBodyUrls: {
|
|
23223
|
+
request: [],
|
|
23224
|
+
response: [],
|
|
23225
|
+
},
|
|
23226
|
+
recordInitialRequests: false,
|
|
23227
|
+
};
|
|
23137
23228
|
|
|
23138
|
-
|
|
23139
|
-
|
|
23140
|
-
|
|
23141
|
-
|
|
23142
|
-
|
|
23143
|
-
|
|
23144
|
-
|
|
23229
|
+
/**
|
|
23230
|
+
* @param {PerformanceEntry} entry
|
|
23231
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23232
|
+
*/
|
|
23233
|
+
function isNavigationTiming(entry) {
|
|
23234
|
+
return entry.entryType === 'navigation';
|
|
23235
|
+
}
|
|
23145
23236
|
|
|
23146
|
-
|
|
23147
|
-
|
|
23237
|
+
/**
|
|
23238
|
+
* @param {PerformanceEntry} entry
|
|
23239
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23240
|
+
*/
|
|
23241
|
+
function isResourceTiming (entry) {
|
|
23242
|
+
return entry.entryType === 'resource';
|
|
23243
|
+
}
|
|
23148
23244
|
|
|
23149
|
-
|
|
23150
|
-
|
|
23245
|
+
function findLast(array, predicate) {
|
|
23246
|
+
var length = array.length;
|
|
23247
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23248
|
+
if (predicate(array[i])) {
|
|
23249
|
+
return array[i];
|
|
23250
|
+
}
|
|
23251
|
+
}
|
|
23252
|
+
}
|
|
23151
23253
|
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23254
|
+
/**
|
|
23255
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23256
|
+
* Adapted from Sentry's `fill` utility:
|
|
23257
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23258
|
+
*
|
|
23259
|
+
* @param {object} source - The object containing the method to patch
|
|
23260
|
+
* @param {string} name - The method name to patch
|
|
23261
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23262
|
+
* @returns {function} A function that restores the original method
|
|
23263
|
+
*/
|
|
23264
|
+
function patch(source, name, replacementFactory) {
|
|
23265
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23266
|
+
return function() {};
|
|
23267
|
+
}
|
|
23268
|
+
var original = source[name];
|
|
23269
|
+
var wrapped = replacementFactory(original);
|
|
23270
|
+
source[name] = wrapped;
|
|
23271
|
+
return function() {
|
|
23272
|
+
source[name] = original;
|
|
23273
|
+
};
|
|
23274
|
+
}
|
|
23155
23275
|
|
|
23156
|
-
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23157
|
-
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23158
|
-
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23159
|
-
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23160
|
-
errorReporter: this.reportError.bind(this),
|
|
23161
|
-
flushOnlyOnInterval: true,
|
|
23162
|
-
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23163
|
-
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23164
|
-
queueStorage: this.queueStorage,
|
|
23165
|
-
sharedLockStorage: options.sharedLockStorage,
|
|
23166
|
-
usePersistence: usePersistence,
|
|
23167
|
-
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23168
23276
|
|
|
23169
|
-
|
|
23170
|
-
|
|
23171
|
-
|
|
23172
|
-
|
|
23173
|
-
sharedLockTimeoutMS: 10 * 1000,
|
|
23174
|
-
});
|
|
23175
|
-
};
|
|
23277
|
+
/**
|
|
23278
|
+
* Maximum body size to record (1MB)
|
|
23279
|
+
*/
|
|
23280
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23176
23281
|
|
|
23177
23282
|
/**
|
|
23178
|
-
*
|
|
23283
|
+
* Truncate string if it exceeds max size
|
|
23284
|
+
* @param {string} str
|
|
23285
|
+
* @returns {string}
|
|
23179
23286
|
*/
|
|
23180
|
-
|
|
23181
|
-
if (
|
|
23182
|
-
return
|
|
23287
|
+
function truncateBody(str) {
|
|
23288
|
+
if (!str || typeof str !== 'string') {
|
|
23289
|
+
return str;
|
|
23290
|
+
}
|
|
23291
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23292
|
+
logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23293
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23183
23294
|
}
|
|
23295
|
+
return str;
|
|
23296
|
+
}
|
|
23184
23297
|
|
|
23185
|
-
|
|
23186
|
-
|
|
23298
|
+
/**
|
|
23299
|
+
* @param {networkCallback} cb
|
|
23300
|
+
* @param {Window} win
|
|
23301
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23302
|
+
* @returns {listenerHandler}
|
|
23303
|
+
*/
|
|
23304
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23305
|
+
if (!win.PerformanceObserver) {
|
|
23306
|
+
logger$4.error('PerformanceObserver not supported');
|
|
23307
|
+
return function() {
|
|
23308
|
+
//
|
|
23309
|
+
};
|
|
23310
|
+
}
|
|
23311
|
+
if (options.recordInitialRequests) {
|
|
23312
|
+
var initialPerformanceEntries = win.performance
|
|
23313
|
+
.getEntries()
|
|
23314
|
+
.filter(function(entry) {
|
|
23315
|
+
return isNavigationTiming(entry) ||
|
|
23316
|
+
(isResourceTiming(entry) &&
|
|
23317
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23318
|
+
});
|
|
23319
|
+
cb({
|
|
23320
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23321
|
+
return {
|
|
23322
|
+
url: entry.name,
|
|
23323
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23324
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23325
|
+
startTime: Math.round(entry.startTime),
|
|
23326
|
+
endTime: Math.round(entry.responseEnd),
|
|
23327
|
+
timeOrigin: getTimeOrigin(win),
|
|
23328
|
+
};
|
|
23329
|
+
}),
|
|
23330
|
+
isInitial: true,
|
|
23331
|
+
});
|
|
23332
|
+
}
|
|
23333
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23334
|
+
var performanceEntries = entries
|
|
23335
|
+
.getEntries()
|
|
23336
|
+
.filter(function(entry) {
|
|
23337
|
+
return isResourceTiming(entry) &&
|
|
23338
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23339
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23340
|
+
entry.initiatorType !== 'fetch';
|
|
23341
|
+
});
|
|
23342
|
+
cb({
|
|
23343
|
+
requests: performanceEntries.map(function(entry) {
|
|
23344
|
+
return {
|
|
23345
|
+
url: entry.name,
|
|
23346
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23347
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23348
|
+
startTime: Math.round(entry.startTime),
|
|
23349
|
+
endTime: Math.round(entry.responseEnd),
|
|
23350
|
+
timeOrigin: getTimeOrigin(win),
|
|
23351
|
+
};
|
|
23352
|
+
}),
|
|
23353
|
+
});
|
|
23354
|
+
});
|
|
23355
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23356
|
+
return function() {
|
|
23357
|
+
observer.disconnect();
|
|
23187
23358
|
};
|
|
23359
|
+
}
|
|
23188
23360
|
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23361
|
+
/**
|
|
23362
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23363
|
+
* @param {'request' | 'response'} type
|
|
23364
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23365
|
+
* @param {string} headerName
|
|
23366
|
+
* @returns {boolean}
|
|
23367
|
+
*/
|
|
23368
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23369
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23370
|
+
return false;
|
|
23197
23371
|
}
|
|
23198
|
-
return userIdInfo;
|
|
23199
|
-
};
|
|
23200
23372
|
|
|
23201
|
-
|
|
23202
|
-
|
|
23373
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23374
|
+
}
|
|
23203
23375
|
|
|
23204
|
-
|
|
23205
|
-
|
|
23206
|
-
|
|
23207
|
-
|
|
23208
|
-
|
|
23209
|
-
|
|
23210
|
-
|
|
23376
|
+
/**
|
|
23377
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23378
|
+
* @param {'request' | 'response'} type
|
|
23379
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23380
|
+
* @param {string} url
|
|
23381
|
+
* @returns {boolean}
|
|
23382
|
+
*/
|
|
23383
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23384
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23385
|
+
return false;
|
|
23386
|
+
}
|
|
23211
23387
|
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
return this.queueStorage.removeItem(this.batcherKey);
|
|
23215
|
-
}.bind(this));
|
|
23216
|
-
}.bind(this));
|
|
23217
|
-
};
|
|
23388
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23389
|
+
}
|
|
23218
23390
|
|
|
23219
|
-
|
|
23220
|
-
|
|
23221
|
-
|
|
23391
|
+
function tryReadXHRBody(body) {
|
|
23392
|
+
if (body === null || body === undefined) {
|
|
23393
|
+
return null;
|
|
23394
|
+
}
|
|
23222
23395
|
|
|
23223
|
-
|
|
23396
|
+
var result;
|
|
23397
|
+
if (typeof body === 'string') {
|
|
23398
|
+
result = body;
|
|
23399
|
+
} else if (body instanceof Document) {
|
|
23400
|
+
result = body.textContent;
|
|
23401
|
+
} else if (body instanceof FormData) {
|
|
23402
|
+
result = _.HTTPBuildQuery(body);
|
|
23403
|
+
} else if (_.isObject(body)) {
|
|
23404
|
+
try {
|
|
23405
|
+
result = JSON.stringify(body);
|
|
23406
|
+
} catch (e) {
|
|
23407
|
+
return 'Failed to stringify response object';
|
|
23408
|
+
}
|
|
23409
|
+
} else {
|
|
23410
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23411
|
+
}
|
|
23412
|
+
|
|
23413
|
+
return truncateBody(result);
|
|
23414
|
+
}
|
|
23415
|
+
|
|
23416
|
+
/**
|
|
23417
|
+
* @param {Request | Response} r
|
|
23418
|
+
* @returns {Promise<string>}
|
|
23419
|
+
*/
|
|
23420
|
+
function tryReadFetchBody(r) {
|
|
23421
|
+
return new Promise(function(resolve) {
|
|
23422
|
+
var timeout = setTimeout(function() {
|
|
23423
|
+
resolve('Timeout while trying to read body');
|
|
23424
|
+
}, 500);
|
|
23425
|
+
try {
|
|
23426
|
+
r.clone()
|
|
23427
|
+
.text()
|
|
23428
|
+
.then(
|
|
23429
|
+
function(txt) {
|
|
23430
|
+
clearTimeout(timeout);
|
|
23431
|
+
resolve(truncateBody(txt));
|
|
23432
|
+
},
|
|
23433
|
+
function(reason) {
|
|
23434
|
+
clearTimeout(timeout);
|
|
23435
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23436
|
+
}
|
|
23437
|
+
);
|
|
23438
|
+
} catch (e) {
|
|
23439
|
+
clearTimeout(timeout);
|
|
23440
|
+
resolve('Failed to read body: ' + String(e));
|
|
23441
|
+
}
|
|
23442
|
+
});
|
|
23443
|
+
}
|
|
23444
|
+
|
|
23445
|
+
/**
|
|
23446
|
+
* @param {Window} win
|
|
23447
|
+
* @param {string} initiatorType
|
|
23448
|
+
* @param {string} url
|
|
23449
|
+
* @param {number} [after]
|
|
23450
|
+
* @param {number} [before]
|
|
23451
|
+
* @param {number} [attempt]
|
|
23452
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23453
|
+
*/
|
|
23454
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23455
|
+
if (attempt === undefined) {
|
|
23456
|
+
attempt = 0;
|
|
23457
|
+
}
|
|
23458
|
+
if (attempt > 10) {
|
|
23459
|
+
logger$4.error('Cannot find performance entry');
|
|
23460
|
+
return Promise.resolve(null);
|
|
23461
|
+
}
|
|
23462
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23463
|
+
win.performance.getEntriesByName(url)
|
|
23464
|
+
);
|
|
23465
|
+
var performanceEntry = findLast(
|
|
23466
|
+
urlPerformanceEntries,
|
|
23467
|
+
function(entry) {
|
|
23468
|
+
return isResourceTiming(entry) &&
|
|
23469
|
+
entry.initiatorType === initiatorType &&
|
|
23470
|
+
(!after || entry.startTime >= after) &&
|
|
23471
|
+
(!before || entry.startTime <= before);
|
|
23472
|
+
}
|
|
23473
|
+
);
|
|
23474
|
+
if (!performanceEntry) {
|
|
23475
|
+
return new Promise(function(resolve) {
|
|
23476
|
+
setTimeout(resolve, 50 * attempt);
|
|
23477
|
+
}).then(function() {
|
|
23478
|
+
return getRequestPerformanceEntry(
|
|
23479
|
+
win,
|
|
23480
|
+
initiatorType,
|
|
23481
|
+
url,
|
|
23482
|
+
after,
|
|
23483
|
+
before,
|
|
23484
|
+
attempt + 1
|
|
23485
|
+
);
|
|
23486
|
+
});
|
|
23487
|
+
}
|
|
23488
|
+
return Promise.resolve(performanceEntry);
|
|
23489
|
+
}
|
|
23490
|
+
|
|
23491
|
+
/**
|
|
23492
|
+
* @param {networkCallback} cb
|
|
23493
|
+
* @param {Window} win
|
|
23494
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23495
|
+
* @returns {listenerHandler}
|
|
23496
|
+
*/
|
|
23497
|
+
function initXhrObserver(cb, win, options) {
|
|
23498
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23499
|
+
return function() {
|
|
23500
|
+
//
|
|
23501
|
+
};
|
|
23502
|
+
}
|
|
23503
|
+
var restorePatch = patch(
|
|
23504
|
+
win.XMLHttpRequest.prototype,
|
|
23505
|
+
'open',
|
|
23506
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23507
|
+
return function(
|
|
23508
|
+
/** @type {string} */ method,
|
|
23509
|
+
/** @type {string | URL} */ url,
|
|
23510
|
+
/** @type {boolean} */ async,
|
|
23511
|
+
username, password
|
|
23512
|
+
) {
|
|
23513
|
+
if (async === undefined) {
|
|
23514
|
+
async = true;
|
|
23515
|
+
}
|
|
23516
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23517
|
+
var req = new Request(url, { method: method });
|
|
23518
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23519
|
+
var networkRequest = {};
|
|
23520
|
+
/** @type {number | undefined} */
|
|
23521
|
+
var after;
|
|
23522
|
+
/** @type {number | undefined} */
|
|
23523
|
+
var before;
|
|
23524
|
+
|
|
23525
|
+
/** @type {Headers} */
|
|
23526
|
+
var requestHeaders = {};
|
|
23527
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23528
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23529
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23530
|
+
requestHeaders[header] = value;
|
|
23531
|
+
}
|
|
23532
|
+
return originalSetRequestHeader(header, value);
|
|
23533
|
+
};
|
|
23534
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23535
|
+
|
|
23536
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23537
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23538
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23539
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23540
|
+
}
|
|
23541
|
+
after = win.performance.now();
|
|
23542
|
+
return originalSend(body);
|
|
23543
|
+
};
|
|
23544
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23545
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23546
|
+
return;
|
|
23547
|
+
}
|
|
23548
|
+
before = win.performance.now();
|
|
23549
|
+
/** @type {Headers} */
|
|
23550
|
+
var responseHeaders = {};
|
|
23551
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23552
|
+
if (rawHeaders) {
|
|
23553
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23554
|
+
headers.forEach(function(line) {
|
|
23555
|
+
if (!line) return;
|
|
23556
|
+
var colonIndex = line.indexOf(': ');
|
|
23557
|
+
if (colonIndex === -1) return;
|
|
23558
|
+
var header = line.substring(0, colonIndex);
|
|
23559
|
+
var value = line.substring(colonIndex + 2);
|
|
23560
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23561
|
+
responseHeaders[header] = value;
|
|
23562
|
+
}
|
|
23563
|
+
});
|
|
23564
|
+
}
|
|
23565
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23566
|
+
if (
|
|
23567
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23568
|
+
) {
|
|
23569
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23570
|
+
}
|
|
23571
|
+
getRequestPerformanceEntry(
|
|
23572
|
+
win,
|
|
23573
|
+
'xmlhttprequest',
|
|
23574
|
+
req.url,
|
|
23575
|
+
after,
|
|
23576
|
+
before
|
|
23577
|
+
)
|
|
23578
|
+
.then(function(entry) {
|
|
23579
|
+
if (!entry) {
|
|
23580
|
+
logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23581
|
+
return;
|
|
23582
|
+
}
|
|
23583
|
+
/** @type {NetworkRequest} */
|
|
23584
|
+
var request = {
|
|
23585
|
+
url: entry.name,
|
|
23586
|
+
method: req.method,
|
|
23587
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23588
|
+
status: xhr.status,
|
|
23589
|
+
startTime: Math.round(entry.startTime),
|
|
23590
|
+
endTime: Math.round(entry.responseEnd),
|
|
23591
|
+
timeOrigin: getTimeOrigin(win),
|
|
23592
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23593
|
+
requestBody: networkRequest.requestBody,
|
|
23594
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23595
|
+
responseBody: networkRequest.responseBody,
|
|
23596
|
+
};
|
|
23597
|
+
cb({ requests: [request] });
|
|
23598
|
+
})
|
|
23599
|
+
.catch(function(e) {
|
|
23600
|
+
logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23601
|
+
});
|
|
23602
|
+
});
|
|
23603
|
+
|
|
23604
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23605
|
+
};
|
|
23606
|
+
}
|
|
23607
|
+
);
|
|
23608
|
+
return function() {
|
|
23609
|
+
restorePatch();
|
|
23610
|
+
};
|
|
23611
|
+
}
|
|
23612
|
+
|
|
23613
|
+
/**
|
|
23614
|
+
* @param {networkCallback} cb
|
|
23615
|
+
* @param {Window} win
|
|
23616
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23617
|
+
* @returns {listenerHandler}
|
|
23618
|
+
*/
|
|
23619
|
+
function initFetchObserver(cb, win, options) {
|
|
23620
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23621
|
+
return function() {
|
|
23622
|
+
//
|
|
23623
|
+
};
|
|
23624
|
+
}
|
|
23625
|
+
|
|
23626
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23627
|
+
return function() {
|
|
23628
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23629
|
+
/** @type {Response | undefined} */
|
|
23630
|
+
var res;
|
|
23631
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23632
|
+
var networkRequest = {};
|
|
23633
|
+
/** @type {number | undefined} */
|
|
23634
|
+
var after;
|
|
23635
|
+
/** @type {number | undefined} */
|
|
23636
|
+
var before;
|
|
23637
|
+
|
|
23638
|
+
var originalFetchPromise;
|
|
23639
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23640
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23641
|
+
try {
|
|
23642
|
+
/** @type {Headers} */
|
|
23643
|
+
var requestHeaders = {};
|
|
23644
|
+
req.headers.forEach(function(value, header) {
|
|
23645
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23646
|
+
requestHeaders[header] = value;
|
|
23647
|
+
}
|
|
23648
|
+
});
|
|
23649
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23650
|
+
|
|
23651
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23652
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23653
|
+
.then(function(body) {
|
|
23654
|
+
networkRequest.requestBody = body;
|
|
23655
|
+
});
|
|
23656
|
+
}
|
|
23657
|
+
|
|
23658
|
+
after = win.performance.now();
|
|
23659
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23660
|
+
res = response;
|
|
23661
|
+
before = win.performance.now();
|
|
23662
|
+
|
|
23663
|
+
/** @type {Headers} */
|
|
23664
|
+
var responseHeaders = {};
|
|
23665
|
+
res.headers.forEach(function(value, header) {
|
|
23666
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23667
|
+
responseHeaders[header] = value;
|
|
23668
|
+
}
|
|
23669
|
+
});
|
|
23670
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23671
|
+
|
|
23672
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23673
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23674
|
+
.then(function(body) {
|
|
23675
|
+
networkRequest.responseBody = body;
|
|
23676
|
+
});
|
|
23677
|
+
}
|
|
23678
|
+
|
|
23679
|
+
return res;
|
|
23680
|
+
});
|
|
23681
|
+
} catch (e) {
|
|
23682
|
+
originalFetchPromise = Promise.reject(e);
|
|
23683
|
+
}
|
|
23684
|
+
|
|
23685
|
+
// await concurrently so we don't delay the fetch response
|
|
23686
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23687
|
+
.then(function () {
|
|
23688
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23689
|
+
})
|
|
23690
|
+
.then(function(entry) {
|
|
23691
|
+
if (!entry) {
|
|
23692
|
+
logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23693
|
+
return;
|
|
23694
|
+
}
|
|
23695
|
+
/** @type {NetworkRequest} */
|
|
23696
|
+
var request = {
|
|
23697
|
+
url: entry.name,
|
|
23698
|
+
method: req.method,
|
|
23699
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23700
|
+
status: res ? res.status : undefined,
|
|
23701
|
+
startTime: Math.round(entry.startTime),
|
|
23702
|
+
endTime: Math.round(entry.responseEnd),
|
|
23703
|
+
timeOrigin: getTimeOrigin(win),
|
|
23704
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23705
|
+
requestBody: networkRequest.requestBody,
|
|
23706
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23707
|
+
responseBody: networkRequest.responseBody,
|
|
23708
|
+
};
|
|
23709
|
+
cb({ requests: [request] });
|
|
23710
|
+
})
|
|
23711
|
+
.catch(function (e) {
|
|
23712
|
+
logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23713
|
+
});
|
|
23714
|
+
|
|
23715
|
+
return originalFetchPromise;
|
|
23716
|
+
};
|
|
23717
|
+
});
|
|
23718
|
+
return function() {
|
|
23719
|
+
restorePatch();
|
|
23720
|
+
};
|
|
23721
|
+
}
|
|
23722
|
+
|
|
23723
|
+
/**
|
|
23724
|
+
* @param {networkCallback} callback
|
|
23725
|
+
* @param {Window} win
|
|
23726
|
+
* @param {NetworkRecordOptions} options
|
|
23727
|
+
* @returns {listenerHandler}
|
|
23728
|
+
*/
|
|
23729
|
+
function initNetworkObserver(callback, win, options) {
|
|
23730
|
+
if (!('performance' in win)) {
|
|
23731
|
+
return function() {
|
|
23732
|
+
//
|
|
23733
|
+
};
|
|
23734
|
+
}
|
|
23735
|
+
|
|
23736
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23737
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23738
|
+
options = Object.assign({}, options, {
|
|
23739
|
+
recordHeaders: recordHeaders,
|
|
23740
|
+
recordBodyUrls: recordBodyUrls,
|
|
23741
|
+
});
|
|
23742
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23743
|
+
|
|
23744
|
+
/** @type {networkCallback} */
|
|
23745
|
+
var cb = function(data) {
|
|
23746
|
+
var requests = data.requests.filter(function(request) {
|
|
23747
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23748
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23749
|
+
});
|
|
23750
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23751
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23752
|
+
}
|
|
23753
|
+
};
|
|
23754
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23755
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23756
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23757
|
+
return function() {
|
|
23758
|
+
performanceObserver();
|
|
23759
|
+
xhrObserver();
|
|
23760
|
+
fetchObserver();
|
|
23761
|
+
};
|
|
23762
|
+
}
|
|
23763
|
+
|
|
23764
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23765
|
+
// a changed format in the mixpanel product.
|
|
23766
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23767
|
+
|
|
23768
|
+
/**
|
|
23769
|
+
* @param {NetworkRecordOptions} [options]
|
|
23770
|
+
* @returns {RecordPlugin}
|
|
23771
|
+
*/
|
|
23772
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23773
|
+
return {
|
|
23774
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23775
|
+
observer: initNetworkObserver,
|
|
23776
|
+
options: options,
|
|
23777
|
+
};
|
|
23778
|
+
};
|
|
23779
|
+
|
|
23780
|
+
/**
|
|
23781
|
+
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23782
|
+
*/
|
|
23783
|
+
|
|
23784
|
+
|
|
23785
|
+
var logger$3 = console_with_prefix('recorder');
|
|
23786
|
+
var CompressionStream = win['CompressionStream'];
|
|
23787
|
+
|
|
23788
|
+
var RECORDER_BATCHER_LIB_CONFIG = {
|
|
23789
|
+
'batch_size': 1000,
|
|
23790
|
+
'batch_flush_interval_ms': 10 * 1000,
|
|
23791
|
+
'batch_request_timeout_ms': 90 * 1000,
|
|
23792
|
+
'batch_autostart': true
|
|
23793
|
+
};
|
|
23794
|
+
|
|
23795
|
+
var ACTIVE_SOURCES = new Set([
|
|
23796
|
+
IncrementalSource.MouseMove,
|
|
23797
|
+
IncrementalSource.MouseInteraction,
|
|
23798
|
+
IncrementalSource.Scroll,
|
|
23799
|
+
IncrementalSource.ViewportResize,
|
|
23800
|
+
IncrementalSource.Input,
|
|
23801
|
+
IncrementalSource.TouchMove,
|
|
23802
|
+
IncrementalSource.MediaInteraction,
|
|
23803
|
+
IncrementalSource.Drag,
|
|
23804
|
+
IncrementalSource.Selection,
|
|
23805
|
+
]);
|
|
23806
|
+
|
|
23807
|
+
function isUserEvent(ev) {
|
|
23808
|
+
return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
|
|
23809
|
+
}
|
|
23810
|
+
|
|
23811
|
+
/**
|
|
23812
|
+
* @typedef {Object} SerializedRecording
|
|
23813
|
+
* @property {number} idleExpires
|
|
23814
|
+
* @property {number} maxExpires
|
|
23815
|
+
* @property {number} replayStartTime
|
|
23816
|
+
* @property {number} lastEventTimestamp
|
|
23817
|
+
* @property {number} seqNo
|
|
23818
|
+
* @property {string} batchStartUrl
|
|
23819
|
+
* @property {string} replayId
|
|
23820
|
+
* @property {string} tabId
|
|
23821
|
+
* @property {string} replayStartUrl
|
|
23822
|
+
*/
|
|
23823
|
+
|
|
23824
|
+
/**
|
|
23825
|
+
* @typedef {Object} SessionRecordingOptions
|
|
23826
|
+
* @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
23827
|
+
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
23828
|
+
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23829
|
+
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23830
|
+
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23831
|
+
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23832
|
+
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23833
|
+
* optional properties for deserialization:
|
|
23834
|
+
* @property {number} idleExpires
|
|
23835
|
+
* @property {number} maxExpires
|
|
23836
|
+
* @property {number} replayStartTime
|
|
23837
|
+
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23838
|
+
* @property {number} seqNo
|
|
23839
|
+
* @property {string} batchStartUrl
|
|
23840
|
+
* @property {string} replayStartUrl
|
|
23841
|
+
*/
|
|
23842
|
+
|
|
23843
|
+
/**
|
|
23844
|
+
* @typedef {Object} UserIdInfo
|
|
23845
|
+
* @property {string} distinct_id
|
|
23846
|
+
* @property {string} user_id
|
|
23847
|
+
* @property {string} device_id
|
|
23848
|
+
*/
|
|
23849
|
+
|
|
23850
|
+
|
|
23851
|
+
/**
|
|
23852
|
+
* This class encapsulates a single session recording and its lifecycle.
|
|
23853
|
+
* @param {SessionRecordingOptions} options
|
|
23854
|
+
*/
|
|
23855
|
+
var SessionRecording = function(options) {
|
|
23856
|
+
this._mixpanel = options.mixpanelInstance;
|
|
23857
|
+
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23858
|
+
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23859
|
+
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23860
|
+
this._rrwebRecord = options.rrwebRecord || null;
|
|
23861
|
+
|
|
23862
|
+
// internal rrweb stopRecording function
|
|
23863
|
+
this._stopRecording = null;
|
|
23864
|
+
this.replayId = options.replayId;
|
|
23865
|
+
|
|
23866
|
+
this.batchStartUrl = options.batchStartUrl || null;
|
|
23867
|
+
this.replayStartUrl = options.replayStartUrl || null;
|
|
23868
|
+
this.idleExpires = options.idleExpires || null;
|
|
23869
|
+
this.maxExpires = options.maxExpires || null;
|
|
23870
|
+
this.replayStartTime = options.replayStartTime || null;
|
|
23871
|
+
this.lastEventTimestamp = options.lastEventTimestamp || null;
|
|
23872
|
+
this.seqNo = options.seqNo || 0;
|
|
23873
|
+
|
|
23874
|
+
this.idleTimeoutId = null;
|
|
23875
|
+
this.maxTimeoutId = null;
|
|
23876
|
+
|
|
23877
|
+
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23878
|
+
this.recordMinMs = 0;
|
|
23879
|
+
|
|
23880
|
+
// disable persistence if localStorage is not supported
|
|
23881
|
+
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
23882
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
|
|
23883
|
+
|
|
23884
|
+
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23885
|
+
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23886
|
+
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23887
|
+
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23888
|
+
errorReporter: this.reportError.bind(this),
|
|
23889
|
+
flushOnlyOnInterval: true,
|
|
23890
|
+
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23891
|
+
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23892
|
+
queueStorage: this.queueStorage,
|
|
23893
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
23894
|
+
usePersistence: usePersistence,
|
|
23895
|
+
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23896
|
+
|
|
23897
|
+
// increased throttle and shared lock timeout because recording events are very high frequency.
|
|
23898
|
+
// this will minimize the amount of lock contention between enqueued events.
|
|
23899
|
+
// for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
|
|
23900
|
+
enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
|
|
23901
|
+
sharedLockTimeoutMS: 10 * 1000,
|
|
23902
|
+
});
|
|
23903
|
+
};
|
|
23904
|
+
|
|
23905
|
+
/**
|
|
23906
|
+
* @returns {UserIdInfo}
|
|
23907
|
+
*/
|
|
23908
|
+
SessionRecording.prototype.getUserIdInfo = function () {
|
|
23909
|
+
if (this.finalFlushUserIdInfo) {
|
|
23910
|
+
return this.finalFlushUserIdInfo;
|
|
23911
|
+
}
|
|
23912
|
+
|
|
23913
|
+
var userIdInfo = {
|
|
23914
|
+
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
23915
|
+
};
|
|
23916
|
+
|
|
23917
|
+
// send ID management props if they exist
|
|
23918
|
+
var deviceId = this._mixpanel.get_property('$device_id');
|
|
23919
|
+
if (deviceId) {
|
|
23920
|
+
userIdInfo['$device_id'] = deviceId;
|
|
23921
|
+
}
|
|
23922
|
+
var userId = this._mixpanel.get_property('$user_id');
|
|
23923
|
+
if (userId) {
|
|
23924
|
+
userIdInfo['$user_id'] = userId;
|
|
23925
|
+
}
|
|
23926
|
+
return userIdInfo;
|
|
23927
|
+
};
|
|
23928
|
+
|
|
23929
|
+
SessionRecording.prototype.unloadPersistedData = function () {
|
|
23930
|
+
this.batcher.stop();
|
|
23931
|
+
|
|
23932
|
+
return this.queueStorage.init().catch(function () {
|
|
23933
|
+
this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
|
|
23934
|
+
}.bind(this)).then(function () {
|
|
23935
|
+
// if the recording is too short, just delete any stored events without flushing
|
|
23936
|
+
if (this.getDurationMs() < this._getRecordMinMs()) {
|
|
23937
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23938
|
+
}
|
|
23939
|
+
|
|
23940
|
+
return this.batcher.flush()
|
|
23941
|
+
.then(function () {
|
|
23942
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23943
|
+
}.bind(this));
|
|
23944
|
+
}.bind(this));
|
|
23945
|
+
};
|
|
23946
|
+
|
|
23947
|
+
SessionRecording.prototype.getConfig = function(configVar) {
|
|
23948
|
+
return this._mixpanel.get_config(configVar);
|
|
23949
|
+
};
|
|
23950
|
+
|
|
23951
|
+
// Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
|
|
23224
23952
|
// reaches into this class instance and expects the snake case version of the function.
|
|
23225
23953
|
// eslint-disable-next-line camelcase
|
|
23226
23954
|
SessionRecording.prototype.get_config = function(configVar) {
|
|
@@ -23234,14 +23962,14 @@
|
|
|
23234
23962
|
}
|
|
23235
23963
|
|
|
23236
23964
|
if (this._stopRecording !== null) {
|
|
23237
|
-
logger$
|
|
23965
|
+
logger$3.log('Recording already in progress, skipping startRecording.');
|
|
23238
23966
|
return;
|
|
23239
23967
|
}
|
|
23240
23968
|
|
|
23241
23969
|
this.recordMaxMs = this.getConfig('record_max_ms');
|
|
23242
23970
|
if (this.recordMaxMs > MAX_RECORDING_MS) {
|
|
23243
23971
|
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23244
|
-
logger$
|
|
23972
|
+
logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
|
|
23245
23973
|
}
|
|
23246
23974
|
|
|
23247
23975
|
if (!this.maxExpires) {
|
|
@@ -23282,6 +24010,31 @@
|
|
|
23282
24010
|
|
|
23283
24011
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23284
24012
|
|
|
24013
|
+
var plugins = [];
|
|
24014
|
+
if (this.getConfig('record_network')) {
|
|
24015
|
+
var options = this.getConfig('record_network_options') || {};
|
|
24016
|
+
// don't track requests to Mixpanel /record API
|
|
24017
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
24018
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
24019
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
24020
|
+
|
|
24021
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
24022
|
+
}
|
|
24023
|
+
|
|
24024
|
+
if (this.getConfig('record_console')) {
|
|
24025
|
+
plugins.push(
|
|
24026
|
+
getRecordConsolePlugin({
|
|
24027
|
+
stringifyOptions: {
|
|
24028
|
+
stringLengthLimit: 1000,
|
|
24029
|
+
numOfKeysLimit: 50,
|
|
24030
|
+
depthOfLimit: 2
|
|
24031
|
+
}
|
|
24032
|
+
})
|
|
24033
|
+
);
|
|
24034
|
+
}
|
|
24035
|
+
|
|
24036
|
+
var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
|
|
24037
|
+
|
|
23285
24038
|
try {
|
|
23286
24039
|
this._stopRecording = this._rrwebRecord({
|
|
23287
24040
|
'emit': function (ev) {
|
|
@@ -23316,19 +24069,13 @@
|
|
|
23316
24069
|
'maskTextSelector': '*',
|
|
23317
24070
|
'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
|
|
23318
24071
|
'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
|
|
24072
|
+
'recordCrossOriginIframes': validatedOrigins.length > 0,
|
|
24073
|
+
'allowedIframeOrigins': validatedOrigins,
|
|
23319
24074
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
23320
24075
|
'sampling': {
|
|
23321
24076
|
'canvas': 15
|
|
23322
24077
|
},
|
|
23323
|
-
'plugins':
|
|
23324
|
-
getRecordConsolePlugin({
|
|
23325
|
-
stringifyOptions: {
|
|
23326
|
-
stringLengthLimit: 1000,
|
|
23327
|
-
numOfKeysLimit: 50,
|
|
23328
|
-
depthOfLimit: 2
|
|
23329
|
-
}
|
|
23330
|
-
})
|
|
23331
|
-
] : []
|
|
24078
|
+
'plugins': plugins,
|
|
23332
24079
|
});
|
|
23333
24080
|
} catch (err) {
|
|
23334
24081
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23443,6 +24190,10 @@
|
|
|
23443
24190
|
return recording;
|
|
23444
24191
|
};
|
|
23445
24192
|
|
|
24193
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24194
|
+
return this.getConfig('api_routes')['record'];
|
|
24195
|
+
};
|
|
24196
|
+
|
|
23446
24197
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23447
24198
|
var onSuccess = function (response, responseBody) {
|
|
23448
24199
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23462,7 +24213,7 @@
|
|
|
23462
24213
|
});
|
|
23463
24214
|
}.bind(this);
|
|
23464
24215
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23465
|
-
win['fetch'](apiHost + '/' + this.
|
|
24216
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23466
24217
|
'method': 'POST',
|
|
23467
24218
|
'headers': {
|
|
23468
24219
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23544,14 +24295,14 @@
|
|
|
23544
24295
|
|
|
23545
24296
|
|
|
23546
24297
|
SessionRecording.prototype.reportError = function(msg, err) {
|
|
23547
|
-
logger$
|
|
24298
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
23548
24299
|
try {
|
|
23549
24300
|
if (!err && !(msg instanceof Error)) {
|
|
23550
24301
|
msg = new Error(msg);
|
|
23551
24302
|
}
|
|
23552
24303
|
this.getConfig('error_reporter')(msg, err);
|
|
23553
24304
|
} catch(err) {
|
|
23554
|
-
logger$
|
|
24305
|
+
logger$3.error(err);
|
|
23555
24306
|
}
|
|
23556
24307
|
};
|
|
23557
24308
|
|
|
@@ -23580,7 +24331,7 @@
|
|
|
23580
24331
|
var configValue = this.getConfig('record_min_ms');
|
|
23581
24332
|
|
|
23582
24333
|
if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
|
|
23583
|
-
logger$
|
|
24334
|
+
logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
|
|
23584
24335
|
return MAX_VALUE_FOR_MIN_RECORDING_MS;
|
|
23585
24336
|
}
|
|
23586
24337
|
|
|
@@ -23743,7 +24494,7 @@
|
|
|
23743
24494
|
.catch(this.handleError.bind(this));
|
|
23744
24495
|
};
|
|
23745
24496
|
|
|
23746
|
-
var logger$
|
|
24497
|
+
var logger$2 = console_with_prefix('recorder');
|
|
23747
24498
|
|
|
23748
24499
|
/**
|
|
23749
24500
|
* Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
|
|
@@ -23759,7 +24510,7 @@
|
|
|
23759
24510
|
*/
|
|
23760
24511
|
this.recordingRegistry = new RecordingRegistry({
|
|
23761
24512
|
mixpanelInstance: this.mixpanelInstance,
|
|
23762
|
-
errorReporter: logger$
|
|
24513
|
+
errorReporter: logger$2.error,
|
|
23763
24514
|
sharedLockStorage: sharedLockStorage
|
|
23764
24515
|
});
|
|
23765
24516
|
this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
|
|
@@ -23771,17 +24522,17 @@
|
|
|
23771
24522
|
MixpanelRecorder.prototype.startRecording = function(options) {
|
|
23772
24523
|
options = options || {};
|
|
23773
24524
|
if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
|
|
23774
|
-
logger$
|
|
24525
|
+
logger$2.log('Recording already in progress, skipping startRecording.');
|
|
23775
24526
|
return;
|
|
23776
24527
|
}
|
|
23777
24528
|
|
|
23778
24529
|
var onIdleTimeout = function () {
|
|
23779
|
-
logger$
|
|
24530
|
+
logger$2.log('Idle timeout reached, restarting recording.');
|
|
23780
24531
|
this.resetRecording();
|
|
23781
24532
|
}.bind(this);
|
|
23782
24533
|
|
|
23783
24534
|
var onMaxLengthReached = function () {
|
|
23784
|
-
logger$
|
|
24535
|
+
logger$2.log('Max recording length reached, stopping recording.');
|
|
23785
24536
|
this.resetRecording();
|
|
23786
24537
|
}.bind(this);
|
|
23787
24538
|
|
|
@@ -23851,7 +24602,7 @@
|
|
|
23851
24602
|
} else if (startNewIfInactive) {
|
|
23852
24603
|
return this.startRecording({shouldStopBatcher: false});
|
|
23853
24604
|
} else {
|
|
23854
|
-
logger$
|
|
24605
|
+
logger$2.log('No resumable recording found.');
|
|
23855
24606
|
return null;
|
|
23856
24607
|
}
|
|
23857
24608
|
}.bind(this));
|
|
@@ -23863,8 +24614,12 @@
|
|
|
23863
24614
|
this.startRecording({shouldStopBatcher: true});
|
|
23864
24615
|
};
|
|
23865
24616
|
|
|
24617
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24618
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24619
|
+
};
|
|
24620
|
+
|
|
23866
24621
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23867
|
-
if (this.
|
|
24622
|
+
if (this.isRecording()) {
|
|
23868
24623
|
return this.activeRecording.replayId;
|
|
23869
24624
|
} else {
|
|
23870
24625
|
return null;
|
|
@@ -24368,53 +25123,6 @@
|
|
|
24368
25123
|
var logicExports = requireLogic();
|
|
24369
25124
|
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
24370
25125
|
|
|
24371
|
-
/**
|
|
24372
|
-
* Shared helper to recursively lowercase strings in nested structures
|
|
24373
|
-
* @param {*} obj - Value to process
|
|
24374
|
-
* @param {boolean} lowercaseKeys - Whether to lowercase object keys
|
|
24375
|
-
* @returns {*} Processed value with lowercased strings
|
|
24376
|
-
*/
|
|
24377
|
-
var lowercaseJson = function(obj, lowercaseKeys) {
|
|
24378
|
-
if (obj === null || obj === undefined) {
|
|
24379
|
-
return obj;
|
|
24380
|
-
} else if (typeof obj === 'string') {
|
|
24381
|
-
return obj.toLowerCase();
|
|
24382
|
-
} else if (Array.isArray(obj)) {
|
|
24383
|
-
return obj.map(function(item) {
|
|
24384
|
-
return lowercaseJson(item, lowercaseKeys);
|
|
24385
|
-
});
|
|
24386
|
-
} else if (obj === Object(obj)) {
|
|
24387
|
-
var result = {};
|
|
24388
|
-
for (var key in obj) {
|
|
24389
|
-
if (obj.hasOwnProperty(key)) {
|
|
24390
|
-
var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
|
|
24391
|
-
result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
|
|
24392
|
-
}
|
|
24393
|
-
}
|
|
24394
|
-
return result;
|
|
24395
|
-
} else {
|
|
24396
|
-
return obj;
|
|
24397
|
-
}
|
|
24398
|
-
};
|
|
24399
|
-
|
|
24400
|
-
/**
|
|
24401
|
-
* Lowercase all string keys and values in a nested structure
|
|
24402
|
-
* @param {*} val - Value to process
|
|
24403
|
-
* @returns {*} Processed value with lowercased strings
|
|
24404
|
-
*/
|
|
24405
|
-
var lowercaseKeysAndValues = function(val) {
|
|
24406
|
-
return lowercaseJson(val, true);
|
|
24407
|
-
};
|
|
24408
|
-
|
|
24409
|
-
/**
|
|
24410
|
-
* Lowercase only leaf node string values in a nested structure (keys unchanged)
|
|
24411
|
-
* @param {*} val - Value to process
|
|
24412
|
-
* @returns {*} Processed value with lowercased leaf strings
|
|
24413
|
-
*/
|
|
24414
|
-
var lowercaseOnlyLeafNodes = function(val) {
|
|
24415
|
-
return lowercaseJson(val, false);
|
|
24416
|
-
};
|
|
24417
|
-
|
|
24418
25126
|
/**
|
|
24419
25127
|
* Check if an event matches the given criteria
|
|
24420
25128
|
* @param {string} eventName - The name of the event being checked
|
|
@@ -24438,13 +25146,8 @@
|
|
|
24438
25146
|
|
|
24439
25147
|
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
24440
25148
|
try {
|
|
24441
|
-
//
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
// Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
|
|
24445
|
-
var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
|
|
24446
|
-
|
|
24447
|
-
filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
|
|
25149
|
+
// Use properties as-is for case-sensitive matching
|
|
25150
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
24448
25151
|
} catch (error) {
|
|
24449
25152
|
return {
|
|
24450
25153
|
matches: false,
|
|
@@ -24564,7 +25267,7 @@
|
|
|
24564
25267
|
observer.observe(shadowRoot, this.observerConfig);
|
|
24565
25268
|
this.shadowObservers.push(observer);
|
|
24566
25269
|
} catch (e) {
|
|
24567
|
-
logger$
|
|
25270
|
+
logger$5.critical('Error while observing shadow root', e);
|
|
24568
25271
|
}
|
|
24569
25272
|
};
|
|
24570
25273
|
|
|
@@ -24575,7 +25278,7 @@
|
|
|
24575
25278
|
}
|
|
24576
25279
|
|
|
24577
25280
|
if (!weakSetSupported()) {
|
|
24578
|
-
logger$
|
|
25281
|
+
logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
24579
25282
|
return;
|
|
24580
25283
|
}
|
|
24581
25284
|
|
|
@@ -24591,7 +25294,7 @@
|
|
|
24591
25294
|
try {
|
|
24592
25295
|
this.shadowObservers[i].disconnect();
|
|
24593
25296
|
} catch (e) {
|
|
24594
|
-
logger$
|
|
25297
|
+
logger$5.critical('Error while disconnecting shadow DOM observer', e);
|
|
24595
25298
|
}
|
|
24596
25299
|
}
|
|
24597
25300
|
this.shadowObservers = [];
|
|
@@ -24779,7 +25482,7 @@
|
|
|
24779
25482
|
|
|
24780
25483
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24781
25484
|
} catch (e) {
|
|
24782
|
-
logger$
|
|
25485
|
+
logger$5.critical('Error while setting up mutation observer', e);
|
|
24783
25486
|
}
|
|
24784
25487
|
}
|
|
24785
25488
|
|
|
@@ -24794,7 +25497,7 @@
|
|
|
24794
25497
|
);
|
|
24795
25498
|
this.shadowDOMObserver.start();
|
|
24796
25499
|
} catch (e) {
|
|
24797
|
-
logger$
|
|
25500
|
+
logger$5.critical('Error while setting up shadow DOM observer', e);
|
|
24798
25501
|
this.shadowDOMObserver = null;
|
|
24799
25502
|
}
|
|
24800
25503
|
}
|
|
@@ -24821,7 +25524,7 @@
|
|
|
24821
25524
|
try {
|
|
24822
25525
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24823
25526
|
} catch (e) {
|
|
24824
|
-
logger$
|
|
25527
|
+
logger$5.critical('Error while removing event listener', e);
|
|
24825
25528
|
}
|
|
24826
25529
|
}
|
|
24827
25530
|
this.eventListeners = [];
|
|
@@ -24830,7 +25533,7 @@
|
|
|
24830
25533
|
try {
|
|
24831
25534
|
this.mutationObserver.disconnect();
|
|
24832
25535
|
} catch (e) {
|
|
24833
|
-
logger$
|
|
25536
|
+
logger$5.critical('Error while disconnecting mutation observer', e);
|
|
24834
25537
|
}
|
|
24835
25538
|
this.mutationObserver = null;
|
|
24836
25539
|
}
|
|
@@ -24839,7 +25542,7 @@
|
|
|
24839
25542
|
try {
|
|
24840
25543
|
this.shadowDOMObserver.stop();
|
|
24841
25544
|
} catch (e) {
|
|
24842
|
-
logger$
|
|
25545
|
+
logger$5.critical('Error while stopping shadow DOM observer', e);
|
|
24843
25546
|
}
|
|
24844
25547
|
this.shadowDOMObserver = null;
|
|
24845
25548
|
}
|
|
@@ -24917,7 +25620,7 @@
|
|
|
24917
25620
|
|
|
24918
25621
|
Autocapture.prototype.init = function() {
|
|
24919
25622
|
if (!minDOMApisSupported()) {
|
|
24920
|
-
logger$
|
|
25623
|
+
logger$5.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24921
25624
|
return;
|
|
24922
25625
|
}
|
|
24923
25626
|
this.initPageListeners();
|
|
@@ -24949,27 +25652,15 @@
|
|
|
24949
25652
|
};
|
|
24950
25653
|
|
|
24951
25654
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24952
|
-
var i;
|
|
24953
25655
|
var currentUrl = _.info.currentUrl();
|
|
24954
25656
|
|
|
24955
25657
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24956
25658
|
if (allowUrlRegexes.length) {
|
|
24957
25659
|
// we're using an allowlist, only track if current URL matches
|
|
24958
|
-
|
|
24959
|
-
|
|
24960
|
-
|
|
24961
|
-
|
|
24962
|
-
if (currentUrl.match(allowRegex)) {
|
|
24963
|
-
allowed = true;
|
|
24964
|
-
break;
|
|
24965
|
-
}
|
|
24966
|
-
} catch (err) {
|
|
24967
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24968
|
-
return true;
|
|
24969
|
-
}
|
|
24970
|
-
}
|
|
24971
|
-
if (!allowed) {
|
|
24972
|
-
// wasn't allowed by any regex
|
|
25660
|
+
try {
|
|
25661
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25662
|
+
} catch (err) {
|
|
25663
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
24973
25664
|
return true;
|
|
24974
25665
|
}
|
|
24975
25666
|
}
|
|
@@ -24979,17 +25670,12 @@
|
|
|
24979
25670
|
return false;
|
|
24980
25671
|
}
|
|
24981
25672
|
|
|
24982
|
-
|
|
24983
|
-
|
|
24984
|
-
|
|
24985
|
-
|
|
24986
|
-
|
|
24987
|
-
} catch (err) {
|
|
24988
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24989
|
-
return true;
|
|
24990
|
-
}
|
|
25673
|
+
try {
|
|
25674
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25675
|
+
} catch (err) {
|
|
25676
|
+
logger$5.critical('Error while checking block URL regexes: ', err);
|
|
25677
|
+
return true;
|
|
24991
25678
|
}
|
|
24992
|
-
return false;
|
|
24993
25679
|
};
|
|
24994
25680
|
|
|
24995
25681
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -25125,7 +25811,7 @@
|
|
|
25125
25811
|
return;
|
|
25126
25812
|
}
|
|
25127
25813
|
|
|
25128
|
-
logger$
|
|
25814
|
+
logger$5.log('Initializing scroll depth tracking');
|
|
25129
25815
|
|
|
25130
25816
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
25131
25817
|
|
|
@@ -25151,7 +25837,7 @@
|
|
|
25151
25837
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
25152
25838
|
return;
|
|
25153
25839
|
}
|
|
25154
|
-
logger$
|
|
25840
|
+
logger$5.log('Initializing click tracking');
|
|
25155
25841
|
|
|
25156
25842
|
this.listenerClick = function(ev) {
|
|
25157
25843
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -25170,7 +25856,7 @@
|
|
|
25170
25856
|
return;
|
|
25171
25857
|
}
|
|
25172
25858
|
|
|
25173
|
-
logger$
|
|
25859
|
+
logger$5.log('Initializing dead click tracking');
|
|
25174
25860
|
if (!this._deadClickTracker) {
|
|
25175
25861
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
25176
25862
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -25204,7 +25890,7 @@
|
|
|
25204
25890
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
25205
25891
|
return;
|
|
25206
25892
|
}
|
|
25207
|
-
logger$
|
|
25893
|
+
logger$5.log('Initializing input tracking');
|
|
25208
25894
|
|
|
25209
25895
|
this.listenerChange = function(ev) {
|
|
25210
25896
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -25221,7 +25907,7 @@
|
|
|
25221
25907
|
if (!this.pageviewTrackingConfig()) {
|
|
25222
25908
|
return;
|
|
25223
25909
|
}
|
|
25224
|
-
logger$
|
|
25910
|
+
logger$5.log('Initializing pageview tracking');
|
|
25225
25911
|
|
|
25226
25912
|
var previousTrackedUrl = '';
|
|
25227
25913
|
var tracked = false;
|
|
@@ -25256,7 +25942,7 @@
|
|
|
25256
25942
|
}
|
|
25257
25943
|
if (didPathChange) {
|
|
25258
25944
|
this.lastScrollCheckpoint = 0;
|
|
25259
|
-
logger$
|
|
25945
|
+
logger$5.log('Path change: re-initializing scroll depth checkpoints');
|
|
25260
25946
|
}
|
|
25261
25947
|
}
|
|
25262
25948
|
}.bind(this));
|
|
@@ -25271,7 +25957,7 @@
|
|
|
25271
25957
|
return;
|
|
25272
25958
|
}
|
|
25273
25959
|
|
|
25274
|
-
logger$
|
|
25960
|
+
logger$5.log('Initializing rage click tracking');
|
|
25275
25961
|
if (!this._rageClickTracker) {
|
|
25276
25962
|
this._rageClickTracker = new RageClickTracker();
|
|
25277
25963
|
}
|
|
@@ -25301,7 +25987,7 @@
|
|
|
25301
25987
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
25302
25988
|
return;
|
|
25303
25989
|
}
|
|
25304
|
-
logger$
|
|
25990
|
+
logger$5.log('Initializing scroll tracking');
|
|
25305
25991
|
this.lastScrollCheckpoint = 0;
|
|
25306
25992
|
|
|
25307
25993
|
var scrollTrackFunction = function() {
|
|
@@ -25338,7 +26024,7 @@
|
|
|
25338
26024
|
}
|
|
25339
26025
|
}
|
|
25340
26026
|
} catch (err) {
|
|
25341
|
-
logger$
|
|
26027
|
+
logger$5.critical('Error while calculating scroll percentage', err);
|
|
25342
26028
|
}
|
|
25343
26029
|
if (shouldTrack) {
|
|
25344
26030
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -25356,7 +26042,7 @@
|
|
|
25356
26042
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
25357
26043
|
return;
|
|
25358
26044
|
}
|
|
25359
|
-
logger$
|
|
26045
|
+
logger$5.log('Initializing submit tracking');
|
|
25360
26046
|
|
|
25361
26047
|
this.listenerSubmit = function(ev) {
|
|
25362
26048
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -25378,7 +26064,7 @@
|
|
|
25378
26064
|
return;
|
|
25379
26065
|
}
|
|
25380
26066
|
|
|
25381
|
-
logger$
|
|
26067
|
+
logger$5.log('Initializing page visibility tracking.');
|
|
25382
26068
|
this._initScrollDepthTracking();
|
|
25383
26069
|
var previousTrackedUrl = _.info.currentUrl();
|
|
25384
26070
|
|
|
@@ -25463,7 +26149,7 @@
|
|
|
25463
26149
|
return win[TARGETING_GLOBAL_NAME];
|
|
25464
26150
|
};
|
|
25465
26151
|
|
|
25466
|
-
var logger = console_with_prefix('flags');
|
|
26152
|
+
var logger$1 = console_with_prefix('flags');
|
|
25467
26153
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
25468
26154
|
|
|
25469
26155
|
var CONFIG_CONTEXT = 'context';
|
|
@@ -25506,7 +26192,7 @@
|
|
|
25506
26192
|
|
|
25507
26193
|
FeatureFlagManager.prototype.init = function() {
|
|
25508
26194
|
if (!this.minApisSupported()) {
|
|
25509
|
-
logger.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
26195
|
+
logger$1.critical('Feature Flags unavailable: missing minimum required APIs');
|
|
25510
26196
|
return;
|
|
25511
26197
|
}
|
|
25512
26198
|
|
|
@@ -25541,7 +26227,7 @@
|
|
|
25541
26227
|
|
|
25542
26228
|
FeatureFlagManager.prototype.updateContext = function(newContext, options) {
|
|
25543
26229
|
if (!this.isSystemEnabled()) {
|
|
25544
|
-
logger.critical('Feature Flags not enabled, cannot update context');
|
|
26230
|
+
logger$1.critical('Feature Flags not enabled, cannot update context');
|
|
25545
26231
|
return Promise.resolve();
|
|
25546
26232
|
}
|
|
25547
26233
|
|
|
@@ -25558,7 +26244,7 @@
|
|
|
25558
26244
|
|
|
25559
26245
|
FeatureFlagManager.prototype.areFlagsReady = function() {
|
|
25560
26246
|
if (!this.isSystemEnabled()) {
|
|
25561
|
-
logger.error('Feature Flags not enabled');
|
|
26247
|
+
logger$1.error('Feature Flags not enabled');
|
|
25562
26248
|
}
|
|
25563
26249
|
return !!this.flags;
|
|
25564
26250
|
};
|
|
@@ -25571,7 +26257,7 @@
|
|
|
25571
26257
|
var distinctId = this.getMpProperty('distinct_id');
|
|
25572
26258
|
var deviceId = this.getMpProperty('$device_id');
|
|
25573
26259
|
var traceparent = generateTraceparent();
|
|
25574
|
-
logger.log('Fetching flags for distinct ID: ' + distinctId);
|
|
26260
|
+
logger$1.log('Fetching flags for distinct ID: ' + distinctId);
|
|
25575
26261
|
|
|
25576
26262
|
var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
|
|
25577
26263
|
var searchParams = new URLSearchParams();
|
|
@@ -25670,11 +26356,11 @@
|
|
|
25670
26356
|
this._loadTargetingIfNeeded();
|
|
25671
26357
|
}.bind(this)).catch(function(error) {
|
|
25672
26358
|
this.markFetchComplete();
|
|
25673
|
-
logger.error(error);
|
|
26359
|
+
logger$1.error(error);
|
|
25674
26360
|
}.bind(this));
|
|
25675
26361
|
}.bind(this)).catch(function(error) {
|
|
25676
26362
|
this.markFetchComplete();
|
|
25677
|
-
logger.error(error);
|
|
26363
|
+
logger$1.error(error);
|
|
25678
26364
|
}.bind(this));
|
|
25679
26365
|
|
|
25680
26366
|
return this.fetchPromise;
|
|
@@ -25682,7 +26368,7 @@
|
|
|
25682
26368
|
|
|
25683
26369
|
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
25684
26370
|
if (!this._fetchInProgressStartTime) {
|
|
25685
|
-
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
26371
|
+
logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
25686
26372
|
return;
|
|
25687
26373
|
}
|
|
25688
26374
|
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
@@ -25704,7 +26390,7 @@
|
|
|
25704
26390
|
|
|
25705
26391
|
if (hasPropertyFilters) {
|
|
25706
26392
|
this.getTargeting().then(function() {
|
|
25707
|
-
logger.log('targeting loaded for property filter evaluation');
|
|
26393
|
+
logger$1.log('targeting loaded for property filter evaluation');
|
|
25708
26394
|
});
|
|
25709
26395
|
}
|
|
25710
26396
|
};
|
|
@@ -25719,7 +26405,7 @@
|
|
|
25719
26405
|
this.loadExtraBundle.bind(this),
|
|
25720
26406
|
this.targetingSrc
|
|
25721
26407
|
).catch(function(error) {
|
|
25722
|
-
logger.error('Failed to load targeting: ' + error);
|
|
26408
|
+
logger$1.error('Failed to load targeting: ' + error);
|
|
25723
26409
|
}.bind(this));
|
|
25724
26410
|
};
|
|
25725
26411
|
|
|
@@ -25773,7 +26459,7 @@
|
|
|
25773
26459
|
|
|
25774
26460
|
// If no targeting library and event has property filters, skip it
|
|
25775
26461
|
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
25776
|
-
logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
26462
|
+
logger$1.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
25777
26463
|
return;
|
|
25778
26464
|
}
|
|
25779
26465
|
|
|
@@ -25796,7 +26482,7 @@
|
|
|
25796
26482
|
}
|
|
25797
26483
|
|
|
25798
26484
|
if (matchResult.error) {
|
|
25799
|
-
logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
26485
|
+
logger$1.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
25800
26486
|
return;
|
|
25801
26487
|
}
|
|
25802
26488
|
|
|
@@ -25804,7 +26490,7 @@
|
|
|
25804
26490
|
return;
|
|
25805
26491
|
}
|
|
25806
26492
|
|
|
25807
|
-
logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
26493
|
+
logger$1.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
25808
26494
|
|
|
25809
26495
|
var newVariant = {
|
|
25810
26496
|
'key': pendingEvent['pending_variant']['variant_key'],
|
|
@@ -25845,7 +26531,7 @@
|
|
|
25845
26531
|
'first_time_event_hash': firstTimeEventHash
|
|
25846
26532
|
};
|
|
25847
26533
|
|
|
25848
|
-
logger.log('Recording first-time event for flag: ' + flagId);
|
|
26534
|
+
logger$1.log('Recording first-time event for flag: ' + flagId);
|
|
25849
26535
|
|
|
25850
26536
|
// Fire-and-forget POST request
|
|
25851
26537
|
this.fetch.call(win, url, {
|
|
@@ -25858,130 +26544,446 @@
|
|
|
25858
26544
|
'body': JSON.stringify(payload)
|
|
25859
26545
|
}).catch(function(error) {
|
|
25860
26546
|
// Silent failure - cohort sync will catch up
|
|
25861
|
-
logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26547
|
+
logger$1.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26548
|
+
});
|
|
26549
|
+
};
|
|
26550
|
+
|
|
26551
|
+
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
26552
|
+
if (!this.fetchPromise) {
|
|
26553
|
+
return new Promise(function(resolve) {
|
|
26554
|
+
logger$1.critical('Feature Flags not initialized');
|
|
26555
|
+
resolve(fallback);
|
|
26556
|
+
});
|
|
26557
|
+
}
|
|
26558
|
+
|
|
26559
|
+
return this.fetchPromise.then(function() {
|
|
26560
|
+
return this.getVariantSync(featureName, fallback);
|
|
26561
|
+
}.bind(this)).catch(function(error) {
|
|
26562
|
+
logger$1.error(error);
|
|
26563
|
+
return fallback;
|
|
26564
|
+
});
|
|
26565
|
+
};
|
|
26566
|
+
|
|
26567
|
+
FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
|
|
26568
|
+
if (!this.areFlagsReady()) {
|
|
26569
|
+
logger$1.log('Flags not loaded yet');
|
|
26570
|
+
return fallback;
|
|
26571
|
+
}
|
|
26572
|
+
var feature = this.flags.get(featureName);
|
|
26573
|
+
if (!feature) {
|
|
26574
|
+
logger$1.log('No flag found: "' + featureName + '"');
|
|
26575
|
+
return fallback;
|
|
26576
|
+
}
|
|
26577
|
+
this.trackFeatureCheck(featureName, feature);
|
|
26578
|
+
return feature;
|
|
26579
|
+
};
|
|
26580
|
+
|
|
26581
|
+
FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
|
|
26582
|
+
return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
|
|
26583
|
+
return feature['value'];
|
|
26584
|
+
}).catch(function(error) {
|
|
26585
|
+
logger$1.error(error);
|
|
26586
|
+
return fallbackValue;
|
|
26587
|
+
});
|
|
26588
|
+
};
|
|
26589
|
+
|
|
26590
|
+
// TODO remove deprecated method
|
|
26591
|
+
FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
|
|
26592
|
+
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.');
|
|
26593
|
+
return this.getVariantValue(featureName, fallbackValue);
|
|
26594
|
+
};
|
|
26595
|
+
|
|
26596
|
+
FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
|
|
26597
|
+
return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
|
|
26598
|
+
};
|
|
26599
|
+
|
|
26600
|
+
FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
|
|
26601
|
+
return this.getVariantValue(featureName).then(function() {
|
|
26602
|
+
return this.isEnabledSync(featureName, fallbackValue);
|
|
26603
|
+
}.bind(this)).catch(function(error) {
|
|
26604
|
+
logger$1.error(error);
|
|
26605
|
+
return fallbackValue;
|
|
25862
26606
|
});
|
|
25863
26607
|
};
|
|
25864
26608
|
|
|
25865
|
-
FeatureFlagManager.prototype.
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
|
|
26609
|
+
FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
|
|
26610
|
+
fallbackValue = fallbackValue || false;
|
|
26611
|
+
var val = this.getVariantValueSync(featureName, fallbackValue);
|
|
26612
|
+
if (val !== true && val !== false) {
|
|
26613
|
+
logger$1.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
26614
|
+
val = fallbackValue;
|
|
26615
|
+
}
|
|
26616
|
+
return val;
|
|
26617
|
+
};
|
|
26618
|
+
|
|
26619
|
+
FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
|
|
26620
|
+
if (this.trackedFeatures.has(featureName)) {
|
|
26621
|
+
return;
|
|
26622
|
+
}
|
|
26623
|
+
this.trackedFeatures.add(featureName);
|
|
26624
|
+
|
|
26625
|
+
var trackingProperties = {
|
|
26626
|
+
'Experiment name': featureName,
|
|
26627
|
+
'Variant name': feature['key'],
|
|
26628
|
+
'$experiment_type': 'feature_flag',
|
|
26629
|
+
'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
|
|
26630
|
+
'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
|
|
26631
|
+
'Variant fetch latency (ms)': this._fetchLatency,
|
|
26632
|
+
'Variant fetch traceparent': this._traceparent,
|
|
26633
|
+
};
|
|
26634
|
+
|
|
26635
|
+
if (feature['experiment_id'] !== 'undefined') {
|
|
26636
|
+
trackingProperties['$experiment_id'] = feature['experiment_id'];
|
|
26637
|
+
}
|
|
26638
|
+
if (feature['is_experiment_active'] !== 'undefined') {
|
|
26639
|
+
trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
|
|
26640
|
+
}
|
|
26641
|
+
if (feature['is_qa_tester'] !== 'undefined') {
|
|
26642
|
+
trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
|
|
26643
|
+
}
|
|
26644
|
+
|
|
26645
|
+
this.track('$experiment_started', trackingProperties);
|
|
26646
|
+
};
|
|
26647
|
+
|
|
26648
|
+
FeatureFlagManager.prototype.minApisSupported = function() {
|
|
26649
|
+
return !!this.fetch &&
|
|
26650
|
+
typeof Promise !== 'undefined' &&
|
|
26651
|
+
typeof Map !== 'undefined' &&
|
|
26652
|
+
typeof Set !== 'undefined';
|
|
26653
|
+
};
|
|
26654
|
+
|
|
26655
|
+
safewrapClass(FeatureFlagManager);
|
|
26656
|
+
|
|
26657
|
+
FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
|
|
26658
|
+
FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
|
|
26659
|
+
FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
|
|
26660
|
+
FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
|
|
26661
|
+
FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
|
|
26662
|
+
FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
|
|
26663
|
+
FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
|
|
26664
|
+
FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
|
|
26665
|
+
|
|
26666
|
+
// Deprecated method
|
|
26667
|
+
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
26668
|
+
|
|
26669
|
+
// Exports intended only for testing
|
|
26670
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26671
|
+
|
|
26672
|
+
/* eslint camelcase: "off" */
|
|
26673
|
+
|
|
26674
|
+
|
|
26675
|
+
var logger = console_with_prefix('recorder');
|
|
26676
|
+
|
|
26677
|
+
var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
|
|
26678
|
+
var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
|
|
26679
|
+
|
|
26680
|
+
|
|
26681
|
+
/**
|
|
26682
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26683
|
+
* @constructor
|
|
26684
|
+
*/
|
|
26685
|
+
var RecorderManager = function(initOptions) {
|
|
26686
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26687
|
+
// but ideally we should be able to remove this dependency.
|
|
26688
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26689
|
+
|
|
26690
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26691
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26692
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26693
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26694
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26695
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26696
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26697
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26698
|
+
|
|
26699
|
+
this._recorder = null;
|
|
26700
|
+
this._parentReplayId = null;
|
|
26701
|
+
this._parentFrameRetryInterval = null;
|
|
26702
|
+
};
|
|
26703
|
+
|
|
26704
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26705
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26706
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26707
|
+
return PromisePolyfill.resolve(false);
|
|
26708
|
+
}
|
|
26709
|
+
|
|
26710
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26711
|
+
var tab_id = this.getTabId();
|
|
26712
|
+
return recording_registry_idb.init()
|
|
26713
|
+
.then(function () {
|
|
26714
|
+
return recording_registry_idb.getAll();
|
|
26715
|
+
})
|
|
26716
|
+
.then(function (recordings) {
|
|
26717
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26718
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26719
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26720
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26721
|
+
return true;
|
|
26722
|
+
}
|
|
26723
|
+
}
|
|
26724
|
+
return false;
|
|
26725
|
+
})
|
|
26726
|
+
.catch(_.bind(function (err) {
|
|
26727
|
+
this.reportError('Error checking recording registry', err);
|
|
26728
|
+
return false;
|
|
26729
|
+
}, this));
|
|
26730
|
+
};
|
|
26731
|
+
|
|
26732
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26733
|
+
if (!win['MutationObserver']) {
|
|
26734
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26735
|
+
return PromisePolyfill.resolve();
|
|
26736
|
+
}
|
|
26737
|
+
|
|
26738
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26739
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26740
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26741
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26742
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26743
|
+
resolve();
|
|
26744
|
+
}, this));
|
|
26745
|
+
|
|
26746
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26747
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26748
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26749
|
+
} else {
|
|
26750
|
+
handleLoadedRecorder();
|
|
26751
|
+
}
|
|
26752
|
+
}, this));
|
|
26753
|
+
}, this);
|
|
26754
|
+
|
|
26755
|
+
// Cross-origin iframe handling
|
|
26756
|
+
var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger);
|
|
26757
|
+
var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
|
|
26758
|
+
|
|
26759
|
+
if (isCrossOriginRecordingEnabled) {
|
|
26760
|
+
// listen for handshake requests from their own child iframes (including nested)
|
|
26761
|
+
this._setupParentFrameListener(allowedOrigins);
|
|
26762
|
+
|
|
26763
|
+
if (win.parent !== win) {
|
|
26764
|
+
// also wait for parent's replay ID
|
|
26765
|
+
this._setupChildFrameListener(allowedOrigins, loadRecorder);
|
|
26766
|
+
this._sendParentFrameRequestWithRetry(allowedOrigins);
|
|
26767
|
+
return PromisePolyfill.resolve();
|
|
26768
|
+
}
|
|
26769
|
+
}
|
|
26770
|
+
|
|
26771
|
+
/**
|
|
26772
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26773
|
+
* 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.
|
|
26774
|
+
*/
|
|
26775
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26776
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26777
|
+
if (force_start || is_sampled) {
|
|
26778
|
+
return loadRecorder(true);
|
|
26779
|
+
} else {
|
|
26780
|
+
return this.shouldLoadRecorder()
|
|
26781
|
+
.then(_.bind(function (shouldLoad) {
|
|
26782
|
+
if (shouldLoad) {
|
|
26783
|
+
return loadRecorder(false);
|
|
26784
|
+
}
|
|
26785
|
+
return PromisePolyfill.resolve();
|
|
26786
|
+
}, this));
|
|
26787
|
+
}
|
|
26788
|
+
};
|
|
26789
|
+
|
|
26790
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26791
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26792
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26793
|
+
return false;
|
|
26794
|
+
}
|
|
26795
|
+
try {
|
|
26796
|
+
return this._recorder['isRecording']();
|
|
26797
|
+
} catch (e) {
|
|
26798
|
+
this.reportError('Error checking if recording is active', e);
|
|
26799
|
+
return false;
|
|
25871
26800
|
}
|
|
26801
|
+
};
|
|
25872
26802
|
|
|
25873
|
-
|
|
25874
|
-
|
|
25875
|
-
|
|
25876
|
-
|
|
25877
|
-
|
|
25878
|
-
|
|
26803
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26804
|
+
var isRecording = this.isRecording();
|
|
26805
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26806
|
+
|
|
26807
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26808
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26809
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26810
|
+
var newRate = trigger['percentage'];
|
|
26811
|
+
var propertyFilters = trigger['property_filters'];
|
|
26812
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26813
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26814
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26815
|
+
.then(function(targeting) {
|
|
26816
|
+
try {
|
|
26817
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26818
|
+
event_name,
|
|
26819
|
+
properties,
|
|
26820
|
+
{
|
|
26821
|
+
'event_name': event_name,
|
|
26822
|
+
'property_filters': propertyFilters
|
|
26823
|
+
}
|
|
26824
|
+
);
|
|
26825
|
+
if (result['matches']) {
|
|
26826
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26827
|
+
}
|
|
26828
|
+
} catch (err) {
|
|
26829
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26830
|
+
}
|
|
26831
|
+
}.bind(this)).catch(function(err) {
|
|
26832
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26833
|
+
});
|
|
26834
|
+
} else {
|
|
26835
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26836
|
+
}
|
|
26837
|
+
}
|
|
26838
|
+
}
|
|
25879
26839
|
};
|
|
25880
26840
|
|
|
25881
|
-
|
|
25882
|
-
if (
|
|
25883
|
-
|
|
25884
|
-
return fallback;
|
|
26841
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26842
|
+
if (this._recorder) {
|
|
26843
|
+
return this._recorder['stopRecording']();
|
|
25885
26844
|
}
|
|
25886
|
-
|
|
25887
|
-
|
|
25888
|
-
|
|
25889
|
-
|
|
26845
|
+
return PromisePolyfill.resolve();
|
|
26846
|
+
};
|
|
26847
|
+
|
|
26848
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26849
|
+
if (this._recorder) {
|
|
26850
|
+
return this._recorder['pauseRecording']();
|
|
25890
26851
|
}
|
|
25891
|
-
|
|
25892
|
-
return feature;
|
|
26852
|
+
return PromisePolyfill.resolve();
|
|
25893
26853
|
};
|
|
25894
26854
|
|
|
25895
|
-
|
|
25896
|
-
|
|
25897
|
-
return
|
|
25898
|
-
}
|
|
25899
|
-
|
|
25900
|
-
return fallbackValue;
|
|
25901
|
-
});
|
|
26855
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26856
|
+
if (this._recorder) {
|
|
26857
|
+
return this._recorder['resumeRecording']();
|
|
26858
|
+
}
|
|
26859
|
+
return PromisePolyfill.resolve();
|
|
25902
26860
|
};
|
|
25903
26861
|
|
|
25904
|
-
|
|
25905
|
-
|
|
25906
|
-
logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
|
|
25907
|
-
return this.getVariantValue(featureName, fallbackValue);
|
|
26862
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26863
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
25908
26864
|
};
|
|
25909
26865
|
|
|
25910
|
-
|
|
25911
|
-
|
|
26866
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26867
|
+
var props = {};
|
|
26868
|
+
var replay_id = this.getSessionReplayId();
|
|
26869
|
+
if (replay_id) {
|
|
26870
|
+
props['$mp_replay_id'] = replay_id;
|
|
26871
|
+
}
|
|
26872
|
+
return props;
|
|
25912
26873
|
};
|
|
25913
26874
|
|
|
25914
|
-
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
|
|
26875
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26876
|
+
var replay_url = null;
|
|
26877
|
+
var replay_id = this.getSessionReplayId();
|
|
26878
|
+
if (replay_id) {
|
|
26879
|
+
var query_params = _.HTTPBuildQuery({
|
|
26880
|
+
'replay_id': replay_id,
|
|
26881
|
+
'distinct_id': this.getDistinctId(),
|
|
26882
|
+
'token': this.getMpConfig('token')
|
|
26883
|
+
});
|
|
26884
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26885
|
+
}
|
|
26886
|
+
return replay_url;
|
|
25921
26887
|
};
|
|
25922
26888
|
|
|
25923
|
-
|
|
25924
|
-
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
|
|
25928
|
-
val = fallbackValue;
|
|
26889
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26890
|
+
// Child iframe uses parent's replay ID
|
|
26891
|
+
if (this._parentReplayId) {
|
|
26892
|
+
return this._parentReplayId;
|
|
25929
26893
|
}
|
|
25930
|
-
|
|
26894
|
+
var replay_id = null;
|
|
26895
|
+
if (this._recorder) {
|
|
26896
|
+
replay_id = this._recorder['replayId'];
|
|
26897
|
+
}
|
|
26898
|
+
return replay_id || null;
|
|
25931
26899
|
};
|
|
25932
26900
|
|
|
25933
|
-
|
|
25934
|
-
|
|
26901
|
+
// "private" public method to reach into the recorder in test cases
|
|
26902
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26903
|
+
return this._recorder;
|
|
26904
|
+
};
|
|
26905
|
+
|
|
26906
|
+
RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
|
|
26907
|
+
if (this._childFrameMessageHandler) {
|
|
25935
26908
|
return;
|
|
25936
26909
|
}
|
|
25937
|
-
this
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
'
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
26910
|
+
var self = this;
|
|
26911
|
+
this._childFrameMessageHandler = function(event) {
|
|
26912
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26913
|
+
var data = event.data;
|
|
26914
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
|
|
26915
|
+
self._parentReplayId = data['replayId'];
|
|
26916
|
+
if (data['distinctId']) {
|
|
26917
|
+
self.mixpanelInstance['identify'](data['distinctId']);
|
|
26918
|
+
}
|
|
26919
|
+
self._parentFrameRetryActive = false;
|
|
26920
|
+
win.removeEventListener('message', self._childFrameMessageHandler);
|
|
26921
|
+
self._childFrameMessageHandler = null;
|
|
26922
|
+
loadRecorder(true);
|
|
26923
|
+
}
|
|
25947
26924
|
};
|
|
26925
|
+
win.addEventListener('message', this._childFrameMessageHandler);
|
|
26926
|
+
};
|
|
25948
26927
|
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
|
|
25952
|
-
|
|
25953
|
-
|
|
25954
|
-
|
|
25955
|
-
|
|
25956
|
-
|
|
26928
|
+
RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
|
|
26929
|
+
var message = {};
|
|
26930
|
+
message['type'] = IFRAME_HANDSHAKE_REQUEST;
|
|
26931
|
+
message['token'] = this.getMpConfig('token');
|
|
26932
|
+
for (var i = 0; i < allowedOrigins.length; i++) {
|
|
26933
|
+
try {
|
|
26934
|
+
win.parent.postMessage(message, allowedOrigins[i]);
|
|
26935
|
+
} catch (e) {
|
|
26936
|
+
// origin mismatch - ignore
|
|
26937
|
+
}
|
|
25957
26938
|
}
|
|
25958
|
-
|
|
25959
|
-
this.track('$experiment_started', trackingProperties);
|
|
25960
26939
|
};
|
|
25961
26940
|
|
|
25962
|
-
|
|
25963
|
-
|
|
25964
|
-
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
26941
|
+
RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
|
|
26942
|
+
var self = this;
|
|
26943
|
+
var maxRetries = 10;
|
|
26944
|
+
var retryCount = 0;
|
|
26945
|
+
var delay = 50;
|
|
26946
|
+
this._parentFrameRetryActive = true;
|
|
25968
26947
|
|
|
25969
|
-
|
|
26948
|
+
this._sendParentFrameRequest(allowedOrigins);
|
|
25970
26949
|
|
|
25971
|
-
|
|
25972
|
-
|
|
25973
|
-
|
|
25974
|
-
|
|
25975
|
-
|
|
25976
|
-
|
|
25977
|
-
|
|
25978
|
-
|
|
26950
|
+
function scheduleRetry() {
|
|
26951
|
+
setTimeout(function() {
|
|
26952
|
+
if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
|
|
26953
|
+
return;
|
|
26954
|
+
}
|
|
26955
|
+
self._sendParentFrameRequest(allowedOrigins);
|
|
26956
|
+
delay *= 2;
|
|
26957
|
+
scheduleRetry();
|
|
26958
|
+
}, delay);
|
|
26959
|
+
}
|
|
26960
|
+
scheduleRetry();
|
|
26961
|
+
};
|
|
25979
26962
|
|
|
25980
|
-
|
|
25981
|
-
|
|
26963
|
+
RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
|
|
26964
|
+
if (this._parentFrameMessageHandler) {
|
|
26965
|
+
return;
|
|
26966
|
+
}
|
|
26967
|
+
var self = this;
|
|
26968
|
+
this._parentFrameMessageHandler = function(event) {
|
|
26969
|
+
if (allowedOrigins.indexOf(event.origin) === -1) return;
|
|
26970
|
+
var data = event.data;
|
|
26971
|
+
if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
|
|
26972
|
+
var replayId = self.getSessionReplayId();
|
|
26973
|
+
if (replayId) {
|
|
26974
|
+
var response = {};
|
|
26975
|
+
response['type'] = IFRAME_HANDSHAKE_RESPONSE;
|
|
26976
|
+
response['token'] = self.getMpConfig('token');
|
|
26977
|
+
response['replayId'] = replayId;
|
|
26978
|
+
response['distinctId'] = self.getDistinctId();
|
|
26979
|
+
event.source.postMessage(response, event.origin);
|
|
26980
|
+
}
|
|
26981
|
+
}
|
|
26982
|
+
};
|
|
26983
|
+
win.addEventListener('message', this._parentFrameMessageHandler);
|
|
26984
|
+
};
|
|
25982
26985
|
|
|
25983
|
-
|
|
25984
|
-
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26986
|
+
safewrapClass(RecorderManager);
|
|
25985
26987
|
|
|
25986
26988
|
/* eslint camelcase: "off" */
|
|
25987
26989
|
|
|
@@ -27355,7 +28357,6 @@
|
|
|
27355
28357
|
/** @const */ var SETTING_FALLBACK = 'fallback';
|
|
27356
28358
|
/** @const */ var SETTING_DISABLED = 'disabled';
|
|
27357
28359
|
|
|
27358
|
-
|
|
27359
28360
|
/*
|
|
27360
28361
|
* Dynamic... constants? Is that an oxymoron?
|
|
27361
28362
|
*/
|
|
@@ -27440,19 +28441,24 @@
|
|
|
27440
28441
|
'batch_request_timeout_ms': 90000,
|
|
27441
28442
|
'batch_autostart': true,
|
|
27442
28443
|
'hooks': {},
|
|
28444
|
+
'record_allowed_iframe_origins': [],
|
|
27443
28445
|
'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
|
|
27444
28446
|
'record_block_selector': 'img, video, audio',
|
|
27445
28447
|
'record_canvas': false,
|
|
27446
28448
|
'record_collect_fonts': false,
|
|
27447
28449
|
'record_console': true,
|
|
27448
28450
|
'record_heatmap_data': false,
|
|
28451
|
+
'recording_event_triggers': {},
|
|
27449
28452
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
27450
28453
|
'record_mask_inputs': true,
|
|
27451
28454
|
'record_max_ms': MAX_RECORDING_MS,
|
|
27452
28455
|
'record_min_ms': 0,
|
|
28456
|
+
'record_network': false,
|
|
28457
|
+
'record_network_options': {},
|
|
27453
28458
|
'record_sessions_percent': 0,
|
|
27454
|
-
'recorder_src':
|
|
27455
|
-
'targeting_src':
|
|
28459
|
+
'recorder_src': null,
|
|
28460
|
+
'targeting_src': null,
|
|
28461
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
27456
28462
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
27457
28463
|
};
|
|
27458
28464
|
|
|
@@ -27606,6 +28612,19 @@
|
|
|
27606
28612
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
27607
28613
|
}));
|
|
27608
28614
|
|
|
28615
|
+
this.recorderManager = new RecorderManager({
|
|
28616
|
+
mixpanelInstance: this,
|
|
28617
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28618
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28619
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28620
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28621
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28622
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28623
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28624
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28625
|
+
loadExtraBundle: load_extra_bundle
|
|
28626
|
+
});
|
|
28627
|
+
|
|
27609
28628
|
this['_jsc'] = NOOP_FUNC;
|
|
27610
28629
|
|
|
27611
28630
|
this.__dom_loaded_queue = [];
|
|
@@ -27684,7 +28703,7 @@
|
|
|
27684
28703
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
27685
28704
|
trackingFunc: _.bind(this.track, this),
|
|
27686
28705
|
loadExtraBundle: load_extra_bundle,
|
|
27687
|
-
targetingSrc: this.get_config('targeting_src')
|
|
28706
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
27688
28707
|
});
|
|
27689
28708
|
this.flags.init();
|
|
27690
28709
|
this['flags'] = this.flags;
|
|
@@ -27697,11 +28716,11 @@
|
|
|
27697
28716
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
27698
28717
|
var mode = this.get_config('remote_settings_mode');
|
|
27699
28718
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
27700
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
27701
|
-
this._check_and_start_session_recording();
|
|
28719
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28720
|
+
return this._check_and_start_session_recording();
|
|
27702
28721
|
}, this));
|
|
27703
28722
|
} else {
|
|
27704
|
-
this._check_and_start_session_recording();
|
|
28723
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
27705
28724
|
}
|
|
27706
28725
|
};
|
|
27707
28726
|
|
|
@@ -27745,132 +28764,50 @@
|
|
|
27745
28764
|
return this.tab_id || null;
|
|
27746
28765
|
};
|
|
27747
28766
|
|
|
27748
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
27749
|
-
if (this.get_config('disable_persistence')) {
|
|
27750
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
27751
|
-
return Promise.resolve(false);
|
|
27752
|
-
}
|
|
27753
|
-
|
|
27754
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
27755
|
-
var tab_id = this.get_tab_id();
|
|
27756
|
-
return recording_registry_idb.init()
|
|
27757
|
-
.then(function () {
|
|
27758
|
-
return recording_registry_idb.getAll();
|
|
27759
|
-
})
|
|
27760
|
-
.then(function (recordings) {
|
|
27761
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
27762
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
27763
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
27764
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
27765
|
-
return true;
|
|
27766
|
-
}
|
|
27767
|
-
}
|
|
27768
|
-
return false;
|
|
27769
|
-
})
|
|
27770
|
-
.catch(_.bind(function (err) {
|
|
27771
|
-
this.report_error('Error checking recording registry', err);
|
|
27772
|
-
}, this));
|
|
27773
|
-
};
|
|
27774
|
-
|
|
27775
28767
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
27776
|
-
|
|
27777
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
27778
|
-
return;
|
|
27779
|
-
}
|
|
27780
|
-
|
|
27781
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
27782
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
27783
|
-
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
|
|
27784
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
27785
|
-
}, this);
|
|
27786
|
-
|
|
27787
|
-
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
27788
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
27789
|
-
} else {
|
|
27790
|
-
handleLoadedRecorder();
|
|
27791
|
-
}
|
|
27792
|
-
}, this);
|
|
27793
|
-
|
|
27794
|
-
/**
|
|
27795
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
27796
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
27797
|
-
*/
|
|
27798
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
27799
|
-
if (force_start || is_sampled) {
|
|
27800
|
-
loadRecorder(true);
|
|
27801
|
-
} else {
|
|
27802
|
-
this._should_load_recorder()
|
|
27803
|
-
.then(function (shouldLoad) {
|
|
27804
|
-
if (shouldLoad) {
|
|
27805
|
-
loadRecorder(false);
|
|
27806
|
-
}
|
|
27807
|
-
});
|
|
27808
|
-
}
|
|
28768
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
27809
28769
|
});
|
|
27810
28770
|
|
|
28771
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28772
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28773
|
+
};
|
|
28774
|
+
|
|
27811
28775
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
27812
|
-
this._check_and_start_session_recording(true);
|
|
28776
|
+
return this._check_and_start_session_recording(true);
|
|
27813
28777
|
};
|
|
27814
28778
|
|
|
27815
28779
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
27816
|
-
|
|
27817
|
-
return this._recorder['stopRecording']();
|
|
27818
|
-
}
|
|
27819
|
-
return Promise.resolve();
|
|
28780
|
+
return this.recorderManager.stopSessionRecording();
|
|
27820
28781
|
};
|
|
27821
28782
|
|
|
27822
28783
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
27823
|
-
|
|
27824
|
-
return this._recorder['pauseRecording']();
|
|
27825
|
-
}
|
|
27826
|
-
return Promise.resolve();
|
|
28784
|
+
return this.recorderManager.pauseSessionRecording();
|
|
27827
28785
|
};
|
|
27828
28786
|
|
|
27829
28787
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
27830
|
-
|
|
27831
|
-
return this._recorder['resumeRecording']();
|
|
27832
|
-
}
|
|
27833
|
-
return Promise.resolve();
|
|
28788
|
+
return this.recorderManager.resumeSessionRecording();
|
|
27834
28789
|
};
|
|
27835
28790
|
|
|
27836
28791
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
27837
|
-
return this.
|
|
28792
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
27838
28793
|
};
|
|
27839
28794
|
|
|
27840
28795
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
27841
|
-
|
|
27842
|
-
var replay_id = this._get_session_replay_id();
|
|
27843
|
-
if (replay_id) {
|
|
27844
|
-
props['$mp_replay_id'] = replay_id;
|
|
27845
|
-
}
|
|
27846
|
-
return props;
|
|
28796
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
27847
28797
|
};
|
|
27848
28798
|
|
|
27849
28799
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
27850
|
-
|
|
27851
|
-
var replay_id = this._get_session_replay_id();
|
|
27852
|
-
if (replay_id) {
|
|
27853
|
-
var query_params = _.HTTPBuildQuery({
|
|
27854
|
-
'replay_id': replay_id,
|
|
27855
|
-
'distinct_id': this.get_distinct_id(),
|
|
27856
|
-
'token': this.get_config('token')
|
|
27857
|
-
});
|
|
27858
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
27859
|
-
}
|
|
27860
|
-
return replay_url;
|
|
27861
|
-
};
|
|
27862
|
-
|
|
27863
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
27864
|
-
var replay_id = null;
|
|
27865
|
-
if (this._recorder) {
|
|
27866
|
-
replay_id = this._recorder['replayId'];
|
|
27867
|
-
}
|
|
27868
|
-
return replay_id || null;
|
|
28800
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
27869
28801
|
};
|
|
27870
28802
|
|
|
27871
28803
|
// "private" public method to reach into the recorder in test cases
|
|
27872
28804
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
27873
|
-
return this.
|
|
28805
|
+
return this.recorderManager.getRecorder();
|
|
28806
|
+
};
|
|
28807
|
+
|
|
28808
|
+
// "private" public method to get session recording init promise in test cases
|
|
28809
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28810
|
+
return this.__session_recording_init_promise;
|
|
27874
28811
|
};
|
|
27875
28812
|
|
|
27876
28813
|
// Private methods
|
|
@@ -28128,6 +29065,7 @@
|
|
|
28128
29065
|
};
|
|
28129
29066
|
|
|
28130
29067
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
29068
|
+
var self = this;
|
|
28131
29069
|
var disableRecordingIfStrict = function() {
|
|
28132
29070
|
if (mode === 'strict') {
|
|
28133
29071
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -28148,7 +29086,6 @@
|
|
|
28148
29086
|
};
|
|
28149
29087
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
28150
29088
|
var full_url = settings_endpoint + '?' + query_string;
|
|
28151
|
-
var self = this;
|
|
28152
29089
|
|
|
28153
29090
|
var abortController = new AbortController();
|
|
28154
29091
|
var timeout_id = setTimeout(function() {
|
|
@@ -28340,6 +29277,34 @@
|
|
|
28340
29277
|
this._execute_array([item]);
|
|
28341
29278
|
};
|
|
28342
29279
|
|
|
29280
|
+
/**
|
|
29281
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29282
|
+
* this function enable tracking of all events. If passed an
|
|
29283
|
+
* array of event names, those events will be enabled, but other
|
|
29284
|
+
* existing disabled events will continue to be not tracked.
|
|
29285
|
+
*
|
|
29286
|
+
* @param {Array} [events] An array of event names to enable
|
|
29287
|
+
*/
|
|
29288
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29289
|
+
var keys, new_disabled_events, i, j;
|
|
29290
|
+
|
|
29291
|
+
if (typeof(events) === 'undefined') {
|
|
29292
|
+
this._flags.disable_all_events = false;
|
|
29293
|
+
} else {
|
|
29294
|
+
keys = {};
|
|
29295
|
+
new_disabled_events = [];
|
|
29296
|
+
for (i = 0; i < events.length; i++) {
|
|
29297
|
+
keys[events[i]] = true;
|
|
29298
|
+
}
|
|
29299
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29300
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29301
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29302
|
+
}
|
|
29303
|
+
}
|
|
29304
|
+
this.__disabled_events = new_disabled_events;
|
|
29305
|
+
}
|
|
29306
|
+
};
|
|
29307
|
+
|
|
28343
29308
|
/**
|
|
28344
29309
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
28345
29310
|
* this function disables tracking of any event. If passed an
|
|
@@ -28513,6 +29478,8 @@
|
|
|
28513
29478
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
28514
29479
|
}
|
|
28515
29480
|
|
|
29481
|
+
this._start_recording_on_event(event_name, properties);
|
|
29482
|
+
|
|
28516
29483
|
var data = {
|
|
28517
29484
|
'event': event_name,
|
|
28518
29485
|
'properties': properties
|
|
@@ -29721,6 +30688,7 @@
|
|
|
29721
30688
|
// MixpanelLib Exports
|
|
29722
30689
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
29723
30690
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30691
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
29724
30692
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
29725
30693
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
29726
30694
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -29764,6 +30732,7 @@
|
|
|
29764
30732
|
|
|
29765
30733
|
// Exports intended only for testing
|
|
29766
30734
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30735
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
29767
30736
|
|
|
29768
30737
|
// MixpanelPersistence Exports
|
|
29769
30738
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|