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