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