mixpanel-browser 2.74.0 → 2.76.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 +3 -1
- package/.github/workflows/integration-tests.yml +2 -2
- package/.github/workflows/unit-tests.yml +3 -3
- package/CHANGELOG.md +15 -0
- package/README.md +2 -2
- package/build.sh +10 -8
- package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
- package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
- package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
- package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
- package/dist/mixpanel-core.cjs.d.ts +68 -0
- package/dist/mixpanel-core.cjs.js +802 -337
- package/dist/mixpanel-recorder.js +828 -40
- package/dist/mixpanel-recorder.min.js +1 -1
- package/dist/mixpanel-recorder.min.js.map +1 -1
- package/dist/mixpanel-targeting.js +2520 -0
- package/dist/mixpanel-targeting.min.js +2 -0
- package/dist/mixpanel-targeting.min.js.map +1 -0
- package/dist/mixpanel-with-async-modules.cjs.d.ts +590 -0
- package/dist/mixpanel-with-async-modules.cjs.js +9867 -0
- package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
- package/dist/mixpanel-with-async-recorder.cjs.js +802 -337
- package/dist/mixpanel-with-recorder.d.ts +68 -0
- package/dist/mixpanel-with-recorder.js +1591 -343
- package/dist/mixpanel-with-recorder.min.d.ts +68 -0
- package/dist/mixpanel-with-recorder.min.js +1 -1
- package/dist/mixpanel.amd.d.ts +68 -0
- package/dist/mixpanel.amd.js +2124 -345
- package/dist/mixpanel.cjs.d.ts +68 -0
- package/dist/mixpanel.cjs.js +2124 -345
- package/dist/mixpanel.globals.js +802 -337
- package/dist/mixpanel.min.js +185 -175
- package/dist/mixpanel.module.d.ts +68 -0
- package/dist/mixpanel.module.js +2124 -345
- package/dist/mixpanel.umd.d.ts +68 -0
- package/dist/mixpanel.umd.js +2124 -345
- package/dist/rrweb-bundled.js +119 -5
- package/dist/rrweb-compiled.js +116 -5
- package/logo.svg +5 -0
- package/package.json +5 -3
- package/rollup.config.mjs +189 -40
- package/src/autocapture/index.js +10 -27
- package/src/config.js +9 -3
- package/src/flags/index.js +269 -9
- package/src/index.d.ts +68 -0
- package/src/loaders/loader-module.js +1 -0
- package/src/mixpanel-core.js +83 -109
- package/src/recorder/index.js +2 -1
- package/src/recorder/recorder.js +5 -1
- package/src/recorder/rrweb-network-plugin.js +649 -0
- package/src/recorder/session-recording.js +31 -11
- package/src/recorder-manager.js +216 -0
- package/src/request-batcher.js +1 -1
- package/src/targeting/event-matcher.js +42 -0
- package/src/targeting/index.js +11 -0
- package/src/targeting/loader.js +36 -0
- package/src/utils.js +14 -9
- package/testServer.js +55 -0
- /package/src/loaders/{loader-module-with-async-recorder.js → loader-module-with-async-modules.js} +0 -0
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
win = window;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
var Config = {
|
|
30
|
+
LIB_VERSION: '2.76.0'
|
|
31
|
+
};
|
|
32
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
33
|
+
|
|
29
34
|
function _array_like_to_array(arr, len) {
|
|
30
35
|
if (len == null || len > arr.length) len = arr.length;
|
|
31
36
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -640,14 +645,16 @@
|
|
|
640
645
|
return this.nodeMetaMap.get(n2) || null;
|
|
641
646
|
};
|
|
642
647
|
// removes the node from idNodeMap
|
|
643
|
-
//
|
|
644
|
-
_proto.removeNodeFromMap = function removeNodeFromMap(n2) {
|
|
648
|
+
// if permanent is true, also removes from nodeMetaMap
|
|
649
|
+
_proto.removeNodeFromMap = function removeNodeFromMap(n2, permanent) {
|
|
645
650
|
var _this = this;
|
|
651
|
+
if (permanent === void 0) permanent = false;
|
|
646
652
|
var id = this.getId(n2);
|
|
647
653
|
this.idNodeMap.delete(id);
|
|
654
|
+
if (permanent) this.nodeMetaMap.delete(n2);
|
|
648
655
|
if (n2.childNodes) {
|
|
649
656
|
n2.childNodes.forEach(function(childNode) {
|
|
650
|
-
return _this.removeNodeFromMap(childNode);
|
|
657
|
+
return _this.removeNodeFromMap(childNode, permanent);
|
|
651
658
|
});
|
|
652
659
|
}
|
|
653
660
|
};
|
|
@@ -10389,6 +10396,15 @@
|
|
|
10389
10396
|
_proto.generateId = function generateId() {
|
|
10390
10397
|
return this.id++;
|
|
10391
10398
|
};
|
|
10399
|
+
_proto.remove = function remove(stylesheet) {
|
|
10400
|
+
var id = this.styleIDMap.get(stylesheet);
|
|
10401
|
+
if (id !== void 0) {
|
|
10402
|
+
this.styleIDMap.delete(stylesheet);
|
|
10403
|
+
this.idStyleMap.delete(id);
|
|
10404
|
+
return true;
|
|
10405
|
+
}
|
|
10406
|
+
return false;
|
|
10407
|
+
};
|
|
10392
10408
|
return StyleSheetMirror;
|
|
10393
10409
|
}();
|
|
10394
10410
|
function getShadowHost(n2) {
|
|
@@ -10711,7 +10727,15 @@
|
|
|
10711
10727
|
}
|
|
10712
10728
|
};
|
|
10713
10729
|
while(_this.mapRemoves.length){
|
|
10714
|
-
_this.
|
|
10730
|
+
var removedNode = _this.mapRemoves.shift();
|
|
10731
|
+
if (removedNode.nodeName === "IFRAME") {
|
|
10732
|
+
try {
|
|
10733
|
+
_this.iframeManager.removeIframe(removedNode);
|
|
10734
|
+
} catch (e2) {}
|
|
10735
|
+
} else {
|
|
10736
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10737
|
+
}
|
|
10738
|
+
_this.mirror.removeNodeFromMap(removedNode);
|
|
10715
10739
|
}
|
|
10716
10740
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
10717
10741
|
var n2 = _step.value;
|
|
@@ -11085,6 +11109,9 @@
|
|
|
11085
11109
|
this.shadowDomManager.reset();
|
|
11086
11110
|
this.canvasManager.reset();
|
|
11087
11111
|
};
|
|
11112
|
+
_proto.getDoc = function getDoc() {
|
|
11113
|
+
return this.doc;
|
|
11114
|
+
};
|
|
11088
11115
|
return MutationBuffer;
|
|
11089
11116
|
}();
|
|
11090
11117
|
function deepDelete(addsSet, n2) {
|
|
@@ -11185,6 +11212,14 @@
|
|
|
11185
11212
|
});
|
|
11186
11213
|
return observer;
|
|
11187
11214
|
}
|
|
11215
|
+
function removeMutationBufferForDoc(doc) {
|
|
11216
|
+
for(var i2 = mutationBuffers.length - 1; i2 >= 0; i2--){
|
|
11217
|
+
var buffer = mutationBuffers[i2];
|
|
11218
|
+
if (buffer.getDoc() === doc) {
|
|
11219
|
+
mutationBuffers.splice(i2, 1);
|
|
11220
|
+
}
|
|
11221
|
+
}
|
|
11222
|
+
}
|
|
11188
11223
|
function initMoveObserver(param) {
|
|
11189
11224
|
var mousemoveCb = param.mousemoveCb, sampling = param.sampling, doc = param.doc, mirror2 = param.mirror;
|
|
11190
11225
|
if (sampling.mousemove === false) {
|
|
@@ -12200,6 +12235,8 @@
|
|
|
12200
12235
|
__publicField$1(this, "crossOriginIframeMirror", new CrossOriginIframeMirror(genId));
|
|
12201
12236
|
__publicField$1(this, "crossOriginIframeStyleMirror");
|
|
12202
12237
|
__publicField$1(this, "crossOriginIframeRootIdMap", /* @__PURE__ */ new WeakMap());
|
|
12238
|
+
__publicField$1(this, "iframeContentDocumentMap", /* @__PURE__ */ new WeakMap());
|
|
12239
|
+
__publicField$1(this, "iframeObserverCleanupMap", /* @__PURE__ */ new WeakMap());
|
|
12203
12240
|
__publicField$1(this, "mirror");
|
|
12204
12241
|
__publicField$1(this, "mutationCb");
|
|
12205
12242
|
__publicField$1(this, "wrappedEmit");
|
|
@@ -12221,6 +12258,31 @@
|
|
|
12221
12258
|
this.iframes.set(iframeEl, true);
|
|
12222
12259
|
if (iframeEl.contentWindow) this.crossOriginIframeMap.set(iframeEl.contentWindow, iframeEl);
|
|
12223
12260
|
};
|
|
12261
|
+
_proto.getIframeContentDocument = function getIframeContentDocument(iframeEl) {
|
|
12262
|
+
return this.iframeContentDocumentMap.get(iframeEl);
|
|
12263
|
+
};
|
|
12264
|
+
_proto.setObserverCleanup = function setObserverCleanup(iframeEl, cleanup) {
|
|
12265
|
+
this.iframeObserverCleanupMap.set(iframeEl, cleanup);
|
|
12266
|
+
};
|
|
12267
|
+
_proto.getObserverCleanup = function getObserverCleanup(iframeEl) {
|
|
12268
|
+
return this.iframeObserverCleanupMap.get(iframeEl);
|
|
12269
|
+
};
|
|
12270
|
+
_proto.removeIframe = function removeIframe(iframeEl) {
|
|
12271
|
+
var storedDoc = this.iframeContentDocumentMap.get(iframeEl);
|
|
12272
|
+
if (storedDoc) {
|
|
12273
|
+
this.stylesheetManager.cleanupStylesheetsForRemovedNode(storedDoc);
|
|
12274
|
+
this.mirror.removeNodeFromMap(storedDoc, true);
|
|
12275
|
+
}
|
|
12276
|
+
this.iframes.delete(iframeEl);
|
|
12277
|
+
this.iframeContentDocumentMap.delete(iframeEl);
|
|
12278
|
+
var observerCleanup = this.iframeObserverCleanupMap.get(iframeEl);
|
|
12279
|
+
if (observerCleanup) {
|
|
12280
|
+
try {
|
|
12281
|
+
observerCleanup();
|
|
12282
|
+
} catch (e2) {}
|
|
12283
|
+
this.iframeObserverCleanupMap.delete(iframeEl);
|
|
12284
|
+
}
|
|
12285
|
+
};
|
|
12224
12286
|
_proto.addLoadListener = function addLoadListener(cb) {
|
|
12225
12287
|
this.loadListener = cb;
|
|
12226
12288
|
};
|
|
@@ -12239,6 +12301,9 @@
|
|
|
12239
12301
|
attributes: [],
|
|
12240
12302
|
isAttachIframe: true
|
|
12241
12303
|
});
|
|
12304
|
+
if (iframeEl.contentDocument) {
|
|
12305
|
+
this.iframeContentDocumentMap.set(iframeEl, iframeEl.contentDocument);
|
|
12306
|
+
}
|
|
12242
12307
|
if (this.recordCrossOriginIframes) (_a2 = iframeEl.contentWindow) == null ? void 0 : _a2.addEventListener("message", this.handleMessage.bind(this));
|
|
12243
12308
|
(_b = this.loadListener) == null ? void 0 : _b.call(this, iframeEl);
|
|
12244
12309
|
if (iframeEl.contentDocument && iframeEl.contentDocument.adoptedStyleSheets && iframeEl.contentDocument.adoptedStyleSheets.length > 0) this.stylesheetManager.adoptStyleSheets(iframeEl.contentDocument.adoptedStyleSheets, this.mirror.getId(iframeEl.contentDocument));
|
|
@@ -13151,6 +13216,41 @@
|
|
|
13151
13216
|
this.styleMirror.reset();
|
|
13152
13217
|
this.trackedLinkElements = /* @__PURE__ */ new WeakSet();
|
|
13153
13218
|
};
|
|
13219
|
+
/**
|
|
13220
|
+
* Cleans up stylesheets associated with a removed node.
|
|
13221
|
+
*
|
|
13222
|
+
* @param removedNode - The node that was removed from the DOM.
|
|
13223
|
+
*/ _proto.cleanupStylesheetsForRemovedNode = function cleanupStylesheetsForRemovedNode(removedNode) {
|
|
13224
|
+
var _this = this;
|
|
13225
|
+
try {
|
|
13226
|
+
if (removedNode.nodeType === Node.DOCUMENT_NODE) {
|
|
13227
|
+
var doc = removedNode;
|
|
13228
|
+
if (doc.adoptedStyleSheets) {
|
|
13229
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(doc.adoptedStyleSheets), _step; !(_step = _iterator()).done;){
|
|
13230
|
+
var sheet = _step.value;
|
|
13231
|
+
this.styleMirror.remove(sheet);
|
|
13232
|
+
}
|
|
13233
|
+
}
|
|
13234
|
+
}
|
|
13235
|
+
if (removedNode.nodeName === "STYLE") {
|
|
13236
|
+
var styleEl = removedNode;
|
|
13237
|
+
if (styleEl.sheet) {
|
|
13238
|
+
this.styleMirror.remove(styleEl.sheet);
|
|
13239
|
+
}
|
|
13240
|
+
}
|
|
13241
|
+
if (removedNode.nodeName === "LINK" && removedNode.rel === "stylesheet") {
|
|
13242
|
+
var linkEl = removedNode;
|
|
13243
|
+
if (linkEl.sheet) {
|
|
13244
|
+
this.styleMirror.remove(linkEl.sheet);
|
|
13245
|
+
}
|
|
13246
|
+
}
|
|
13247
|
+
if (removedNode.childNodes) {
|
|
13248
|
+
removedNode.childNodes.forEach(function(child) {
|
|
13249
|
+
_this.cleanupStylesheetsForRemovedNode(child);
|
|
13250
|
+
});
|
|
13251
|
+
}
|
|
13252
|
+
} catch (e2) {}
|
|
13253
|
+
};
|
|
13154
13254
|
// TODO: take snapshot on stylesheet reload by applying event listener
|
|
13155
13255
|
_proto.trackStylesheetInLinkElement = function trackStylesheetInLinkElement(_linkEl) {};
|
|
13156
13256
|
return StylesheetManager;
|
|
@@ -13605,7 +13705,23 @@
|
|
|
13605
13705
|
};
|
|
13606
13706
|
iframeManager.addLoadListener(function(iframeEl) {
|
|
13607
13707
|
try {
|
|
13608
|
-
|
|
13708
|
+
var iframeDoc = iframeEl.contentDocument;
|
|
13709
|
+
var iframeHandler = observe(iframeDoc);
|
|
13710
|
+
handlers.push(iframeHandler);
|
|
13711
|
+
var existingCleanup = iframeManager.getObserverCleanup(iframeEl);
|
|
13712
|
+
iframeManager.setObserverCleanup(iframeEl, function() {
|
|
13713
|
+
if (existingCleanup) {
|
|
13714
|
+
try {
|
|
13715
|
+
existingCleanup();
|
|
13716
|
+
} catch (e2) {}
|
|
13717
|
+
}
|
|
13718
|
+
try {
|
|
13719
|
+
iframeHandler();
|
|
13720
|
+
var idx = handlers.indexOf(iframeHandler);
|
|
13721
|
+
if (idx !== -1) handlers.splice(idx, 1);
|
|
13722
|
+
removeMutationBufferForDoc(iframeDoc);
|
|
13723
|
+
} catch (e2) {}
|
|
13724
|
+
});
|
|
13609
13725
|
} catch (error) {
|
|
13610
13726
|
console.warn(error);
|
|
13611
13727
|
}
|
|
@@ -18014,7 +18130,7 @@
|
|
|
18014
18130
|
var __publicField = function(obj, key, value) {
|
|
18015
18131
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18016
18132
|
};
|
|
18017
|
-
function patch(source, name, replacement) {
|
|
18133
|
+
function patch$3(source, name, replacement) {
|
|
18018
18134
|
try {
|
|
18019
18135
|
if (!(name in source)) {
|
|
18020
18136
|
return function() {};
|
|
@@ -18431,7 +18547,7 @@
|
|
|
18431
18547
|
if (!_logger[level]) {
|
|
18432
18548
|
return function() {};
|
|
18433
18549
|
}
|
|
18434
|
-
return patch(_logger, level, function(original) {
|
|
18550
|
+
return patch$3(_logger, level, function(original) {
|
|
18435
18551
|
var _this1 = _this;
|
|
18436
18552
|
return function() {
|
|
18437
18553
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18852,10 +18968,6 @@
|
|
|
18852
18968
|
PromisePolyfill = NpoPromise;
|
|
18853
18969
|
}
|
|
18854
18970
|
|
|
18855
|
-
var Config = {
|
|
18856
|
-
LIB_VERSION: '2.74.0'
|
|
18857
|
-
};
|
|
18858
|
-
|
|
18859
18971
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18860
18972
|
|
|
18861
18973
|
// Maximum allowed session recording length
|
|
@@ -19007,15 +19119,8 @@
|
|
|
19007
19119
|
return toString.call(obj) === '[object Array]';
|
|
19008
19120
|
};
|
|
19009
19121
|
|
|
19010
|
-
// from a comment on http://dbj.org/dbj/?p=286
|
|
19011
|
-
// fails on only one very rare and deliberate custom object:
|
|
19012
|
-
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
19013
19122
|
_.isFunction = function(f) {
|
|
19014
|
-
|
|
19015
|
-
return /^\s*\bfunction\b/.test(f);
|
|
19016
|
-
} catch (x) {
|
|
19017
|
-
return false;
|
|
19018
|
-
}
|
|
19123
|
+
return typeof f === 'function';
|
|
19019
19124
|
};
|
|
19020
19125
|
|
|
19021
19126
|
_.isArguments = function(obj) {
|
|
@@ -20528,6 +20633,17 @@
|
|
|
20528
20633
|
|
|
20529
20634
|
var NOOP_FUNC = function () {};
|
|
20530
20635
|
|
|
20636
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20637
|
+
var matches = false;
|
|
20638
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20639
|
+
if (url.match(regexList[i])) {
|
|
20640
|
+
matches = true;
|
|
20641
|
+
break;
|
|
20642
|
+
}
|
|
20643
|
+
}
|
|
20644
|
+
return matches;
|
|
20645
|
+
};
|
|
20646
|
+
|
|
20531
20647
|
var JSONStringify = null, JSONParse = null;
|
|
20532
20648
|
if (typeof JSON !== 'undefined') {
|
|
20533
20649
|
JSONStringify = JSON.stringify;
|
|
@@ -20867,7 +20983,7 @@
|
|
|
20867
20983
|
};
|
|
20868
20984
|
}
|
|
20869
20985
|
|
|
20870
|
-
var logger$
|
|
20986
|
+
var logger$5 = console_with_prefix('lock');
|
|
20871
20987
|
|
|
20872
20988
|
/**
|
|
20873
20989
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -20919,7 +21035,7 @@
|
|
|
20919
21035
|
|
|
20920
21036
|
var delay = function(cb) {
|
|
20921
21037
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
20922
|
-
logger$
|
|
21038
|
+
logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
20923
21039
|
storage.removeItem(keyZ);
|
|
20924
21040
|
storage.removeItem(keyY);
|
|
20925
21041
|
loop();
|
|
@@ -21066,7 +21182,7 @@
|
|
|
21066
21182
|
}, this));
|
|
21067
21183
|
};
|
|
21068
21184
|
|
|
21069
|
-
var logger$
|
|
21185
|
+
var logger$4 = console_with_prefix('batch');
|
|
21070
21186
|
|
|
21071
21187
|
/**
|
|
21072
21188
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21095,7 +21211,7 @@
|
|
|
21095
21211
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21096
21212
|
});
|
|
21097
21213
|
}
|
|
21098
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21214
|
+
this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
|
|
21099
21215
|
|
|
21100
21216
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21101
21217
|
|
|
@@ -21428,7 +21544,7 @@
|
|
|
21428
21544
|
// maximum interval between request retries after exponential backoff
|
|
21429
21545
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21430
21546
|
|
|
21431
|
-
var logger$
|
|
21547
|
+
var logger$3 = console_with_prefix('batch');
|
|
21432
21548
|
|
|
21433
21549
|
/**
|
|
21434
21550
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21556,7 +21672,7 @@
|
|
|
21556
21672
|
*/
|
|
21557
21673
|
RequestBatcher.prototype.flush = function(options) {
|
|
21558
21674
|
if (this.requestInProgress) {
|
|
21559
|
-
logger$
|
|
21675
|
+
logger$3.log('Flush: Request already in progress');
|
|
21560
21676
|
return PromisePolyfill.resolve();
|
|
21561
21677
|
}
|
|
21562
21678
|
|
|
@@ -21733,7 +21849,7 @@
|
|
|
21733
21849
|
if (options.unloading) {
|
|
21734
21850
|
requestOptions.transport = 'sendBeacon';
|
|
21735
21851
|
}
|
|
21736
|
-
logger$
|
|
21852
|
+
logger$3.log('MIXPANEL REQUEST:', dataForRequest);
|
|
21737
21853
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
21738
21854
|
}, this))
|
|
21739
21855
|
.catch(_.bind(function(err) {
|
|
@@ -21746,7 +21862,7 @@
|
|
|
21746
21862
|
* Log error to global logger and optional user-defined logger.
|
|
21747
21863
|
*/
|
|
21748
21864
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
21749
|
-
logger$
|
|
21865
|
+
logger$3.error.apply(logger$3.error, arguments);
|
|
21750
21866
|
if (this.errorReporter) {
|
|
21751
21867
|
try {
|
|
21752
21868
|
if (!(err instanceof Error)) {
|
|
@@ -21754,7 +21870,7 @@
|
|
|
21754
21870
|
}
|
|
21755
21871
|
this.errorReporter(msg, err);
|
|
21756
21872
|
} catch(err) {
|
|
21757
|
-
logger$
|
|
21873
|
+
logger$3.error(err);
|
|
21758
21874
|
}
|
|
21759
21875
|
}
|
|
21760
21876
|
};
|
|
@@ -21972,6 +22088,655 @@
|
|
|
21972
22088
|
}
|
|
21973
22089
|
}
|
|
21974
22090
|
|
|
22091
|
+
/**
|
|
22092
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
22093
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
22094
|
+
*
|
|
22095
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
22096
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
22097
|
+
*
|
|
22098
|
+
*/
|
|
22099
|
+
|
|
22100
|
+
var logger$2 = console_with_prefix('network-plugin');
|
|
22101
|
+
|
|
22102
|
+
/**
|
|
22103
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
22104
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
22105
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
22106
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
22107
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
22108
|
+
* @param {Window} win
|
|
22109
|
+
* @returns {number}
|
|
22110
|
+
*/
|
|
22111
|
+
function getTimeOrigin(win) {
|
|
22112
|
+
return Math.round(Date.now() - win.performance.now());
|
|
22113
|
+
}
|
|
22114
|
+
|
|
22115
|
+
/**
|
|
22116
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
22117
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
22118
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
22119
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
22120
|
+
*/
|
|
22121
|
+
|
|
22122
|
+
/**
|
|
22123
|
+
* @typedef {Record<string, string>} Headers
|
|
22124
|
+
*/
|
|
22125
|
+
|
|
22126
|
+
/**
|
|
22127
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
22128
|
+
*/
|
|
22129
|
+
|
|
22130
|
+
/**
|
|
22131
|
+
* @callback networkCallback
|
|
22132
|
+
* @param {NetworkData} data
|
|
22133
|
+
* @returns {void}
|
|
22134
|
+
*/
|
|
22135
|
+
|
|
22136
|
+
/**
|
|
22137
|
+
* @callback listenerHandler
|
|
22138
|
+
* @returns {void}
|
|
22139
|
+
*/
|
|
22140
|
+
|
|
22141
|
+
/**
|
|
22142
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
22143
|
+
*/
|
|
22144
|
+
|
|
22145
|
+
/**
|
|
22146
|
+
* @typedef {Object} RecordPlugin
|
|
22147
|
+
* @property {string} name
|
|
22148
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
22149
|
+
* @property {NetworkRecordOptions} [options]
|
|
22150
|
+
*/
|
|
22151
|
+
|
|
22152
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
22153
|
+
var defaultNetworkOptions = {
|
|
22154
|
+
initiatorTypes: [
|
|
22155
|
+
'audio',
|
|
22156
|
+
'beacon',
|
|
22157
|
+
'body',
|
|
22158
|
+
'css',
|
|
22159
|
+
'early-hint',
|
|
22160
|
+
'embed',
|
|
22161
|
+
'fetch',
|
|
22162
|
+
'frame',
|
|
22163
|
+
'iframe',
|
|
22164
|
+
'icon',
|
|
22165
|
+
'image',
|
|
22166
|
+
'img',
|
|
22167
|
+
'input',
|
|
22168
|
+
'link',
|
|
22169
|
+
'navigation',
|
|
22170
|
+
'object',
|
|
22171
|
+
'ping',
|
|
22172
|
+
'script',
|
|
22173
|
+
'track',
|
|
22174
|
+
'video',
|
|
22175
|
+
'xmlhttprequest',
|
|
22176
|
+
],
|
|
22177
|
+
ignoreRequestFn: function() { return false; },
|
|
22178
|
+
recordHeaders: {
|
|
22179
|
+
request: [],
|
|
22180
|
+
response: [],
|
|
22181
|
+
},
|
|
22182
|
+
recordBodyUrls: {
|
|
22183
|
+
request: [],
|
|
22184
|
+
response: [],
|
|
22185
|
+
},
|
|
22186
|
+
recordInitialRequests: false,
|
|
22187
|
+
};
|
|
22188
|
+
|
|
22189
|
+
/**
|
|
22190
|
+
* @param {PerformanceEntry} entry
|
|
22191
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
22192
|
+
*/
|
|
22193
|
+
function isNavigationTiming(entry) {
|
|
22194
|
+
return entry.entryType === 'navigation';
|
|
22195
|
+
}
|
|
22196
|
+
|
|
22197
|
+
/**
|
|
22198
|
+
* @param {PerformanceEntry} entry
|
|
22199
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
22200
|
+
*/
|
|
22201
|
+
function isResourceTiming (entry) {
|
|
22202
|
+
return entry.entryType === 'resource';
|
|
22203
|
+
}
|
|
22204
|
+
|
|
22205
|
+
function findLast(array, predicate) {
|
|
22206
|
+
var length = array.length;
|
|
22207
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
22208
|
+
if (predicate(array[i])) {
|
|
22209
|
+
return array[i];
|
|
22210
|
+
}
|
|
22211
|
+
}
|
|
22212
|
+
}
|
|
22213
|
+
|
|
22214
|
+
/**
|
|
22215
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
22216
|
+
* Adapted from Sentry's `fill` utility:
|
|
22217
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
22218
|
+
*
|
|
22219
|
+
* @param {object} source - The object containing the method to patch
|
|
22220
|
+
* @param {string} name - The method name to patch
|
|
22221
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
22222
|
+
* @returns {function} A function that restores the original method
|
|
22223
|
+
*/
|
|
22224
|
+
function patch(source, name, replacementFactory) {
|
|
22225
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
22226
|
+
return function() {};
|
|
22227
|
+
}
|
|
22228
|
+
var original = source[name];
|
|
22229
|
+
var wrapped = replacementFactory(original);
|
|
22230
|
+
source[name] = wrapped;
|
|
22231
|
+
return function() {
|
|
22232
|
+
source[name] = original;
|
|
22233
|
+
};
|
|
22234
|
+
}
|
|
22235
|
+
|
|
22236
|
+
|
|
22237
|
+
/**
|
|
22238
|
+
* Maximum body size to record (1MB)
|
|
22239
|
+
*/
|
|
22240
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
22241
|
+
|
|
22242
|
+
/**
|
|
22243
|
+
* Truncate string if it exceeds max size
|
|
22244
|
+
* @param {string} str
|
|
22245
|
+
* @returns {string}
|
|
22246
|
+
*/
|
|
22247
|
+
function truncateBody(str) {
|
|
22248
|
+
if (!str || typeof str !== 'string') {
|
|
22249
|
+
return str;
|
|
22250
|
+
}
|
|
22251
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
22252
|
+
logger$2.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
22253
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
22254
|
+
}
|
|
22255
|
+
return str;
|
|
22256
|
+
}
|
|
22257
|
+
|
|
22258
|
+
/**
|
|
22259
|
+
* @param {networkCallback} cb
|
|
22260
|
+
* @param {Window} win
|
|
22261
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22262
|
+
* @returns {listenerHandler}
|
|
22263
|
+
*/
|
|
22264
|
+
function initPerformanceObserver(cb, win, options) {
|
|
22265
|
+
if (!win.PerformanceObserver) {
|
|
22266
|
+
logger$2.error('PerformanceObserver not supported');
|
|
22267
|
+
return function() {
|
|
22268
|
+
//
|
|
22269
|
+
};
|
|
22270
|
+
}
|
|
22271
|
+
if (options.recordInitialRequests) {
|
|
22272
|
+
var initialPerformanceEntries = win.performance
|
|
22273
|
+
.getEntries()
|
|
22274
|
+
.filter(function(entry) {
|
|
22275
|
+
return isNavigationTiming(entry) ||
|
|
22276
|
+
(isResourceTiming(entry) &&
|
|
22277
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
22278
|
+
});
|
|
22279
|
+
cb({
|
|
22280
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
22281
|
+
return {
|
|
22282
|
+
url: entry.name,
|
|
22283
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22284
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
22285
|
+
startTime: Math.round(entry.startTime),
|
|
22286
|
+
endTime: Math.round(entry.responseEnd),
|
|
22287
|
+
timeOrigin: getTimeOrigin(win),
|
|
22288
|
+
};
|
|
22289
|
+
}),
|
|
22290
|
+
isInitial: true,
|
|
22291
|
+
});
|
|
22292
|
+
}
|
|
22293
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
22294
|
+
var performanceEntries = entries
|
|
22295
|
+
.getEntries()
|
|
22296
|
+
.filter(function(entry) {
|
|
22297
|
+
return isResourceTiming(entry) &&
|
|
22298
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
22299
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
22300
|
+
entry.initiatorType !== 'fetch';
|
|
22301
|
+
});
|
|
22302
|
+
cb({
|
|
22303
|
+
requests: performanceEntries.map(function(entry) {
|
|
22304
|
+
return {
|
|
22305
|
+
url: entry.name,
|
|
22306
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22307
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
22308
|
+
startTime: Math.round(entry.startTime),
|
|
22309
|
+
endTime: Math.round(entry.responseEnd),
|
|
22310
|
+
timeOrigin: getTimeOrigin(win),
|
|
22311
|
+
};
|
|
22312
|
+
}),
|
|
22313
|
+
});
|
|
22314
|
+
});
|
|
22315
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
22316
|
+
return function() {
|
|
22317
|
+
observer.disconnect();
|
|
22318
|
+
};
|
|
22319
|
+
}
|
|
22320
|
+
|
|
22321
|
+
/**
|
|
22322
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
22323
|
+
* @param {'request' | 'response'} type
|
|
22324
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
22325
|
+
* @param {string} headerName
|
|
22326
|
+
* @returns {boolean}
|
|
22327
|
+
*/
|
|
22328
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
22329
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
22330
|
+
return false;
|
|
22331
|
+
}
|
|
22332
|
+
|
|
22333
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
22334
|
+
}
|
|
22335
|
+
|
|
22336
|
+
/**
|
|
22337
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
22338
|
+
* @param {'request' | 'response'} type
|
|
22339
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
22340
|
+
* @param {string} url
|
|
22341
|
+
* @returns {boolean}
|
|
22342
|
+
*/
|
|
22343
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
22344
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
22345
|
+
return false;
|
|
22346
|
+
}
|
|
22347
|
+
|
|
22348
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
22349
|
+
}
|
|
22350
|
+
|
|
22351
|
+
function tryReadXHRBody(body) {
|
|
22352
|
+
if (body === null || body === undefined) {
|
|
22353
|
+
return null;
|
|
22354
|
+
}
|
|
22355
|
+
|
|
22356
|
+
var result;
|
|
22357
|
+
if (typeof body === 'string') {
|
|
22358
|
+
result = body;
|
|
22359
|
+
} else if (body instanceof Document) {
|
|
22360
|
+
result = body.textContent;
|
|
22361
|
+
} else if (body instanceof FormData) {
|
|
22362
|
+
result = _.HTTPBuildQuery(body);
|
|
22363
|
+
} else if (_.isObject(body)) {
|
|
22364
|
+
try {
|
|
22365
|
+
result = JSON.stringify(body);
|
|
22366
|
+
} catch (e) {
|
|
22367
|
+
return 'Failed to stringify response object';
|
|
22368
|
+
}
|
|
22369
|
+
} else {
|
|
22370
|
+
return 'Cannot read body of type ' + typeof body;
|
|
22371
|
+
}
|
|
22372
|
+
|
|
22373
|
+
return truncateBody(result);
|
|
22374
|
+
}
|
|
22375
|
+
|
|
22376
|
+
/**
|
|
22377
|
+
* @param {Request | Response} r
|
|
22378
|
+
* @returns {Promise<string>}
|
|
22379
|
+
*/
|
|
22380
|
+
function tryReadFetchBody(r) {
|
|
22381
|
+
return new Promise(function(resolve) {
|
|
22382
|
+
var timeout = setTimeout(function() {
|
|
22383
|
+
resolve('Timeout while trying to read body');
|
|
22384
|
+
}, 500);
|
|
22385
|
+
try {
|
|
22386
|
+
r.clone()
|
|
22387
|
+
.text()
|
|
22388
|
+
.then(
|
|
22389
|
+
function(txt) {
|
|
22390
|
+
clearTimeout(timeout);
|
|
22391
|
+
resolve(truncateBody(txt));
|
|
22392
|
+
},
|
|
22393
|
+
function(reason) {
|
|
22394
|
+
clearTimeout(timeout);
|
|
22395
|
+
resolve('Failed to read body: ' + String(reason));
|
|
22396
|
+
}
|
|
22397
|
+
);
|
|
22398
|
+
} catch (e) {
|
|
22399
|
+
clearTimeout(timeout);
|
|
22400
|
+
resolve('Failed to read body: ' + String(e));
|
|
22401
|
+
}
|
|
22402
|
+
});
|
|
22403
|
+
}
|
|
22404
|
+
|
|
22405
|
+
/**
|
|
22406
|
+
* @param {Window} win
|
|
22407
|
+
* @param {string} initiatorType
|
|
22408
|
+
* @param {string} url
|
|
22409
|
+
* @param {number} [after]
|
|
22410
|
+
* @param {number} [before]
|
|
22411
|
+
* @param {number} [attempt]
|
|
22412
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
22413
|
+
*/
|
|
22414
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
22415
|
+
if (attempt === undefined) {
|
|
22416
|
+
attempt = 0;
|
|
22417
|
+
}
|
|
22418
|
+
if (attempt > 10) {
|
|
22419
|
+
logger$2.error('Cannot find performance entry');
|
|
22420
|
+
return Promise.resolve(null);
|
|
22421
|
+
}
|
|
22422
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
22423
|
+
win.performance.getEntriesByName(url)
|
|
22424
|
+
);
|
|
22425
|
+
var performanceEntry = findLast(
|
|
22426
|
+
urlPerformanceEntries,
|
|
22427
|
+
function(entry) {
|
|
22428
|
+
return isResourceTiming(entry) &&
|
|
22429
|
+
entry.initiatorType === initiatorType &&
|
|
22430
|
+
(!after || entry.startTime >= after) &&
|
|
22431
|
+
(!before || entry.startTime <= before);
|
|
22432
|
+
}
|
|
22433
|
+
);
|
|
22434
|
+
if (!performanceEntry) {
|
|
22435
|
+
return new Promise(function(resolve) {
|
|
22436
|
+
setTimeout(resolve, 50 * attempt);
|
|
22437
|
+
}).then(function() {
|
|
22438
|
+
return getRequestPerformanceEntry(
|
|
22439
|
+
win,
|
|
22440
|
+
initiatorType,
|
|
22441
|
+
url,
|
|
22442
|
+
after,
|
|
22443
|
+
before,
|
|
22444
|
+
attempt + 1
|
|
22445
|
+
);
|
|
22446
|
+
});
|
|
22447
|
+
}
|
|
22448
|
+
return Promise.resolve(performanceEntry);
|
|
22449
|
+
}
|
|
22450
|
+
|
|
22451
|
+
/**
|
|
22452
|
+
* @param {networkCallback} cb
|
|
22453
|
+
* @param {Window} win
|
|
22454
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22455
|
+
* @returns {listenerHandler}
|
|
22456
|
+
*/
|
|
22457
|
+
function initXhrObserver(cb, win, options) {
|
|
22458
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
22459
|
+
return function() {
|
|
22460
|
+
//
|
|
22461
|
+
};
|
|
22462
|
+
}
|
|
22463
|
+
var restorePatch = patch(
|
|
22464
|
+
win.XMLHttpRequest.prototype,
|
|
22465
|
+
'open',
|
|
22466
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
22467
|
+
return function(
|
|
22468
|
+
/** @type {string} */ method,
|
|
22469
|
+
/** @type {string | URL} */ url,
|
|
22470
|
+
/** @type {boolean} */ async,
|
|
22471
|
+
username, password
|
|
22472
|
+
) {
|
|
22473
|
+
if (async === undefined) {
|
|
22474
|
+
async = true;
|
|
22475
|
+
}
|
|
22476
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
22477
|
+
var req = new Request(url, { method: method });
|
|
22478
|
+
/** @type {Partial<NetworkRequest>} */
|
|
22479
|
+
var networkRequest = {};
|
|
22480
|
+
/** @type {number | undefined} */
|
|
22481
|
+
var after;
|
|
22482
|
+
/** @type {number | undefined} */
|
|
22483
|
+
var before;
|
|
22484
|
+
|
|
22485
|
+
/** @type {Headers} */
|
|
22486
|
+
var requestHeaders = {};
|
|
22487
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
22488
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
22489
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
22490
|
+
requestHeaders[header] = value;
|
|
22491
|
+
}
|
|
22492
|
+
return originalSetRequestHeader(header, value);
|
|
22493
|
+
};
|
|
22494
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
22495
|
+
|
|
22496
|
+
var originalSend = xhr.send.bind(xhr);
|
|
22497
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
22498
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
22499
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
22500
|
+
}
|
|
22501
|
+
after = win.performance.now();
|
|
22502
|
+
return originalSend(body);
|
|
22503
|
+
};
|
|
22504
|
+
xhr.addEventListener('readystatechange', function() {
|
|
22505
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
22506
|
+
return;
|
|
22507
|
+
}
|
|
22508
|
+
before = win.performance.now();
|
|
22509
|
+
/** @type {Headers} */
|
|
22510
|
+
var responseHeaders = {};
|
|
22511
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
22512
|
+
if (rawHeaders) {
|
|
22513
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
22514
|
+
headers.forEach(function(line) {
|
|
22515
|
+
if (!line) return;
|
|
22516
|
+
var colonIndex = line.indexOf(': ');
|
|
22517
|
+
if (colonIndex === -1) return;
|
|
22518
|
+
var header = line.substring(0, colonIndex);
|
|
22519
|
+
var value = line.substring(colonIndex + 2);
|
|
22520
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
22521
|
+
responseHeaders[header] = value;
|
|
22522
|
+
}
|
|
22523
|
+
});
|
|
22524
|
+
}
|
|
22525
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
22526
|
+
if (
|
|
22527
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
22528
|
+
) {
|
|
22529
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
22530
|
+
}
|
|
22531
|
+
getRequestPerformanceEntry(
|
|
22532
|
+
win,
|
|
22533
|
+
'xmlhttprequest',
|
|
22534
|
+
req.url,
|
|
22535
|
+
after,
|
|
22536
|
+
before
|
|
22537
|
+
)
|
|
22538
|
+
.then(function(entry) {
|
|
22539
|
+
if (!entry) {
|
|
22540
|
+
logger$2.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
22541
|
+
return;
|
|
22542
|
+
}
|
|
22543
|
+
/** @type {NetworkRequest} */
|
|
22544
|
+
var request = {
|
|
22545
|
+
url: entry.name,
|
|
22546
|
+
method: req.method,
|
|
22547
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22548
|
+
status: xhr.status,
|
|
22549
|
+
startTime: Math.round(entry.startTime),
|
|
22550
|
+
endTime: Math.round(entry.responseEnd),
|
|
22551
|
+
timeOrigin: getTimeOrigin(win),
|
|
22552
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
22553
|
+
requestBody: networkRequest.requestBody,
|
|
22554
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
22555
|
+
responseBody: networkRequest.responseBody,
|
|
22556
|
+
};
|
|
22557
|
+
cb({ requests: [request] });
|
|
22558
|
+
})
|
|
22559
|
+
.catch(function(e) {
|
|
22560
|
+
logger$2.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
22561
|
+
});
|
|
22562
|
+
});
|
|
22563
|
+
|
|
22564
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
22565
|
+
};
|
|
22566
|
+
}
|
|
22567
|
+
);
|
|
22568
|
+
return function() {
|
|
22569
|
+
restorePatch();
|
|
22570
|
+
};
|
|
22571
|
+
}
|
|
22572
|
+
|
|
22573
|
+
/**
|
|
22574
|
+
* @param {networkCallback} cb
|
|
22575
|
+
* @param {Window} win
|
|
22576
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
22577
|
+
* @returns {listenerHandler}
|
|
22578
|
+
*/
|
|
22579
|
+
function initFetchObserver(cb, win, options) {
|
|
22580
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
22581
|
+
return function() {
|
|
22582
|
+
//
|
|
22583
|
+
};
|
|
22584
|
+
}
|
|
22585
|
+
|
|
22586
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
22587
|
+
return function() {
|
|
22588
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
22589
|
+
/** @type {Response | undefined} */
|
|
22590
|
+
var res;
|
|
22591
|
+
/** @type {Partial<NetworkRequest>} */
|
|
22592
|
+
var networkRequest = {};
|
|
22593
|
+
/** @type {number | undefined} */
|
|
22594
|
+
var after;
|
|
22595
|
+
/** @type {number | undefined} */
|
|
22596
|
+
var before;
|
|
22597
|
+
|
|
22598
|
+
var originalFetchPromise;
|
|
22599
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
22600
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
22601
|
+
try {
|
|
22602
|
+
/** @type {Headers} */
|
|
22603
|
+
var requestHeaders = {};
|
|
22604
|
+
req.headers.forEach(function(value, header) {
|
|
22605
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
22606
|
+
requestHeaders[header] = value;
|
|
22607
|
+
}
|
|
22608
|
+
});
|
|
22609
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
22610
|
+
|
|
22611
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
22612
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
22613
|
+
.then(function(body) {
|
|
22614
|
+
networkRequest.requestBody = body;
|
|
22615
|
+
});
|
|
22616
|
+
}
|
|
22617
|
+
|
|
22618
|
+
after = win.performance.now();
|
|
22619
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
22620
|
+
res = response;
|
|
22621
|
+
before = win.performance.now();
|
|
22622
|
+
|
|
22623
|
+
/** @type {Headers} */
|
|
22624
|
+
var responseHeaders = {};
|
|
22625
|
+
res.headers.forEach(function(value, header) {
|
|
22626
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
22627
|
+
responseHeaders[header] = value;
|
|
22628
|
+
}
|
|
22629
|
+
});
|
|
22630
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
22631
|
+
|
|
22632
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
22633
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
22634
|
+
.then(function(body) {
|
|
22635
|
+
networkRequest.responseBody = body;
|
|
22636
|
+
});
|
|
22637
|
+
}
|
|
22638
|
+
|
|
22639
|
+
return res;
|
|
22640
|
+
});
|
|
22641
|
+
} catch (e) {
|
|
22642
|
+
originalFetchPromise = Promise.reject(e);
|
|
22643
|
+
}
|
|
22644
|
+
|
|
22645
|
+
// await concurrently so we don't delay the fetch response
|
|
22646
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
22647
|
+
.then(function () {
|
|
22648
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
22649
|
+
})
|
|
22650
|
+
.then(function(entry) {
|
|
22651
|
+
if (!entry) {
|
|
22652
|
+
logger$2.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
22653
|
+
return;
|
|
22654
|
+
}
|
|
22655
|
+
/** @type {NetworkRequest} */
|
|
22656
|
+
var request = {
|
|
22657
|
+
url: entry.name,
|
|
22658
|
+
method: req.method,
|
|
22659
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
22660
|
+
status: res ? res.status : undefined,
|
|
22661
|
+
startTime: Math.round(entry.startTime),
|
|
22662
|
+
endTime: Math.round(entry.responseEnd),
|
|
22663
|
+
timeOrigin: getTimeOrigin(win),
|
|
22664
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
22665
|
+
requestBody: networkRequest.requestBody,
|
|
22666
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
22667
|
+
responseBody: networkRequest.responseBody,
|
|
22668
|
+
};
|
|
22669
|
+
cb({ requests: [request] });
|
|
22670
|
+
})
|
|
22671
|
+
.catch(function (e) {
|
|
22672
|
+
logger$2.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
22673
|
+
});
|
|
22674
|
+
|
|
22675
|
+
return originalFetchPromise;
|
|
22676
|
+
};
|
|
22677
|
+
});
|
|
22678
|
+
return function() {
|
|
22679
|
+
restorePatch();
|
|
22680
|
+
};
|
|
22681
|
+
}
|
|
22682
|
+
|
|
22683
|
+
/**
|
|
22684
|
+
* @param {networkCallback} callback
|
|
22685
|
+
* @param {Window} win
|
|
22686
|
+
* @param {NetworkRecordOptions} options
|
|
22687
|
+
* @returns {listenerHandler}
|
|
22688
|
+
*/
|
|
22689
|
+
function initNetworkObserver(callback, win, options) {
|
|
22690
|
+
if (!('performance' in win)) {
|
|
22691
|
+
return function() {
|
|
22692
|
+
//
|
|
22693
|
+
};
|
|
22694
|
+
}
|
|
22695
|
+
|
|
22696
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
22697
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
22698
|
+
options = Object.assign({}, options, {
|
|
22699
|
+
recordHeaders: recordHeaders,
|
|
22700
|
+
recordBodyUrls: recordBodyUrls,
|
|
22701
|
+
});
|
|
22702
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
22703
|
+
|
|
22704
|
+
/** @type {networkCallback} */
|
|
22705
|
+
var cb = function(data) {
|
|
22706
|
+
var requests = data.requests.filter(function(request) {
|
|
22707
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
22708
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
22709
|
+
});
|
|
22710
|
+
if (requests.length > 0 || data.isInitial) {
|
|
22711
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
22712
|
+
}
|
|
22713
|
+
};
|
|
22714
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
22715
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
22716
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
22717
|
+
return function() {
|
|
22718
|
+
performanceObserver();
|
|
22719
|
+
xhrObserver();
|
|
22720
|
+
fetchObserver();
|
|
22721
|
+
};
|
|
22722
|
+
}
|
|
22723
|
+
|
|
22724
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
22725
|
+
// a changed format in the mixpanel product.
|
|
22726
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
22727
|
+
|
|
22728
|
+
/**
|
|
22729
|
+
* @param {NetworkRecordOptions} [options]
|
|
22730
|
+
* @returns {RecordPlugin}
|
|
22731
|
+
*/
|
|
22732
|
+
var getRecordNetworkPlugin = function(options) {
|
|
22733
|
+
return {
|
|
22734
|
+
name: NETWORK_PLUGIN_NAME,
|
|
22735
|
+
observer: initNetworkObserver,
|
|
22736
|
+
options: options,
|
|
22737
|
+
};
|
|
22738
|
+
};
|
|
22739
|
+
|
|
21975
22740
|
/**
|
|
21976
22741
|
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
21977
22742
|
*/
|
|
@@ -22205,6 +22970,29 @@
|
|
|
22205
22970
|
|
|
22206
22971
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
22207
22972
|
|
|
22973
|
+
var plugins = [];
|
|
22974
|
+
if (this.getConfig('record_network')) {
|
|
22975
|
+
var options = this.getConfig('record_network_options') || {};
|
|
22976
|
+
// don't track requests to Mixpanel /record API
|
|
22977
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
22978
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
22979
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
22980
|
+
|
|
22981
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
22982
|
+
}
|
|
22983
|
+
|
|
22984
|
+
if (this.getConfig('record_console')) {
|
|
22985
|
+
plugins.push(
|
|
22986
|
+
getRecordConsolePlugin({
|
|
22987
|
+
stringifyOptions: {
|
|
22988
|
+
stringLengthLimit: 1000,
|
|
22989
|
+
numOfKeysLimit: 50,
|
|
22990
|
+
depthOfLimit: 2
|
|
22991
|
+
}
|
|
22992
|
+
})
|
|
22993
|
+
);
|
|
22994
|
+
}
|
|
22995
|
+
|
|
22208
22996
|
try {
|
|
22209
22997
|
this._stopRecording = this._rrwebRecord({
|
|
22210
22998
|
'emit': function (ev) {
|
|
@@ -22243,15 +23031,7 @@
|
|
|
22243
23031
|
'sampling': {
|
|
22244
23032
|
'canvas': 15
|
|
22245
23033
|
},
|
|
22246
|
-
'plugins':
|
|
22247
|
-
getRecordConsolePlugin({
|
|
22248
|
-
stringifyOptions: {
|
|
22249
|
-
stringLengthLimit: 1000,
|
|
22250
|
-
numOfKeysLimit: 50,
|
|
22251
|
-
depthOfLimit: 2
|
|
22252
|
-
}
|
|
22253
|
-
})
|
|
22254
|
-
] : []
|
|
23034
|
+
'plugins': plugins,
|
|
22255
23035
|
});
|
|
22256
23036
|
} catch (err) {
|
|
22257
23037
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -22366,6 +23146,10 @@
|
|
|
22366
23146
|
return recording;
|
|
22367
23147
|
};
|
|
22368
23148
|
|
|
23149
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
23150
|
+
return this.getConfig('api_routes')['record'];
|
|
23151
|
+
};
|
|
23152
|
+
|
|
22369
23153
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
22370
23154
|
var onSuccess = function (response, responseBody) {
|
|
22371
23155
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -22385,7 +23169,7 @@
|
|
|
22385
23169
|
});
|
|
22386
23170
|
}.bind(this);
|
|
22387
23171
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
22388
|
-
win['fetch'](apiHost + '/' + this.
|
|
23172
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
22389
23173
|
'method': 'POST',
|
|
22390
23174
|
'headers': {
|
|
22391
23175
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -22786,8 +23570,12 @@
|
|
|
22786
23570
|
this.startRecording({shouldStopBatcher: true});
|
|
22787
23571
|
};
|
|
22788
23572
|
|
|
23573
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
23574
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
23575
|
+
};
|
|
23576
|
+
|
|
22789
23577
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
22790
|
-
if (this.
|
|
23578
|
+
if (this.isRecording()) {
|
|
22791
23579
|
return this.activeRecording.replayId;
|
|
22792
23580
|
} else {
|
|
22793
23581
|
return null;
|
|
@@ -22802,6 +23590,6 @@
|
|
|
22802
23590
|
}
|
|
22803
23591
|
});
|
|
22804
23592
|
|
|
22805
|
-
win[
|
|
23593
|
+
win[RECORDER_GLOBAL_NAME] = MixpanelRecorder;
|
|
22806
23594
|
|
|
22807
23595
|
})();
|