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