mixpanel-browser 2.75.0 → 2.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +14 -0
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/integration-tests.yml +4 -4
- package/.github/workflows/unit-tests.yml +4 -4
- package/CHANGELOG.md +14 -0
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-DLKbUIEE.js +23669 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-CmVvUyFM.js +2520 -0
- package/dist/mixpanel-core.cjs.d.ts +70 -1
- package/dist/mixpanel-core.cjs.js +724 -426
- package/dist/mixpanel-recorder.js +791 -41
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +6 -62
- package/dist/mixpanel-targeting.min.js +1 -1
- package/dist/mixpanel-targeting.min.js.map +1 -1
- package/dist/mixpanel-with-async-modules.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-modules.cjs.js +724 -426
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +70 -1
- package/dist/mixpanel-with-async-recorder.cjs.js +724 -426
- package/dist/mixpanel-with-recorder.d.ts +70 -1
- package/dist/mixpanel-with-recorder.js +1471 -450
- package/dist/mixpanel-with-recorder.min.d.ts +70 -1
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +70 -1
- package/dist/mixpanel.amd.js +1473 -504
- package/dist/mixpanel.cjs.d.ts +70 -1
- package/dist/mixpanel.cjs.js +1473 -504
- package/dist/mixpanel.globals.js +724 -426
- package/dist/mixpanel.min.js +189 -182
- package/dist/mixpanel.module.d.ts +70 -1
- package/dist/mixpanel.module.js +1473 -504
- package/dist/mixpanel.umd.d.ts +70 -1
- package/dist/mixpanel.umd.js +1473 -504
- package/dist/rrweb-bundled.js +61 -9
- package/dist/rrweb-compiled.js +56 -9
- package/logo.svg +5 -0
- package/package.json +6 -4
- package/rollup.config.mjs +163 -46
- package/src/autocapture/index.js +10 -27
- package/src/config.js +9 -3
- package/src/flags/index.js +1 -2
- package/src/index.d.ts +70 -1
- package/src/mixpanel-core.js +77 -112
- package/src/recorder/index.js +1 -1
- package/src/recorder/recorder.js +5 -1
- package/src/recorder/rrweb-network-plugin.js +649 -0
- package/src/recorder/session-recording.js +36 -12
- package/src/recorder/utils.js +27 -1
- package/src/recorder-manager.js +324 -0
- package/src/request-batcher.js +1 -1
- package/src/targeting/event-matcher.js +2 -57
- package/src/targeting/index.js +1 -1
- package/src/targeting/loader.js +1 -1
- package/src/utils.js +13 -1
- package/testServer.js +69 -1
- package/src/globals.js +0 -14
- /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
|
@@ -26,12 +26,9 @@
|
|
|
26
26
|
win = window;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// Recorder library global (used by recorder and mixpanel-core)
|
|
29
|
+
var Config = {
|
|
30
|
+
LIB_VERSION: '2.77.0'
|
|
31
|
+
};
|
|
35
32
|
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
36
33
|
|
|
37
34
|
function _array_like_to_array(arr, len) {
|
|
@@ -10731,13 +10728,7 @@
|
|
|
10731
10728
|
};
|
|
10732
10729
|
while(_this.mapRemoves.length){
|
|
10733
10730
|
var removedNode = _this.mapRemoves.shift();
|
|
10734
|
-
|
|
10735
|
-
try {
|
|
10736
|
-
_this.iframeManager.removeIframe(removedNode);
|
|
10737
|
-
} catch (e2) {}
|
|
10738
|
-
} else {
|
|
10739
|
-
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10740
|
-
}
|
|
10731
|
+
_this.cleanupRemovedNode(removedNode);
|
|
10741
10732
|
_this.mirror.removeNodeFromMap(removedNode);
|
|
10742
10733
|
}
|
|
10743
10734
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
@@ -11057,6 +11048,20 @@
|
|
|
11057
11048
|
}
|
|
11058
11049
|
}
|
|
11059
11050
|
});
|
|
11051
|
+
__publicField$1(this, "cleanupRemovedNode", function(node2) {
|
|
11052
|
+
if (node2.nodeName === "IFRAME") {
|
|
11053
|
+
try {
|
|
11054
|
+
_this.iframeManager.removeIframe(node2);
|
|
11055
|
+
} catch (e2) {}
|
|
11056
|
+
} else {
|
|
11057
|
+
try {
|
|
11058
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(node2);
|
|
11059
|
+
} catch (e2) {}
|
|
11060
|
+
}
|
|
11061
|
+
node2.childNodes.forEach(function(child) {
|
|
11062
|
+
_this.cleanupRemovedNode(child);
|
|
11063
|
+
});
|
|
11064
|
+
});
|
|
11060
11065
|
}
|
|
11061
11066
|
var _proto = MutationBuffer.prototype;
|
|
11062
11067
|
_proto.init = function init(options) {
|
|
@@ -13284,6 +13289,31 @@
|
|
|
13284
13289
|
_proto.destroy = function destroy() {};
|
|
13285
13290
|
return ProcessedNodeManager;
|
|
13286
13291
|
}();
|
|
13292
|
+
function toOrigin(url) {
|
|
13293
|
+
try {
|
|
13294
|
+
var origin = new URL(url).origin;
|
|
13295
|
+
return origin !== "null" ? origin : null;
|
|
13296
|
+
} catch (e) {
|
|
13297
|
+
return null;
|
|
13298
|
+
}
|
|
13299
|
+
}
|
|
13300
|
+
function buildAllowedOriginSet(origins) {
|
|
13301
|
+
if (!Array.isArray(origins) || origins.length === 0) {
|
|
13302
|
+
throw new Error("[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.");
|
|
13303
|
+
}
|
|
13304
|
+
var set = /* @__PURE__ */ new Set();
|
|
13305
|
+
for(var i2 = 0; i2 < origins.length; i2++){
|
|
13306
|
+
var entry = origins[i2];
|
|
13307
|
+
if (typeof entry !== "string") {
|
|
13308
|
+
throw new Error("[rrweb] allowedIframeOrigins[" + i2 + "] must be a string, got " + (typeof entry === "undefined" ? "undefined" : _type_of(entry)) + ".");
|
|
13309
|
+
}
|
|
13310
|
+
var origin = toOrigin(entry);
|
|
13311
|
+
if (origin) {
|
|
13312
|
+
set.add(origin);
|
|
13313
|
+
}
|
|
13314
|
+
}
|
|
13315
|
+
return Object.freeze(set);
|
|
13316
|
+
}
|
|
13287
13317
|
var wrappedEmit;
|
|
13288
13318
|
var takeFullSnapshot$1;
|
|
13289
13319
|
var canvasManager;
|
|
@@ -13305,10 +13335,17 @@
|
|
|
13305
13335
|
var mirror = createMirror$2();
|
|
13306
13336
|
function record(options) {
|
|
13307
13337
|
if (options === void 0) options = {};
|
|
13308
|
-
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() {
|
|
13338
|
+
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() {
|
|
13309
13339
|
return false;
|
|
13310
13340
|
} : _options_keepIframeSrcFn, _options_ignoreCSSAttributes = options.ignoreCSSAttributes, ignoreCSSAttributes = _options_ignoreCSSAttributes === void 0 ? /* @__PURE__ */ new Set([]) : _options_ignoreCSSAttributes, errorHandler2 = options.errorHandler;
|
|
13311
13341
|
registerErrorHandler(errorHandler2);
|
|
13342
|
+
var validatedOrigins;
|
|
13343
|
+
if (recordCrossOriginIframes && allowedIframeOrigins && allowedIframeOrigins.length > 0) {
|
|
13344
|
+
validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
|
|
13345
|
+
if (validatedOrigins.size === 0) {
|
|
13346
|
+
validatedOrigins = void 0;
|
|
13347
|
+
}
|
|
13348
|
+
}
|
|
13312
13349
|
var inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
|
|
13313
13350
|
var passEmitsToParent = false;
|
|
13314
13351
|
if (!inEmittingFrame) {
|
|
@@ -13400,7 +13437,14 @@
|
|
|
13400
13437
|
origin: window.location.origin,
|
|
13401
13438
|
isCheckout: isCheckout
|
|
13402
13439
|
};
|
|
13403
|
-
|
|
13440
|
+
if (validatedOrigins) {
|
|
13441
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(validatedOrigins), _step; !(_step = _iterator()).done;){
|
|
13442
|
+
var targetOrigin = _step.value;
|
|
13443
|
+
window.parent.postMessage(message, targetOrigin);
|
|
13444
|
+
}
|
|
13445
|
+
} else {
|
|
13446
|
+
window.parent.postMessage(message, "*");
|
|
13447
|
+
}
|
|
13404
13448
|
}
|
|
13405
13449
|
if (e2.type === EventType.FullSnapshot) {
|
|
13406
13450
|
lastFullSnapshotEvent = e2;
|
|
@@ -18133,7 +18177,7 @@
|
|
|
18133
18177
|
var __publicField = function(obj, key, value) {
|
|
18134
18178
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18135
18179
|
};
|
|
18136
|
-
function patch(source, name, replacement) {
|
|
18180
|
+
function patch$3(source, name, replacement) {
|
|
18137
18181
|
try {
|
|
18138
18182
|
if (!(name in source)) {
|
|
18139
18183
|
return function() {};
|
|
@@ -18550,7 +18594,7 @@
|
|
|
18550
18594
|
if (!_logger[level]) {
|
|
18551
18595
|
return function() {};
|
|
18552
18596
|
}
|
|
18553
|
-
return patch(_logger, level, function(original) {
|
|
18597
|
+
return patch$3(_logger, level, function(original) {
|
|
18554
18598
|
var _this1 = _this;
|
|
18555
18599
|
return function() {
|
|
18556
18600
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18971,10 +19015,6 @@
|
|
|
18971
19015
|
PromisePolyfill = NpoPromise;
|
|
18972
19016
|
}
|
|
18973
19017
|
|
|
18974
|
-
var Config = {
|
|
18975
|
-
LIB_VERSION: '2.75.0'
|
|
18976
|
-
};
|
|
18977
|
-
|
|
18978
19018
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18979
19019
|
|
|
18980
19020
|
// Maximum allowed session recording length
|
|
@@ -20640,6 +20680,17 @@
|
|
|
20640
20680
|
|
|
20641
20681
|
var NOOP_FUNC = function () {};
|
|
20642
20682
|
|
|
20683
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20684
|
+
var matches = false;
|
|
20685
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20686
|
+
if (url.match(regexList[i])) {
|
|
20687
|
+
matches = true;
|
|
20688
|
+
break;
|
|
20689
|
+
}
|
|
20690
|
+
}
|
|
20691
|
+
return matches;
|
|
20692
|
+
};
|
|
20693
|
+
|
|
20643
20694
|
var JSONStringify = null, JSONParse = null;
|
|
20644
20695
|
if (typeof JSON !== 'undefined') {
|
|
20645
20696
|
JSONStringify = JSON.stringify;
|
|
@@ -20979,7 +21030,7 @@
|
|
|
20979
21030
|
};
|
|
20980
21031
|
}
|
|
20981
21032
|
|
|
20982
|
-
var logger$
|
|
21033
|
+
var logger$5 = console_with_prefix('lock');
|
|
20983
21034
|
|
|
20984
21035
|
/**
|
|
20985
21036
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21031,7 +21082,7 @@
|
|
|
21031
21082
|
|
|
21032
21083
|
var delay = function(cb) {
|
|
21033
21084
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21034
|
-
logger$
|
|
21085
|
+
logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21035
21086
|
storage.removeItem(keyZ);
|
|
21036
21087
|
storage.removeItem(keyY);
|
|
21037
21088
|
loop();
|
|
@@ -21178,7 +21229,7 @@
|
|
|
21178
21229
|
}, this));
|
|
21179
21230
|
};
|
|
21180
21231
|
|
|
21181
|
-
var logger$
|
|
21232
|
+
var logger$4 = console_with_prefix('batch');
|
|
21182
21233
|
|
|
21183
21234
|
/**
|
|
21184
21235
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21207,7 +21258,7 @@
|
|
|
21207
21258
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21208
21259
|
});
|
|
21209
21260
|
}
|
|
21210
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21261
|
+
this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
|
|
21211
21262
|
|
|
21212
21263
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21213
21264
|
|
|
@@ -21540,7 +21591,7 @@
|
|
|
21540
21591
|
// maximum interval between request retries after exponential backoff
|
|
21541
21592
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21542
21593
|
|
|
21543
|
-
var logger$
|
|
21594
|
+
var logger$3 = console_with_prefix('batch');
|
|
21544
21595
|
|
|
21545
21596
|
/**
|
|
21546
21597
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21668,7 +21719,7 @@
|
|
|
21668
21719
|
*/
|
|
21669
21720
|
RequestBatcher.prototype.flush = function(options) {
|
|
21670
21721
|
if (this.requestInProgress) {
|
|
21671
|
-
logger$
|
|
21722
|
+
logger$3.log('Flush: Request already in progress');
|
|
21672
21723
|
return PromisePolyfill.resolve();
|
|
21673
21724
|
}
|
|
21674
21725
|
|
|
@@ -21845,7 +21896,7 @@
|
|
|
21845
21896
|
if (options.unloading) {
|
|
21846
21897
|
requestOptions.transport = 'sendBeacon';
|
|
21847
21898
|
}
|
|
21848
|
-
logger$
|
|
21899
|
+
logger$3.log('MIXPANEL REQUEST:', dataForRequest);
|
|
21849
21900
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
21850
21901
|
}, this))
|
|
21851
21902
|
.catch(_.bind(function(err) {
|
|
@@ -21858,7 +21909,7 @@
|
|
|
21858
21909
|
* Log error to global logger and optional user-defined logger.
|
|
21859
21910
|
*/
|
|
21860
21911
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
21861
|
-
logger$
|
|
21912
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
21862
21913
|
if (this.errorReporter) {
|
|
21863
21914
|
try {
|
|
21864
21915
|
if (!(err instanceof Error)) {
|
|
@@ -21866,7 +21917,7 @@
|
|
|
21866
21917
|
}
|
|
21867
21918
|
this.errorReporter(msg, err);
|
|
21868
21919
|
} catch(err) {
|
|
21869
|
-
logger$
|
|
21920
|
+
logger$3.error(err);
|
|
21870
21921
|
}
|
|
21871
21922
|
}
|
|
21872
21923
|
};
|
|
@@ -21883,6 +21934,29 @@
|
|
|
21883
21934
|
|
|
21884
21935
|
var RECORD_ENQUEUE_THROTTLE_MS = 250;
|
|
21885
21936
|
|
|
21937
|
+
var validateAllowedOrigins = function(origins, logger) {
|
|
21938
|
+
if (!_.isArray(origins)) {
|
|
21939
|
+
if (origins) {
|
|
21940
|
+
logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
|
|
21941
|
+
}
|
|
21942
|
+
return [];
|
|
21943
|
+
}
|
|
21944
|
+
var valid = [];
|
|
21945
|
+
for (var i = 0; i < origins.length; i++) {
|
|
21946
|
+
try {
|
|
21947
|
+
var origin = new URL(origins[i]).origin;
|
|
21948
|
+
if (origin === 'null') {
|
|
21949
|
+
logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
|
|
21950
|
+
continue;
|
|
21951
|
+
}
|
|
21952
|
+
valid.push(origin);
|
|
21953
|
+
} catch (e) {
|
|
21954
|
+
logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
|
|
21955
|
+
}
|
|
21956
|
+
}
|
|
21957
|
+
return valid;
|
|
21958
|
+
};
|
|
21959
|
+
|
|
21886
21960
|
// stateless utils
|
|
21887
21961
|
// mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
|
|
21888
21962
|
|
|
@@ -22084,6 +22158,655 @@
|
|
|
22084
22158
|
}
|
|
22085
22159
|
}
|
|
22086
22160
|
|
|
22161
|
+
/**
|
|
22162
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
22163
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
22164
|
+
*
|
|
22165
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
22166
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
22167
|
+
*
|
|
22168
|
+
*/
|
|
22169
|
+
|
|
22170
|
+
var logger$2 = console_with_prefix('network-plugin');
|
|
22171
|
+
|
|
22172
|
+
/**
|
|
22173
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
22174
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
22175
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
22176
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
22177
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
22178
|
+
* @param {Window} win
|
|
22179
|
+
* @returns {number}
|
|
22180
|
+
*/
|
|
22181
|
+
function getTimeOrigin(win) {
|
|
22182
|
+
return Math.round(Date.now() - win.performance.now());
|
|
22183
|
+
}
|
|
22184
|
+
|
|
22185
|
+
/**
|
|
22186
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
22187
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
22188
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
22189
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
22190
|
+
*/
|
|
22191
|
+
|
|
22192
|
+
/**
|
|
22193
|
+
* @typedef {Record<string, string>} Headers
|
|
22194
|
+
*/
|
|
22195
|
+
|
|
22196
|
+
/**
|
|
22197
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
22198
|
+
*/
|
|
22199
|
+
|
|
22200
|
+
/**
|
|
22201
|
+
* @callback networkCallback
|
|
22202
|
+
* @param {NetworkData} data
|
|
22203
|
+
* @returns {void}
|
|
22204
|
+
*/
|
|
22205
|
+
|
|
22206
|
+
/**
|
|
22207
|
+
* @callback listenerHandler
|
|
22208
|
+
* @returns {void}
|
|
22209
|
+
*/
|
|
22210
|
+
|
|
22211
|
+
/**
|
|
22212
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
22213
|
+
*/
|
|
22214
|
+
|
|
22215
|
+
/**
|
|
22216
|
+
* @typedef {Object} RecordPlugin
|
|
22217
|
+
* @property {string} name
|
|
22218
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
22219
|
+
* @property {NetworkRecordOptions} [options]
|
|
22220
|
+
*/
|
|
22221
|
+
|
|
22222
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
22223
|
+
var defaultNetworkOptions = {
|
|
22224
|
+
initiatorTypes: [
|
|
22225
|
+
'audio',
|
|
22226
|
+
'beacon',
|
|
22227
|
+
'body',
|
|
22228
|
+
'css',
|
|
22229
|
+
'early-hint',
|
|
22230
|
+
'embed',
|
|
22231
|
+
'fetch',
|
|
22232
|
+
'frame',
|
|
22233
|
+
'iframe',
|
|
22234
|
+
'icon',
|
|
22235
|
+
'image',
|
|
22236
|
+
'img',
|
|
22237
|
+
'input',
|
|
22238
|
+
'link',
|
|
22239
|
+
'navigation',
|
|
22240
|
+
'object',
|
|
22241
|
+
'ping',
|
|
22242
|
+
'script',
|
|
22243
|
+
'track',
|
|
22244
|
+
'video',
|
|
22245
|
+
'xmlhttprequest',
|
|
22246
|
+
],
|
|
22247
|
+
ignoreRequestFn: function() { return false; },
|
|
22248
|
+
recordHeaders: {
|
|
22249
|
+
request: [],
|
|
22250
|
+
response: [],
|
|
22251
|
+
},
|
|
22252
|
+
recordBodyUrls: {
|
|
22253
|
+
request: [],
|
|
22254
|
+
response: [],
|
|
22255
|
+
},
|
|
22256
|
+
recordInitialRequests: false,
|
|
22257
|
+
};
|
|
22258
|
+
|
|
22259
|
+
/**
|
|
22260
|
+
* @param {PerformanceEntry} entry
|
|
22261
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
22262
|
+
*/
|
|
22263
|
+
function isNavigationTiming(entry) {
|
|
22264
|
+
return entry.entryType === 'navigation';
|
|
22265
|
+
}
|
|
22266
|
+
|
|
22267
|
+
/**
|
|
22268
|
+
* @param {PerformanceEntry} entry
|
|
22269
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
22270
|
+
*/
|
|
22271
|
+
function isResourceTiming (entry) {
|
|
22272
|
+
return entry.entryType === 'resource';
|
|
22273
|
+
}
|
|
22274
|
+
|
|
22275
|
+
function findLast(array, predicate) {
|
|
22276
|
+
var length = array.length;
|
|
22277
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
22278
|
+
if (predicate(array[i])) {
|
|
22279
|
+
return array[i];
|
|
22280
|
+
}
|
|
22281
|
+
}
|
|
22282
|
+
}
|
|
22283
|
+
|
|
22284
|
+
/**
|
|
22285
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
22286
|
+
* Adapted from Sentry's `fill` utility:
|
|
22287
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
22288
|
+
*
|
|
22289
|
+
* @param {object} source - The object containing the method to patch
|
|
22290
|
+
* @param {string} name - The method name to patch
|
|
22291
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
22292
|
+
* @returns {function} A function that restores the original method
|
|
22293
|
+
*/
|
|
22294
|
+
function patch(source, name, replacementFactory) {
|
|
22295
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
22296
|
+
return function() {};
|
|
22297
|
+
}
|
|
22298
|
+
var original = source[name];
|
|
22299
|
+
var wrapped = replacementFactory(original);
|
|
22300
|
+
source[name] = wrapped;
|
|
22301
|
+
return function() {
|
|
22302
|
+
source[name] = original;
|
|
22303
|
+
};
|
|
22304
|
+
}
|
|
22305
|
+
|
|
22306
|
+
|
|
22307
|
+
/**
|
|
22308
|
+
* Maximum body size to record (1MB)
|
|
22309
|
+
*/
|
|
22310
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
22311
|
+
|
|
22312
|
+
/**
|
|
22313
|
+
* Truncate string if it exceeds max size
|
|
22314
|
+
* @param {string} str
|
|
22315
|
+
* @returns {string}
|
|
22316
|
+
*/
|
|
22317
|
+
function truncateBody(str) {
|
|
22318
|
+
if (!str || typeof str !== 'string') {
|
|
22319
|
+
return str;
|
|
22320
|
+
}
|
|
22321
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
22322
|
+
logger$2.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
22323
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
22324
|
+
}
|
|
22325
|
+
return str;
|
|
22326
|
+
}
|
|
22327
|
+
|
|
22328
|
+
/**
|
|
22329
|
+
* @param {networkCallback} cb
|
|
22330
|
+
* @param {Window} win
|
|
22331
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22332
|
+
* @returns {listenerHandler}
|
|
22333
|
+
*/
|
|
22334
|
+
function initPerformanceObserver(cb, win, options) {
|
|
22335
|
+
if (!win.PerformanceObserver) {
|
|
22336
|
+
logger$2.error('PerformanceObserver not supported');
|
|
22337
|
+
return function() {
|
|
22338
|
+
//
|
|
22339
|
+
};
|
|
22340
|
+
}
|
|
22341
|
+
if (options.recordInitialRequests) {
|
|
22342
|
+
var initialPerformanceEntries = win.performance
|
|
22343
|
+
.getEntries()
|
|
22344
|
+
.filter(function(entry) {
|
|
22345
|
+
return isNavigationTiming(entry) ||
|
|
22346
|
+
(isResourceTiming(entry) &&
|
|
22347
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
22348
|
+
});
|
|
22349
|
+
cb({
|
|
22350
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
22351
|
+
return {
|
|
22352
|
+
url: entry.name,
|
|
22353
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22354
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
22355
|
+
startTime: Math.round(entry.startTime),
|
|
22356
|
+
endTime: Math.round(entry.responseEnd),
|
|
22357
|
+
timeOrigin: getTimeOrigin(win),
|
|
22358
|
+
};
|
|
22359
|
+
}),
|
|
22360
|
+
isInitial: true,
|
|
22361
|
+
});
|
|
22362
|
+
}
|
|
22363
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
22364
|
+
var performanceEntries = entries
|
|
22365
|
+
.getEntries()
|
|
22366
|
+
.filter(function(entry) {
|
|
22367
|
+
return isResourceTiming(entry) &&
|
|
22368
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
22369
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
22370
|
+
entry.initiatorType !== 'fetch';
|
|
22371
|
+
});
|
|
22372
|
+
cb({
|
|
22373
|
+
requests: performanceEntries.map(function(entry) {
|
|
22374
|
+
return {
|
|
22375
|
+
url: entry.name,
|
|
22376
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22377
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
22378
|
+
startTime: Math.round(entry.startTime),
|
|
22379
|
+
endTime: Math.round(entry.responseEnd),
|
|
22380
|
+
timeOrigin: getTimeOrigin(win),
|
|
22381
|
+
};
|
|
22382
|
+
}),
|
|
22383
|
+
});
|
|
22384
|
+
});
|
|
22385
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
22386
|
+
return function() {
|
|
22387
|
+
observer.disconnect();
|
|
22388
|
+
};
|
|
22389
|
+
}
|
|
22390
|
+
|
|
22391
|
+
/**
|
|
22392
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
22393
|
+
* @param {'request' | 'response'} type
|
|
22394
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
22395
|
+
* @param {string} headerName
|
|
22396
|
+
* @returns {boolean}
|
|
22397
|
+
*/
|
|
22398
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
22399
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
22400
|
+
return false;
|
|
22401
|
+
}
|
|
22402
|
+
|
|
22403
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
22404
|
+
}
|
|
22405
|
+
|
|
22406
|
+
/**
|
|
22407
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
22408
|
+
* @param {'request' | 'response'} type
|
|
22409
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
22410
|
+
* @param {string} url
|
|
22411
|
+
* @returns {boolean}
|
|
22412
|
+
*/
|
|
22413
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
22414
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
22415
|
+
return false;
|
|
22416
|
+
}
|
|
22417
|
+
|
|
22418
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
22419
|
+
}
|
|
22420
|
+
|
|
22421
|
+
function tryReadXHRBody(body) {
|
|
22422
|
+
if (body === null || body === undefined) {
|
|
22423
|
+
return null;
|
|
22424
|
+
}
|
|
22425
|
+
|
|
22426
|
+
var result;
|
|
22427
|
+
if (typeof body === 'string') {
|
|
22428
|
+
result = body;
|
|
22429
|
+
} else if (body instanceof Document) {
|
|
22430
|
+
result = body.textContent;
|
|
22431
|
+
} else if (body instanceof FormData) {
|
|
22432
|
+
result = _.HTTPBuildQuery(body);
|
|
22433
|
+
} else if (_.isObject(body)) {
|
|
22434
|
+
try {
|
|
22435
|
+
result = JSON.stringify(body);
|
|
22436
|
+
} catch (e) {
|
|
22437
|
+
return 'Failed to stringify response object';
|
|
22438
|
+
}
|
|
22439
|
+
} else {
|
|
22440
|
+
return 'Cannot read body of type ' + typeof body;
|
|
22441
|
+
}
|
|
22442
|
+
|
|
22443
|
+
return truncateBody(result);
|
|
22444
|
+
}
|
|
22445
|
+
|
|
22446
|
+
/**
|
|
22447
|
+
* @param {Request | Response} r
|
|
22448
|
+
* @returns {Promise<string>}
|
|
22449
|
+
*/
|
|
22450
|
+
function tryReadFetchBody(r) {
|
|
22451
|
+
return new Promise(function(resolve) {
|
|
22452
|
+
var timeout = setTimeout(function() {
|
|
22453
|
+
resolve('Timeout while trying to read body');
|
|
22454
|
+
}, 500);
|
|
22455
|
+
try {
|
|
22456
|
+
r.clone()
|
|
22457
|
+
.text()
|
|
22458
|
+
.then(
|
|
22459
|
+
function(txt) {
|
|
22460
|
+
clearTimeout(timeout);
|
|
22461
|
+
resolve(truncateBody(txt));
|
|
22462
|
+
},
|
|
22463
|
+
function(reason) {
|
|
22464
|
+
clearTimeout(timeout);
|
|
22465
|
+
resolve('Failed to read body: ' + String(reason));
|
|
22466
|
+
}
|
|
22467
|
+
);
|
|
22468
|
+
} catch (e) {
|
|
22469
|
+
clearTimeout(timeout);
|
|
22470
|
+
resolve('Failed to read body: ' + String(e));
|
|
22471
|
+
}
|
|
22472
|
+
});
|
|
22473
|
+
}
|
|
22474
|
+
|
|
22475
|
+
/**
|
|
22476
|
+
* @param {Window} win
|
|
22477
|
+
* @param {string} initiatorType
|
|
22478
|
+
* @param {string} url
|
|
22479
|
+
* @param {number} [after]
|
|
22480
|
+
* @param {number} [before]
|
|
22481
|
+
* @param {number} [attempt]
|
|
22482
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
22483
|
+
*/
|
|
22484
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
22485
|
+
if (attempt === undefined) {
|
|
22486
|
+
attempt = 0;
|
|
22487
|
+
}
|
|
22488
|
+
if (attempt > 10) {
|
|
22489
|
+
logger$2.error('Cannot find performance entry');
|
|
22490
|
+
return Promise.resolve(null);
|
|
22491
|
+
}
|
|
22492
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
22493
|
+
win.performance.getEntriesByName(url)
|
|
22494
|
+
);
|
|
22495
|
+
var performanceEntry = findLast(
|
|
22496
|
+
urlPerformanceEntries,
|
|
22497
|
+
function(entry) {
|
|
22498
|
+
return isResourceTiming(entry) &&
|
|
22499
|
+
entry.initiatorType === initiatorType &&
|
|
22500
|
+
(!after || entry.startTime >= after) &&
|
|
22501
|
+
(!before || entry.startTime <= before);
|
|
22502
|
+
}
|
|
22503
|
+
);
|
|
22504
|
+
if (!performanceEntry) {
|
|
22505
|
+
return new Promise(function(resolve) {
|
|
22506
|
+
setTimeout(resolve, 50 * attempt);
|
|
22507
|
+
}).then(function() {
|
|
22508
|
+
return getRequestPerformanceEntry(
|
|
22509
|
+
win,
|
|
22510
|
+
initiatorType,
|
|
22511
|
+
url,
|
|
22512
|
+
after,
|
|
22513
|
+
before,
|
|
22514
|
+
attempt + 1
|
|
22515
|
+
);
|
|
22516
|
+
});
|
|
22517
|
+
}
|
|
22518
|
+
return Promise.resolve(performanceEntry);
|
|
22519
|
+
}
|
|
22520
|
+
|
|
22521
|
+
/**
|
|
22522
|
+
* @param {networkCallback} cb
|
|
22523
|
+
* @param {Window} win
|
|
22524
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22525
|
+
* @returns {listenerHandler}
|
|
22526
|
+
*/
|
|
22527
|
+
function initXhrObserver(cb, win, options) {
|
|
22528
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
22529
|
+
return function() {
|
|
22530
|
+
//
|
|
22531
|
+
};
|
|
22532
|
+
}
|
|
22533
|
+
var restorePatch = patch(
|
|
22534
|
+
win.XMLHttpRequest.prototype,
|
|
22535
|
+
'open',
|
|
22536
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
22537
|
+
return function(
|
|
22538
|
+
/** @type {string} */ method,
|
|
22539
|
+
/** @type {string | URL} */ url,
|
|
22540
|
+
/** @type {boolean} */ async,
|
|
22541
|
+
username, password
|
|
22542
|
+
) {
|
|
22543
|
+
if (async === undefined) {
|
|
22544
|
+
async = true;
|
|
22545
|
+
}
|
|
22546
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
22547
|
+
var req = new Request(url, { method: method });
|
|
22548
|
+
/** @type {Partial<NetworkRequest>} */
|
|
22549
|
+
var networkRequest = {};
|
|
22550
|
+
/** @type {number | undefined} */
|
|
22551
|
+
var after;
|
|
22552
|
+
/** @type {number | undefined} */
|
|
22553
|
+
var before;
|
|
22554
|
+
|
|
22555
|
+
/** @type {Headers} */
|
|
22556
|
+
var requestHeaders = {};
|
|
22557
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
22558
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
22559
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
22560
|
+
requestHeaders[header] = value;
|
|
22561
|
+
}
|
|
22562
|
+
return originalSetRequestHeader(header, value);
|
|
22563
|
+
};
|
|
22564
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
22565
|
+
|
|
22566
|
+
var originalSend = xhr.send.bind(xhr);
|
|
22567
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
22568
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
22569
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
22570
|
+
}
|
|
22571
|
+
after = win.performance.now();
|
|
22572
|
+
return originalSend(body);
|
|
22573
|
+
};
|
|
22574
|
+
xhr.addEventListener('readystatechange', function() {
|
|
22575
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
22576
|
+
return;
|
|
22577
|
+
}
|
|
22578
|
+
before = win.performance.now();
|
|
22579
|
+
/** @type {Headers} */
|
|
22580
|
+
var responseHeaders = {};
|
|
22581
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
22582
|
+
if (rawHeaders) {
|
|
22583
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
22584
|
+
headers.forEach(function(line) {
|
|
22585
|
+
if (!line) return;
|
|
22586
|
+
var colonIndex = line.indexOf(': ');
|
|
22587
|
+
if (colonIndex === -1) return;
|
|
22588
|
+
var header = line.substring(0, colonIndex);
|
|
22589
|
+
var value = line.substring(colonIndex + 2);
|
|
22590
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
22591
|
+
responseHeaders[header] = value;
|
|
22592
|
+
}
|
|
22593
|
+
});
|
|
22594
|
+
}
|
|
22595
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
22596
|
+
if (
|
|
22597
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
22598
|
+
) {
|
|
22599
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
22600
|
+
}
|
|
22601
|
+
getRequestPerformanceEntry(
|
|
22602
|
+
win,
|
|
22603
|
+
'xmlhttprequest',
|
|
22604
|
+
req.url,
|
|
22605
|
+
after,
|
|
22606
|
+
before
|
|
22607
|
+
)
|
|
22608
|
+
.then(function(entry) {
|
|
22609
|
+
if (!entry) {
|
|
22610
|
+
logger$2.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
22611
|
+
return;
|
|
22612
|
+
}
|
|
22613
|
+
/** @type {NetworkRequest} */
|
|
22614
|
+
var request = {
|
|
22615
|
+
url: entry.name,
|
|
22616
|
+
method: req.method,
|
|
22617
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22618
|
+
status: xhr.status,
|
|
22619
|
+
startTime: Math.round(entry.startTime),
|
|
22620
|
+
endTime: Math.round(entry.responseEnd),
|
|
22621
|
+
timeOrigin: getTimeOrigin(win),
|
|
22622
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
22623
|
+
requestBody: networkRequest.requestBody,
|
|
22624
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
22625
|
+
responseBody: networkRequest.responseBody,
|
|
22626
|
+
};
|
|
22627
|
+
cb({ requests: [request] });
|
|
22628
|
+
})
|
|
22629
|
+
.catch(function(e) {
|
|
22630
|
+
logger$2.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
22631
|
+
});
|
|
22632
|
+
});
|
|
22633
|
+
|
|
22634
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
22635
|
+
};
|
|
22636
|
+
}
|
|
22637
|
+
);
|
|
22638
|
+
return function() {
|
|
22639
|
+
restorePatch();
|
|
22640
|
+
};
|
|
22641
|
+
}
|
|
22642
|
+
|
|
22643
|
+
/**
|
|
22644
|
+
* @param {networkCallback} cb
|
|
22645
|
+
* @param {Window} win
|
|
22646
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22647
|
+
* @returns {listenerHandler}
|
|
22648
|
+
*/
|
|
22649
|
+
function initFetchObserver(cb, win, options) {
|
|
22650
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
22651
|
+
return function() {
|
|
22652
|
+
//
|
|
22653
|
+
};
|
|
22654
|
+
}
|
|
22655
|
+
|
|
22656
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
22657
|
+
return function() {
|
|
22658
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
22659
|
+
/** @type {Response | undefined} */
|
|
22660
|
+
var res;
|
|
22661
|
+
/** @type {Partial<NetworkRequest>} */
|
|
22662
|
+
var networkRequest = {};
|
|
22663
|
+
/** @type {number | undefined} */
|
|
22664
|
+
var after;
|
|
22665
|
+
/** @type {number | undefined} */
|
|
22666
|
+
var before;
|
|
22667
|
+
|
|
22668
|
+
var originalFetchPromise;
|
|
22669
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
22670
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
22671
|
+
try {
|
|
22672
|
+
/** @type {Headers} */
|
|
22673
|
+
var requestHeaders = {};
|
|
22674
|
+
req.headers.forEach(function(value, header) {
|
|
22675
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
22676
|
+
requestHeaders[header] = value;
|
|
22677
|
+
}
|
|
22678
|
+
});
|
|
22679
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
22680
|
+
|
|
22681
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
22682
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
22683
|
+
.then(function(body) {
|
|
22684
|
+
networkRequest.requestBody = body;
|
|
22685
|
+
});
|
|
22686
|
+
}
|
|
22687
|
+
|
|
22688
|
+
after = win.performance.now();
|
|
22689
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
22690
|
+
res = response;
|
|
22691
|
+
before = win.performance.now();
|
|
22692
|
+
|
|
22693
|
+
/** @type {Headers} */
|
|
22694
|
+
var responseHeaders = {};
|
|
22695
|
+
res.headers.forEach(function(value, header) {
|
|
22696
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
22697
|
+
responseHeaders[header] = value;
|
|
22698
|
+
}
|
|
22699
|
+
});
|
|
22700
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
22701
|
+
|
|
22702
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
22703
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
22704
|
+
.then(function(body) {
|
|
22705
|
+
networkRequest.responseBody = body;
|
|
22706
|
+
});
|
|
22707
|
+
}
|
|
22708
|
+
|
|
22709
|
+
return res;
|
|
22710
|
+
});
|
|
22711
|
+
} catch (e) {
|
|
22712
|
+
originalFetchPromise = Promise.reject(e);
|
|
22713
|
+
}
|
|
22714
|
+
|
|
22715
|
+
// await concurrently so we don't delay the fetch response
|
|
22716
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
22717
|
+
.then(function () {
|
|
22718
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
22719
|
+
})
|
|
22720
|
+
.then(function(entry) {
|
|
22721
|
+
if (!entry) {
|
|
22722
|
+
logger$2.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
22723
|
+
return;
|
|
22724
|
+
}
|
|
22725
|
+
/** @type {NetworkRequest} */
|
|
22726
|
+
var request = {
|
|
22727
|
+
url: entry.name,
|
|
22728
|
+
method: req.method,
|
|
22729
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22730
|
+
status: res ? res.status : undefined,
|
|
22731
|
+
startTime: Math.round(entry.startTime),
|
|
22732
|
+
endTime: Math.round(entry.responseEnd),
|
|
22733
|
+
timeOrigin: getTimeOrigin(win),
|
|
22734
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
22735
|
+
requestBody: networkRequest.requestBody,
|
|
22736
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
22737
|
+
responseBody: networkRequest.responseBody,
|
|
22738
|
+
};
|
|
22739
|
+
cb({ requests: [request] });
|
|
22740
|
+
})
|
|
22741
|
+
.catch(function (e) {
|
|
22742
|
+
logger$2.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
22743
|
+
});
|
|
22744
|
+
|
|
22745
|
+
return originalFetchPromise;
|
|
22746
|
+
};
|
|
22747
|
+
});
|
|
22748
|
+
return function() {
|
|
22749
|
+
restorePatch();
|
|
22750
|
+
};
|
|
22751
|
+
}
|
|
22752
|
+
|
|
22753
|
+
/**
|
|
22754
|
+
* @param {networkCallback} callback
|
|
22755
|
+
* @param {Window} win
|
|
22756
|
+
* @param {NetworkRecordOptions} options
|
|
22757
|
+
* @returns {listenerHandler}
|
|
22758
|
+
*/
|
|
22759
|
+
function initNetworkObserver(callback, win, options) {
|
|
22760
|
+
if (!('performance' in win)) {
|
|
22761
|
+
return function() {
|
|
22762
|
+
//
|
|
22763
|
+
};
|
|
22764
|
+
}
|
|
22765
|
+
|
|
22766
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
22767
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
22768
|
+
options = Object.assign({}, options, {
|
|
22769
|
+
recordHeaders: recordHeaders,
|
|
22770
|
+
recordBodyUrls: recordBodyUrls,
|
|
22771
|
+
});
|
|
22772
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
22773
|
+
|
|
22774
|
+
/** @type {networkCallback} */
|
|
22775
|
+
var cb = function(data) {
|
|
22776
|
+
var requests = data.requests.filter(function(request) {
|
|
22777
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
22778
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
22779
|
+
});
|
|
22780
|
+
if (requests.length > 0 || data.isInitial) {
|
|
22781
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
22782
|
+
}
|
|
22783
|
+
};
|
|
22784
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
22785
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
22786
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
22787
|
+
return function() {
|
|
22788
|
+
performanceObserver();
|
|
22789
|
+
xhrObserver();
|
|
22790
|
+
fetchObserver();
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22793
|
+
|
|
22794
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
22795
|
+
// a changed format in the mixpanel product.
|
|
22796
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
22797
|
+
|
|
22798
|
+
/**
|
|
22799
|
+
* @param {NetworkRecordOptions} [options]
|
|
22800
|
+
* @returns {RecordPlugin}
|
|
22801
|
+
*/
|
|
22802
|
+
var getRecordNetworkPlugin = function(options) {
|
|
22803
|
+
return {
|
|
22804
|
+
name: NETWORK_PLUGIN_NAME,
|
|
22805
|
+
observer: initNetworkObserver,
|
|
22806
|
+
options: options,
|
|
22807
|
+
};
|
|
22808
|
+
};
|
|
22809
|
+
|
|
22087
22810
|
/**
|
|
22088
22811
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
22089
22812
|
*/
|
|
@@ -22317,6 +23040,31 @@
|
|
|
22317
23040
|
|
|
22318
23041
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
22319
23042
|
|
|
23043
|
+
var plugins = [];
|
|
23044
|
+
if (this.getConfig('record_network')) {
|
|
23045
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23046
|
+
// don't track requests to Mixpanel /record API
|
|
23047
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23048
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23049
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23050
|
+
|
|
23051
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23052
|
+
}
|
|
23053
|
+
|
|
23054
|
+
if (this.getConfig('record_console')) {
|
|
23055
|
+
plugins.push(
|
|
23056
|
+
getRecordConsolePlugin({
|
|
23057
|
+
stringifyOptions: {
|
|
23058
|
+
stringLengthLimit: 1000,
|
|
23059
|
+
numOfKeysLimit: 50,
|
|
23060
|
+
depthOfLimit: 2
|
|
23061
|
+
}
|
|
23062
|
+
})
|
|
23063
|
+
);
|
|
23064
|
+
}
|
|
23065
|
+
|
|
23066
|
+
var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$1);
|
|
23067
|
+
|
|
22320
23068
|
try {
|
|
22321
23069
|
this._stopRecording = this._rrwebRecord({
|
|
22322
23070
|
'emit': function (ev) {
|
|
@@ -22351,19 +23099,13 @@
|
|
|
22351
23099
|
'maskTextSelector': '*',
|
|
22352
23100
|
'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
|
|
22353
23101
|
'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
|
|
23102
|
+
'recordCrossOriginIframes': validatedOrigins.length > 0,
|
|
23103
|
+
'allowedIframeOrigins': validatedOrigins,
|
|
22354
23104
|
'recordCanvas': this.getConfig('record_canvas'),
|
|
22355
23105
|
'sampling': {
|
|
22356
23106
|
'canvas': 15
|
|
22357
23107
|
},
|
|
22358
|
-
'plugins':
|
|
22359
|
-
getRecordConsolePlugin({
|
|
22360
|
-
stringifyOptions: {
|
|
22361
|
-
stringLengthLimit: 1000,
|
|
22362
|
-
numOfKeysLimit: 50,
|
|
22363
|
-
depthOfLimit: 2
|
|
22364
|
-
}
|
|
22365
|
-
})
|
|
22366
|
-
] : []
|
|
23108
|
+
'plugins': plugins,
|
|
22367
23109
|
});
|
|
22368
23110
|
} catch (err) {
|
|
22369
23111
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -22478,6 +23220,10 @@
|
|
|
22478
23220
|
return recording;
|
|
22479
23221
|
};
|
|
22480
23222
|
|
|
23223
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
23224
|
+
return this.getConfig('api_routes')['record'];
|
|
23225
|
+
};
|
|
23226
|
+
|
|
22481
23227
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
22482
23228
|
var onSuccess = function (response, responseBody) {
|
|
22483
23229
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -22497,7 +23243,7 @@
|
|
|
22497
23243
|
});
|
|
22498
23244
|
}.bind(this);
|
|
22499
23245
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
22500
|
-
win['fetch'](apiHost + '/' + this.
|
|
23246
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
22501
23247
|
'method': 'POST',
|
|
22502
23248
|
'headers': {
|
|
22503
23249
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -22898,8 +23644,12 @@
|
|
|
22898
23644
|
this.startRecording({shouldStopBatcher: true});
|
|
22899
23645
|
};
|
|
22900
23646
|
|
|
23647
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
23648
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
23649
|
+
};
|
|
23650
|
+
|
|
22901
23651
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
22902
|
-
if (this.
|
|
23652
|
+
if (this.isRecording()) {
|
|
22903
23653
|
return this.activeRecording.replayId;
|
|
22904
23654
|
} else {
|
|
22905
23655
|
return null;
|