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
package/dist/mixpanel.module.js
CHANGED
|
@@ -23,6 +23,19 @@ if (typeof(window) === 'undefined') {
|
|
|
23
23
|
win = window;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
var Config = {
|
|
27
|
+
DEBUG: false,
|
|
28
|
+
LIB_VERSION: '2.76.0'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Window global names for async modules
|
|
32
|
+
var TARGETING_GLOBAL_NAME = '__mp_targeting';
|
|
33
|
+
var RECORDER_GLOBAL_NAME = '__mp_recorder';
|
|
34
|
+
|
|
35
|
+
// Constants that are injected at build-time for the names of async modules.
|
|
36
|
+
var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
|
|
37
|
+
var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
|
|
38
|
+
|
|
26
39
|
function _array_like_to_array(arr, len) {
|
|
27
40
|
if (len == null || len > arr.length) len = arr.length;
|
|
28
41
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -637,14 +650,16 @@ var Mirror = /*#__PURE__*/ function() {
|
|
|
637
650
|
return this.nodeMetaMap.get(n2) || null;
|
|
638
651
|
};
|
|
639
652
|
// removes the node from idNodeMap
|
|
640
|
-
//
|
|
641
|
-
_proto.removeNodeFromMap = function removeNodeFromMap(n2) {
|
|
653
|
+
// if permanent is true, also removes from nodeMetaMap
|
|
654
|
+
_proto.removeNodeFromMap = function removeNodeFromMap(n2, permanent) {
|
|
642
655
|
var _this = this;
|
|
656
|
+
if (permanent === void 0) permanent = false;
|
|
643
657
|
var id = this.getId(n2);
|
|
644
658
|
this.idNodeMap.delete(id);
|
|
659
|
+
if (permanent) this.nodeMetaMap.delete(n2);
|
|
645
660
|
if (n2.childNodes) {
|
|
646
661
|
n2.childNodes.forEach(function(childNode) {
|
|
647
|
-
return _this.removeNodeFromMap(childNode);
|
|
662
|
+
return _this.removeNodeFromMap(childNode, permanent);
|
|
648
663
|
});
|
|
649
664
|
}
|
|
650
665
|
};
|
|
@@ -10386,6 +10401,15 @@ var StyleSheetMirror = /*#__PURE__*/ function() {
|
|
|
10386
10401
|
_proto.generateId = function generateId() {
|
|
10387
10402
|
return this.id++;
|
|
10388
10403
|
};
|
|
10404
|
+
_proto.remove = function remove(stylesheet) {
|
|
10405
|
+
var id = this.styleIDMap.get(stylesheet);
|
|
10406
|
+
if (id !== void 0) {
|
|
10407
|
+
this.styleIDMap.delete(stylesheet);
|
|
10408
|
+
this.idStyleMap.delete(id);
|
|
10409
|
+
return true;
|
|
10410
|
+
}
|
|
10411
|
+
return false;
|
|
10412
|
+
};
|
|
10389
10413
|
return StyleSheetMirror;
|
|
10390
10414
|
}();
|
|
10391
10415
|
function getShadowHost(n2) {
|
|
@@ -10708,7 +10732,15 @@ var MutationBuffer = /*#__PURE__*/ function() {
|
|
|
10708
10732
|
}
|
|
10709
10733
|
};
|
|
10710
10734
|
while(_this.mapRemoves.length){
|
|
10711
|
-
_this.
|
|
10735
|
+
var removedNode = _this.mapRemoves.shift();
|
|
10736
|
+
if (removedNode.nodeName === "IFRAME") {
|
|
10737
|
+
try {
|
|
10738
|
+
_this.iframeManager.removeIframe(removedNode);
|
|
10739
|
+
} catch (e2) {}
|
|
10740
|
+
} else {
|
|
10741
|
+
_this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
|
|
10742
|
+
}
|
|
10743
|
+
_this.mirror.removeNodeFromMap(removedNode);
|
|
10712
10744
|
}
|
|
10713
10745
|
for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
|
|
10714
10746
|
var n2 = _step.value;
|
|
@@ -11082,6 +11114,9 @@ var MutationBuffer = /*#__PURE__*/ function() {
|
|
|
11082
11114
|
this.shadowDomManager.reset();
|
|
11083
11115
|
this.canvasManager.reset();
|
|
11084
11116
|
};
|
|
11117
|
+
_proto.getDoc = function getDoc() {
|
|
11118
|
+
return this.doc;
|
|
11119
|
+
};
|
|
11085
11120
|
return MutationBuffer;
|
|
11086
11121
|
}();
|
|
11087
11122
|
function deepDelete(addsSet, n2) {
|
|
@@ -11182,6 +11217,14 @@ function initMutationObserver(options, rootEl) {
|
|
|
11182
11217
|
});
|
|
11183
11218
|
return observer;
|
|
11184
11219
|
}
|
|
11220
|
+
function removeMutationBufferForDoc(doc) {
|
|
11221
|
+
for(var i2 = mutationBuffers.length - 1; i2 >= 0; i2--){
|
|
11222
|
+
var buffer = mutationBuffers[i2];
|
|
11223
|
+
if (buffer.getDoc() === doc) {
|
|
11224
|
+
mutationBuffers.splice(i2, 1);
|
|
11225
|
+
}
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11185
11228
|
function initMoveObserver(param) {
|
|
11186
11229
|
var mousemoveCb = param.mousemoveCb, sampling = param.sampling, doc = param.doc, mirror2 = param.mirror;
|
|
11187
11230
|
if (sampling.mousemove === false) {
|
|
@@ -12197,6 +12240,8 @@ var IframeManager = /*#__PURE__*/ function() {
|
|
|
12197
12240
|
__publicField$1(this, "crossOriginIframeMirror", new CrossOriginIframeMirror(genId));
|
|
12198
12241
|
__publicField$1(this, "crossOriginIframeStyleMirror");
|
|
12199
12242
|
__publicField$1(this, "crossOriginIframeRootIdMap", /* @__PURE__ */ new WeakMap());
|
|
12243
|
+
__publicField$1(this, "iframeContentDocumentMap", /* @__PURE__ */ new WeakMap());
|
|
12244
|
+
__publicField$1(this, "iframeObserverCleanupMap", /* @__PURE__ */ new WeakMap());
|
|
12200
12245
|
__publicField$1(this, "mirror");
|
|
12201
12246
|
__publicField$1(this, "mutationCb");
|
|
12202
12247
|
__publicField$1(this, "wrappedEmit");
|
|
@@ -12218,6 +12263,31 @@ var IframeManager = /*#__PURE__*/ function() {
|
|
|
12218
12263
|
this.iframes.set(iframeEl, true);
|
|
12219
12264
|
if (iframeEl.contentWindow) this.crossOriginIframeMap.set(iframeEl.contentWindow, iframeEl);
|
|
12220
12265
|
};
|
|
12266
|
+
_proto.getIframeContentDocument = function getIframeContentDocument(iframeEl) {
|
|
12267
|
+
return this.iframeContentDocumentMap.get(iframeEl);
|
|
12268
|
+
};
|
|
12269
|
+
_proto.setObserverCleanup = function setObserverCleanup(iframeEl, cleanup) {
|
|
12270
|
+
this.iframeObserverCleanupMap.set(iframeEl, cleanup);
|
|
12271
|
+
};
|
|
12272
|
+
_proto.getObserverCleanup = function getObserverCleanup(iframeEl) {
|
|
12273
|
+
return this.iframeObserverCleanupMap.get(iframeEl);
|
|
12274
|
+
};
|
|
12275
|
+
_proto.removeIframe = function removeIframe(iframeEl) {
|
|
12276
|
+
var storedDoc = this.iframeContentDocumentMap.get(iframeEl);
|
|
12277
|
+
if (storedDoc) {
|
|
12278
|
+
this.stylesheetManager.cleanupStylesheetsForRemovedNode(storedDoc);
|
|
12279
|
+
this.mirror.removeNodeFromMap(storedDoc, true);
|
|
12280
|
+
}
|
|
12281
|
+
this.iframes.delete(iframeEl);
|
|
12282
|
+
this.iframeContentDocumentMap.delete(iframeEl);
|
|
12283
|
+
var observerCleanup = this.iframeObserverCleanupMap.get(iframeEl);
|
|
12284
|
+
if (observerCleanup) {
|
|
12285
|
+
try {
|
|
12286
|
+
observerCleanup();
|
|
12287
|
+
} catch (e2) {}
|
|
12288
|
+
this.iframeObserverCleanupMap.delete(iframeEl);
|
|
12289
|
+
}
|
|
12290
|
+
};
|
|
12221
12291
|
_proto.addLoadListener = function addLoadListener(cb) {
|
|
12222
12292
|
this.loadListener = cb;
|
|
12223
12293
|
};
|
|
@@ -12236,6 +12306,9 @@ var IframeManager = /*#__PURE__*/ function() {
|
|
|
12236
12306
|
attributes: [],
|
|
12237
12307
|
isAttachIframe: true
|
|
12238
12308
|
});
|
|
12309
|
+
if (iframeEl.contentDocument) {
|
|
12310
|
+
this.iframeContentDocumentMap.set(iframeEl, iframeEl.contentDocument);
|
|
12311
|
+
}
|
|
12239
12312
|
if (this.recordCrossOriginIframes) (_a2 = iframeEl.contentWindow) == null ? void 0 : _a2.addEventListener("message", this.handleMessage.bind(this));
|
|
12240
12313
|
(_b = this.loadListener) == null ? void 0 : _b.call(this, iframeEl);
|
|
12241
12314
|
if (iframeEl.contentDocument && iframeEl.contentDocument.adoptedStyleSheets && iframeEl.contentDocument.adoptedStyleSheets.length > 0) this.stylesheetManager.adoptStyleSheets(iframeEl.contentDocument.adoptedStyleSheets, this.mirror.getId(iframeEl.contentDocument));
|
|
@@ -13148,6 +13221,41 @@ var StylesheetManager = /*#__PURE__*/ function() {
|
|
|
13148
13221
|
this.styleMirror.reset();
|
|
13149
13222
|
this.trackedLinkElements = /* @__PURE__ */ new WeakSet();
|
|
13150
13223
|
};
|
|
13224
|
+
/**
|
|
13225
|
+
* Cleans up stylesheets associated with a removed node.
|
|
13226
|
+
*
|
|
13227
|
+
* @param removedNode - The node that was removed from the DOM.
|
|
13228
|
+
*/ _proto.cleanupStylesheetsForRemovedNode = function cleanupStylesheetsForRemovedNode(removedNode) {
|
|
13229
|
+
var _this = this;
|
|
13230
|
+
try {
|
|
13231
|
+
if (removedNode.nodeType === Node.DOCUMENT_NODE) {
|
|
13232
|
+
var doc = removedNode;
|
|
13233
|
+
if (doc.adoptedStyleSheets) {
|
|
13234
|
+
for(var _iterator = _create_for_of_iterator_helper_loose(doc.adoptedStyleSheets), _step; !(_step = _iterator()).done;){
|
|
13235
|
+
var sheet = _step.value;
|
|
13236
|
+
this.styleMirror.remove(sheet);
|
|
13237
|
+
}
|
|
13238
|
+
}
|
|
13239
|
+
}
|
|
13240
|
+
if (removedNode.nodeName === "STYLE") {
|
|
13241
|
+
var styleEl = removedNode;
|
|
13242
|
+
if (styleEl.sheet) {
|
|
13243
|
+
this.styleMirror.remove(styleEl.sheet);
|
|
13244
|
+
}
|
|
13245
|
+
}
|
|
13246
|
+
if (removedNode.nodeName === "LINK" && removedNode.rel === "stylesheet") {
|
|
13247
|
+
var linkEl = removedNode;
|
|
13248
|
+
if (linkEl.sheet) {
|
|
13249
|
+
this.styleMirror.remove(linkEl.sheet);
|
|
13250
|
+
}
|
|
13251
|
+
}
|
|
13252
|
+
if (removedNode.childNodes) {
|
|
13253
|
+
removedNode.childNodes.forEach(function(child) {
|
|
13254
|
+
_this.cleanupStylesheetsForRemovedNode(child);
|
|
13255
|
+
});
|
|
13256
|
+
}
|
|
13257
|
+
} catch (e2) {}
|
|
13258
|
+
};
|
|
13151
13259
|
// TODO: take snapshot on stylesheet reload by applying event listener
|
|
13152
13260
|
_proto.trackStylesheetInLinkElement = function trackStylesheetInLinkElement(_linkEl) {};
|
|
13153
13261
|
return StylesheetManager;
|
|
@@ -13602,7 +13710,23 @@ function record(options) {
|
|
|
13602
13710
|
};
|
|
13603
13711
|
iframeManager.addLoadListener(function(iframeEl) {
|
|
13604
13712
|
try {
|
|
13605
|
-
|
|
13713
|
+
var iframeDoc = iframeEl.contentDocument;
|
|
13714
|
+
var iframeHandler = observe(iframeDoc);
|
|
13715
|
+
handlers.push(iframeHandler);
|
|
13716
|
+
var existingCleanup = iframeManager.getObserverCleanup(iframeEl);
|
|
13717
|
+
iframeManager.setObserverCleanup(iframeEl, function() {
|
|
13718
|
+
if (existingCleanup) {
|
|
13719
|
+
try {
|
|
13720
|
+
existingCleanup();
|
|
13721
|
+
} catch (e2) {}
|
|
13722
|
+
}
|
|
13723
|
+
try {
|
|
13724
|
+
iframeHandler();
|
|
13725
|
+
var idx = handlers.indexOf(iframeHandler);
|
|
13726
|
+
if (idx !== -1) handlers.splice(idx, 1);
|
|
13727
|
+
removeMutationBufferForDoc(iframeDoc);
|
|
13728
|
+
} catch (e2) {}
|
|
13729
|
+
});
|
|
13606
13730
|
} catch (error) {
|
|
13607
13731
|
console.warn(error);
|
|
13608
13732
|
}
|
|
@@ -13859,7 +13983,7 @@ function classMatchesRegex(node2, regex, checkAncestors) {
|
|
|
13859
13983
|
}
|
|
13860
13984
|
return classMatchesRegex(index.parentNode(node2), regex);
|
|
13861
13985
|
}
|
|
13862
|
-
function getDefaultExportFromCjs(x2) {
|
|
13986
|
+
function getDefaultExportFromCjs$3(x2) {
|
|
13863
13987
|
return x2 && x2.__esModule && Object.prototype.hasOwnProperty.call(x2, "default") ? x2["default"] : x2;
|
|
13864
13988
|
}
|
|
13865
13989
|
function getAugmentedNamespace(n) {
|
|
@@ -17974,7 +18098,7 @@ postcss.Node = Node2;
|
|
|
17974
18098
|
LazyResult2.registerPostcss(postcss);
|
|
17975
18099
|
var postcss_1 = postcss;
|
|
17976
18100
|
postcss.default = postcss;
|
|
17977
|
-
var postcss$1 = /* @__PURE__ */ getDefaultExportFromCjs(postcss_1);
|
|
18101
|
+
var postcss$1 = /* @__PURE__ */ getDefaultExportFromCjs$3(postcss_1);
|
|
17978
18102
|
postcss$1.stringify;
|
|
17979
18103
|
postcss$1.fromJSON;
|
|
17980
18104
|
postcss$1.plugin;
|
|
@@ -18011,7 +18135,7 @@ var __defNormalProp = function(obj, key, value) {
|
|
|
18011
18135
|
var __publicField = function(obj, key, value) {
|
|
18012
18136
|
return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
|
|
18013
18137
|
};
|
|
18014
|
-
function patch(source, name, replacement) {
|
|
18138
|
+
function patch$3(source, name, replacement) {
|
|
18015
18139
|
try {
|
|
18016
18140
|
if (!(name in source)) {
|
|
18017
18141
|
return function() {};
|
|
@@ -18428,7 +18552,7 @@ function initLogObserver(cb, win, options) {
|
|
|
18428
18552
|
if (!_logger[level]) {
|
|
18429
18553
|
return function() {};
|
|
18430
18554
|
}
|
|
18431
|
-
return patch(_logger, level, function(original) {
|
|
18555
|
+
return patch$3(_logger, level, function(original) {
|
|
18432
18556
|
var _this1 = _this;
|
|
18433
18557
|
return function() {
|
|
18434
18558
|
for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
|
|
@@ -18849,11 +18973,6 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
|
|
|
18849
18973
|
PromisePolyfill = NpoPromise;
|
|
18850
18974
|
}
|
|
18851
18975
|
|
|
18852
|
-
var Config = {
|
|
18853
|
-
DEBUG: false,
|
|
18854
|
-
LIB_VERSION: '2.74.0'
|
|
18855
|
-
};
|
|
18856
|
-
|
|
18857
18976
|
/* eslint camelcase: "off", eqeqeq: "off" */
|
|
18858
18977
|
|
|
18859
18978
|
// Maximum allowed session recording length
|
|
@@ -19057,15 +19176,8 @@ _.isArray = nativeIsArray || function(obj) {
|
|
|
19057
19176
|
return toString.call(obj) === '[object Array]';
|
|
19058
19177
|
};
|
|
19059
19178
|
|
|
19060
|
-
// from a comment on http://dbj.org/dbj/?p=286
|
|
19061
|
-
// fails on only one very rare and deliberate custom object:
|
|
19062
|
-
// var bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
|
|
19063
19179
|
_.isFunction = function(f) {
|
|
19064
|
-
|
|
19065
|
-
return /^\s*\bfunction\b/.test(f);
|
|
19066
|
-
} catch (x) {
|
|
19067
|
-
return false;
|
|
19068
|
-
}
|
|
19180
|
+
return typeof f === 'function';
|
|
19069
19181
|
};
|
|
19070
19182
|
|
|
19071
19183
|
_.isArguments = function(obj) {
|
|
@@ -20592,6 +20704,17 @@ var isOnline = function() {
|
|
|
20592
20704
|
|
|
20593
20705
|
var NOOP_FUNC = function () {};
|
|
20594
20706
|
|
|
20707
|
+
var urlMatchesRegexList = function (url, regexList) {
|
|
20708
|
+
var matches = false;
|
|
20709
|
+
for (var i = 0; i < regexList.length; i++) {
|
|
20710
|
+
if (url.match(regexList[i])) {
|
|
20711
|
+
matches = true;
|
|
20712
|
+
break;
|
|
20713
|
+
}
|
|
20714
|
+
}
|
|
20715
|
+
return matches;
|
|
20716
|
+
};
|
|
20717
|
+
|
|
20595
20718
|
var JSONStringify = null, JSONParse = null;
|
|
20596
20719
|
if (typeof JSON !== 'undefined') {
|
|
20597
20720
|
JSONStringify = JSON.stringify;
|
|
@@ -21063,7 +21186,7 @@ function _addOptOutCheck(method, getConfigValue) {
|
|
|
21063
21186
|
};
|
|
21064
21187
|
}
|
|
21065
21188
|
|
|
21066
|
-
var logger$
|
|
21189
|
+
var logger$7 = console_with_prefix('lock');
|
|
21067
21190
|
|
|
21068
21191
|
/**
|
|
21069
21192
|
* SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
|
|
@@ -21115,7 +21238,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
|
|
|
21115
21238
|
|
|
21116
21239
|
var delay = function(cb) {
|
|
21117
21240
|
if (new Date().getTime() - startTime > timeoutMS) {
|
|
21118
|
-
logger$
|
|
21241
|
+
logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
|
|
21119
21242
|
storage.removeItem(keyZ);
|
|
21120
21243
|
storage.removeItem(keyY);
|
|
21121
21244
|
loop();
|
|
@@ -21262,7 +21385,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
|
|
|
21262
21385
|
}, this));
|
|
21263
21386
|
};
|
|
21264
21387
|
|
|
21265
|
-
var logger$
|
|
21388
|
+
var logger$6 = console_with_prefix('batch');
|
|
21266
21389
|
|
|
21267
21390
|
/**
|
|
21268
21391
|
* RequestQueue: queue for batching API requests with localStorage backup for retries.
|
|
@@ -21291,7 +21414,7 @@ var RequestQueue = function (storageKey, options) {
|
|
|
21291
21414
|
timeoutMS: options.sharedLockTimeoutMS,
|
|
21292
21415
|
});
|
|
21293
21416
|
}
|
|
21294
|
-
this.reportError = options.errorReporter || _.bind(logger$
|
|
21417
|
+
this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
|
|
21295
21418
|
|
|
21296
21419
|
this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
|
|
21297
21420
|
|
|
@@ -21624,7 +21747,7 @@ RequestQueue.prototype.clear = function () {
|
|
|
21624
21747
|
// maximum interval between request retries after exponential backoff
|
|
21625
21748
|
var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
|
|
21626
21749
|
|
|
21627
|
-
var logger$
|
|
21750
|
+
var logger$5 = console_with_prefix('batch');
|
|
21628
21751
|
|
|
21629
21752
|
/**
|
|
21630
21753
|
* RequestBatcher: manages the queueing, flushing, retry etc of requests of one
|
|
@@ -21752,7 +21875,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
|
|
|
21752
21875
|
*/
|
|
21753
21876
|
RequestBatcher.prototype.flush = function(options) {
|
|
21754
21877
|
if (this.requestInProgress) {
|
|
21755
|
-
logger$
|
|
21878
|
+
logger$5.log('Flush: Request already in progress');
|
|
21756
21879
|
return PromisePolyfill.resolve();
|
|
21757
21880
|
}
|
|
21758
21881
|
|
|
@@ -21929,7 +22052,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
21929
22052
|
if (options.unloading) {
|
|
21930
22053
|
requestOptions.transport = 'sendBeacon';
|
|
21931
22054
|
}
|
|
21932
|
-
logger$
|
|
22055
|
+
logger$5.log('MIXPANEL REQUEST:', dataForRequest);
|
|
21933
22056
|
return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
|
|
21934
22057
|
}, this))
|
|
21935
22058
|
.catch(_.bind(function(err) {
|
|
@@ -21942,7 +22065,7 @@ RequestBatcher.prototype.flush = function(options) {
|
|
|
21942
22065
|
* Log error to global logger and optional user-defined logger.
|
|
21943
22066
|
*/
|
|
21944
22067
|
RequestBatcher.prototype.reportError = function(msg, err) {
|
|
21945
|
-
logger$
|
|
22068
|
+
logger$5.error.apply(logger$5.error, arguments);
|
|
21946
22069
|
if (this.errorReporter) {
|
|
21947
22070
|
try {
|
|
21948
22071
|
if (!(err instanceof Error)) {
|
|
@@ -21950,7 +22073,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
|
|
|
21950
22073
|
}
|
|
21951
22074
|
this.errorReporter(msg, err);
|
|
21952
22075
|
} catch(err) {
|
|
21953
|
-
logger$
|
|
22076
|
+
logger$5.error(err);
|
|
21954
22077
|
}
|
|
21955
22078
|
}
|
|
21956
22079
|
};
|
|
@@ -22072,7 +22195,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
|
|
|
22072
22195
|
|
|
22073
22196
|
var MAX_DEPTH = 5;
|
|
22074
22197
|
|
|
22075
|
-
var logger$
|
|
22198
|
+
var logger$4 = console_with_prefix('autocapture');
|
|
22076
22199
|
|
|
22077
22200
|
|
|
22078
22201
|
function getClasses(el) {
|
|
@@ -22336,7 +22459,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22336
22459
|
return false;
|
|
22337
22460
|
}
|
|
22338
22461
|
} catch (err) {
|
|
22339
|
-
logger$
|
|
22462
|
+
logger$4.critical('Error while checking element in allowElementCallback', err);
|
|
22340
22463
|
return false;
|
|
22341
22464
|
}
|
|
22342
22465
|
}
|
|
@@ -22353,7 +22476,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
|
|
|
22353
22476
|
return true;
|
|
22354
22477
|
}
|
|
22355
22478
|
} catch (err) {
|
|
22356
|
-
logger$
|
|
22479
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22357
22480
|
}
|
|
22358
22481
|
}
|
|
22359
22482
|
return false;
|
|
@@ -22368,7 +22491,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22368
22491
|
return true;
|
|
22369
22492
|
}
|
|
22370
22493
|
} catch (err) {
|
|
22371
|
-
logger$
|
|
22494
|
+
logger$4.critical('Error while checking element in blockElementCallback', err);
|
|
22372
22495
|
return true;
|
|
22373
22496
|
}
|
|
22374
22497
|
}
|
|
@@ -22382,7 +22505,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
|
|
|
22382
22505
|
return true;
|
|
22383
22506
|
}
|
|
22384
22507
|
} catch (err) {
|
|
22385
|
-
logger$
|
|
22508
|
+
logger$4.critical('Error while checking selector: ' + sel, err);
|
|
22386
22509
|
}
|
|
22387
22510
|
}
|
|
22388
22511
|
}
|
|
@@ -22930,173 +23053,822 @@ function shouldMaskText(element, privacyConfig) {
|
|
|
22930
23053
|
}
|
|
22931
23054
|
|
|
22932
23055
|
/**
|
|
22933
|
-
*
|
|
23056
|
+
* This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
|
|
23057
|
+
* the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
|
|
23058
|
+
*
|
|
23059
|
+
* This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
|
|
23060
|
+
* header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
|
|
23061
|
+
*
|
|
22934
23062
|
*/
|
|
22935
23063
|
|
|
23064
|
+
var logger$3 = console_with_prefix('network-plugin');
|
|
22936
23065
|
|
|
22937
|
-
|
|
22938
|
-
|
|
22939
|
-
|
|
22940
|
-
|
|
22941
|
-
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
23066
|
+
/**
|
|
23067
|
+
* Get the time origin for converting performance timestamps to absolute timestamps.
|
|
23068
|
+
* Uses Date.now() - performance.now() instead of performance.timeOrigin because
|
|
23069
|
+
* browsers can report timeOrigin values that are skewed from actual time, and some
|
|
23070
|
+
* browsers (notably older Safari versions) don't implement timeOrigin at all.
|
|
23071
|
+
* See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
|
|
23072
|
+
* @param {Window} win
|
|
23073
|
+
* @returns {number}
|
|
23074
|
+
*/
|
|
23075
|
+
function getTimeOrigin(win) {
|
|
23076
|
+
return Math.round(Date.now() - win.performance.now());
|
|
23077
|
+
}
|
|
22946
23078
|
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
22952
|
-
|
|
22953
|
-
IncrementalSource.TouchMove,
|
|
22954
|
-
IncrementalSource.MediaInteraction,
|
|
22955
|
-
IncrementalSource.Drag,
|
|
22956
|
-
IncrementalSource.Selection,
|
|
22957
|
-
]);
|
|
23079
|
+
/**
|
|
23080
|
+
* @typedef {import('../index.d.ts').InitiatorType} InitiatorType
|
|
23081
|
+
* @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
|
|
23082
|
+
* @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
|
|
23083
|
+
* @typedef {import('../index.d.ts').NetworkData} NetworkData
|
|
23084
|
+
*/
|
|
22958
23085
|
|
|
22959
|
-
|
|
22960
|
-
|
|
22961
|
-
|
|
23086
|
+
/**
|
|
23087
|
+
* @typedef {Record<string, string>} Headers
|
|
23088
|
+
*/
|
|
22962
23089
|
|
|
22963
23090
|
/**
|
|
22964
|
-
* @typedef {
|
|
22965
|
-
* @property {number} idleExpires
|
|
22966
|
-
* @property {number} maxExpires
|
|
22967
|
-
* @property {number} replayStartTime
|
|
22968
|
-
* @property {number} lastEventTimestamp
|
|
22969
|
-
* @property {number} seqNo
|
|
22970
|
-
* @property {string} batchStartUrl
|
|
22971
|
-
* @property {string} replayId
|
|
22972
|
-
* @property {string} tabId
|
|
22973
|
-
* @property {string} replayStartUrl
|
|
23091
|
+
* @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
|
|
22974
23092
|
*/
|
|
22975
23093
|
|
|
22976
23094
|
/**
|
|
22977
|
-
* @
|
|
22978
|
-
* @
|
|
22979
|
-
* @
|
|
22980
|
-
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
22981
|
-
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
22982
|
-
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
22983
|
-
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
22984
|
-
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
22985
|
-
* optional properties for deserialization:
|
|
22986
|
-
* @property {number} idleExpires
|
|
22987
|
-
* @property {number} maxExpires
|
|
22988
|
-
* @property {number} replayStartTime
|
|
22989
|
-
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
22990
|
-
* @property {number} seqNo
|
|
22991
|
-
* @property {string} batchStartUrl
|
|
22992
|
-
* @property {string} replayStartUrl
|
|
23095
|
+
* @callback networkCallback
|
|
23096
|
+
* @param {NetworkData} data
|
|
23097
|
+
* @returns {void}
|
|
22993
23098
|
*/
|
|
22994
23099
|
|
|
22995
23100
|
/**
|
|
22996
|
-
* @
|
|
22997
|
-
* @
|
|
22998
|
-
* @property {string} user_id
|
|
22999
|
-
* @property {string} device_id
|
|
23101
|
+
* @callback listenerHandler
|
|
23102
|
+
* @returns {void}
|
|
23000
23103
|
*/
|
|
23001
23104
|
|
|
23105
|
+
/**
|
|
23106
|
+
* @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
|
|
23107
|
+
*/
|
|
23002
23108
|
|
|
23003
23109
|
/**
|
|
23004
|
-
*
|
|
23005
|
-
* @
|
|
23110
|
+
* @typedef {Object} RecordPlugin
|
|
23111
|
+
* @property {string} name
|
|
23112
|
+
* @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
|
|
23113
|
+
* @property {NetworkRecordOptions} [options]
|
|
23006
23114
|
*/
|
|
23007
|
-
var SessionRecording = function(options) {
|
|
23008
|
-
this._mixpanel = options.mixpanelInstance;
|
|
23009
|
-
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23010
|
-
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23011
|
-
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23012
|
-
this._rrwebRecord = options.rrwebRecord || null;
|
|
23013
23115
|
|
|
23014
|
-
|
|
23015
|
-
|
|
23016
|
-
|
|
23116
|
+
/** @type {Required<NetworkRecordOptions>} */
|
|
23117
|
+
var defaultNetworkOptions = {
|
|
23118
|
+
initiatorTypes: [
|
|
23119
|
+
'audio',
|
|
23120
|
+
'beacon',
|
|
23121
|
+
'body',
|
|
23122
|
+
'css',
|
|
23123
|
+
'early-hint',
|
|
23124
|
+
'embed',
|
|
23125
|
+
'fetch',
|
|
23126
|
+
'frame',
|
|
23127
|
+
'iframe',
|
|
23128
|
+
'icon',
|
|
23129
|
+
'image',
|
|
23130
|
+
'img',
|
|
23131
|
+
'input',
|
|
23132
|
+
'link',
|
|
23133
|
+
'navigation',
|
|
23134
|
+
'object',
|
|
23135
|
+
'ping',
|
|
23136
|
+
'script',
|
|
23137
|
+
'track',
|
|
23138
|
+
'video',
|
|
23139
|
+
'xmlhttprequest',
|
|
23140
|
+
],
|
|
23141
|
+
ignoreRequestFn: function() { return false; },
|
|
23142
|
+
recordHeaders: {
|
|
23143
|
+
request: [],
|
|
23144
|
+
response: [],
|
|
23145
|
+
},
|
|
23146
|
+
recordBodyUrls: {
|
|
23147
|
+
request: [],
|
|
23148
|
+
response: [],
|
|
23149
|
+
},
|
|
23150
|
+
recordInitialRequests: false,
|
|
23151
|
+
};
|
|
23017
23152
|
|
|
23018
|
-
|
|
23019
|
-
|
|
23020
|
-
|
|
23021
|
-
|
|
23022
|
-
|
|
23023
|
-
|
|
23024
|
-
|
|
23153
|
+
/**
|
|
23154
|
+
* @param {PerformanceEntry} entry
|
|
23155
|
+
* @returns {entry is PerformanceNavigationTiming}
|
|
23156
|
+
*/
|
|
23157
|
+
function isNavigationTiming(entry) {
|
|
23158
|
+
return entry.entryType === 'navigation';
|
|
23159
|
+
}
|
|
23025
23160
|
|
|
23026
|
-
|
|
23027
|
-
|
|
23161
|
+
/**
|
|
23162
|
+
* @param {PerformanceEntry} entry
|
|
23163
|
+
* @returns {entry is PerformanceResourceTiming}
|
|
23164
|
+
*/
|
|
23165
|
+
function isResourceTiming (entry) {
|
|
23166
|
+
return entry.entryType === 'resource';
|
|
23167
|
+
}
|
|
23028
23168
|
|
|
23029
|
-
|
|
23030
|
-
|
|
23169
|
+
function findLast(array, predicate) {
|
|
23170
|
+
var length = array.length;
|
|
23171
|
+
for (var i = length - 1; i >= 0; i -= 1) {
|
|
23172
|
+
if (predicate(array[i])) {
|
|
23173
|
+
return array[i];
|
|
23174
|
+
}
|
|
23175
|
+
}
|
|
23176
|
+
}
|
|
23031
23177
|
|
|
23032
|
-
|
|
23033
|
-
|
|
23034
|
-
|
|
23178
|
+
/**
|
|
23179
|
+
* Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
|
|
23180
|
+
* Adapted from Sentry's `fill` utility:
|
|
23181
|
+
* https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
|
|
23182
|
+
*
|
|
23183
|
+
* @param {object} source - The object containing the method to patch
|
|
23184
|
+
* @param {string} name - The method name to patch
|
|
23185
|
+
* @param {function} replacementFactory - A function that receives the original method and returns the replacement
|
|
23186
|
+
* @returns {function} A function that restores the original method
|
|
23187
|
+
*/
|
|
23188
|
+
function patch(source, name, replacementFactory) {
|
|
23189
|
+
if (!(name in source) || typeof source[name] !== 'function') {
|
|
23190
|
+
return function() {};
|
|
23191
|
+
}
|
|
23192
|
+
var original = source[name];
|
|
23193
|
+
var wrapped = replacementFactory(original);
|
|
23194
|
+
source[name] = wrapped;
|
|
23195
|
+
return function() {
|
|
23196
|
+
source[name] = original;
|
|
23197
|
+
};
|
|
23198
|
+
}
|
|
23035
23199
|
|
|
23036
|
-
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23037
|
-
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23038
|
-
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23039
|
-
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23040
|
-
errorReporter: this.reportError.bind(this),
|
|
23041
|
-
flushOnlyOnInterval: true,
|
|
23042
|
-
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23043
|
-
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23044
|
-
queueStorage: this.queueStorage,
|
|
23045
|
-
sharedLockStorage: options.sharedLockStorage,
|
|
23046
|
-
usePersistence: usePersistence,
|
|
23047
|
-
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23048
23200
|
|
|
23049
|
-
|
|
23050
|
-
|
|
23051
|
-
|
|
23052
|
-
|
|
23053
|
-
sharedLockTimeoutMS: 10 * 1000,
|
|
23054
|
-
});
|
|
23055
|
-
};
|
|
23201
|
+
/**
|
|
23202
|
+
* Maximum body size to record (1MB)
|
|
23203
|
+
*/
|
|
23204
|
+
var MAX_BODY_SIZE = 1024 * 1024;
|
|
23056
23205
|
|
|
23057
23206
|
/**
|
|
23058
|
-
*
|
|
23207
|
+
* Truncate string if it exceeds max size
|
|
23208
|
+
* @param {string} str
|
|
23209
|
+
* @returns {string}
|
|
23059
23210
|
*/
|
|
23060
|
-
|
|
23061
|
-
if (
|
|
23062
|
-
return
|
|
23211
|
+
function truncateBody(str) {
|
|
23212
|
+
if (!str || typeof str !== 'string') {
|
|
23213
|
+
return str;
|
|
23214
|
+
}
|
|
23215
|
+
if (str.length > MAX_BODY_SIZE) {
|
|
23216
|
+
logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
|
|
23217
|
+
return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
|
|
23063
23218
|
}
|
|
23219
|
+
return str;
|
|
23220
|
+
}
|
|
23064
23221
|
|
|
23065
|
-
|
|
23066
|
-
|
|
23222
|
+
/**
|
|
23223
|
+
* @param {networkCallback} cb
|
|
23224
|
+
* @param {Window} win
|
|
23225
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23226
|
+
* @returns {listenerHandler}
|
|
23227
|
+
*/
|
|
23228
|
+
function initPerformanceObserver(cb, win, options) {
|
|
23229
|
+
if (!win.PerformanceObserver) {
|
|
23230
|
+
logger$3.error('PerformanceObserver not supported');
|
|
23231
|
+
return function() {
|
|
23232
|
+
//
|
|
23233
|
+
};
|
|
23234
|
+
}
|
|
23235
|
+
if (options.recordInitialRequests) {
|
|
23236
|
+
var initialPerformanceEntries = win.performance
|
|
23237
|
+
.getEntries()
|
|
23238
|
+
.filter(function(entry) {
|
|
23239
|
+
return isNavigationTiming(entry) ||
|
|
23240
|
+
(isResourceTiming(entry) &&
|
|
23241
|
+
options.initiatorTypes.includes(entry.initiatorType));
|
|
23242
|
+
});
|
|
23243
|
+
cb({
|
|
23244
|
+
requests: initialPerformanceEntries.map(function(entry) {
|
|
23245
|
+
return {
|
|
23246
|
+
url: entry.name,
|
|
23247
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23248
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23249
|
+
startTime: Math.round(entry.startTime),
|
|
23250
|
+
endTime: Math.round(entry.responseEnd),
|
|
23251
|
+
timeOrigin: getTimeOrigin(win),
|
|
23252
|
+
};
|
|
23253
|
+
}),
|
|
23254
|
+
isInitial: true,
|
|
23255
|
+
});
|
|
23256
|
+
}
|
|
23257
|
+
var observer = new win.PerformanceObserver(function(entries) {
|
|
23258
|
+
var performanceEntries = entries
|
|
23259
|
+
.getEntries()
|
|
23260
|
+
.filter(function(entry) {
|
|
23261
|
+
return isResourceTiming(entry) &&
|
|
23262
|
+
options.initiatorTypes.includes(entry.initiatorType) &&
|
|
23263
|
+
entry.initiatorType !== 'xmlhttprequest' &&
|
|
23264
|
+
entry.initiatorType !== 'fetch';
|
|
23265
|
+
});
|
|
23266
|
+
cb({
|
|
23267
|
+
requests: performanceEntries.map(function(entry) {
|
|
23268
|
+
return {
|
|
23269
|
+
url: entry.name,
|
|
23270
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23271
|
+
status: 'responseStatus' in entry ? entry.responseStatus : undefined,
|
|
23272
|
+
startTime: Math.round(entry.startTime),
|
|
23273
|
+
endTime: Math.round(entry.responseEnd),
|
|
23274
|
+
timeOrigin: getTimeOrigin(win),
|
|
23275
|
+
};
|
|
23276
|
+
}),
|
|
23277
|
+
});
|
|
23278
|
+
});
|
|
23279
|
+
observer.observe({ entryTypes: ['navigation', 'resource'] });
|
|
23280
|
+
return function() {
|
|
23281
|
+
observer.disconnect();
|
|
23067
23282
|
};
|
|
23283
|
+
}
|
|
23068
23284
|
|
|
23069
|
-
|
|
23070
|
-
|
|
23071
|
-
|
|
23072
|
-
|
|
23285
|
+
/**
|
|
23286
|
+
* Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
|
|
23287
|
+
* @param {'request' | 'response'} type
|
|
23288
|
+
* @param {NetworkRecordOptions['recordHeaders']} recordHeaders
|
|
23289
|
+
* @param {string} headerName
|
|
23290
|
+
* @returns {boolean}
|
|
23291
|
+
*/
|
|
23292
|
+
function shouldRecordHeader(type, recordHeaders, headerName) {
|
|
23293
|
+
if (!recordHeaders[type] || recordHeaders[type].length === 0) {
|
|
23294
|
+
return false;
|
|
23073
23295
|
}
|
|
23074
|
-
|
|
23075
|
-
|
|
23076
|
-
|
|
23296
|
+
|
|
23297
|
+
return recordHeaders[type].includes(headerName.toLowerCase());
|
|
23298
|
+
}
|
|
23299
|
+
|
|
23300
|
+
/**
|
|
23301
|
+
* Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
|
|
23302
|
+
* @param {'request' | 'response'} type
|
|
23303
|
+
* @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
|
|
23304
|
+
* @param {string} url
|
|
23305
|
+
* @returns {boolean}
|
|
23306
|
+
*/
|
|
23307
|
+
function shouldRecordBody(type, recordBodyUrls, url) {
|
|
23308
|
+
if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
|
|
23309
|
+
return false;
|
|
23077
23310
|
}
|
|
23078
|
-
return userIdInfo;
|
|
23079
|
-
};
|
|
23080
23311
|
|
|
23081
|
-
|
|
23082
|
-
|
|
23312
|
+
return urlMatchesRegexList(url, recordBodyUrls[type]);
|
|
23313
|
+
}
|
|
23083
23314
|
|
|
23084
|
-
|
|
23085
|
-
|
|
23086
|
-
|
|
23087
|
-
|
|
23088
|
-
|
|
23089
|
-
|
|
23315
|
+
function tryReadXHRBody(body) {
|
|
23316
|
+
if (body === null || body === undefined) {
|
|
23317
|
+
return null;
|
|
23318
|
+
}
|
|
23319
|
+
|
|
23320
|
+
var result;
|
|
23321
|
+
if (typeof body === 'string') {
|
|
23322
|
+
result = body;
|
|
23323
|
+
} else if (body instanceof Document) {
|
|
23324
|
+
result = body.textContent;
|
|
23325
|
+
} else if (body instanceof FormData) {
|
|
23326
|
+
result = _.HTTPBuildQuery(body);
|
|
23327
|
+
} else if (_.isObject(body)) {
|
|
23328
|
+
try {
|
|
23329
|
+
result = JSON.stringify(body);
|
|
23330
|
+
} catch (e) {
|
|
23331
|
+
return 'Failed to stringify response object';
|
|
23090
23332
|
}
|
|
23333
|
+
} else {
|
|
23334
|
+
return 'Cannot read body of type ' + typeof body;
|
|
23335
|
+
}
|
|
23091
23336
|
|
|
23092
|
-
|
|
23093
|
-
|
|
23094
|
-
return this.queueStorage.removeItem(this.batcherKey);
|
|
23095
|
-
}.bind(this));
|
|
23096
|
-
}.bind(this));
|
|
23097
|
-
};
|
|
23337
|
+
return truncateBody(result);
|
|
23338
|
+
}
|
|
23098
23339
|
|
|
23099
|
-
|
|
23340
|
+
/**
|
|
23341
|
+
* @param {Request | Response} r
|
|
23342
|
+
* @returns {Promise<string>}
|
|
23343
|
+
*/
|
|
23344
|
+
function tryReadFetchBody(r) {
|
|
23345
|
+
return new Promise(function(resolve) {
|
|
23346
|
+
var timeout = setTimeout(function() {
|
|
23347
|
+
resolve('Timeout while trying to read body');
|
|
23348
|
+
}, 500);
|
|
23349
|
+
try {
|
|
23350
|
+
r.clone()
|
|
23351
|
+
.text()
|
|
23352
|
+
.then(
|
|
23353
|
+
function(txt) {
|
|
23354
|
+
clearTimeout(timeout);
|
|
23355
|
+
resolve(truncateBody(txt));
|
|
23356
|
+
},
|
|
23357
|
+
function(reason) {
|
|
23358
|
+
clearTimeout(timeout);
|
|
23359
|
+
resolve('Failed to read body: ' + String(reason));
|
|
23360
|
+
}
|
|
23361
|
+
);
|
|
23362
|
+
} catch (e) {
|
|
23363
|
+
clearTimeout(timeout);
|
|
23364
|
+
resolve('Failed to read body: ' + String(e));
|
|
23365
|
+
}
|
|
23366
|
+
});
|
|
23367
|
+
}
|
|
23368
|
+
|
|
23369
|
+
/**
|
|
23370
|
+
* @param {Window} win
|
|
23371
|
+
* @param {string} initiatorType
|
|
23372
|
+
* @param {string} url
|
|
23373
|
+
* @param {number} [after]
|
|
23374
|
+
* @param {number} [before]
|
|
23375
|
+
* @param {number} [attempt]
|
|
23376
|
+
* @returns {Promise<PerformanceResourceTiming>}
|
|
23377
|
+
*/
|
|
23378
|
+
function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
|
|
23379
|
+
if (attempt === undefined) {
|
|
23380
|
+
attempt = 0;
|
|
23381
|
+
}
|
|
23382
|
+
if (attempt > 10) {
|
|
23383
|
+
logger$3.error('Cannot find performance entry');
|
|
23384
|
+
return Promise.resolve(null);
|
|
23385
|
+
}
|
|
23386
|
+
var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
|
|
23387
|
+
win.performance.getEntriesByName(url)
|
|
23388
|
+
);
|
|
23389
|
+
var performanceEntry = findLast(
|
|
23390
|
+
urlPerformanceEntries,
|
|
23391
|
+
function(entry) {
|
|
23392
|
+
return isResourceTiming(entry) &&
|
|
23393
|
+
entry.initiatorType === initiatorType &&
|
|
23394
|
+
(!after || entry.startTime >= after) &&
|
|
23395
|
+
(!before || entry.startTime <= before);
|
|
23396
|
+
}
|
|
23397
|
+
);
|
|
23398
|
+
if (!performanceEntry) {
|
|
23399
|
+
return new Promise(function(resolve) {
|
|
23400
|
+
setTimeout(resolve, 50 * attempt);
|
|
23401
|
+
}).then(function() {
|
|
23402
|
+
return getRequestPerformanceEntry(
|
|
23403
|
+
win,
|
|
23404
|
+
initiatorType,
|
|
23405
|
+
url,
|
|
23406
|
+
after,
|
|
23407
|
+
before,
|
|
23408
|
+
attempt + 1
|
|
23409
|
+
);
|
|
23410
|
+
});
|
|
23411
|
+
}
|
|
23412
|
+
return Promise.resolve(performanceEntry);
|
|
23413
|
+
}
|
|
23414
|
+
|
|
23415
|
+
/**
|
|
23416
|
+
* @param {networkCallback} cb
|
|
23417
|
+
* @param {Window} win
|
|
23418
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23419
|
+
* @returns {listenerHandler}
|
|
23420
|
+
*/
|
|
23421
|
+
function initXhrObserver(cb, win, options) {
|
|
23422
|
+
if (!options.initiatorTypes.includes('xmlhttprequest')) {
|
|
23423
|
+
return function() {
|
|
23424
|
+
//
|
|
23425
|
+
};
|
|
23426
|
+
}
|
|
23427
|
+
var restorePatch = patch(
|
|
23428
|
+
win.XMLHttpRequest.prototype,
|
|
23429
|
+
'open',
|
|
23430
|
+
function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
|
|
23431
|
+
return function(
|
|
23432
|
+
/** @type {string} */ method,
|
|
23433
|
+
/** @type {string | URL} */ url,
|
|
23434
|
+
/** @type {boolean} */ async,
|
|
23435
|
+
username, password
|
|
23436
|
+
) {
|
|
23437
|
+
if (async === undefined) {
|
|
23438
|
+
async = true;
|
|
23439
|
+
}
|
|
23440
|
+
var xhr = /** @type {XMLHttpRequest} */ (this);
|
|
23441
|
+
var req = new Request(url, { method: method });
|
|
23442
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23443
|
+
var networkRequest = {};
|
|
23444
|
+
/** @type {number | undefined} */
|
|
23445
|
+
var after;
|
|
23446
|
+
/** @type {number | undefined} */
|
|
23447
|
+
var before;
|
|
23448
|
+
|
|
23449
|
+
/** @type {Headers} */
|
|
23450
|
+
var requestHeaders = {};
|
|
23451
|
+
var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
|
|
23452
|
+
xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
|
|
23453
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23454
|
+
requestHeaders[header] = value;
|
|
23455
|
+
}
|
|
23456
|
+
return originalSetRequestHeader(header, value);
|
|
23457
|
+
};
|
|
23458
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23459
|
+
|
|
23460
|
+
var originalSend = xhr.send.bind(xhr);
|
|
23461
|
+
xhr.send = function(/** @type {Body} */ body) {
|
|
23462
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23463
|
+
networkRequest.requestBody = tryReadXHRBody(body);
|
|
23464
|
+
}
|
|
23465
|
+
after = win.performance.now();
|
|
23466
|
+
return originalSend(body);
|
|
23467
|
+
};
|
|
23468
|
+
xhr.addEventListener('readystatechange', function() {
|
|
23469
|
+
if (xhr.readyState !== xhr.DONE) {
|
|
23470
|
+
return;
|
|
23471
|
+
}
|
|
23472
|
+
before = win.performance.now();
|
|
23473
|
+
/** @type {Headers} */
|
|
23474
|
+
var responseHeaders = {};
|
|
23475
|
+
var rawHeaders = xhr.getAllResponseHeaders();
|
|
23476
|
+
if (rawHeaders) {
|
|
23477
|
+
var headers = rawHeaders.trim().split(/[\r\n]+/);
|
|
23478
|
+
headers.forEach(function(line) {
|
|
23479
|
+
if (!line) return;
|
|
23480
|
+
var colonIndex = line.indexOf(': ');
|
|
23481
|
+
if (colonIndex === -1) return;
|
|
23482
|
+
var header = line.substring(0, colonIndex);
|
|
23483
|
+
var value = line.substring(colonIndex + 2);
|
|
23484
|
+
if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23485
|
+
responseHeaders[header] = value;
|
|
23486
|
+
}
|
|
23487
|
+
});
|
|
23488
|
+
}
|
|
23489
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23490
|
+
if (
|
|
23491
|
+
shouldRecordBody('response', options.recordBodyUrls, req.url)
|
|
23492
|
+
) {
|
|
23493
|
+
networkRequest.responseBody = tryReadXHRBody(xhr.response);
|
|
23494
|
+
}
|
|
23495
|
+
getRequestPerformanceEntry(
|
|
23496
|
+
win,
|
|
23497
|
+
'xmlhttprequest',
|
|
23498
|
+
req.url,
|
|
23499
|
+
after,
|
|
23500
|
+
before
|
|
23501
|
+
)
|
|
23502
|
+
.then(function(entry) {
|
|
23503
|
+
if (!entry) {
|
|
23504
|
+
logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
|
|
23505
|
+
return;
|
|
23506
|
+
}
|
|
23507
|
+
/** @type {NetworkRequest} */
|
|
23508
|
+
var request = {
|
|
23509
|
+
url: entry.name,
|
|
23510
|
+
method: req.method,
|
|
23511
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23512
|
+
status: xhr.status,
|
|
23513
|
+
startTime: Math.round(entry.startTime),
|
|
23514
|
+
endTime: Math.round(entry.responseEnd),
|
|
23515
|
+
timeOrigin: getTimeOrigin(win),
|
|
23516
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23517
|
+
requestBody: networkRequest.requestBody,
|
|
23518
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23519
|
+
responseBody: networkRequest.responseBody,
|
|
23520
|
+
};
|
|
23521
|
+
cb({ requests: [request] });
|
|
23522
|
+
})
|
|
23523
|
+
.catch(function(e) {
|
|
23524
|
+
logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
|
|
23525
|
+
});
|
|
23526
|
+
});
|
|
23527
|
+
|
|
23528
|
+
originalOpen.call(xhr, method, url, async, username, password);
|
|
23529
|
+
};
|
|
23530
|
+
}
|
|
23531
|
+
);
|
|
23532
|
+
return function() {
|
|
23533
|
+
restorePatch();
|
|
23534
|
+
};
|
|
23535
|
+
}
|
|
23536
|
+
|
|
23537
|
+
/**
|
|
23538
|
+
* @param {networkCallback} cb
|
|
23539
|
+
* @param {Window} win
|
|
23540
|
+
* @param {Required<NetworkRecordOptions>} options
|
|
23541
|
+
* @returns {listenerHandler}
|
|
23542
|
+
*/
|
|
23543
|
+
function initFetchObserver(cb, win, options) {
|
|
23544
|
+
if (!options.initiatorTypes.includes('fetch')) {
|
|
23545
|
+
return function() {
|
|
23546
|
+
//
|
|
23547
|
+
};
|
|
23548
|
+
}
|
|
23549
|
+
|
|
23550
|
+
var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
|
|
23551
|
+
return function() {
|
|
23552
|
+
var req = new Request(arguments[0], arguments[1]);
|
|
23553
|
+
/** @type {Response | undefined} */
|
|
23554
|
+
var res;
|
|
23555
|
+
/** @type {Partial<NetworkRequest>} */
|
|
23556
|
+
var networkRequest = {};
|
|
23557
|
+
/** @type {number | undefined} */
|
|
23558
|
+
var after;
|
|
23559
|
+
/** @type {number | undefined} */
|
|
23560
|
+
var before;
|
|
23561
|
+
|
|
23562
|
+
var originalFetchPromise;
|
|
23563
|
+
var requestBodyPromise = Promise.resolve(undefined);
|
|
23564
|
+
var responseBodyPromise = Promise.resolve(undefined);
|
|
23565
|
+
try {
|
|
23566
|
+
/** @type {Headers} */
|
|
23567
|
+
var requestHeaders = {};
|
|
23568
|
+
req.headers.forEach(function(value, header) {
|
|
23569
|
+
if (shouldRecordHeader('request', options.recordHeaders, header)) {
|
|
23570
|
+
requestHeaders[header] = value;
|
|
23571
|
+
}
|
|
23572
|
+
});
|
|
23573
|
+
networkRequest.requestHeaders = requestHeaders;
|
|
23574
|
+
|
|
23575
|
+
if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
|
|
23576
|
+
requestBodyPromise = tryReadFetchBody(req)
|
|
23577
|
+
.then(function(body) {
|
|
23578
|
+
networkRequest.requestBody = body;
|
|
23579
|
+
});
|
|
23580
|
+
}
|
|
23581
|
+
|
|
23582
|
+
after = win.performance.now();
|
|
23583
|
+
originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
|
|
23584
|
+
res = response;
|
|
23585
|
+
before = win.performance.now();
|
|
23586
|
+
|
|
23587
|
+
/** @type {Headers} */
|
|
23588
|
+
var responseHeaders = {};
|
|
23589
|
+
res.headers.forEach(function(value, header) {
|
|
23590
|
+
if (shouldRecordHeader('response', options.recordHeaders, header)) {
|
|
23591
|
+
responseHeaders[header] = value;
|
|
23592
|
+
}
|
|
23593
|
+
});
|
|
23594
|
+
networkRequest.responseHeaders = responseHeaders;
|
|
23595
|
+
|
|
23596
|
+
if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
|
|
23597
|
+
responseBodyPromise = tryReadFetchBody(res)
|
|
23598
|
+
.then(function(body) {
|
|
23599
|
+
networkRequest.responseBody = body;
|
|
23600
|
+
});
|
|
23601
|
+
}
|
|
23602
|
+
|
|
23603
|
+
return res;
|
|
23604
|
+
});
|
|
23605
|
+
} catch (e) {
|
|
23606
|
+
originalFetchPromise = Promise.reject(e);
|
|
23607
|
+
}
|
|
23608
|
+
|
|
23609
|
+
// await concurrently so we don't delay the fetch response
|
|
23610
|
+
Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
|
|
23611
|
+
.then(function () {
|
|
23612
|
+
return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
|
|
23613
|
+
})
|
|
23614
|
+
.then(function(entry) {
|
|
23615
|
+
if (!entry) {
|
|
23616
|
+
logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
|
|
23617
|
+
return;
|
|
23618
|
+
}
|
|
23619
|
+
/** @type {NetworkRequest} */
|
|
23620
|
+
var request = {
|
|
23621
|
+
url: entry.name,
|
|
23622
|
+
method: req.method,
|
|
23623
|
+
initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
|
|
23624
|
+
status: res ? res.status : undefined,
|
|
23625
|
+
startTime: Math.round(entry.startTime),
|
|
23626
|
+
endTime: Math.round(entry.responseEnd),
|
|
23627
|
+
timeOrigin: getTimeOrigin(win),
|
|
23628
|
+
requestHeaders: networkRequest.requestHeaders,
|
|
23629
|
+
requestBody: networkRequest.requestBody,
|
|
23630
|
+
responseHeaders: networkRequest.responseHeaders,
|
|
23631
|
+
responseBody: networkRequest.responseBody,
|
|
23632
|
+
};
|
|
23633
|
+
cb({ requests: [request] });
|
|
23634
|
+
})
|
|
23635
|
+
.catch(function (e) {
|
|
23636
|
+
logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
|
|
23637
|
+
});
|
|
23638
|
+
|
|
23639
|
+
return originalFetchPromise;
|
|
23640
|
+
};
|
|
23641
|
+
});
|
|
23642
|
+
return function() {
|
|
23643
|
+
restorePatch();
|
|
23644
|
+
};
|
|
23645
|
+
}
|
|
23646
|
+
|
|
23647
|
+
/**
|
|
23648
|
+
* @param {networkCallback} callback
|
|
23649
|
+
* @param {Window} win
|
|
23650
|
+
* @param {NetworkRecordOptions} options
|
|
23651
|
+
* @returns {listenerHandler}
|
|
23652
|
+
*/
|
|
23653
|
+
function initNetworkObserver(callback, win, options) {
|
|
23654
|
+
if (!('performance' in win)) {
|
|
23655
|
+
return function() {
|
|
23656
|
+
//
|
|
23657
|
+
};
|
|
23658
|
+
}
|
|
23659
|
+
|
|
23660
|
+
var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
|
|
23661
|
+
var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
|
|
23662
|
+
options = Object.assign({}, options, {
|
|
23663
|
+
recordHeaders: recordHeaders,
|
|
23664
|
+
recordBodyUrls: recordBodyUrls,
|
|
23665
|
+
});
|
|
23666
|
+
var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
|
|
23667
|
+
|
|
23668
|
+
/** @type {networkCallback} */
|
|
23669
|
+
var cb = function(data) {
|
|
23670
|
+
var requests = data.requests.filter(function(request) {
|
|
23671
|
+
var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
|
|
23672
|
+
return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
|
|
23673
|
+
});
|
|
23674
|
+
if (requests.length > 0 || data.isInitial) {
|
|
23675
|
+
callback(Object.assign({}, data, { requests: requests }));
|
|
23676
|
+
}
|
|
23677
|
+
};
|
|
23678
|
+
var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
|
|
23679
|
+
var xhrObserver = initXhrObserver(cb, win, networkOptions);
|
|
23680
|
+
var fetchObserver = initFetchObserver(cb, win, networkOptions);
|
|
23681
|
+
return function() {
|
|
23682
|
+
performanceObserver();
|
|
23683
|
+
xhrObserver();
|
|
23684
|
+
fetchObserver();
|
|
23685
|
+
};
|
|
23686
|
+
}
|
|
23687
|
+
|
|
23688
|
+
// arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
|
|
23689
|
+
// a changed format in the mixpanel product.
|
|
23690
|
+
var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
|
|
23691
|
+
|
|
23692
|
+
/**
|
|
23693
|
+
* @param {NetworkRecordOptions} [options]
|
|
23694
|
+
* @returns {RecordPlugin}
|
|
23695
|
+
*/
|
|
23696
|
+
var getRecordNetworkPlugin = function(options) {
|
|
23697
|
+
return {
|
|
23698
|
+
name: NETWORK_PLUGIN_NAME,
|
|
23699
|
+
observer: initNetworkObserver,
|
|
23700
|
+
options: options,
|
|
23701
|
+
};
|
|
23702
|
+
};
|
|
23703
|
+
|
|
23704
|
+
/**
|
|
23705
|
+
* @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
|
|
23706
|
+
*/
|
|
23707
|
+
|
|
23708
|
+
|
|
23709
|
+
var logger$2 = console_with_prefix('recorder');
|
|
23710
|
+
var CompressionStream = win['CompressionStream'];
|
|
23711
|
+
|
|
23712
|
+
var RECORDER_BATCHER_LIB_CONFIG = {
|
|
23713
|
+
'batch_size': 1000,
|
|
23714
|
+
'batch_flush_interval_ms': 10 * 1000,
|
|
23715
|
+
'batch_request_timeout_ms': 90 * 1000,
|
|
23716
|
+
'batch_autostart': true
|
|
23717
|
+
};
|
|
23718
|
+
|
|
23719
|
+
var ACTIVE_SOURCES = new Set([
|
|
23720
|
+
IncrementalSource.MouseMove,
|
|
23721
|
+
IncrementalSource.MouseInteraction,
|
|
23722
|
+
IncrementalSource.Scroll,
|
|
23723
|
+
IncrementalSource.ViewportResize,
|
|
23724
|
+
IncrementalSource.Input,
|
|
23725
|
+
IncrementalSource.TouchMove,
|
|
23726
|
+
IncrementalSource.MediaInteraction,
|
|
23727
|
+
IncrementalSource.Drag,
|
|
23728
|
+
IncrementalSource.Selection,
|
|
23729
|
+
]);
|
|
23730
|
+
|
|
23731
|
+
function isUserEvent(ev) {
|
|
23732
|
+
return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
|
|
23733
|
+
}
|
|
23734
|
+
|
|
23735
|
+
/**
|
|
23736
|
+
* @typedef {Object} SerializedRecording
|
|
23737
|
+
* @property {number} idleExpires
|
|
23738
|
+
* @property {number} maxExpires
|
|
23739
|
+
* @property {number} replayStartTime
|
|
23740
|
+
* @property {number} lastEventTimestamp
|
|
23741
|
+
* @property {number} seqNo
|
|
23742
|
+
* @property {string} batchStartUrl
|
|
23743
|
+
* @property {string} replayId
|
|
23744
|
+
* @property {string} tabId
|
|
23745
|
+
* @property {string} replayStartUrl
|
|
23746
|
+
*/
|
|
23747
|
+
|
|
23748
|
+
/**
|
|
23749
|
+
* @typedef {Object} SessionRecordingOptions
|
|
23750
|
+
* @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
|
|
23751
|
+
* @property {String} [options.replayId] - unique uuid for a single replay
|
|
23752
|
+
* @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
|
|
23753
|
+
* @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
|
|
23754
|
+
* @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
|
|
23755
|
+
* @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
|
|
23756
|
+
* @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
|
|
23757
|
+
* optional properties for deserialization:
|
|
23758
|
+
* @property {number} idleExpires
|
|
23759
|
+
* @property {number} maxExpires
|
|
23760
|
+
* @property {number} replayStartTime
|
|
23761
|
+
* @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
|
|
23762
|
+
* @property {number} seqNo
|
|
23763
|
+
* @property {string} batchStartUrl
|
|
23764
|
+
* @property {string} replayStartUrl
|
|
23765
|
+
*/
|
|
23766
|
+
|
|
23767
|
+
/**
|
|
23768
|
+
* @typedef {Object} UserIdInfo
|
|
23769
|
+
* @property {string} distinct_id
|
|
23770
|
+
* @property {string} user_id
|
|
23771
|
+
* @property {string} device_id
|
|
23772
|
+
*/
|
|
23773
|
+
|
|
23774
|
+
|
|
23775
|
+
/**
|
|
23776
|
+
* This class encapsulates a single session recording and its lifecycle.
|
|
23777
|
+
* @param {SessionRecordingOptions} options
|
|
23778
|
+
*/
|
|
23779
|
+
var SessionRecording = function(options) {
|
|
23780
|
+
this._mixpanel = options.mixpanelInstance;
|
|
23781
|
+
this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
|
|
23782
|
+
this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
|
|
23783
|
+
this._onBatchSent = options.onBatchSent || NOOP_FUNC;
|
|
23784
|
+
this._rrwebRecord = options.rrwebRecord || null;
|
|
23785
|
+
|
|
23786
|
+
// internal rrweb stopRecording function
|
|
23787
|
+
this._stopRecording = null;
|
|
23788
|
+
this.replayId = options.replayId;
|
|
23789
|
+
|
|
23790
|
+
this.batchStartUrl = options.batchStartUrl || null;
|
|
23791
|
+
this.replayStartUrl = options.replayStartUrl || null;
|
|
23792
|
+
this.idleExpires = options.idleExpires || null;
|
|
23793
|
+
this.maxExpires = options.maxExpires || null;
|
|
23794
|
+
this.replayStartTime = options.replayStartTime || null;
|
|
23795
|
+
this.lastEventTimestamp = options.lastEventTimestamp || null;
|
|
23796
|
+
this.seqNo = options.seqNo || 0;
|
|
23797
|
+
|
|
23798
|
+
this.idleTimeoutId = null;
|
|
23799
|
+
this.maxTimeoutId = null;
|
|
23800
|
+
|
|
23801
|
+
this.recordMaxMs = MAX_RECORDING_MS;
|
|
23802
|
+
this.recordMinMs = 0;
|
|
23803
|
+
|
|
23804
|
+
// disable persistence if localStorage is not supported
|
|
23805
|
+
// request-queue will automatically disable persistence if indexedDB fails to initialize
|
|
23806
|
+
var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
|
|
23807
|
+
|
|
23808
|
+
// each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
|
|
23809
|
+
this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
|
|
23810
|
+
this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
|
|
23811
|
+
this.batcher = new RequestBatcher(this.batcherKey, {
|
|
23812
|
+
errorReporter: this.reportError.bind(this),
|
|
23813
|
+
flushOnlyOnInterval: true,
|
|
23814
|
+
libConfig: RECORDER_BATCHER_LIB_CONFIG,
|
|
23815
|
+
sendRequestFunc: this.flushEventsWithOptOut.bind(this),
|
|
23816
|
+
queueStorage: this.queueStorage,
|
|
23817
|
+
sharedLockStorage: options.sharedLockStorage,
|
|
23818
|
+
usePersistence: usePersistence,
|
|
23819
|
+
stopAllBatchingFunc: this.stopRecording.bind(this),
|
|
23820
|
+
|
|
23821
|
+
// increased throttle and shared lock timeout because recording events are very high frequency.
|
|
23822
|
+
// this will minimize the amount of lock contention between enqueued events.
|
|
23823
|
+
// for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
|
|
23824
|
+
enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
|
|
23825
|
+
sharedLockTimeoutMS: 10 * 1000,
|
|
23826
|
+
});
|
|
23827
|
+
};
|
|
23828
|
+
|
|
23829
|
+
/**
|
|
23830
|
+
* @returns {UserIdInfo}
|
|
23831
|
+
*/
|
|
23832
|
+
SessionRecording.prototype.getUserIdInfo = function () {
|
|
23833
|
+
if (this.finalFlushUserIdInfo) {
|
|
23834
|
+
return this.finalFlushUserIdInfo;
|
|
23835
|
+
}
|
|
23836
|
+
|
|
23837
|
+
var userIdInfo = {
|
|
23838
|
+
'distinct_id': String(this._mixpanel.get_distinct_id()),
|
|
23839
|
+
};
|
|
23840
|
+
|
|
23841
|
+
// send ID management props if they exist
|
|
23842
|
+
var deviceId = this._mixpanel.get_property('$device_id');
|
|
23843
|
+
if (deviceId) {
|
|
23844
|
+
userIdInfo['$device_id'] = deviceId;
|
|
23845
|
+
}
|
|
23846
|
+
var userId = this._mixpanel.get_property('$user_id');
|
|
23847
|
+
if (userId) {
|
|
23848
|
+
userIdInfo['$user_id'] = userId;
|
|
23849
|
+
}
|
|
23850
|
+
return userIdInfo;
|
|
23851
|
+
};
|
|
23852
|
+
|
|
23853
|
+
SessionRecording.prototype.unloadPersistedData = function () {
|
|
23854
|
+
this.batcher.stop();
|
|
23855
|
+
|
|
23856
|
+
return this.queueStorage.init().catch(function () {
|
|
23857
|
+
this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
|
|
23858
|
+
}.bind(this)).then(function () {
|
|
23859
|
+
// if the recording is too short, just delete any stored events without flushing
|
|
23860
|
+
if (this.getDurationMs() < this._getRecordMinMs()) {
|
|
23861
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23862
|
+
}
|
|
23863
|
+
|
|
23864
|
+
return this.batcher.flush()
|
|
23865
|
+
.then(function () {
|
|
23866
|
+
return this.queueStorage.removeItem(this.batcherKey);
|
|
23867
|
+
}.bind(this));
|
|
23868
|
+
}.bind(this));
|
|
23869
|
+
};
|
|
23870
|
+
|
|
23871
|
+
SessionRecording.prototype.getConfig = function(configVar) {
|
|
23100
23872
|
return this._mixpanel.get_config(configVar);
|
|
23101
23873
|
};
|
|
23102
23874
|
|
|
@@ -23162,6 +23934,29 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23162
23934
|
|
|
23163
23935
|
var privacyConfig = getPrivacyConfig(this._mixpanel);
|
|
23164
23936
|
|
|
23937
|
+
var plugins = [];
|
|
23938
|
+
if (this.getConfig('record_network')) {
|
|
23939
|
+
var options = this.getConfig('record_network_options') || {};
|
|
23940
|
+
// don't track requests to Mixpanel /record API
|
|
23941
|
+
var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
|
|
23942
|
+
ignoreRequestUrls.push(this._getApiRoute());
|
|
23943
|
+
options.ignoreRequestUrls = ignoreRequestUrls;
|
|
23944
|
+
|
|
23945
|
+
plugins.push(getRecordNetworkPlugin(options));
|
|
23946
|
+
}
|
|
23947
|
+
|
|
23948
|
+
if (this.getConfig('record_console')) {
|
|
23949
|
+
plugins.push(
|
|
23950
|
+
getRecordConsolePlugin({
|
|
23951
|
+
stringifyOptions: {
|
|
23952
|
+
stringLengthLimit: 1000,
|
|
23953
|
+
numOfKeysLimit: 50,
|
|
23954
|
+
depthOfLimit: 2
|
|
23955
|
+
}
|
|
23956
|
+
})
|
|
23957
|
+
);
|
|
23958
|
+
}
|
|
23959
|
+
|
|
23165
23960
|
try {
|
|
23166
23961
|
this._stopRecording = this._rrwebRecord({
|
|
23167
23962
|
'emit': function (ev) {
|
|
@@ -23200,15 +23995,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
|
|
|
23200
23995
|
'sampling': {
|
|
23201
23996
|
'canvas': 15
|
|
23202
23997
|
},
|
|
23203
|
-
'plugins':
|
|
23204
|
-
getRecordConsolePlugin({
|
|
23205
|
-
stringifyOptions: {
|
|
23206
|
-
stringLengthLimit: 1000,
|
|
23207
|
-
numOfKeysLimit: 50,
|
|
23208
|
-
depthOfLimit: 2
|
|
23209
|
-
}
|
|
23210
|
-
})
|
|
23211
|
-
] : []
|
|
23998
|
+
'plugins': plugins,
|
|
23212
23999
|
});
|
|
23213
24000
|
} catch (err) {
|
|
23214
24001
|
this.reportError('Unexpected error when starting rrweb recording.', err);
|
|
@@ -23323,6 +24110,10 @@ SessionRecording.deserialize = function (serializedRecording, options) {
|
|
|
23323
24110
|
return recording;
|
|
23324
24111
|
};
|
|
23325
24112
|
|
|
24113
|
+
SessionRecording.prototype._getApiRoute = function () {
|
|
24114
|
+
return this.getConfig('api_routes')['record'];
|
|
24115
|
+
};
|
|
24116
|
+
|
|
23326
24117
|
SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
|
|
23327
24118
|
var onSuccess = function (response, responseBody) {
|
|
23328
24119
|
// Update batch specific props only if the request was successful to guarantee ordering.
|
|
@@ -23342,7 +24133,7 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
|
|
|
23342
24133
|
});
|
|
23343
24134
|
}.bind(this);
|
|
23344
24135
|
var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
|
|
23345
|
-
win['fetch'](apiHost + '/' + this.
|
|
24136
|
+
win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
|
|
23346
24137
|
'method': 'POST',
|
|
23347
24138
|
'headers': {
|
|
23348
24139
|
'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
|
|
@@ -23743,8 +24534,12 @@ MixpanelRecorder.prototype.resetRecording = function () {
|
|
|
23743
24534
|
this.startRecording({shouldStopBatcher: true});
|
|
23744
24535
|
};
|
|
23745
24536
|
|
|
24537
|
+
MixpanelRecorder.prototype.isRecording = function () {
|
|
24538
|
+
return this.activeRecording && !this.activeRecording.isRrwebStopped();
|
|
24539
|
+
};
|
|
24540
|
+
|
|
23746
24541
|
MixpanelRecorder.prototype.getActiveReplayId = function () {
|
|
23747
|
-
if (this.
|
|
24542
|
+
if (this.isRecording()) {
|
|
23748
24543
|
return this.activeRecording.replayId;
|
|
23749
24544
|
} else {
|
|
23750
24545
|
return null;
|
|
@@ -23759,7 +24554,538 @@ Object.defineProperty(MixpanelRecorder.prototype, 'replayId', {
|
|
|
23759
24554
|
}
|
|
23760
24555
|
});
|
|
23761
24556
|
|
|
23762
|
-
win[
|
|
24557
|
+
win[RECORDER_GLOBAL_NAME] = MixpanelRecorder;
|
|
24558
|
+
|
|
24559
|
+
function getDefaultExportFromCjs (x) {
|
|
24560
|
+
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
24561
|
+
}
|
|
24562
|
+
|
|
24563
|
+
var logic$1 = {exports: {}};
|
|
24564
|
+
|
|
24565
|
+
/* globals define,module */
|
|
24566
|
+
var logic = logic$1.exports;
|
|
24567
|
+
|
|
24568
|
+
var hasRequiredLogic;
|
|
24569
|
+
|
|
24570
|
+
function requireLogic () {
|
|
24571
|
+
if (hasRequiredLogic) return logic$1.exports;
|
|
24572
|
+
hasRequiredLogic = 1;
|
|
24573
|
+
(function (module, exports) {
|
|
24574
|
+
(function(root, factory) {
|
|
24575
|
+
{
|
|
24576
|
+
module.exports = factory();
|
|
24577
|
+
}
|
|
24578
|
+
}(logic, function() {
|
|
24579
|
+
/* globals console:false */
|
|
24580
|
+
|
|
24581
|
+
if ( ! Array.isArray) {
|
|
24582
|
+
Array.isArray = function(arg) {
|
|
24583
|
+
return Object.prototype.toString.call(arg) === "[object Array]";
|
|
24584
|
+
};
|
|
24585
|
+
}
|
|
24586
|
+
|
|
24587
|
+
/**
|
|
24588
|
+
* Return an array that contains no duplicates (original not modified)
|
|
24589
|
+
* @param {array} array Original reference array
|
|
24590
|
+
* @return {array} New array with no duplicates
|
|
24591
|
+
*/
|
|
24592
|
+
function arrayUnique(array) {
|
|
24593
|
+
var a = [];
|
|
24594
|
+
for (var i=0, l=array.length; i<l; i++) {
|
|
24595
|
+
if (a.indexOf(array[i]) === -1) {
|
|
24596
|
+
a.push(array[i]);
|
|
24597
|
+
}
|
|
24598
|
+
}
|
|
24599
|
+
return a;
|
|
24600
|
+
}
|
|
24601
|
+
|
|
24602
|
+
var jsonLogic = {};
|
|
24603
|
+
var operations = {
|
|
24604
|
+
"==": function(a, b) {
|
|
24605
|
+
return a == b;
|
|
24606
|
+
},
|
|
24607
|
+
"===": function(a, b) {
|
|
24608
|
+
return a === b;
|
|
24609
|
+
},
|
|
24610
|
+
"!=": function(a, b) {
|
|
24611
|
+
return a != b;
|
|
24612
|
+
},
|
|
24613
|
+
"!==": function(a, b) {
|
|
24614
|
+
return a !== b;
|
|
24615
|
+
},
|
|
24616
|
+
">": function(a, b) {
|
|
24617
|
+
return a > b;
|
|
24618
|
+
},
|
|
24619
|
+
">=": function(a, b) {
|
|
24620
|
+
return a >= b;
|
|
24621
|
+
},
|
|
24622
|
+
"<": function(a, b, c) {
|
|
24623
|
+
return (c === undefined) ? a < b : (a < b) && (b < c);
|
|
24624
|
+
},
|
|
24625
|
+
"<=": function(a, b, c) {
|
|
24626
|
+
return (c === undefined) ? a <= b : (a <= b) && (b <= c);
|
|
24627
|
+
},
|
|
24628
|
+
"!!": function(a) {
|
|
24629
|
+
return jsonLogic.truthy(a);
|
|
24630
|
+
},
|
|
24631
|
+
"!": function(a) {
|
|
24632
|
+
return !jsonLogic.truthy(a);
|
|
24633
|
+
},
|
|
24634
|
+
"%": function(a, b) {
|
|
24635
|
+
return a % b;
|
|
24636
|
+
},
|
|
24637
|
+
"log": function(a) {
|
|
24638
|
+
console.log(a); return a;
|
|
24639
|
+
},
|
|
24640
|
+
"in": function(a, b) {
|
|
24641
|
+
if (!b || typeof b.indexOf === "undefined") return false;
|
|
24642
|
+
return (b.indexOf(a) !== -1);
|
|
24643
|
+
},
|
|
24644
|
+
"cat": function() {
|
|
24645
|
+
return Array.prototype.join.call(arguments, "");
|
|
24646
|
+
},
|
|
24647
|
+
"substr": function(source, start, end) {
|
|
24648
|
+
if (end < 0) {
|
|
24649
|
+
// JavaScript doesn't support negative end, this emulates PHP behavior
|
|
24650
|
+
var temp = String(source).substr(start);
|
|
24651
|
+
return temp.substr(0, temp.length + end);
|
|
24652
|
+
}
|
|
24653
|
+
return String(source).substr(start, end);
|
|
24654
|
+
},
|
|
24655
|
+
"+": function() {
|
|
24656
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
24657
|
+
return parseFloat(a, 10) + parseFloat(b, 10);
|
|
24658
|
+
}, 0);
|
|
24659
|
+
},
|
|
24660
|
+
"*": function() {
|
|
24661
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
24662
|
+
return parseFloat(a, 10) * parseFloat(b, 10);
|
|
24663
|
+
});
|
|
24664
|
+
},
|
|
24665
|
+
"-": function(a, b) {
|
|
24666
|
+
if (b === undefined) {
|
|
24667
|
+
return -a;
|
|
24668
|
+
} else {
|
|
24669
|
+
return a - b;
|
|
24670
|
+
}
|
|
24671
|
+
},
|
|
24672
|
+
"/": function(a, b) {
|
|
24673
|
+
return a / b;
|
|
24674
|
+
},
|
|
24675
|
+
"min": function() {
|
|
24676
|
+
return Math.min.apply(this, arguments);
|
|
24677
|
+
},
|
|
24678
|
+
"max": function() {
|
|
24679
|
+
return Math.max.apply(this, arguments);
|
|
24680
|
+
},
|
|
24681
|
+
"merge": function() {
|
|
24682
|
+
return Array.prototype.reduce.call(arguments, function(a, b) {
|
|
24683
|
+
return a.concat(b);
|
|
24684
|
+
}, []);
|
|
24685
|
+
},
|
|
24686
|
+
"var": function(a, b) {
|
|
24687
|
+
var not_found = (b === undefined) ? null : b;
|
|
24688
|
+
var data = this;
|
|
24689
|
+
if (typeof a === "undefined" || a==="" || a===null) {
|
|
24690
|
+
return data;
|
|
24691
|
+
}
|
|
24692
|
+
var sub_props = String(a).split(".");
|
|
24693
|
+
for (var i = 0; i < sub_props.length; i++) {
|
|
24694
|
+
if (data === null || data === undefined) {
|
|
24695
|
+
return not_found;
|
|
24696
|
+
}
|
|
24697
|
+
// Descending into data
|
|
24698
|
+
data = data[sub_props[i]];
|
|
24699
|
+
if (data === undefined) {
|
|
24700
|
+
return not_found;
|
|
24701
|
+
}
|
|
24702
|
+
}
|
|
24703
|
+
return data;
|
|
24704
|
+
},
|
|
24705
|
+
"missing": function() {
|
|
24706
|
+
/*
|
|
24707
|
+
Missing can receive many keys as many arguments, like {"missing:[1,2]}
|
|
24708
|
+
Missing can also receive *one* argument that is an array of keys,
|
|
24709
|
+
which typically happens if it's actually acting on the output of another command
|
|
24710
|
+
(like 'if' or 'merge')
|
|
24711
|
+
*/
|
|
24712
|
+
|
|
24713
|
+
var missing = [];
|
|
24714
|
+
var keys = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
|
24715
|
+
|
|
24716
|
+
for (var i = 0; i < keys.length; i++) {
|
|
24717
|
+
var key = keys[i];
|
|
24718
|
+
var value = jsonLogic.apply({"var": key}, this);
|
|
24719
|
+
if (value === null || value === "") {
|
|
24720
|
+
missing.push(key);
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
|
|
24724
|
+
return missing;
|
|
24725
|
+
},
|
|
24726
|
+
"missing_some": function(need_count, options) {
|
|
24727
|
+
// missing_some takes two arguments, how many (minimum) items must be present, and an array of keys (just like 'missing') to check for presence.
|
|
24728
|
+
var are_missing = jsonLogic.apply({"missing": options}, this);
|
|
24729
|
+
|
|
24730
|
+
if (options.length - are_missing.length >= need_count) {
|
|
24731
|
+
return [];
|
|
24732
|
+
} else {
|
|
24733
|
+
return are_missing;
|
|
24734
|
+
}
|
|
24735
|
+
},
|
|
24736
|
+
};
|
|
24737
|
+
|
|
24738
|
+
jsonLogic.is_logic = function(logic) {
|
|
24739
|
+
return (
|
|
24740
|
+
typeof logic === "object" && // An object
|
|
24741
|
+
logic !== null && // but not null
|
|
24742
|
+
! Array.isArray(logic) && // and not an array
|
|
24743
|
+
Object.keys(logic).length === 1 // with exactly one key
|
|
24744
|
+
);
|
|
24745
|
+
};
|
|
24746
|
+
|
|
24747
|
+
/*
|
|
24748
|
+
This helper will defer to the JsonLogic spec as a tie-breaker when different language interpreters define different behavior for the truthiness of primitives. E.g., PHP considers empty arrays to be falsy, but Javascript considers them to be truthy. JsonLogic, as an ecosystem, needs one consistent answer.
|
|
24749
|
+
|
|
24750
|
+
Spec and rationale here: http://jsonlogic.com/truthy
|
|
24751
|
+
*/
|
|
24752
|
+
jsonLogic.truthy = function(value) {
|
|
24753
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
24754
|
+
return false;
|
|
24755
|
+
}
|
|
24756
|
+
return !! value;
|
|
24757
|
+
};
|
|
24758
|
+
|
|
24759
|
+
|
|
24760
|
+
jsonLogic.get_operator = function(logic) {
|
|
24761
|
+
return Object.keys(logic)[0];
|
|
24762
|
+
};
|
|
24763
|
+
|
|
24764
|
+
jsonLogic.get_values = function(logic) {
|
|
24765
|
+
return logic[jsonLogic.get_operator(logic)];
|
|
24766
|
+
};
|
|
24767
|
+
|
|
24768
|
+
jsonLogic.apply = function(logic, data) {
|
|
24769
|
+
// Does this array contain logic? Only one way to find out.
|
|
24770
|
+
if (Array.isArray(logic)) {
|
|
24771
|
+
return logic.map(function(l) {
|
|
24772
|
+
return jsonLogic.apply(l, data);
|
|
24773
|
+
});
|
|
24774
|
+
}
|
|
24775
|
+
// You've recursed to a primitive, stop!
|
|
24776
|
+
if ( ! jsonLogic.is_logic(logic) ) {
|
|
24777
|
+
return logic;
|
|
24778
|
+
}
|
|
24779
|
+
|
|
24780
|
+
var op = jsonLogic.get_operator(logic);
|
|
24781
|
+
var values = logic[op];
|
|
24782
|
+
var i;
|
|
24783
|
+
var current;
|
|
24784
|
+
var scopedLogic;
|
|
24785
|
+
var scopedData;
|
|
24786
|
+
var initial;
|
|
24787
|
+
|
|
24788
|
+
// easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
|
|
24789
|
+
if ( ! Array.isArray(values)) {
|
|
24790
|
+
values = [values];
|
|
24791
|
+
}
|
|
24792
|
+
|
|
24793
|
+
// 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed.
|
|
24794
|
+
if (op === "if" || op == "?:") {
|
|
24795
|
+
/* 'if' should be called with a odd number of parameters, 3 or greater
|
|
24796
|
+
This works on the pattern:
|
|
24797
|
+
if( 0 ){ 1 }else{ 2 };
|
|
24798
|
+
if( 0 ){ 1 }else if( 2 ){ 3 }else{ 4 };
|
|
24799
|
+
if( 0 ){ 1 }else if( 2 ){ 3 }else if( 4 ){ 5 }else{ 6 };
|
|
24800
|
+
|
|
24801
|
+
The implementation is:
|
|
24802
|
+
For pairs of values (0,1 then 2,3 then 4,5 etc)
|
|
24803
|
+
If the first evaluates truthy, evaluate and return the second
|
|
24804
|
+
If the first evaluates falsy, jump to the next pair (e.g, 0,1 to 2,3)
|
|
24805
|
+
given one parameter, evaluate and return it. (it's an Else and all the If/ElseIf were false)
|
|
24806
|
+
given 0 parameters, return NULL (not great practice, but there was no Else)
|
|
24807
|
+
*/
|
|
24808
|
+
for (i = 0; i < values.length - 1; i += 2) {
|
|
24809
|
+
if ( jsonLogic.truthy( jsonLogic.apply(values[i], data) ) ) {
|
|
24810
|
+
return jsonLogic.apply(values[i+1], data);
|
|
24811
|
+
}
|
|
24812
|
+
}
|
|
24813
|
+
if (values.length === i+1) {
|
|
24814
|
+
return jsonLogic.apply(values[i], data);
|
|
24815
|
+
}
|
|
24816
|
+
return null;
|
|
24817
|
+
} else if (op === "and") { // Return first falsy, or last
|
|
24818
|
+
for (i=0; i < values.length; i+=1) {
|
|
24819
|
+
current = jsonLogic.apply(values[i], data);
|
|
24820
|
+
if ( ! jsonLogic.truthy(current)) {
|
|
24821
|
+
return current;
|
|
24822
|
+
}
|
|
24823
|
+
}
|
|
24824
|
+
return current; // Last
|
|
24825
|
+
} else if (op === "or") {// Return first truthy, or last
|
|
24826
|
+
for (i=0; i < values.length; i+=1) {
|
|
24827
|
+
current = jsonLogic.apply(values[i], data);
|
|
24828
|
+
if ( jsonLogic.truthy(current) ) {
|
|
24829
|
+
return current;
|
|
24830
|
+
}
|
|
24831
|
+
}
|
|
24832
|
+
return current; // Last
|
|
24833
|
+
} else if (op === "filter") {
|
|
24834
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24835
|
+
scopedLogic = values[1];
|
|
24836
|
+
|
|
24837
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24838
|
+
return [];
|
|
24839
|
+
}
|
|
24840
|
+
// Return only the elements from the array in the first argument,
|
|
24841
|
+
// that return truthy when passed to the logic in the second argument.
|
|
24842
|
+
// For parity with JavaScript, reindex the returned array
|
|
24843
|
+
return scopedData.filter(function(datum) {
|
|
24844
|
+
return jsonLogic.truthy( jsonLogic.apply(scopedLogic, datum));
|
|
24845
|
+
});
|
|
24846
|
+
} else if (op === "map") {
|
|
24847
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24848
|
+
scopedLogic = values[1];
|
|
24849
|
+
|
|
24850
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24851
|
+
return [];
|
|
24852
|
+
}
|
|
24853
|
+
|
|
24854
|
+
return scopedData.map(function(datum) {
|
|
24855
|
+
return jsonLogic.apply(scopedLogic, datum);
|
|
24856
|
+
});
|
|
24857
|
+
} else if (op === "reduce") {
|
|
24858
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24859
|
+
scopedLogic = values[1];
|
|
24860
|
+
initial = typeof values[2] !== "undefined" ? jsonLogic.apply(values[2], data) : null;
|
|
24861
|
+
|
|
24862
|
+
if ( ! Array.isArray(scopedData)) {
|
|
24863
|
+
return initial;
|
|
24864
|
+
}
|
|
24865
|
+
|
|
24866
|
+
return scopedData.reduce(
|
|
24867
|
+
function(accumulator, current) {
|
|
24868
|
+
return jsonLogic.apply(
|
|
24869
|
+
scopedLogic,
|
|
24870
|
+
{current: current, accumulator: accumulator}
|
|
24871
|
+
);
|
|
24872
|
+
},
|
|
24873
|
+
initial
|
|
24874
|
+
);
|
|
24875
|
+
} else if (op === "all") {
|
|
24876
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24877
|
+
scopedLogic = values[1];
|
|
24878
|
+
// All of an empty set is false. Note, some and none have correct fallback after the for loop
|
|
24879
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24880
|
+
return false;
|
|
24881
|
+
}
|
|
24882
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24883
|
+
if ( ! jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24884
|
+
return false; // First falsy, short circuit
|
|
24885
|
+
}
|
|
24886
|
+
}
|
|
24887
|
+
return true; // All were truthy
|
|
24888
|
+
} else if (op === "none") {
|
|
24889
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24890
|
+
scopedLogic = values[1];
|
|
24891
|
+
|
|
24892
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24893
|
+
return true;
|
|
24894
|
+
}
|
|
24895
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24896
|
+
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24897
|
+
return false; // First truthy, short circuit
|
|
24898
|
+
}
|
|
24899
|
+
}
|
|
24900
|
+
return true; // None were truthy
|
|
24901
|
+
} else if (op === "some") {
|
|
24902
|
+
scopedData = jsonLogic.apply(values[0], data);
|
|
24903
|
+
scopedLogic = values[1];
|
|
24904
|
+
|
|
24905
|
+
if ( ! Array.isArray(scopedData) || ! scopedData.length) {
|
|
24906
|
+
return false;
|
|
24907
|
+
}
|
|
24908
|
+
for (i=0; i < scopedData.length; i+=1) {
|
|
24909
|
+
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) {
|
|
24910
|
+
return true; // First truthy, short circuit
|
|
24911
|
+
}
|
|
24912
|
+
}
|
|
24913
|
+
return false; // None were truthy
|
|
24914
|
+
}
|
|
24915
|
+
|
|
24916
|
+
// Everyone else gets immediate depth-first recursion
|
|
24917
|
+
values = values.map(function(val) {
|
|
24918
|
+
return jsonLogic.apply(val, data);
|
|
24919
|
+
});
|
|
24920
|
+
|
|
24921
|
+
|
|
24922
|
+
// The operation is called with "data" bound to its "this" and "values" passed as arguments.
|
|
24923
|
+
// Structured commands like % or > can name formal arguments while flexible commands (like missing or merge) can operate on the pseudo-array arguments
|
|
24924
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments
|
|
24925
|
+
if (operations.hasOwnProperty(op) && typeof operations[op] === "function") {
|
|
24926
|
+
return operations[op].apply(data, values);
|
|
24927
|
+
} else if (op.indexOf(".") > 0) { // Contains a dot, and not in the 0th position
|
|
24928
|
+
var sub_ops = String(op).split(".");
|
|
24929
|
+
var operation = operations;
|
|
24930
|
+
for (i = 0; i < sub_ops.length; i++) {
|
|
24931
|
+
if (!operation.hasOwnProperty(sub_ops[i])) {
|
|
24932
|
+
throw new Error("Unrecognized operation " + op +
|
|
24933
|
+
" (failed at " + sub_ops.slice(0, i+1).join(".") + ")");
|
|
24934
|
+
}
|
|
24935
|
+
// Descending into operations
|
|
24936
|
+
operation = operation[sub_ops[i]];
|
|
24937
|
+
}
|
|
24938
|
+
|
|
24939
|
+
return operation.apply(data, values);
|
|
24940
|
+
}
|
|
24941
|
+
|
|
24942
|
+
throw new Error("Unrecognized operation " + op );
|
|
24943
|
+
};
|
|
24944
|
+
|
|
24945
|
+
jsonLogic.uses_data = function(logic) {
|
|
24946
|
+
var collection = [];
|
|
24947
|
+
|
|
24948
|
+
if (jsonLogic.is_logic(logic)) {
|
|
24949
|
+
var op = jsonLogic.get_operator(logic);
|
|
24950
|
+
var values = logic[op];
|
|
24951
|
+
|
|
24952
|
+
if ( ! Array.isArray(values)) {
|
|
24953
|
+
values = [values];
|
|
24954
|
+
}
|
|
24955
|
+
|
|
24956
|
+
if (op === "var") {
|
|
24957
|
+
// This doesn't cover the case where the arg to var is itself a rule.
|
|
24958
|
+
collection.push(values[0]);
|
|
24959
|
+
} else {
|
|
24960
|
+
// Recursion!
|
|
24961
|
+
values.forEach(function(val) {
|
|
24962
|
+
collection.push.apply(collection, jsonLogic.uses_data(val) );
|
|
24963
|
+
});
|
|
24964
|
+
}
|
|
24965
|
+
}
|
|
24966
|
+
|
|
24967
|
+
return arrayUnique(collection);
|
|
24968
|
+
};
|
|
24969
|
+
|
|
24970
|
+
jsonLogic.add_operation = function(name, code) {
|
|
24971
|
+
operations[name] = code;
|
|
24972
|
+
};
|
|
24973
|
+
|
|
24974
|
+
jsonLogic.rm_operation = function(name) {
|
|
24975
|
+
delete operations[name];
|
|
24976
|
+
};
|
|
24977
|
+
|
|
24978
|
+
jsonLogic.rule_like = function(rule, pattern) {
|
|
24979
|
+
// console.log("Is ". JSON.stringify(rule) . " like " . JSON.stringify(pattern) . "?");
|
|
24980
|
+
if (pattern === rule) {
|
|
24981
|
+
return true;
|
|
24982
|
+
} // TODO : Deep object equivalency?
|
|
24983
|
+
if (pattern === "@") {
|
|
24984
|
+
return true;
|
|
24985
|
+
} // Wildcard!
|
|
24986
|
+
if (pattern === "number") {
|
|
24987
|
+
return (typeof rule === "number");
|
|
24988
|
+
}
|
|
24989
|
+
if (pattern === "string") {
|
|
24990
|
+
return (typeof rule === "string");
|
|
24991
|
+
}
|
|
24992
|
+
if (pattern === "array") {
|
|
24993
|
+
// !logic test might be superfluous in JavaScript
|
|
24994
|
+
return Array.isArray(rule) && ! jsonLogic.is_logic(rule);
|
|
24995
|
+
}
|
|
24996
|
+
|
|
24997
|
+
if (jsonLogic.is_logic(pattern)) {
|
|
24998
|
+
if (jsonLogic.is_logic(rule)) {
|
|
24999
|
+
var pattern_op = jsonLogic.get_operator(pattern);
|
|
25000
|
+
var rule_op = jsonLogic.get_operator(rule);
|
|
25001
|
+
|
|
25002
|
+
if (pattern_op === "@" || pattern_op === rule_op) {
|
|
25003
|
+
// echo "\nOperators match, go deeper\n";
|
|
25004
|
+
return jsonLogic.rule_like(
|
|
25005
|
+
jsonLogic.get_values(rule, false),
|
|
25006
|
+
jsonLogic.get_values(pattern, false)
|
|
25007
|
+
);
|
|
25008
|
+
}
|
|
25009
|
+
}
|
|
25010
|
+
return false; // pattern is logic, rule isn't, can't be eq
|
|
25011
|
+
}
|
|
25012
|
+
|
|
25013
|
+
if (Array.isArray(pattern)) {
|
|
25014
|
+
if (Array.isArray(rule)) {
|
|
25015
|
+
if (pattern.length !== rule.length) {
|
|
25016
|
+
return false;
|
|
25017
|
+
}
|
|
25018
|
+
/*
|
|
25019
|
+
Note, array order MATTERS, because we're using this array test logic to consider arguments, where order can matter. (e.g., + is commutative, but '-' or 'if' or 'var' are NOT)
|
|
25020
|
+
*/
|
|
25021
|
+
for (var i = 0; i < pattern.length; i += 1) {
|
|
25022
|
+
// If any fail, we fail
|
|
25023
|
+
if ( ! jsonLogic.rule_like(rule[i], pattern[i])) {
|
|
25024
|
+
return false;
|
|
25025
|
+
}
|
|
25026
|
+
}
|
|
25027
|
+
return true; // If they *all* passed, we pass
|
|
25028
|
+
} else {
|
|
25029
|
+
return false; // Pattern is array, rule isn't
|
|
25030
|
+
}
|
|
25031
|
+
}
|
|
25032
|
+
|
|
25033
|
+
// Not logic, not array, not a === match for rule.
|
|
25034
|
+
return false;
|
|
25035
|
+
};
|
|
25036
|
+
|
|
25037
|
+
return jsonLogic;
|
|
25038
|
+
}));
|
|
25039
|
+
} (logic$1));
|
|
25040
|
+
return logic$1.exports;
|
|
25041
|
+
}
|
|
25042
|
+
|
|
25043
|
+
var logicExports = requireLogic();
|
|
25044
|
+
var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
|
|
25045
|
+
|
|
25046
|
+
/**
|
|
25047
|
+
* Check if an event matches the given criteria
|
|
25048
|
+
* @param {string} eventName - The name of the event being checked
|
|
25049
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
25050
|
+
* @param {Object} criteria - Criteria to match against, with:
|
|
25051
|
+
* - event_name: string - Required event name (case-sensitive match)
|
|
25052
|
+
* - property_filters: Object - Optional JsonLogic filters for properties
|
|
25053
|
+
* @returns {Object} Result object with:
|
|
25054
|
+
* - matches: boolean - Whether the event matches the criteria
|
|
25055
|
+
* - error: string|undefined - Error message if evaluation failed
|
|
25056
|
+
*/
|
|
25057
|
+
var eventMatchesCriteria = function(eventName, properties, criteria) {
|
|
25058
|
+
// Check exact event name match (case-sensitive)
|
|
25059
|
+
if (eventName !== criteria.event_name) {
|
|
25060
|
+
return { matches: false };
|
|
25061
|
+
}
|
|
25062
|
+
|
|
25063
|
+
// Evaluate property filters using JsonLogic
|
|
25064
|
+
var propertyFilters = criteria.property_filters;
|
|
25065
|
+
var filtersMatch = true; // default to true if no filters
|
|
25066
|
+
|
|
25067
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
25068
|
+
try {
|
|
25069
|
+
// Use properties as-is for case-sensitive matching
|
|
25070
|
+
filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
|
|
25071
|
+
} catch (error) {
|
|
25072
|
+
return {
|
|
25073
|
+
matches: false,
|
|
25074
|
+
error: error.toString()
|
|
25075
|
+
};
|
|
25076
|
+
}
|
|
25077
|
+
}
|
|
25078
|
+
|
|
25079
|
+
return { matches: filtersMatch };
|
|
25080
|
+
};
|
|
25081
|
+
|
|
25082
|
+
// Create targeting library object
|
|
25083
|
+
var targetingLibrary = {};
|
|
25084
|
+
targetingLibrary['eventMatchesCriteria'] = eventMatchesCriteria;
|
|
25085
|
+
|
|
25086
|
+
// Set global Promise (use bracket notation to prevent minification)
|
|
25087
|
+
// This is the ONE AND ONLY global - matches recorder pattern
|
|
25088
|
+
win[TARGETING_GLOBAL_NAME] = Promise.resolve(targetingLibrary);
|
|
23763
25089
|
|
|
23764
25090
|
/** @const */ var DEFAULT_RAGE_CLICK_THRESHOLD_PX = 30;
|
|
23765
25091
|
/** @const */ var DEFAULT_RAGE_CLICK_TIMEOUT_MS = 1000;
|
|
@@ -23861,7 +25187,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
|
|
|
23861
25187
|
observer.observe(shadowRoot, this.observerConfig);
|
|
23862
25188
|
this.shadowObservers.push(observer);
|
|
23863
25189
|
} catch (e) {
|
|
23864
|
-
logger$
|
|
25190
|
+
logger$4.critical('Error while observing shadow root', e);
|
|
23865
25191
|
}
|
|
23866
25192
|
};
|
|
23867
25193
|
|
|
@@ -23872,7 +25198,7 @@ ShadowDOMObserver.prototype.start = function() {
|
|
|
23872
25198
|
}
|
|
23873
25199
|
|
|
23874
25200
|
if (!weakSetSupported()) {
|
|
23875
|
-
logger$
|
|
25201
|
+
logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
|
|
23876
25202
|
return;
|
|
23877
25203
|
}
|
|
23878
25204
|
|
|
@@ -23888,7 +25214,7 @@ ShadowDOMObserver.prototype.stop = function() {
|
|
|
23888
25214
|
try {
|
|
23889
25215
|
this.shadowObservers[i].disconnect();
|
|
23890
25216
|
} catch (e) {
|
|
23891
|
-
logger$
|
|
25217
|
+
logger$4.critical('Error while disconnecting shadow DOM observer', e);
|
|
23892
25218
|
}
|
|
23893
25219
|
}
|
|
23894
25220
|
this.shadowObservers = [];
|
|
@@ -24076,7 +25402,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24076
25402
|
|
|
24077
25403
|
this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
|
|
24078
25404
|
} catch (e) {
|
|
24079
|
-
logger$
|
|
25405
|
+
logger$4.critical('Error while setting up mutation observer', e);
|
|
24080
25406
|
}
|
|
24081
25407
|
}
|
|
24082
25408
|
|
|
@@ -24091,7 +25417,7 @@ DeadClickTracker.prototype.startTracking = function() {
|
|
|
24091
25417
|
);
|
|
24092
25418
|
this.shadowDOMObserver.start();
|
|
24093
25419
|
} catch (e) {
|
|
24094
|
-
logger$
|
|
25420
|
+
logger$4.critical('Error while setting up shadow DOM observer', e);
|
|
24095
25421
|
this.shadowDOMObserver = null;
|
|
24096
25422
|
}
|
|
24097
25423
|
}
|
|
@@ -24118,7 +25444,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24118
25444
|
try {
|
|
24119
25445
|
listener.target.removeEventListener(listener.event, listener.handler, listener.options);
|
|
24120
25446
|
} catch (e) {
|
|
24121
|
-
logger$
|
|
25447
|
+
logger$4.critical('Error while removing event listener', e);
|
|
24122
25448
|
}
|
|
24123
25449
|
}
|
|
24124
25450
|
this.eventListeners = [];
|
|
@@ -24127,7 +25453,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24127
25453
|
try {
|
|
24128
25454
|
this.mutationObserver.disconnect();
|
|
24129
25455
|
} catch (e) {
|
|
24130
|
-
logger$
|
|
25456
|
+
logger$4.critical('Error while disconnecting mutation observer', e);
|
|
24131
25457
|
}
|
|
24132
25458
|
this.mutationObserver = null;
|
|
24133
25459
|
}
|
|
@@ -24136,7 +25462,7 @@ DeadClickTracker.prototype.stopTracking = function() {
|
|
|
24136
25462
|
try {
|
|
24137
25463
|
this.shadowDOMObserver.stop();
|
|
24138
25464
|
} catch (e) {
|
|
24139
|
-
logger$
|
|
25465
|
+
logger$4.critical('Error while stopping shadow DOM observer', e);
|
|
24140
25466
|
}
|
|
24141
25467
|
this.shadowDOMObserver = null;
|
|
24142
25468
|
}
|
|
@@ -24214,7 +25540,7 @@ var Autocapture = function(mp) {
|
|
|
24214
25540
|
|
|
24215
25541
|
Autocapture.prototype.init = function() {
|
|
24216
25542
|
if (!minDOMApisSupported()) {
|
|
24217
|
-
logger$
|
|
25543
|
+
logger$4.critical('Autocapture unavailable: missing required DOM APIs');
|
|
24218
25544
|
return;
|
|
24219
25545
|
}
|
|
24220
25546
|
this.initPageListeners();
|
|
@@ -24246,27 +25572,15 @@ Autocapture.prototype.getConfig = function(key) {
|
|
|
24246
25572
|
};
|
|
24247
25573
|
|
|
24248
25574
|
Autocapture.prototype.currentUrlBlocked = function() {
|
|
24249
|
-
var i;
|
|
24250
25575
|
var currentUrl = _.info.currentUrl();
|
|
24251
25576
|
|
|
24252
25577
|
var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
|
|
24253
25578
|
if (allowUrlRegexes.length) {
|
|
24254
25579
|
// we're using an allowlist, only track if current URL matches
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
if (currentUrl.match(allowRegex)) {
|
|
24260
|
-
allowed = true;
|
|
24261
|
-
break;
|
|
24262
|
-
}
|
|
24263
|
-
} catch (err) {
|
|
24264
|
-
logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
|
|
24265
|
-
return true;
|
|
24266
|
-
}
|
|
24267
|
-
}
|
|
24268
|
-
if (!allowed) {
|
|
24269
|
-
// wasn't allowed by any regex
|
|
25580
|
+
try {
|
|
25581
|
+
return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
|
|
25582
|
+
} catch (err) {
|
|
25583
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
24270
25584
|
return true;
|
|
24271
25585
|
}
|
|
24272
25586
|
}
|
|
@@ -24276,17 +25590,12 @@ Autocapture.prototype.currentUrlBlocked = function() {
|
|
|
24276
25590
|
return false;
|
|
24277
25591
|
}
|
|
24278
25592
|
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
} catch (err) {
|
|
24285
|
-
logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
|
|
24286
|
-
return true;
|
|
24287
|
-
}
|
|
25593
|
+
try {
|
|
25594
|
+
return urlMatchesRegexList(currentUrl, blockUrlRegexes);
|
|
25595
|
+
} catch (err) {
|
|
25596
|
+
logger$4.critical('Error while checking block URL regexes: ', err);
|
|
25597
|
+
return true;
|
|
24288
25598
|
}
|
|
24289
|
-
return false;
|
|
24290
25599
|
};
|
|
24291
25600
|
|
|
24292
25601
|
Autocapture.prototype.pageviewTrackingConfig = function() {
|
|
@@ -24422,7 +25731,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
|
|
|
24422
25731
|
return;
|
|
24423
25732
|
}
|
|
24424
25733
|
|
|
24425
|
-
logger$
|
|
25734
|
+
logger$4.log('Initializing scroll depth tracking');
|
|
24426
25735
|
|
|
24427
25736
|
this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
|
|
24428
25737
|
|
|
@@ -24448,7 +25757,7 @@ Autocapture.prototype.initClickTracking = function() {
|
|
|
24448
25757
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
|
|
24449
25758
|
return;
|
|
24450
25759
|
}
|
|
24451
|
-
logger$
|
|
25760
|
+
logger$4.log('Initializing click tracking');
|
|
24452
25761
|
|
|
24453
25762
|
this.listenerClick = function(ev) {
|
|
24454
25763
|
if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
|
|
@@ -24467,7 +25776,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
|
|
|
24467
25776
|
return;
|
|
24468
25777
|
}
|
|
24469
25778
|
|
|
24470
|
-
logger$
|
|
25779
|
+
logger$4.log('Initializing dead click tracking');
|
|
24471
25780
|
if (!this._deadClickTracker) {
|
|
24472
25781
|
this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
|
|
24473
25782
|
this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
|
|
@@ -24501,7 +25810,7 @@ Autocapture.prototype.initInputTracking = function() {
|
|
|
24501
25810
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
24502
25811
|
return;
|
|
24503
25812
|
}
|
|
24504
|
-
logger$
|
|
25813
|
+
logger$4.log('Initializing input tracking');
|
|
24505
25814
|
|
|
24506
25815
|
this.listenerChange = function(ev) {
|
|
24507
25816
|
if (!this.getConfig(CONFIG_TRACK_INPUT)) {
|
|
@@ -24518,7 +25827,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
24518
25827
|
if (!this.pageviewTrackingConfig()) {
|
|
24519
25828
|
return;
|
|
24520
25829
|
}
|
|
24521
|
-
logger$
|
|
25830
|
+
logger$4.log('Initializing pageview tracking');
|
|
24522
25831
|
|
|
24523
25832
|
var previousTrackedUrl = '';
|
|
24524
25833
|
var tracked = false;
|
|
@@ -24553,7 +25862,7 @@ Autocapture.prototype.initPageviewTracking = function() {
|
|
|
24553
25862
|
}
|
|
24554
25863
|
if (didPathChange) {
|
|
24555
25864
|
this.lastScrollCheckpoint = 0;
|
|
24556
|
-
logger$
|
|
25865
|
+
logger$4.log('Path change: re-initializing scroll depth checkpoints');
|
|
24557
25866
|
}
|
|
24558
25867
|
}
|
|
24559
25868
|
}.bind(this));
|
|
@@ -24568,7 +25877,7 @@ Autocapture.prototype.initRageClickTracking = function() {
|
|
|
24568
25877
|
return;
|
|
24569
25878
|
}
|
|
24570
25879
|
|
|
24571
|
-
logger$
|
|
25880
|
+
logger$4.log('Initializing rage click tracking');
|
|
24572
25881
|
if (!this._rageClickTracker) {
|
|
24573
25882
|
this._rageClickTracker = new RageClickTracker();
|
|
24574
25883
|
}
|
|
@@ -24598,7 +25907,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
24598
25907
|
if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
|
|
24599
25908
|
return;
|
|
24600
25909
|
}
|
|
24601
|
-
logger$
|
|
25910
|
+
logger$4.log('Initializing scroll tracking');
|
|
24602
25911
|
this.lastScrollCheckpoint = 0;
|
|
24603
25912
|
|
|
24604
25913
|
var scrollTrackFunction = function() {
|
|
@@ -24635,7 +25944,7 @@ Autocapture.prototype.initScrollTracking = function() {
|
|
|
24635
25944
|
}
|
|
24636
25945
|
}
|
|
24637
25946
|
} catch (err) {
|
|
24638
|
-
logger$
|
|
25947
|
+
logger$4.critical('Error while calculating scroll percentage', err);
|
|
24639
25948
|
}
|
|
24640
25949
|
if (shouldTrack) {
|
|
24641
25950
|
this.mp.track(MP_EV_SCROLL, props);
|
|
@@ -24653,7 +25962,7 @@ Autocapture.prototype.initSubmitTracking = function() {
|
|
|
24653
25962
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
24654
25963
|
return;
|
|
24655
25964
|
}
|
|
24656
|
-
logger$
|
|
25965
|
+
logger$4.log('Initializing submit tracking');
|
|
24657
25966
|
|
|
24658
25967
|
this.listenerSubmit = function(ev) {
|
|
24659
25968
|
if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
|
|
@@ -24675,7 +25984,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
|
|
|
24675
25984
|
return;
|
|
24676
25985
|
}
|
|
24677
25986
|
|
|
24678
|
-
logger$
|
|
25987
|
+
logger$4.log('Initializing page visibility tracking.');
|
|
24679
25988
|
this._initScrollDepthTracking();
|
|
24680
25989
|
var previousTrackedUrl = _.info.currentUrl();
|
|
24681
25990
|
|
|
@@ -24730,14 +26039,62 @@ Autocapture.prototype.stopDeadClickTracking = function() {
|
|
|
24730
26039
|
// TODO integrate error_reporter from mixpanel instance
|
|
24731
26040
|
safewrapClass(Autocapture);
|
|
24732
26041
|
|
|
24733
|
-
|
|
26042
|
+
/**
|
|
26043
|
+
* Get the promise-based targeting loader
|
|
26044
|
+
* @param {Function} loadExtraBundle - Function to load external bundle (callback-based)
|
|
26045
|
+
* @param {string} targetingSrc - URL to targeting bundle
|
|
26046
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
26047
|
+
*/
|
|
26048
|
+
var getTargetingPromise = function(loadExtraBundle, targetingSrc) {
|
|
26049
|
+
// Return existing promise if already initialized or loading
|
|
26050
|
+
if (win[TARGETING_GLOBAL_NAME] && typeof win[TARGETING_GLOBAL_NAME].then === 'function') {
|
|
26051
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
26052
|
+
}
|
|
26053
|
+
|
|
26054
|
+
// Create loading promise and set it as the global immediately
|
|
26055
|
+
// This makes minified build behavior consistent with dev/CJS builds
|
|
26056
|
+
win[TARGETING_GLOBAL_NAME] = new Promise(function (resolve) {
|
|
26057
|
+
loadExtraBundle(targetingSrc, resolve);
|
|
26058
|
+
}).then(function () {
|
|
26059
|
+
var p = win[TARGETING_GLOBAL_NAME];
|
|
26060
|
+
if (p && typeof p.then === 'function') {
|
|
26061
|
+
return p;
|
|
26062
|
+
}
|
|
26063
|
+
throw new Error('targeting failed to load');
|
|
26064
|
+
}).catch(function (err) {
|
|
26065
|
+
delete win[TARGETING_GLOBAL_NAME];
|
|
26066
|
+
throw err;
|
|
26067
|
+
});
|
|
24734
26068
|
|
|
26069
|
+
return win[TARGETING_GLOBAL_NAME];
|
|
26070
|
+
};
|
|
26071
|
+
|
|
26072
|
+
var logger = console_with_prefix('flags');
|
|
24735
26073
|
var FLAGS_CONFIG_KEY = 'flags';
|
|
24736
26074
|
|
|
24737
26075
|
var CONFIG_CONTEXT = 'context';
|
|
24738
26076
|
var CONFIG_DEFAULTS = {};
|
|
24739
26077
|
CONFIG_DEFAULTS[CONFIG_CONTEXT] = {};
|
|
24740
26078
|
|
|
26079
|
+
/**
|
|
26080
|
+
* Generate a unique key for a pending first-time event
|
|
26081
|
+
* @param {string} flagKey - The flag key
|
|
26082
|
+
* @param {string} firstTimeEventHash - The first_time_event_hash from the pending event definition
|
|
26083
|
+
* @returns {string} Composite key in format "flagKey:firstTimeEventHash"
|
|
26084
|
+
*/
|
|
26085
|
+
var getPendingEventKey = function(flagKey, firstTimeEventHash) {
|
|
26086
|
+
return flagKey + ':' + firstTimeEventHash;
|
|
26087
|
+
};
|
|
26088
|
+
|
|
26089
|
+
/**
|
|
26090
|
+
* Extract the flag key from a pending event key
|
|
26091
|
+
* @param {string} eventKey - The composite event key in format "flagKey:firstTimeEventHash"
|
|
26092
|
+
* @returns {string} The flag key portion
|
|
26093
|
+
*/
|
|
26094
|
+
var getFlagKeyFromPendingEventKey = function(eventKey) {
|
|
26095
|
+
return eventKey.split(':')[0];
|
|
26096
|
+
};
|
|
26097
|
+
|
|
24741
26098
|
/**
|
|
24742
26099
|
* FeatureFlagManager: support for Mixpanel's feature flagging product
|
|
24743
26100
|
* @constructor
|
|
@@ -24749,6 +26106,8 @@ var FeatureFlagManager = function(initOptions) {
|
|
|
24749
26106
|
this.setMpConfig = initOptions.setConfigFunc;
|
|
24750
26107
|
this.getMpProperty = initOptions.getPropertyFunc;
|
|
24751
26108
|
this.track = initOptions.trackingFunc;
|
|
26109
|
+
this.loadExtraBundle = initOptions.loadExtraBundle || function() {};
|
|
26110
|
+
this.targetingSrc = initOptions.targetingSrc || '';
|
|
24752
26111
|
};
|
|
24753
26112
|
|
|
24754
26113
|
FeatureFlagManager.prototype.init = function() {
|
|
@@ -24761,6 +26120,8 @@ FeatureFlagManager.prototype.init = function() {
|
|
|
24761
26120
|
this.fetchFlags();
|
|
24762
26121
|
|
|
24763
26122
|
this.trackedFeatures = new Set();
|
|
26123
|
+
this.pendingFirstTimeEvents = {};
|
|
26124
|
+
this.activatedFirstTimeEvents = {};
|
|
24764
26125
|
};
|
|
24765
26126
|
|
|
24766
26127
|
FeatureFlagManager.prototype.getFullConfig = function() {
|
|
@@ -24841,17 +26202,78 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
24841
26202
|
throw new Error('No flags in API response');
|
|
24842
26203
|
}
|
|
24843
26204
|
var flags = new Map();
|
|
26205
|
+
var pendingFirstTimeEvents = {};
|
|
26206
|
+
|
|
26207
|
+
// Process flags from response
|
|
24844
26208
|
_.each(responseFlags, function(data, key) {
|
|
24845
|
-
|
|
24846
|
-
|
|
24847
|
-
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
26209
|
+
// Check if this flag has any activated first-time events this session
|
|
26210
|
+
var hasActivatedEvent = false;
|
|
26211
|
+
var prefix = key + ':';
|
|
26212
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
26213
|
+
if (eventKey.startsWith(prefix)) {
|
|
26214
|
+
hasActivatedEvent = true;
|
|
26215
|
+
}
|
|
24851
26216
|
});
|
|
24852
|
-
|
|
26217
|
+
|
|
26218
|
+
if (hasActivatedEvent) {
|
|
26219
|
+
// Preserve the activated variant, don't overwrite with server's current variant
|
|
26220
|
+
var currentFlag = this.flags && this.flags.get(key);
|
|
26221
|
+
if (currentFlag) {
|
|
26222
|
+
flags.set(key, currentFlag);
|
|
26223
|
+
}
|
|
26224
|
+
} else {
|
|
26225
|
+
// Use server's current variant
|
|
26226
|
+
flags.set(key, {
|
|
26227
|
+
'key': data['variant_key'],
|
|
26228
|
+
'value': data['variant_value'],
|
|
26229
|
+
'experiment_id': data['experiment_id'],
|
|
26230
|
+
'is_experiment_active': data['is_experiment_active'],
|
|
26231
|
+
'is_qa_tester': data['is_qa_tester']
|
|
26232
|
+
});
|
|
26233
|
+
}
|
|
26234
|
+
}, this);
|
|
26235
|
+
|
|
26236
|
+
// Process top-level pending_first_time_events array
|
|
26237
|
+
var topLevelDefinitions = responseBody['pending_first_time_events'];
|
|
26238
|
+
if (topLevelDefinitions && topLevelDefinitions.length > 0) {
|
|
26239
|
+
_.each(topLevelDefinitions, function(def) {
|
|
26240
|
+
var flagKey = def['flag_key'];
|
|
26241
|
+
var eventKey = getPendingEventKey(flagKey, def['first_time_event_hash']);
|
|
26242
|
+
|
|
26243
|
+
// Skip if this specific event has already been activated this session
|
|
26244
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
26245
|
+
return;
|
|
26246
|
+
}
|
|
26247
|
+
|
|
26248
|
+
// Store pending event definition using composite key
|
|
26249
|
+
pendingFirstTimeEvents[eventKey] = {
|
|
26250
|
+
'flag_key': flagKey,
|
|
26251
|
+
'flag_id': def['flag_id'],
|
|
26252
|
+
'project_id': def['project_id'],
|
|
26253
|
+
'first_time_event_hash': def['first_time_event_hash'],
|
|
26254
|
+
'event_name': def['event_name'],
|
|
26255
|
+
'property_filters': def['property_filters'],
|
|
26256
|
+
'pending_variant': def['pending_variant']
|
|
26257
|
+
};
|
|
26258
|
+
}, this);
|
|
26259
|
+
}
|
|
26260
|
+
|
|
26261
|
+
// Preserve any activated orphaned flags (flags that were activated but are no longer in response)
|
|
26262
|
+
if (this.activatedFirstTimeEvents) {
|
|
26263
|
+
_.each(this.activatedFirstTimeEvents, function(activated, eventKey) {
|
|
26264
|
+
var flagKey = getFlagKeyFromPendingEventKey(eventKey);
|
|
26265
|
+
if (activated && !flags.has(flagKey) && this.flags && this.flags.has(flagKey)) {
|
|
26266
|
+
// Keep the activated flag even though it's not in the new response
|
|
26267
|
+
flags.set(flagKey, this.flags.get(flagKey));
|
|
26268
|
+
}
|
|
26269
|
+
}, this);
|
|
26270
|
+
}
|
|
26271
|
+
|
|
24853
26272
|
this.flags = flags;
|
|
26273
|
+
this.pendingFirstTimeEvents = pendingFirstTimeEvents;
|
|
24854
26274
|
this._traceparent = traceparent;
|
|
26275
|
+
|
|
26276
|
+
this._loadTargetingIfNeeded();
|
|
24855
26277
|
}.bind(this)).catch(function(error) {
|
|
24856
26278
|
this.markFetchComplete();
|
|
24857
26279
|
logger.error(error);
|
|
@@ -24864,15 +26286,186 @@ FeatureFlagManager.prototype.fetchFlags = function() {
|
|
|
24864
26286
|
return this.fetchPromise;
|
|
24865
26287
|
};
|
|
24866
26288
|
|
|
24867
|
-
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
24868
|
-
if (!this._fetchInProgressStartTime) {
|
|
24869
|
-
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
24870
|
-
return;
|
|
24871
|
-
}
|
|
24872
|
-
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
24873
|
-
this._fetchCompleteTime = Date.now();
|
|
24874
|
-
this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
|
|
24875
|
-
this._fetchInProgressStartTime = null;
|
|
26289
|
+
FeatureFlagManager.prototype.markFetchComplete = function() {
|
|
26290
|
+
if (!this._fetchInProgressStartTime) {
|
|
26291
|
+
logger.error('Fetch in progress started time not set, cannot mark fetch complete');
|
|
26292
|
+
return;
|
|
26293
|
+
}
|
|
26294
|
+
this._fetchStartTime = this._fetchInProgressStartTime;
|
|
26295
|
+
this._fetchCompleteTime = Date.now();
|
|
26296
|
+
this._fetchLatency = this._fetchCompleteTime - this._fetchStartTime;
|
|
26297
|
+
this._fetchInProgressStartTime = null;
|
|
26298
|
+
};
|
|
26299
|
+
|
|
26300
|
+
/**
|
|
26301
|
+
* Proactively load targeting bundle if any pending events have property filters
|
|
26302
|
+
*/
|
|
26303
|
+
FeatureFlagManager.prototype._loadTargetingIfNeeded = function() {
|
|
26304
|
+
var hasPropertyFilters = false;
|
|
26305
|
+
_.each(this.pendingFirstTimeEvents, function(evt) {
|
|
26306
|
+
if (evt['property_filters'] && !_.isEmptyObject(evt['property_filters'])) {
|
|
26307
|
+
hasPropertyFilters = true;
|
|
26308
|
+
}
|
|
26309
|
+
});
|
|
26310
|
+
|
|
26311
|
+
if (hasPropertyFilters) {
|
|
26312
|
+
this.getTargeting().then(function() {
|
|
26313
|
+
logger.log('targeting loaded for property filter evaluation');
|
|
26314
|
+
});
|
|
26315
|
+
}
|
|
26316
|
+
};
|
|
26317
|
+
|
|
26318
|
+
/**
|
|
26319
|
+
* Get the targeting library (initializes if not already loaded)
|
|
26320
|
+
* This method is primarily for testing - production code should rely on automatic loading
|
|
26321
|
+
* @returns {Promise} Promise that resolves with targeting library
|
|
26322
|
+
*/
|
|
26323
|
+
FeatureFlagManager.prototype.getTargeting = function() {
|
|
26324
|
+
return getTargetingPromise(
|
|
26325
|
+
this.loadExtraBundle.bind(this),
|
|
26326
|
+
this.targetingSrc
|
|
26327
|
+
).catch(function(error) {
|
|
26328
|
+
logger.error('Failed to load targeting: ' + error);
|
|
26329
|
+
}.bind(this));
|
|
26330
|
+
};
|
|
26331
|
+
|
|
26332
|
+
/**
|
|
26333
|
+
* Check if a tracked event matches any pending first-time events and activate the corresponding flag variant
|
|
26334
|
+
* @param {string} eventName - The name of the event being tracked
|
|
26335
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
26336
|
+
*
|
|
26337
|
+
* When a match is found (event name matches and property filters pass), this method:
|
|
26338
|
+
* - Switches the flag to the pending variant
|
|
26339
|
+
* - Marks the event as activated for this session
|
|
26340
|
+
* - Records the activation via the API (fire-and-forget)
|
|
26341
|
+
*/
|
|
26342
|
+
FeatureFlagManager.prototype.checkFirstTimeEvents = function(eventName, properties) {
|
|
26343
|
+
if (!this.pendingFirstTimeEvents || _.isEmptyObject(this.pendingFirstTimeEvents)) {
|
|
26344
|
+
return;
|
|
26345
|
+
}
|
|
26346
|
+
|
|
26347
|
+
// Check if targeting promise exists (either bundled or async loaded)
|
|
26348
|
+
if (win[TARGETING_GLOBAL_NAME] && _.isFunction(win[TARGETING_GLOBAL_NAME].then)) {
|
|
26349
|
+
win[TARGETING_GLOBAL_NAME].then(function(library) {
|
|
26350
|
+
this._processFirstTimeEventCheck(eventName, properties, library);
|
|
26351
|
+
}.bind(this)).catch(function() {
|
|
26352
|
+
// If targeting failed to load, process with null
|
|
26353
|
+
// Events without property filters will still match
|
|
26354
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
26355
|
+
}.bind(this));
|
|
26356
|
+
} else {
|
|
26357
|
+
// No targeting available, process with null
|
|
26358
|
+
// Events without property filters will still match
|
|
26359
|
+
this._processFirstTimeEventCheck(eventName, properties, null);
|
|
26360
|
+
}
|
|
26361
|
+
};
|
|
26362
|
+
|
|
26363
|
+
/**
|
|
26364
|
+
* Internal method to process first-time event checks with loaded targeting library
|
|
26365
|
+
* @param {string} eventName - The name of the event being tracked
|
|
26366
|
+
* @param {Object} properties - Event properties to evaluate against property filters
|
|
26367
|
+
* @param {Object} targeting - The loaded targeting library
|
|
26368
|
+
*/
|
|
26369
|
+
FeatureFlagManager.prototype._processFirstTimeEventCheck = function(eventName, properties, targeting) {
|
|
26370
|
+
_.each(this.pendingFirstTimeEvents, function(pendingEvent, eventKey) {
|
|
26371
|
+
if (this.activatedFirstTimeEvents[eventKey]) {
|
|
26372
|
+
return;
|
|
26373
|
+
}
|
|
26374
|
+
|
|
26375
|
+
var flagKey = pendingEvent['flag_key'];
|
|
26376
|
+
|
|
26377
|
+
// Use targeting module to check if event matches
|
|
26378
|
+
var matchResult;
|
|
26379
|
+
|
|
26380
|
+
// If no targeting library and event has property filters, skip it
|
|
26381
|
+
if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
|
|
26382
|
+
logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
|
|
26383
|
+
return;
|
|
26384
|
+
}
|
|
26385
|
+
|
|
26386
|
+
// For simple events (no property filters), just check event name
|
|
26387
|
+
if (!targeting) {
|
|
26388
|
+
matchResult = {
|
|
26389
|
+
matches: eventName === pendingEvent['event_name'],
|
|
26390
|
+
error: null
|
|
26391
|
+
};
|
|
26392
|
+
} else {
|
|
26393
|
+
var criteria = {
|
|
26394
|
+
'event_name': pendingEvent['event_name'],
|
|
26395
|
+
'property_filters': pendingEvent['property_filters']
|
|
26396
|
+
};
|
|
26397
|
+
matchResult = targeting['eventMatchesCriteria'](
|
|
26398
|
+
eventName,
|
|
26399
|
+
properties,
|
|
26400
|
+
criteria
|
|
26401
|
+
);
|
|
26402
|
+
}
|
|
26403
|
+
|
|
26404
|
+
if (matchResult.error) {
|
|
26405
|
+
logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
|
|
26406
|
+
return;
|
|
26407
|
+
}
|
|
26408
|
+
|
|
26409
|
+
if (!matchResult.matches) {
|
|
26410
|
+
return;
|
|
26411
|
+
}
|
|
26412
|
+
|
|
26413
|
+
logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
|
|
26414
|
+
|
|
26415
|
+
var newVariant = {
|
|
26416
|
+
'key': pendingEvent['pending_variant']['variant_key'],
|
|
26417
|
+
'value': pendingEvent['pending_variant']['variant_value'],
|
|
26418
|
+
'experiment_id': pendingEvent['pending_variant']['experiment_id'],
|
|
26419
|
+
'is_experiment_active': pendingEvent['pending_variant']['is_experiment_active']
|
|
26420
|
+
};
|
|
26421
|
+
|
|
26422
|
+
this.flags.set(flagKey, newVariant);
|
|
26423
|
+
this.activatedFirstTimeEvents[eventKey] = true;
|
|
26424
|
+
|
|
26425
|
+
this.recordFirstTimeEvent(
|
|
26426
|
+
pendingEvent['flag_id'],
|
|
26427
|
+
pendingEvent['project_id'],
|
|
26428
|
+
pendingEvent['first_time_event_hash']
|
|
26429
|
+
);
|
|
26430
|
+
}, this);
|
|
26431
|
+
};
|
|
26432
|
+
|
|
26433
|
+
FeatureFlagManager.prototype.getFirstTimeEventApiRoute = function(flagId) {
|
|
26434
|
+
// Construct URL: {api_host}/flags/{flagId}/first-time-events
|
|
26435
|
+
return this.getFullApiRoute() + '/' + flagId + '/first-time-events';
|
|
26436
|
+
};
|
|
26437
|
+
|
|
26438
|
+
FeatureFlagManager.prototype.recordFirstTimeEvent = function(flagId, projectId, firstTimeEventHash) {
|
|
26439
|
+
var distinctId = this.getMpProperty('distinct_id');
|
|
26440
|
+
var traceparent = generateTraceparent();
|
|
26441
|
+
|
|
26442
|
+
// Build URL with query string parameters
|
|
26443
|
+
var searchParams = new URLSearchParams();
|
|
26444
|
+
searchParams.set('mp_lib', 'web');
|
|
26445
|
+
searchParams.set('$lib_version', Config.LIB_VERSION);
|
|
26446
|
+
var url = this.getFirstTimeEventApiRoute(flagId) + '?' + searchParams.toString();
|
|
26447
|
+
|
|
26448
|
+
var payload = {
|
|
26449
|
+
'distinct_id': distinctId,
|
|
26450
|
+
'project_id': projectId,
|
|
26451
|
+
'first_time_event_hash': firstTimeEventHash
|
|
26452
|
+
};
|
|
26453
|
+
|
|
26454
|
+
logger.log('Recording first-time event for flag: ' + flagId);
|
|
26455
|
+
|
|
26456
|
+
// Fire-and-forget POST request
|
|
26457
|
+
this.fetch.call(win, url, {
|
|
26458
|
+
'method': 'POST',
|
|
26459
|
+
'headers': {
|
|
26460
|
+
'Content-Type': 'application/json',
|
|
26461
|
+
'Authorization': 'Basic ' + btoa(this.getMpConfig('token') + ':'),
|
|
26462
|
+
'traceparent': traceparent
|
|
26463
|
+
},
|
|
26464
|
+
'body': JSON.stringify(payload)
|
|
26465
|
+
}).catch(function(error) {
|
|
26466
|
+
// Silent failure - cohort sync will catch up
|
|
26467
|
+
logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
|
|
26468
|
+
});
|
|
24876
26469
|
};
|
|
24877
26470
|
|
|
24878
26471
|
FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
|
|
@@ -24993,6 +26586,217 @@ FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.up
|
|
|
24993
26586
|
// Deprecated method
|
|
24994
26587
|
FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
|
|
24995
26588
|
|
|
26589
|
+
// Exports intended only for testing
|
|
26590
|
+
FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
|
|
26591
|
+
|
|
26592
|
+
/* eslint camelcase: "off" */
|
|
26593
|
+
|
|
26594
|
+
|
|
26595
|
+
/**
|
|
26596
|
+
* RecorderManager: manages session recording initialization, lifecycle and state
|
|
26597
|
+
* @constructor
|
|
26598
|
+
*/
|
|
26599
|
+
var RecorderManager = function(initOptions) {
|
|
26600
|
+
// TODO - Passing in mixpanel instance as it is still needed for recorder creation
|
|
26601
|
+
// but ideally we should be able to remove this dependency.
|
|
26602
|
+
this.mixpanelInstance = initOptions.mixpanelInstance;
|
|
26603
|
+
|
|
26604
|
+
this.getMpConfig = initOptions.getConfigFunc;
|
|
26605
|
+
this.getTabId = initOptions.getTabIdFunc;
|
|
26606
|
+
this.reportError = initOptions.reportErrorFunc;
|
|
26607
|
+
this.getDistinctId = initOptions.getDistinctIdFunc;
|
|
26608
|
+
this.loadExtraBundle = initOptions.loadExtraBundle;
|
|
26609
|
+
this.recorderSrc = initOptions.recorderSrc;
|
|
26610
|
+
this.targetingSrc = initOptions.targetingSrc;
|
|
26611
|
+
this.libBasePath = initOptions.libBasePath;
|
|
26612
|
+
|
|
26613
|
+
this._recorder = null;
|
|
26614
|
+
};
|
|
26615
|
+
|
|
26616
|
+
RecorderManager.prototype.shouldLoadRecorder = function() {
|
|
26617
|
+
if (this.getMpConfig('disable_persistence')) {
|
|
26618
|
+
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26619
|
+
return PromisePolyfill.resolve(false);
|
|
26620
|
+
}
|
|
26621
|
+
|
|
26622
|
+
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26623
|
+
var tab_id = this.getTabId();
|
|
26624
|
+
return recording_registry_idb.init()
|
|
26625
|
+
.then(function () {
|
|
26626
|
+
return recording_registry_idb.getAll();
|
|
26627
|
+
})
|
|
26628
|
+
.then(function (recordings) {
|
|
26629
|
+
for (var i = 0; i < recordings.length; i++) {
|
|
26630
|
+
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26631
|
+
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26632
|
+
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26633
|
+
return true;
|
|
26634
|
+
}
|
|
26635
|
+
}
|
|
26636
|
+
return false;
|
|
26637
|
+
})
|
|
26638
|
+
.catch(_.bind(function (err) {
|
|
26639
|
+
this.reportError('Error checking recording registry', err);
|
|
26640
|
+
return false;
|
|
26641
|
+
}, this));
|
|
26642
|
+
};
|
|
26643
|
+
|
|
26644
|
+
RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
|
|
26645
|
+
if (!win['MutationObserver']) {
|
|
26646
|
+
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26647
|
+
return PromisePolyfill.resolve();
|
|
26648
|
+
}
|
|
26649
|
+
|
|
26650
|
+
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26651
|
+
return new PromisePolyfill(_.bind(function(resolve) {
|
|
26652
|
+
var handleLoadedRecorder = safewrap(_.bind(function() {
|
|
26653
|
+
this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
|
|
26654
|
+
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26655
|
+
resolve();
|
|
26656
|
+
}, this));
|
|
26657
|
+
|
|
26658
|
+
if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
|
|
26659
|
+
var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
|
|
26660
|
+
this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
|
|
26661
|
+
} else {
|
|
26662
|
+
handleLoadedRecorder();
|
|
26663
|
+
}
|
|
26664
|
+
}, this));
|
|
26665
|
+
}, this);
|
|
26666
|
+
|
|
26667
|
+
/**
|
|
26668
|
+
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26669
|
+
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
26670
|
+
*/
|
|
26671
|
+
var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
|
|
26672
|
+
var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
|
|
26673
|
+
if (force_start || is_sampled) {
|
|
26674
|
+
return loadRecorder(true);
|
|
26675
|
+
} else {
|
|
26676
|
+
return this.shouldLoadRecorder()
|
|
26677
|
+
.then(_.bind(function (shouldLoad) {
|
|
26678
|
+
if (shouldLoad) {
|
|
26679
|
+
return loadRecorder(false);
|
|
26680
|
+
}
|
|
26681
|
+
return PromisePolyfill.resolve();
|
|
26682
|
+
}, this));
|
|
26683
|
+
}
|
|
26684
|
+
};
|
|
26685
|
+
|
|
26686
|
+
RecorderManager.prototype.isRecording = function() {
|
|
26687
|
+
// Safety check: ensure isRecording method exists (older CDN builds may not have it)
|
|
26688
|
+
if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
|
|
26689
|
+
return false;
|
|
26690
|
+
}
|
|
26691
|
+
try {
|
|
26692
|
+
return this._recorder['isRecording']();
|
|
26693
|
+
} catch (e) {
|
|
26694
|
+
this.reportError('Error checking if recording is active', e);
|
|
26695
|
+
return false;
|
|
26696
|
+
}
|
|
26697
|
+
};
|
|
26698
|
+
|
|
26699
|
+
RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
|
|
26700
|
+
var isRecording = this.isRecording();
|
|
26701
|
+
var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
|
|
26702
|
+
|
|
26703
|
+
if (!isRecording && recordingTriggerEvents) {
|
|
26704
|
+
var trigger = recordingTriggerEvents[event_name];
|
|
26705
|
+
if (trigger && typeof trigger['percentage'] === 'number') {
|
|
26706
|
+
var newRate = trigger['percentage'];
|
|
26707
|
+
var propertyFilters = trigger['property_filters'];
|
|
26708
|
+
if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
|
|
26709
|
+
var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
|
|
26710
|
+
getTargetingPromise(this.loadExtraBundle, targetingSrc)
|
|
26711
|
+
.then(function(targeting) {
|
|
26712
|
+
try {
|
|
26713
|
+
var result = targeting['eventMatchesCriteria'](
|
|
26714
|
+
event_name,
|
|
26715
|
+
properties,
|
|
26716
|
+
{
|
|
26717
|
+
'event_name': event_name,
|
|
26718
|
+
'property_filters': propertyFilters
|
|
26719
|
+
}
|
|
26720
|
+
);
|
|
26721
|
+
if (result['matches']) {
|
|
26722
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26723
|
+
}
|
|
26724
|
+
} catch (err) {
|
|
26725
|
+
console$1.critical('Could not parse recording event trigger properties logic:', err);
|
|
26726
|
+
}
|
|
26727
|
+
}.bind(this)).catch(function(err) {
|
|
26728
|
+
console$1.critical('Failed to load targeting library:', err);
|
|
26729
|
+
});
|
|
26730
|
+
} else {
|
|
26731
|
+
this.checkAndStartSessionRecording(false, newRate);
|
|
26732
|
+
}
|
|
26733
|
+
}
|
|
26734
|
+
}
|
|
26735
|
+
};
|
|
26736
|
+
|
|
26737
|
+
RecorderManager.prototype.stopSessionRecording = function() {
|
|
26738
|
+
if (this._recorder) {
|
|
26739
|
+
return this._recorder['stopRecording']();
|
|
26740
|
+
}
|
|
26741
|
+
return PromisePolyfill.resolve();
|
|
26742
|
+
};
|
|
26743
|
+
|
|
26744
|
+
RecorderManager.prototype.pauseSessionRecording = function() {
|
|
26745
|
+
if (this._recorder) {
|
|
26746
|
+
return this._recorder['pauseRecording']();
|
|
26747
|
+
}
|
|
26748
|
+
return PromisePolyfill.resolve();
|
|
26749
|
+
};
|
|
26750
|
+
|
|
26751
|
+
RecorderManager.prototype.resumeSessionRecording = function() {
|
|
26752
|
+
if (this._recorder) {
|
|
26753
|
+
return this._recorder['resumeRecording']();
|
|
26754
|
+
}
|
|
26755
|
+
return PromisePolyfill.resolve();
|
|
26756
|
+
};
|
|
26757
|
+
|
|
26758
|
+
RecorderManager.prototype.isRecordingHeatmapData = function() {
|
|
26759
|
+
return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
|
|
26760
|
+
};
|
|
26761
|
+
|
|
26762
|
+
RecorderManager.prototype.getSessionRecordingProperties = function() {
|
|
26763
|
+
var props = {};
|
|
26764
|
+
var replay_id = this.getSessionReplayId();
|
|
26765
|
+
if (replay_id) {
|
|
26766
|
+
props['$mp_replay_id'] = replay_id;
|
|
26767
|
+
}
|
|
26768
|
+
return props;
|
|
26769
|
+
};
|
|
26770
|
+
|
|
26771
|
+
RecorderManager.prototype.getSessionReplayUrl = function() {
|
|
26772
|
+
var replay_url = null;
|
|
26773
|
+
var replay_id = this.getSessionReplayId();
|
|
26774
|
+
if (replay_id) {
|
|
26775
|
+
var query_params = _.HTTPBuildQuery({
|
|
26776
|
+
'replay_id': replay_id,
|
|
26777
|
+
'distinct_id': this.getDistinctId(),
|
|
26778
|
+
'token': this.getMpConfig('token')
|
|
26779
|
+
});
|
|
26780
|
+
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26781
|
+
}
|
|
26782
|
+
return replay_url;
|
|
26783
|
+
};
|
|
26784
|
+
|
|
26785
|
+
RecorderManager.prototype.getSessionReplayId = function() {
|
|
26786
|
+
var replay_id = null;
|
|
26787
|
+
if (this._recorder) {
|
|
26788
|
+
replay_id = this._recorder['replayId'];
|
|
26789
|
+
}
|
|
26790
|
+
return replay_id || null;
|
|
26791
|
+
};
|
|
26792
|
+
|
|
26793
|
+
// "private" public method to reach into the recorder in test cases
|
|
26794
|
+
RecorderManager.prototype.getRecorder = function() {
|
|
26795
|
+
return this._recorder;
|
|
26796
|
+
};
|
|
26797
|
+
|
|
26798
|
+
safewrapClass(RecorderManager);
|
|
26799
|
+
|
|
24996
26800
|
/* eslint camelcase: "off" */
|
|
24997
26801
|
|
|
24998
26802
|
|
|
@@ -26456,12 +28260,17 @@ var DEFAULT_CONFIG = {
|
|
|
26456
28260
|
'record_collect_fonts': false,
|
|
26457
28261
|
'record_console': true,
|
|
26458
28262
|
'record_heatmap_data': false,
|
|
28263
|
+
'recording_event_triggers': {},
|
|
26459
28264
|
'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
|
|
26460
28265
|
'record_mask_inputs': true,
|
|
26461
28266
|
'record_max_ms': MAX_RECORDING_MS,
|
|
26462
28267
|
'record_min_ms': 0,
|
|
28268
|
+
'record_network': false,
|
|
28269
|
+
'record_network_options': {},
|
|
26463
28270
|
'record_sessions_percent': 0,
|
|
26464
|
-
'recorder_src':
|
|
28271
|
+
'recorder_src': null,
|
|
28272
|
+
'targeting_src': null,
|
|
28273
|
+
'lib_base_path': 'https://cdn.mxpnl.com/libs/',
|
|
26465
28274
|
'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
|
|
26466
28275
|
};
|
|
26467
28276
|
|
|
@@ -26615,6 +28424,19 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
26615
28424
|
'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
|
|
26616
28425
|
}));
|
|
26617
28426
|
|
|
28427
|
+
this.recorderManager = new RecorderManager({
|
|
28428
|
+
mixpanelInstance: this,
|
|
28429
|
+
getConfigFunc: _.bind(this.get_config, this),
|
|
28430
|
+
setConfigFunc: _.bind(this.set_config, this),
|
|
28431
|
+
getTabIdFunc: _.bind(this.get_tab_id, this),
|
|
28432
|
+
reportErrorFunc: _.bind(this.report_error, this),
|
|
28433
|
+
getDistinctIdFunc: _.bind(this.get_distinct_id, this),
|
|
28434
|
+
recorderSrc: this.get_config('recorder_src'),
|
|
28435
|
+
targetingSrc: this.get_config('targeting_src'),
|
|
28436
|
+
libBasePath: this.get_config('lib_base_path'),
|
|
28437
|
+
loadExtraBundle: load_extra_bundle
|
|
28438
|
+
});
|
|
28439
|
+
|
|
26618
28440
|
this['_jsc'] = NOOP_FUNC;
|
|
26619
28441
|
|
|
26620
28442
|
this.__dom_loaded_queue = [];
|
|
@@ -26691,7 +28513,9 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
26691
28513
|
getConfigFunc: _.bind(this.get_config, this),
|
|
26692
28514
|
setConfigFunc: _.bind(this.set_config, this),
|
|
26693
28515
|
getPropertyFunc: _.bind(this.get_property, this),
|
|
26694
|
-
trackingFunc: _.bind(this.track, this)
|
|
28516
|
+
trackingFunc: _.bind(this.track, this),
|
|
28517
|
+
loadExtraBundle: load_extra_bundle,
|
|
28518
|
+
targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
|
|
26695
28519
|
});
|
|
26696
28520
|
this.flags.init();
|
|
26697
28521
|
this['flags'] = this.flags;
|
|
@@ -26704,11 +28528,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
|
|
|
26704
28528
|
// Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
|
|
26705
28529
|
var mode = this.get_config('remote_settings_mode');
|
|
26706
28530
|
if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
|
|
26707
|
-
this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
26708
|
-
this._check_and_start_session_recording();
|
|
28531
|
+
this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
|
|
28532
|
+
return this._check_and_start_session_recording();
|
|
26709
28533
|
}, this));
|
|
26710
28534
|
} else {
|
|
26711
|
-
this._check_and_start_session_recording();
|
|
28535
|
+
this.__session_recording_init_promise = this._check_and_start_session_recording();
|
|
26712
28536
|
}
|
|
26713
28537
|
};
|
|
26714
28538
|
|
|
@@ -26752,132 +28576,50 @@ MixpanelLib.prototype.get_tab_id = function () {
|
|
|
26752
28576
|
return this.tab_id || null;
|
|
26753
28577
|
};
|
|
26754
28578
|
|
|
26755
|
-
MixpanelLib.prototype._should_load_recorder = function () {
|
|
26756
|
-
if (this.get_config('disable_persistence')) {
|
|
26757
|
-
console$1.log('Load recorder check skipped due to disable_persistence config');
|
|
26758
|
-
return Promise.resolve(false);
|
|
26759
|
-
}
|
|
26760
|
-
|
|
26761
|
-
var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
|
|
26762
|
-
var tab_id = this.get_tab_id();
|
|
26763
|
-
return recording_registry_idb.init()
|
|
26764
|
-
.then(function () {
|
|
26765
|
-
return recording_registry_idb.getAll();
|
|
26766
|
-
})
|
|
26767
|
-
.then(function (recordings) {
|
|
26768
|
-
for (var i = 0; i < recordings.length; i++) {
|
|
26769
|
-
// if there are expired recordings in the registry, we should load the recorder to flush them
|
|
26770
|
-
// if there's a recording for this tab id, we should load the recorder to continue the recording
|
|
26771
|
-
if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
|
|
26772
|
-
return true;
|
|
26773
|
-
}
|
|
26774
|
-
}
|
|
26775
|
-
return false;
|
|
26776
|
-
})
|
|
26777
|
-
.catch(_.bind(function (err) {
|
|
26778
|
-
this.report_error('Error checking recording registry', err);
|
|
26779
|
-
}, this));
|
|
26780
|
-
};
|
|
26781
|
-
|
|
26782
28579
|
MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
|
|
26783
|
-
|
|
26784
|
-
console$1.critical('Browser does not support MutationObserver; skipping session recording');
|
|
26785
|
-
return;
|
|
26786
|
-
}
|
|
26787
|
-
|
|
26788
|
-
var loadRecorder = _.bind(function(startNewIfInactive) {
|
|
26789
|
-
var handleLoadedRecorder = _.bind(function() {
|
|
26790
|
-
this._recorder = this._recorder || new win['__mp_recorder'](this);
|
|
26791
|
-
this._recorder['resumeRecording'](startNewIfInactive);
|
|
26792
|
-
}, this);
|
|
26793
|
-
|
|
26794
|
-
if (_.isUndefined(win['__mp_recorder'])) {
|
|
26795
|
-
load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
|
|
26796
|
-
} else {
|
|
26797
|
-
handleLoadedRecorder();
|
|
26798
|
-
}
|
|
26799
|
-
}, this);
|
|
26800
|
-
|
|
26801
|
-
/**
|
|
26802
|
-
* If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
|
|
26803
|
-
* Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
|
|
26804
|
-
*/
|
|
26805
|
-
var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
|
|
26806
|
-
if (force_start || is_sampled) {
|
|
26807
|
-
loadRecorder(true);
|
|
26808
|
-
} else {
|
|
26809
|
-
this._should_load_recorder()
|
|
26810
|
-
.then(function (shouldLoad) {
|
|
26811
|
-
if (shouldLoad) {
|
|
26812
|
-
loadRecorder(false);
|
|
26813
|
-
}
|
|
26814
|
-
});
|
|
26815
|
-
}
|
|
28580
|
+
return this.recorderManager.checkAndStartSessionRecording(force_start);
|
|
26816
28581
|
});
|
|
26817
28582
|
|
|
28583
|
+
MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
|
|
28584
|
+
return this.recorderManager.startRecordingOnEvent(event_name, properties);
|
|
28585
|
+
};
|
|
28586
|
+
|
|
26818
28587
|
MixpanelLib.prototype.start_session_recording = function () {
|
|
26819
|
-
this._check_and_start_session_recording(true);
|
|
28588
|
+
return this._check_and_start_session_recording(true);
|
|
26820
28589
|
};
|
|
26821
28590
|
|
|
26822
28591
|
MixpanelLib.prototype.stop_session_recording = function () {
|
|
26823
|
-
|
|
26824
|
-
return this._recorder['stopRecording']();
|
|
26825
|
-
}
|
|
26826
|
-
return Promise.resolve();
|
|
28592
|
+
return this.recorderManager.stopSessionRecording();
|
|
26827
28593
|
};
|
|
26828
28594
|
|
|
26829
28595
|
MixpanelLib.prototype.pause_session_recording = function () {
|
|
26830
|
-
|
|
26831
|
-
return this._recorder['pauseRecording']();
|
|
26832
|
-
}
|
|
26833
|
-
return Promise.resolve();
|
|
28596
|
+
return this.recorderManager.pauseSessionRecording();
|
|
26834
28597
|
};
|
|
26835
28598
|
|
|
26836
28599
|
MixpanelLib.prototype.resume_session_recording = function () {
|
|
26837
|
-
|
|
26838
|
-
return this._recorder['resumeRecording']();
|
|
26839
|
-
}
|
|
26840
|
-
return Promise.resolve();
|
|
28600
|
+
return this.recorderManager.resumeSessionRecording();
|
|
26841
28601
|
};
|
|
26842
28602
|
|
|
26843
28603
|
MixpanelLib.prototype.is_recording_heatmap_data = function () {
|
|
26844
|
-
return this.
|
|
28604
|
+
return this.recorderManager.isRecordingHeatmapData();
|
|
26845
28605
|
};
|
|
26846
28606
|
|
|
26847
28607
|
MixpanelLib.prototype.get_session_recording_properties = function () {
|
|
26848
|
-
|
|
26849
|
-
var replay_id = this._get_session_replay_id();
|
|
26850
|
-
if (replay_id) {
|
|
26851
|
-
props['$mp_replay_id'] = replay_id;
|
|
26852
|
-
}
|
|
26853
|
-
return props;
|
|
28608
|
+
return this.recorderManager.getSessionRecordingProperties();
|
|
26854
28609
|
};
|
|
26855
28610
|
|
|
26856
28611
|
MixpanelLib.prototype.get_session_replay_url = function () {
|
|
26857
|
-
|
|
26858
|
-
var replay_id = this._get_session_replay_id();
|
|
26859
|
-
if (replay_id) {
|
|
26860
|
-
var query_params = _.HTTPBuildQuery({
|
|
26861
|
-
'replay_id': replay_id,
|
|
26862
|
-
'distinct_id': this.get_distinct_id(),
|
|
26863
|
-
'token': this.get_config('token')
|
|
26864
|
-
});
|
|
26865
|
-
replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
|
|
26866
|
-
}
|
|
26867
|
-
return replay_url;
|
|
26868
|
-
};
|
|
26869
|
-
|
|
26870
|
-
MixpanelLib.prototype._get_session_replay_id = function () {
|
|
26871
|
-
var replay_id = null;
|
|
26872
|
-
if (this._recorder) {
|
|
26873
|
-
replay_id = this._recorder['replayId'];
|
|
26874
|
-
}
|
|
26875
|
-
return replay_id || null;
|
|
28612
|
+
return this.recorderManager.getSessionReplayUrl();
|
|
26876
28613
|
};
|
|
26877
28614
|
|
|
26878
28615
|
// "private" public method to reach into the recorder in test cases
|
|
26879
28616
|
MixpanelLib.prototype.__get_recorder = function () {
|
|
26880
|
-
return this.
|
|
28617
|
+
return this.recorderManager.getRecorder();
|
|
28618
|
+
};
|
|
28619
|
+
|
|
28620
|
+
// "private" public method to get session recording init promise in test cases
|
|
28621
|
+
MixpanelLib.prototype.__get_recording_init_promise = function () {
|
|
28622
|
+
return this.__session_recording_init_promise;
|
|
26881
28623
|
};
|
|
26882
28624
|
|
|
26883
28625
|
// Private methods
|
|
@@ -27135,6 +28877,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
|
|
|
27135
28877
|
};
|
|
27136
28878
|
|
|
27137
28879
|
MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
28880
|
+
var self = this;
|
|
27138
28881
|
var disableRecordingIfStrict = function() {
|
|
27139
28882
|
if (mode === 'strict') {
|
|
27140
28883
|
self.set_config({'record_sessions_percent': 0});
|
|
@@ -27155,7 +28898,6 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
|
|
|
27155
28898
|
};
|
|
27156
28899
|
var query_string = _.HTTPBuildQuery(request_params);
|
|
27157
28900
|
var full_url = settings_endpoint + '?' + query_string;
|
|
27158
|
-
var self = this;
|
|
27159
28901
|
|
|
27160
28902
|
var abortController = new AbortController();
|
|
27161
28903
|
var timeout_id = setTimeout(function() {
|
|
@@ -27347,6 +29089,34 @@ MixpanelLib.prototype.push = function(item) {
|
|
|
27347
29089
|
this._execute_array([item]);
|
|
27348
29090
|
};
|
|
27349
29091
|
|
|
29092
|
+
/**
|
|
29093
|
+
* Enables events on the Mixpanel object. If passed no arguments,
|
|
29094
|
+
* this function enable tracking of all events. If passed an
|
|
29095
|
+
* array of event names, those events will be enabled, but other
|
|
29096
|
+
* existing disabled events will continue to be not tracked.
|
|
29097
|
+
*
|
|
29098
|
+
* @param {Array} [events] An array of event names to enable
|
|
29099
|
+
*/
|
|
29100
|
+
MixpanelLib.prototype.enable = function(events) {
|
|
29101
|
+
var keys, new_disabled_events, i, j;
|
|
29102
|
+
|
|
29103
|
+
if (typeof(events) === 'undefined') {
|
|
29104
|
+
this._flags.disable_all_events = false;
|
|
29105
|
+
} else {
|
|
29106
|
+
keys = {};
|
|
29107
|
+
new_disabled_events = [];
|
|
29108
|
+
for (i = 0; i < events.length; i++) {
|
|
29109
|
+
keys[events[i]] = true;
|
|
29110
|
+
}
|
|
29111
|
+
for (j = 0; j < this.__disabled_events.length; j++) {
|
|
29112
|
+
if (!keys[this.__disabled_events[j]]) {
|
|
29113
|
+
new_disabled_events.push(this.__disabled_events[j]);
|
|
29114
|
+
}
|
|
29115
|
+
}
|
|
29116
|
+
this.__disabled_events = new_disabled_events;
|
|
29117
|
+
}
|
|
29118
|
+
};
|
|
29119
|
+
|
|
27350
29120
|
/**
|
|
27351
29121
|
* Disable events on the Mixpanel object. If passed no arguments,
|
|
27352
29122
|
* this function disables tracking of any event. If passed an
|
|
@@ -27520,6 +29290,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
27520
29290
|
this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
|
|
27521
29291
|
}
|
|
27522
29292
|
|
|
29293
|
+
this._start_recording_on_event(event_name, properties);
|
|
29294
|
+
|
|
27523
29295
|
var data = {
|
|
27524
29296
|
'event': event_name,
|
|
27525
29297
|
'properties': properties
|
|
@@ -27533,6 +29305,11 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
|
|
|
27533
29305
|
send_request_options: options
|
|
27534
29306
|
}, callback);
|
|
27535
29307
|
|
|
29308
|
+
// Check for first-time event matches
|
|
29309
|
+
if (this.flags && this.flags.checkFirstTimeEvents) {
|
|
29310
|
+
this.flags.checkFirstTimeEvents(event_name, properties);
|
|
29311
|
+
}
|
|
29312
|
+
|
|
27536
29313
|
return ret;
|
|
27537
29314
|
});
|
|
27538
29315
|
|
|
@@ -28723,6 +30500,7 @@ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
|
|
|
28723
30500
|
// MixpanelLib Exports
|
|
28724
30501
|
MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
|
|
28725
30502
|
MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
|
|
30503
|
+
MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
|
|
28726
30504
|
MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
|
|
28727
30505
|
MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
|
|
28728
30506
|
MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
|
|
@@ -28766,6 +30544,7 @@ MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES
|
|
|
28766
30544
|
|
|
28767
30545
|
// Exports intended only for testing
|
|
28768
30546
|
MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
|
|
30547
|
+
MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
|
|
28769
30548
|
|
|
28770
30549
|
// MixpanelPersistence Exports
|
|
28771
30550
|
MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;
|