mixpanel-browser 2.75.0 → 2.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.github/dependabot.yml +8 -0
  3. package/.github/workflows/integration-tests.yml +4 -4
  4. package/.github/workflows/unit-tests.yml +4 -4
  5. package/CHANGELOG.md +14 -0
  6. package/build.sh +10 -8
  7. package/dist/async-modules/mixpanel-recorder-DLKbUIEE.js +23669 -0
  8. package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js +2 -0
  9. package/dist/async-modules/mixpanel-recorder-wIWnMDLA.min.js.map +1 -0
  10. package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-CTcftSJC.min.js.map +1 -0
  12. package/dist/async-modules/mixpanel-targeting-CmVvUyFM.js +2520 -0
  13. package/dist/mixpanel-core.cjs.d.ts +70 -1
  14. package/dist/mixpanel-core.cjs.js +724 -426
  15. package/dist/mixpanel-recorder.js +791 -41
  16. package/dist/mixpanel-recorder.min.js +1 -1
  17. package/dist/mixpanel-recorder.min.js.map +1 -1
  18. package/dist/mixpanel-targeting.js +6 -62
  19. package/dist/mixpanel-targeting.min.js +1 -1
  20. package/dist/mixpanel-targeting.min.js.map +1 -1
  21. package/dist/mixpanel-with-async-modules.cjs.d.ts +70 -1
  22. package/dist/mixpanel-with-async-modules.cjs.js +724 -426
  23. package/dist/mixpanel-with-async-recorder.cjs.d.ts +70 -1
  24. package/dist/mixpanel-with-async-recorder.cjs.js +724 -426
  25. package/dist/mixpanel-with-recorder.d.ts +70 -1
  26. package/dist/mixpanel-with-recorder.js +1471 -450
  27. package/dist/mixpanel-with-recorder.min.d.ts +70 -1
  28. package/dist/mixpanel-with-recorder.min.js +1 -1
  29. package/dist/mixpanel.amd.d.ts +70 -1
  30. package/dist/mixpanel.amd.js +1473 -504
  31. package/dist/mixpanel.cjs.d.ts +70 -1
  32. package/dist/mixpanel.cjs.js +1473 -504
  33. package/dist/mixpanel.globals.js +724 -426
  34. package/dist/mixpanel.min.js +189 -182
  35. package/dist/mixpanel.module.d.ts +70 -1
  36. package/dist/mixpanel.module.js +1473 -504
  37. package/dist/mixpanel.umd.d.ts +70 -1
  38. package/dist/mixpanel.umd.js +1473 -504
  39. package/dist/rrweb-bundled.js +61 -9
  40. package/dist/rrweb-compiled.js +56 -9
  41. package/logo.svg +5 -0
  42. package/package.json +6 -4
  43. package/rollup.config.mjs +163 -46
  44. package/src/autocapture/index.js +10 -27
  45. package/src/config.js +9 -3
  46. package/src/flags/index.js +1 -2
  47. package/src/index.d.ts +70 -1
  48. package/src/mixpanel-core.js +77 -112
  49. package/src/recorder/index.js +1 -1
  50. package/src/recorder/recorder.js +5 -1
  51. package/src/recorder/rrweb-network-plugin.js +649 -0
  52. package/src/recorder/session-recording.js +36 -12
  53. package/src/recorder/utils.js +27 -1
  54. package/src/recorder-manager.js +324 -0
  55. package/src/request-batcher.js +1 -1
  56. package/src/targeting/event-matcher.js +2 -57
  57. package/src/targeting/index.js +1 -1
  58. package/src/targeting/loader.js +1 -1
  59. package/src/utils.js +13 -1
  60. package/testServer.js +69 -1
  61. package/src/globals.js +0 -14
  62. /package/src/loaders/{loader-module-with-async-recorder.d.ts → loader-module-with-async-modules.d.ts} +0 -0
@@ -25,16 +25,19 @@ define((function () { 'use strict';
25
25
  win = window;
26
26
  }
27
27
 
28
- /**
29
- * Shared global window property names used across modules
30
- */
28
+ var Config = {
29
+ DEBUG: false,
30
+ LIB_VERSION: '2.77.0'
31
+ };
31
32
 
32
- // Targeting library global (used by flags and targeting modules)
33
+ // Window global names for async modules
33
34
  var TARGETING_GLOBAL_NAME = '__mp_targeting';
34
-
35
- // Recorder library global (used by recorder and mixpanel-core)
36
35
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
37
36
 
37
+ // Constants that are injected at build-time for the names of async modules.
38
+ var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
39
+ var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
40
+
38
41
  function _array_like_to_array(arr, len) {
39
42
  if (len == null || len > arr.length) len = arr.length;
40
43
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -10732,13 +10735,7 @@ define((function () { 'use strict';
10732
10735
  };
10733
10736
  while(_this.mapRemoves.length){
10734
10737
  var removedNode = _this.mapRemoves.shift();
10735
- if (removedNode.nodeName === "IFRAME") {
10736
- try {
10737
- _this.iframeManager.removeIframe(removedNode);
10738
- } catch (e2) {}
10739
- } else {
10740
- _this.stylesheetManager.cleanupStylesheetsForRemovedNode(removedNode);
10741
- }
10738
+ _this.cleanupRemovedNode(removedNode);
10742
10739
  _this.mirror.removeNodeFromMap(removedNode);
10743
10740
  }
10744
10741
  for(var _iterator = _create_for_of_iterator_helper_loose(_this.movedSet), _step; !(_step = _iterator()).done;){
@@ -11058,6 +11055,20 @@ define((function () { 'use strict';
11058
11055
  }
11059
11056
  }
11060
11057
  });
11058
+ __publicField$1(this, "cleanupRemovedNode", function(node2) {
11059
+ if (node2.nodeName === "IFRAME") {
11060
+ try {
11061
+ _this.iframeManager.removeIframe(node2);
11062
+ } catch (e2) {}
11063
+ } else {
11064
+ try {
11065
+ _this.stylesheetManager.cleanupStylesheetsForRemovedNode(node2);
11066
+ } catch (e2) {}
11067
+ }
11068
+ node2.childNodes.forEach(function(child) {
11069
+ _this.cleanupRemovedNode(child);
11070
+ });
11071
+ });
11061
11072
  }
11062
11073
  var _proto = MutationBuffer.prototype;
11063
11074
  _proto.init = function init(options) {
@@ -13285,6 +13296,31 @@ define((function () { 'use strict';
13285
13296
  _proto.destroy = function destroy() {};
13286
13297
  return ProcessedNodeManager;
13287
13298
  }();
13299
+ function toOrigin(url) {
13300
+ try {
13301
+ var origin = new URL(url).origin;
13302
+ return origin !== "null" ? origin : null;
13303
+ } catch (e) {
13304
+ return null;
13305
+ }
13306
+ }
13307
+ function buildAllowedOriginSet(origins) {
13308
+ if (!Array.isArray(origins) || origins.length === 0) {
13309
+ throw new Error("[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.");
13310
+ }
13311
+ var set = /* @__PURE__ */ new Set();
13312
+ for(var i2 = 0; i2 < origins.length; i2++){
13313
+ var entry = origins[i2];
13314
+ if (typeof entry !== "string") {
13315
+ throw new Error("[rrweb] allowedIframeOrigins[" + i2 + "] must be a string, got " + (typeof entry === "undefined" ? "undefined" : _type_of(entry)) + ".");
13316
+ }
13317
+ var origin = toOrigin(entry);
13318
+ if (origin) {
13319
+ set.add(origin);
13320
+ }
13321
+ }
13322
+ return Object.freeze(set);
13323
+ }
13288
13324
  var wrappedEmit;
13289
13325
  var takeFullSnapshot$1;
13290
13326
  var canvasManager;
@@ -13306,10 +13342,17 @@ define((function () { 'use strict';
13306
13342
  var mirror = createMirror$2();
13307
13343
  function record(options) {
13308
13344
  if (options === void 0) options = {};
13309
- var emit = options.emit, checkoutEveryNms = options.checkoutEveryNms, checkoutEveryNth = options.checkoutEveryNth, _options_blockClass = options.blockClass, blockClass = _options_blockClass === void 0 ? "rr-block" : _options_blockClass, _options_blockSelector = options.blockSelector, blockSelector = _options_blockSelector === void 0 ? null : _options_blockSelector, _options_ignoreClass = options.ignoreClass, ignoreClass = _options_ignoreClass === void 0 ? "rr-ignore" : _options_ignoreClass, _options_ignoreSelector = options.ignoreSelector, ignoreSelector = _options_ignoreSelector === void 0 ? null : _options_ignoreSelector, _options_maskTextClass = options.maskTextClass, maskTextClass = _options_maskTextClass === void 0 ? "rr-mask" : _options_maskTextClass, _options_maskTextSelector = options.maskTextSelector, maskTextSelector = _options_maskTextSelector === void 0 ? null : _options_maskTextSelector, _options_inlineStylesheet = options.inlineStylesheet, inlineStylesheet = _options_inlineStylesheet === void 0 ? true : _options_inlineStylesheet, maskAllInputs = options.maskAllInputs, _maskInputOptions = options.maskInputOptions, _slimDOMOptions = options.slimDOMOptions, maskInputFn = options.maskInputFn, maskTextFn = options.maskTextFn, hooks = options.hooks, packFn = options.packFn, _options_sampling = options.sampling, sampling = _options_sampling === void 0 ? {} : _options_sampling, _options_dataURLOptions = options.dataURLOptions, dataURLOptions = _options_dataURLOptions === void 0 ? {} : _options_dataURLOptions, mousemoveWait = options.mousemoveWait, _options_recordDOM = options.recordDOM, recordDOM = _options_recordDOM === void 0 ? true : _options_recordDOM, _options_recordCanvas = options.recordCanvas, recordCanvas = _options_recordCanvas === void 0 ? false : _options_recordCanvas, _options_recordCrossOriginIframes = options.recordCrossOriginIframes, recordCrossOriginIframes = _options_recordCrossOriginIframes === void 0 ? false : _options_recordCrossOriginIframes, _options_recordAfter = options.recordAfter, recordAfter = _options_recordAfter === void 0 ? options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load" : _options_recordAfter, _options_userTriggeredOnInput = options.userTriggeredOnInput, userTriggeredOnInput = _options_userTriggeredOnInput === void 0 ? false : _options_userTriggeredOnInput, _options_collectFonts = options.collectFonts, collectFonts = _options_collectFonts === void 0 ? false : _options_collectFonts, _options_inlineImages = options.inlineImages, inlineImages = _options_inlineImages === void 0 ? false : _options_inlineImages, plugins = options.plugins, _options_keepIframeSrcFn = options.keepIframeSrcFn, keepIframeSrcFn = _options_keepIframeSrcFn === void 0 ? function() {
13345
+ var emit = options.emit, checkoutEveryNms = options.checkoutEveryNms, checkoutEveryNth = options.checkoutEveryNth, _options_blockClass = options.blockClass, blockClass = _options_blockClass === void 0 ? "rr-block" : _options_blockClass, _options_blockSelector = options.blockSelector, blockSelector = _options_blockSelector === void 0 ? null : _options_blockSelector, _options_ignoreClass = options.ignoreClass, ignoreClass = _options_ignoreClass === void 0 ? "rr-ignore" : _options_ignoreClass, _options_ignoreSelector = options.ignoreSelector, ignoreSelector = _options_ignoreSelector === void 0 ? null : _options_ignoreSelector, _options_maskTextClass = options.maskTextClass, maskTextClass = _options_maskTextClass === void 0 ? "rr-mask" : _options_maskTextClass, _options_maskTextSelector = options.maskTextSelector, maskTextSelector = _options_maskTextSelector === void 0 ? null : _options_maskTextSelector, _options_inlineStylesheet = options.inlineStylesheet, inlineStylesheet = _options_inlineStylesheet === void 0 ? true : _options_inlineStylesheet, maskAllInputs = options.maskAllInputs, _maskInputOptions = options.maskInputOptions, _slimDOMOptions = options.slimDOMOptions, maskInputFn = options.maskInputFn, maskTextFn = options.maskTextFn, hooks = options.hooks, packFn = options.packFn, _options_sampling = options.sampling, sampling = _options_sampling === void 0 ? {} : _options_sampling, _options_dataURLOptions = options.dataURLOptions, dataURLOptions = _options_dataURLOptions === void 0 ? {} : _options_dataURLOptions, mousemoveWait = options.mousemoveWait, _options_recordDOM = options.recordDOM, recordDOM = _options_recordDOM === void 0 ? true : _options_recordDOM, _options_recordCanvas = options.recordCanvas, recordCanvas = _options_recordCanvas === void 0 ? false : _options_recordCanvas, _options_recordCrossOriginIframes = options.recordCrossOriginIframes, recordCrossOriginIframes = _options_recordCrossOriginIframes === void 0 ? false : _options_recordCrossOriginIframes, allowedIframeOrigins = options.allowedIframeOrigins, _options_recordAfter = options.recordAfter, recordAfter = _options_recordAfter === void 0 ? options.recordAfter === "DOMContentLoaded" ? options.recordAfter : "load" : _options_recordAfter, _options_userTriggeredOnInput = options.userTriggeredOnInput, userTriggeredOnInput = _options_userTriggeredOnInput === void 0 ? false : _options_userTriggeredOnInput, _options_collectFonts = options.collectFonts, collectFonts = _options_collectFonts === void 0 ? false : _options_collectFonts, _options_inlineImages = options.inlineImages, inlineImages = _options_inlineImages === void 0 ? false : _options_inlineImages, plugins = options.plugins, _options_keepIframeSrcFn = options.keepIframeSrcFn, keepIframeSrcFn = _options_keepIframeSrcFn === void 0 ? function() {
13310
13346
  return false;
13311
13347
  } : _options_keepIframeSrcFn, _options_ignoreCSSAttributes = options.ignoreCSSAttributes, ignoreCSSAttributes = _options_ignoreCSSAttributes === void 0 ? /* @__PURE__ */ new Set([]) : _options_ignoreCSSAttributes, errorHandler2 = options.errorHandler;
13312
13348
  registerErrorHandler(errorHandler2);
13349
+ var validatedOrigins;
13350
+ if (recordCrossOriginIframes && allowedIframeOrigins && allowedIframeOrigins.length > 0) {
13351
+ validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
13352
+ if (validatedOrigins.size === 0) {
13353
+ validatedOrigins = void 0;
13354
+ }
13355
+ }
13313
13356
  var inEmittingFrame = recordCrossOriginIframes ? window.parent === window : true;
13314
13357
  var passEmitsToParent = false;
13315
13358
  if (!inEmittingFrame) {
@@ -13401,7 +13444,14 @@ define((function () { 'use strict';
13401
13444
  origin: window.location.origin,
13402
13445
  isCheckout: isCheckout
13403
13446
  };
13404
- window.parent.postMessage(message, "*");
13447
+ if (validatedOrigins) {
13448
+ for(var _iterator = _create_for_of_iterator_helper_loose(validatedOrigins), _step; !(_step = _iterator()).done;){
13449
+ var targetOrigin = _step.value;
13450
+ window.parent.postMessage(message, targetOrigin);
13451
+ }
13452
+ } else {
13453
+ window.parent.postMessage(message, "*");
13454
+ }
13405
13455
  }
13406
13456
  if (e2.type === EventType.FullSnapshot) {
13407
13457
  lastFullSnapshotEvent = e2;
@@ -18134,7 +18184,7 @@ define((function () { 'use strict';
18134
18184
  var __publicField = function(obj, key, value) {
18135
18185
  return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
18136
18186
  };
18137
- function patch(source, name, replacement) {
18187
+ function patch$3(source, name, replacement) {
18138
18188
  try {
18139
18189
  if (!(name in source)) {
18140
18190
  return function() {};
@@ -18551,7 +18601,7 @@ define((function () { 'use strict';
18551
18601
  if (!_logger[level]) {
18552
18602
  return function() {};
18553
18603
  }
18554
- return patch(_logger, level, function(original) {
18604
+ return patch$3(_logger, level, function(original) {
18555
18605
  var _this1 = _this;
18556
18606
  return function() {
18557
18607
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
@@ -18972,11 +19022,6 @@ define((function () { 'use strict';
18972
19022
  PromisePolyfill = NpoPromise;
18973
19023
  }
18974
19024
 
18975
- var Config = {
18976
- DEBUG: false,
18977
- LIB_VERSION: '2.75.0'
18978
- };
18979
-
18980
19025
  /* eslint camelcase: "off", eqeqeq: "off" */
18981
19026
 
18982
19027
  // Maximum allowed session recording length
@@ -20708,6 +20753,17 @@ define((function () { 'use strict';
20708
20753
 
20709
20754
  var NOOP_FUNC = function () {};
20710
20755
 
20756
+ var urlMatchesRegexList = function (url, regexList) {
20757
+ var matches = false;
20758
+ for (var i = 0; i < regexList.length; i++) {
20759
+ if (url.match(regexList[i])) {
20760
+ matches = true;
20761
+ break;
20762
+ }
20763
+ }
20764
+ return matches;
20765
+ };
20766
+
20711
20767
  var JSONStringify = null, JSONParse = null;
20712
20768
  if (typeof JSON !== 'undefined') {
20713
20769
  JSONStringify = JSON.stringify;
@@ -21179,7 +21235,7 @@ define((function () { 'use strict';
21179
21235
  };
21180
21236
  }
21181
21237
 
21182
- var logger$6 = console_with_prefix('lock');
21238
+ var logger$8 = console_with_prefix('lock');
21183
21239
 
21184
21240
  /**
21185
21241
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21231,7 +21287,7 @@ define((function () { 'use strict';
21231
21287
 
21232
21288
  var delay = function(cb) {
21233
21289
  if (new Date().getTime() - startTime > timeoutMS) {
21234
- logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21290
+ logger$8.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21235
21291
  storage.removeItem(keyZ);
21236
21292
  storage.removeItem(keyY);
21237
21293
  loop();
@@ -21378,7 +21434,7 @@ define((function () { 'use strict';
21378
21434
  }, this));
21379
21435
  };
21380
21436
 
21381
- var logger$5 = console_with_prefix('batch');
21437
+ var logger$7 = console_with_prefix('batch');
21382
21438
 
21383
21439
  /**
21384
21440
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21407,7 +21463,7 @@ define((function () { 'use strict';
21407
21463
  timeoutMS: options.sharedLockTimeoutMS,
21408
21464
  });
21409
21465
  }
21410
- this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
21466
+ this.reportError = options.errorReporter || _.bind(logger$7.error, logger$7);
21411
21467
 
21412
21468
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21413
21469
 
@@ -21740,7 +21796,7 @@ define((function () { 'use strict';
21740
21796
  // maximum interval between request retries after exponential backoff
21741
21797
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21742
21798
 
21743
- var logger$4 = console_with_prefix('batch');
21799
+ var logger$6 = console_with_prefix('batch');
21744
21800
 
21745
21801
  /**
21746
21802
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21868,7 +21924,7 @@ define((function () { 'use strict';
21868
21924
  */
21869
21925
  RequestBatcher.prototype.flush = function(options) {
21870
21926
  if (this.requestInProgress) {
21871
- logger$4.log('Flush: Request already in progress');
21927
+ logger$6.log('Flush: Request already in progress');
21872
21928
  return PromisePolyfill.resolve();
21873
21929
  }
21874
21930
 
@@ -22045,7 +22101,7 @@ define((function () { 'use strict';
22045
22101
  if (options.unloading) {
22046
22102
  requestOptions.transport = 'sendBeacon';
22047
22103
  }
22048
- logger$4.log('MIXPANEL REQUEST:', dataForRequest);
22104
+ logger$6.log('MIXPANEL REQUEST:', dataForRequest);
22049
22105
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22050
22106
  }, this))
22051
22107
  .catch(_.bind(function(err) {
@@ -22058,7 +22114,7 @@ define((function () { 'use strict';
22058
22114
  * Log error to global logger and optional user-defined logger.
22059
22115
  */
22060
22116
  RequestBatcher.prototype.reportError = function(msg, err) {
22061
- logger$4.error.apply(logger$4.error, arguments);
22117
+ logger$6.error.apply(logger$6.error, arguments);
22062
22118
  if (this.errorReporter) {
22063
22119
  try {
22064
22120
  if (!(err instanceof Error)) {
@@ -22066,7 +22122,7 @@ define((function () { 'use strict';
22066
22122
  }
22067
22123
  this.errorReporter(msg, err);
22068
22124
  } catch(err) {
22069
- logger$4.error(err);
22125
+ logger$6.error(err);
22070
22126
  }
22071
22127
  }
22072
22128
  };
@@ -22083,6 +22139,29 @@ define((function () { 'use strict';
22083
22139
 
22084
22140
  var RECORD_ENQUEUE_THROTTLE_MS = 250;
22085
22141
 
22142
+ var validateAllowedOrigins = function(origins, logger) {
22143
+ if (!_.isArray(origins)) {
22144
+ if (origins) {
22145
+ logger.critical('record_allowed_iframe_origins must be an array of origin strings, cross-origin recording will be disabled.');
22146
+ }
22147
+ return [];
22148
+ }
22149
+ var valid = [];
22150
+ for (var i = 0; i < origins.length; i++) {
22151
+ try {
22152
+ var origin = new URL(origins[i]).origin;
22153
+ if (origin === 'null') {
22154
+ logger.critical(origins[i] + ' has an opaque origin. Skipping this entry.');
22155
+ continue;
22156
+ }
22157
+ valid.push(origin);
22158
+ } catch (e) {
22159
+ logger.critical(origins[i] + ' is not a valid origin URL. Skipping this entry.');
22160
+ }
22161
+ }
22162
+ return valid;
22163
+ };
22164
+
22086
22165
  // stateless utils
22087
22166
  // mostly from https://github.com/mixpanel/mixpanel-js/blob/989ada50f518edab47b9c4fd9535f9fbd5ec5fc0/src/autotrack-utils.js
22088
22167
 
@@ -22188,7 +22267,7 @@ define((function () { 'use strict';
22188
22267
 
22189
22268
  var MAX_DEPTH = 5;
22190
22269
 
22191
- var logger$3 = console_with_prefix('autocapture');
22270
+ var logger$5 = console_with_prefix('autocapture');
22192
22271
 
22193
22272
 
22194
22273
  function getClasses(el) {
@@ -22452,7 +22531,7 @@ define((function () { 'use strict';
22452
22531
  return false;
22453
22532
  }
22454
22533
  } catch (err) {
22455
- logger$3.critical('Error while checking element in allowElementCallback', err);
22534
+ logger$5.critical('Error while checking element in allowElementCallback', err);
22456
22535
  return false;
22457
22536
  }
22458
22537
  }
@@ -22469,7 +22548,7 @@ define((function () { 'use strict';
22469
22548
  return true;
22470
22549
  }
22471
22550
  } catch (err) {
22472
- logger$3.critical('Error while checking selector: ' + sel, err);
22551
+ logger$5.critical('Error while checking selector: ' + sel, err);
22473
22552
  }
22474
22553
  }
22475
22554
  return false;
@@ -22484,7 +22563,7 @@ define((function () { 'use strict';
22484
22563
  return true;
22485
22564
  }
22486
22565
  } catch (err) {
22487
- logger$3.critical('Error while checking element in blockElementCallback', err);
22566
+ logger$5.critical('Error while checking element in blockElementCallback', err);
22488
22567
  return true;
22489
22568
  }
22490
22569
  }
@@ -22498,7 +22577,7 @@ define((function () { 'use strict';
22498
22577
  return true;
22499
22578
  }
22500
22579
  } catch (err) {
22501
- logger$3.critical('Error while checking selector: ' + sel, err);
22580
+ logger$5.critical('Error while checking selector: ' + sel, err);
22502
22581
  }
22503
22582
  }
22504
22583
  }
@@ -23046,177 +23125,826 @@ define((function () { 'use strict';
23046
23125
  }
23047
23126
 
23048
23127
  /**
23049
- * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
23128
+ * This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
23129
+ * the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
23130
+ *
23131
+ * This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
23132
+ * header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
23133
+ *
23050
23134
  */
23051
23135
 
23136
+ var logger$4 = console_with_prefix('network-plugin');
23052
23137
 
23053
- var logger$2 = console_with_prefix('recorder');
23054
- var CompressionStream = win['CompressionStream'];
23055
-
23056
- var RECORDER_BATCHER_LIB_CONFIG = {
23057
- 'batch_size': 1000,
23058
- 'batch_flush_interval_ms': 10 * 1000,
23059
- 'batch_request_timeout_ms': 90 * 1000,
23060
- 'batch_autostart': true
23061
- };
23138
+ /**
23139
+ * Get the time origin for converting performance timestamps to absolute timestamps.
23140
+ * Uses Date.now() - performance.now() instead of performance.timeOrigin because
23141
+ * browsers can report timeOrigin values that are skewed from actual time, and some
23142
+ * browsers (notably older Safari versions) don't implement timeOrigin at all.
23143
+ * See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
23144
+ * @param {Window} win
23145
+ * @returns {number}
23146
+ */
23147
+ function getTimeOrigin(win) {
23148
+ return Math.round(Date.now() - win.performance.now());
23149
+ }
23062
23150
 
23063
- var ACTIVE_SOURCES = new Set([
23064
- IncrementalSource.MouseMove,
23065
- IncrementalSource.MouseInteraction,
23066
- IncrementalSource.Scroll,
23067
- IncrementalSource.ViewportResize,
23068
- IncrementalSource.Input,
23069
- IncrementalSource.TouchMove,
23070
- IncrementalSource.MediaInteraction,
23071
- IncrementalSource.Drag,
23072
- IncrementalSource.Selection,
23073
- ]);
23151
+ /**
23152
+ * @typedef {import('../index.d.ts').InitiatorType} InitiatorType
23153
+ * @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
23154
+ * @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
23155
+ * @typedef {import('../index.d.ts').NetworkData} NetworkData
23156
+ */
23074
23157
 
23075
- function isUserEvent(ev) {
23076
- return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
23077
- }
23158
+ /**
23159
+ * @typedef {Record<string, string>} Headers
23160
+ */
23078
23161
 
23079
23162
  /**
23080
- * @typedef {Object} SerializedRecording
23081
- * @property {number} idleExpires
23082
- * @property {number} maxExpires
23083
- * @property {number} replayStartTime
23084
- * @property {number} lastEventTimestamp
23085
- * @property {number} seqNo
23086
- * @property {string} batchStartUrl
23087
- * @property {string} replayId
23088
- * @property {string} tabId
23089
- * @property {string} replayStartUrl
23163
+ * @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
23090
23164
  */
23091
23165
 
23092
23166
  /**
23093
- * @typedef {Object} SessionRecordingOptions
23094
- * @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
23095
- * @property {String} [options.replayId] - unique uuid for a single replay
23096
- * @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
23097
- * @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
23098
- * @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
23099
- * @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
23100
- * @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
23101
- * optional properties for deserialization:
23102
- * @property {number} idleExpires
23103
- * @property {number} maxExpires
23104
- * @property {number} replayStartTime
23105
- * @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
23106
- * @property {number} seqNo
23107
- * @property {string} batchStartUrl
23108
- * @property {string} replayStartUrl
23167
+ * @callback networkCallback
23168
+ * @param {NetworkData} data
23169
+ * @returns {void}
23109
23170
  */
23110
23171
 
23111
23172
  /**
23112
- * @typedef {Object} UserIdInfo
23113
- * @property {string} distinct_id
23114
- * @property {string} user_id
23115
- * @property {string} device_id
23173
+ * @callback listenerHandler
23174
+ * @returns {void}
23116
23175
  */
23117
23176
 
23177
+ /**
23178
+ * @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
23179
+ */
23118
23180
 
23119
23181
  /**
23120
- * This class encapsulates a single session recording and its lifecycle.
23121
- * @param {SessionRecordingOptions} options
23182
+ * @typedef {Object} RecordPlugin
23183
+ * @property {string} name
23184
+ * @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
23185
+ * @property {NetworkRecordOptions} [options]
23122
23186
  */
23123
- var SessionRecording = function(options) {
23124
- this._mixpanel = options.mixpanelInstance;
23125
- this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
23126
- this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
23127
- this._onBatchSent = options.onBatchSent || NOOP_FUNC;
23128
- this._rrwebRecord = options.rrwebRecord || null;
23129
23187
 
23130
- // internal rrweb stopRecording function
23131
- this._stopRecording = null;
23132
- this.replayId = options.replayId;
23188
+ /** @type {Required<NetworkRecordOptions>} */
23189
+ var defaultNetworkOptions = {
23190
+ initiatorTypes: [
23191
+ 'audio',
23192
+ 'beacon',
23193
+ 'body',
23194
+ 'css',
23195
+ 'early-hint',
23196
+ 'embed',
23197
+ 'fetch',
23198
+ 'frame',
23199
+ 'iframe',
23200
+ 'icon',
23201
+ 'image',
23202
+ 'img',
23203
+ 'input',
23204
+ 'link',
23205
+ 'navigation',
23206
+ 'object',
23207
+ 'ping',
23208
+ 'script',
23209
+ 'track',
23210
+ 'video',
23211
+ 'xmlhttprequest',
23212
+ ],
23213
+ ignoreRequestFn: function() { return false; },
23214
+ recordHeaders: {
23215
+ request: [],
23216
+ response: [],
23217
+ },
23218
+ recordBodyUrls: {
23219
+ request: [],
23220
+ response: [],
23221
+ },
23222
+ recordInitialRequests: false,
23223
+ };
23133
23224
 
23134
- this.batchStartUrl = options.batchStartUrl || null;
23135
- this.replayStartUrl = options.replayStartUrl || null;
23136
- this.idleExpires = options.idleExpires || null;
23137
- this.maxExpires = options.maxExpires || null;
23138
- this.replayStartTime = options.replayStartTime || null;
23139
- this.lastEventTimestamp = options.lastEventTimestamp || null;
23140
- this.seqNo = options.seqNo || 0;
23225
+ /**
23226
+ * @param {PerformanceEntry} entry
23227
+ * @returns {entry is PerformanceNavigationTiming}
23228
+ */
23229
+ function isNavigationTiming(entry) {
23230
+ return entry.entryType === 'navigation';
23231
+ }
23141
23232
 
23142
- this.idleTimeoutId = null;
23143
- this.maxTimeoutId = null;
23233
+ /**
23234
+ * @param {PerformanceEntry} entry
23235
+ * @returns {entry is PerformanceResourceTiming}
23236
+ */
23237
+ function isResourceTiming (entry) {
23238
+ return entry.entryType === 'resource';
23239
+ }
23144
23240
 
23145
- this.recordMaxMs = MAX_RECORDING_MS;
23146
- this.recordMinMs = 0;
23241
+ function findLast(array, predicate) {
23242
+ var length = array.length;
23243
+ for (var i = length - 1; i >= 0; i -= 1) {
23244
+ if (predicate(array[i])) {
23245
+ return array[i];
23246
+ }
23247
+ }
23248
+ }
23147
23249
 
23148
- // disable persistence if localStorage is not supported
23149
- // request-queue will automatically disable persistence if indexedDB fails to initialize
23150
- var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23250
+ /**
23251
+ * Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
23252
+ * Adapted from Sentry's `fill` utility:
23253
+ * https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
23254
+ *
23255
+ * @param {object} source - The object containing the method to patch
23256
+ * @param {string} name - The method name to patch
23257
+ * @param {function} replacementFactory - A function that receives the original method and returns the replacement
23258
+ * @returns {function} A function that restores the original method
23259
+ */
23260
+ function patch(source, name, replacementFactory) {
23261
+ if (!(name in source) || typeof source[name] !== 'function') {
23262
+ return function() {};
23263
+ }
23264
+ var original = source[name];
23265
+ var wrapped = replacementFactory(original);
23266
+ source[name] = wrapped;
23267
+ return function() {
23268
+ source[name] = original;
23269
+ };
23270
+ }
23151
23271
 
23152
- // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23153
- this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23154
- this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23155
- this.batcher = new RequestBatcher(this.batcherKey, {
23156
- errorReporter: this.reportError.bind(this),
23157
- flushOnlyOnInterval: true,
23158
- libConfig: RECORDER_BATCHER_LIB_CONFIG,
23159
- sendRequestFunc: this.flushEventsWithOptOut.bind(this),
23160
- queueStorage: this.queueStorage,
23161
- sharedLockStorage: options.sharedLockStorage,
23162
- usePersistence: usePersistence,
23163
- stopAllBatchingFunc: this.stopRecording.bind(this),
23164
23272
 
23165
- // increased throttle and shared lock timeout because recording events are very high frequency.
23166
- // this will minimize the amount of lock contention between enqueued events.
23167
- // for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
23168
- enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
23169
- sharedLockTimeoutMS: 10 * 1000,
23170
- });
23171
- };
23273
+ /**
23274
+ * Maximum body size to record (1MB)
23275
+ */
23276
+ var MAX_BODY_SIZE = 1024 * 1024;
23172
23277
 
23173
23278
  /**
23174
- * @returns {UserIdInfo}
23279
+ * Truncate string if it exceeds max size
23280
+ * @param {string} str
23281
+ * @returns {string}
23175
23282
  */
23176
- SessionRecording.prototype.getUserIdInfo = function () {
23177
- if (this.finalFlushUserIdInfo) {
23178
- return this.finalFlushUserIdInfo;
23283
+ function truncateBody(str) {
23284
+ if (!str || typeof str !== 'string') {
23285
+ return str;
23286
+ }
23287
+ if (str.length > MAX_BODY_SIZE) {
23288
+ logger$4.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23289
+ return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23179
23290
  }
23291
+ return str;
23292
+ }
23180
23293
 
23181
- var userIdInfo = {
23182
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
23294
+ /**
23295
+ * @param {networkCallback} cb
23296
+ * @param {Window} win
23297
+ * @param {Required<NetworkRecordOptions>} options
23298
+ * @returns {listenerHandler}
23299
+ */
23300
+ function initPerformanceObserver(cb, win, options) {
23301
+ if (!win.PerformanceObserver) {
23302
+ logger$4.error('PerformanceObserver not supported');
23303
+ return function() {
23304
+ //
23305
+ };
23306
+ }
23307
+ if (options.recordInitialRequests) {
23308
+ var initialPerformanceEntries = win.performance
23309
+ .getEntries()
23310
+ .filter(function(entry) {
23311
+ return isNavigationTiming(entry) ||
23312
+ (isResourceTiming(entry) &&
23313
+ options.initiatorTypes.includes(entry.initiatorType));
23314
+ });
23315
+ cb({
23316
+ requests: initialPerformanceEntries.map(function(entry) {
23317
+ return {
23318
+ url: entry.name,
23319
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23320
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23321
+ startTime: Math.round(entry.startTime),
23322
+ endTime: Math.round(entry.responseEnd),
23323
+ timeOrigin: getTimeOrigin(win),
23324
+ };
23325
+ }),
23326
+ isInitial: true,
23327
+ });
23328
+ }
23329
+ var observer = new win.PerformanceObserver(function(entries) {
23330
+ var performanceEntries = entries
23331
+ .getEntries()
23332
+ .filter(function(entry) {
23333
+ return isResourceTiming(entry) &&
23334
+ options.initiatorTypes.includes(entry.initiatorType) &&
23335
+ entry.initiatorType !== 'xmlhttprequest' &&
23336
+ entry.initiatorType !== 'fetch';
23337
+ });
23338
+ cb({
23339
+ requests: performanceEntries.map(function(entry) {
23340
+ return {
23341
+ url: entry.name,
23342
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23343
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23344
+ startTime: Math.round(entry.startTime),
23345
+ endTime: Math.round(entry.responseEnd),
23346
+ timeOrigin: getTimeOrigin(win),
23347
+ };
23348
+ }),
23349
+ });
23350
+ });
23351
+ observer.observe({ entryTypes: ['navigation', 'resource'] });
23352
+ return function() {
23353
+ observer.disconnect();
23183
23354
  };
23355
+ }
23184
23356
 
23185
- // send ID management props if they exist
23186
- var deviceId = this._mixpanel.get_property('$device_id');
23187
- if (deviceId) {
23188
- userIdInfo['$device_id'] = deviceId;
23189
- }
23190
- var userId = this._mixpanel.get_property('$user_id');
23191
- if (userId) {
23192
- userIdInfo['$user_id'] = userId;
23357
+ /**
23358
+ * Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
23359
+ * @param {'request' | 'response'} type
23360
+ * @param {NetworkRecordOptions['recordHeaders']} recordHeaders
23361
+ * @param {string} headerName
23362
+ * @returns {boolean}
23363
+ */
23364
+ function shouldRecordHeader(type, recordHeaders, headerName) {
23365
+ if (!recordHeaders[type] || recordHeaders[type].length === 0) {
23366
+ return false;
23193
23367
  }
23194
- return userIdInfo;
23195
- };
23196
23368
 
23197
- SessionRecording.prototype.unloadPersistedData = function () {
23198
- this.batcher.stop();
23369
+ return recordHeaders[type].includes(headerName.toLowerCase());
23370
+ }
23199
23371
 
23200
- return this.queueStorage.init().catch(function () {
23201
- this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
23202
- }.bind(this)).then(function () {
23203
- // if the recording is too short, just delete any stored events without flushing
23204
- if (this.getDurationMs() < this._getRecordMinMs()) {
23205
- return this.queueStorage.removeItem(this.batcherKey);
23206
- }
23372
+ /**
23373
+ * Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
23374
+ * @param {'request' | 'response'} type
23375
+ * @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
23376
+ * @param {string} url
23377
+ * @returns {boolean}
23378
+ */
23379
+ function shouldRecordBody(type, recordBodyUrls, url) {
23380
+ if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
23381
+ return false;
23382
+ }
23207
23383
 
23208
- return this.batcher.flush()
23209
- .then(function () {
23210
- return this.queueStorage.removeItem(this.batcherKey);
23211
- }.bind(this));
23212
- }.bind(this));
23213
- };
23384
+ return urlMatchesRegexList(url, recordBodyUrls[type]);
23385
+ }
23214
23386
 
23215
- SessionRecording.prototype.getConfig = function(configVar) {
23216
- return this._mixpanel.get_config(configVar);
23217
- };
23387
+ function tryReadXHRBody(body) {
23388
+ if (body === null || body === undefined) {
23389
+ return null;
23390
+ }
23218
23391
 
23219
- // Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
23392
+ var result;
23393
+ if (typeof body === 'string') {
23394
+ result = body;
23395
+ } else if (body instanceof Document) {
23396
+ result = body.textContent;
23397
+ } else if (body instanceof FormData) {
23398
+ result = _.HTTPBuildQuery(body);
23399
+ } else if (_.isObject(body)) {
23400
+ try {
23401
+ result = JSON.stringify(body);
23402
+ } catch (e) {
23403
+ return 'Failed to stringify response object';
23404
+ }
23405
+ } else {
23406
+ return 'Cannot read body of type ' + typeof body;
23407
+ }
23408
+
23409
+ return truncateBody(result);
23410
+ }
23411
+
23412
+ /**
23413
+ * @param {Request | Response} r
23414
+ * @returns {Promise<string>}
23415
+ */
23416
+ function tryReadFetchBody(r) {
23417
+ return new Promise(function(resolve) {
23418
+ var timeout = setTimeout(function() {
23419
+ resolve('Timeout while trying to read body');
23420
+ }, 500);
23421
+ try {
23422
+ r.clone()
23423
+ .text()
23424
+ .then(
23425
+ function(txt) {
23426
+ clearTimeout(timeout);
23427
+ resolve(truncateBody(txt));
23428
+ },
23429
+ function(reason) {
23430
+ clearTimeout(timeout);
23431
+ resolve('Failed to read body: ' + String(reason));
23432
+ }
23433
+ );
23434
+ } catch (e) {
23435
+ clearTimeout(timeout);
23436
+ resolve('Failed to read body: ' + String(e));
23437
+ }
23438
+ });
23439
+ }
23440
+
23441
+ /**
23442
+ * @param {Window} win
23443
+ * @param {string} initiatorType
23444
+ * @param {string} url
23445
+ * @param {number} [after]
23446
+ * @param {number} [before]
23447
+ * @param {number} [attempt]
23448
+ * @returns {Promise<PerformanceResourceTiming>}
23449
+ */
23450
+ function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
23451
+ if (attempt === undefined) {
23452
+ attempt = 0;
23453
+ }
23454
+ if (attempt > 10) {
23455
+ logger$4.error('Cannot find performance entry');
23456
+ return Promise.resolve(null);
23457
+ }
23458
+ var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
23459
+ win.performance.getEntriesByName(url)
23460
+ );
23461
+ var performanceEntry = findLast(
23462
+ urlPerformanceEntries,
23463
+ function(entry) {
23464
+ return isResourceTiming(entry) &&
23465
+ entry.initiatorType === initiatorType &&
23466
+ (!after || entry.startTime >= after) &&
23467
+ (!before || entry.startTime <= before);
23468
+ }
23469
+ );
23470
+ if (!performanceEntry) {
23471
+ return new Promise(function(resolve) {
23472
+ setTimeout(resolve, 50 * attempt);
23473
+ }).then(function() {
23474
+ return getRequestPerformanceEntry(
23475
+ win,
23476
+ initiatorType,
23477
+ url,
23478
+ after,
23479
+ before,
23480
+ attempt + 1
23481
+ );
23482
+ });
23483
+ }
23484
+ return Promise.resolve(performanceEntry);
23485
+ }
23486
+
23487
+ /**
23488
+ * @param {networkCallback} cb
23489
+ * @param {Window} win
23490
+ * @param {Required<NetworkRecordOptions>} options
23491
+ * @returns {listenerHandler}
23492
+ */
23493
+ function initXhrObserver(cb, win, options) {
23494
+ if (!options.initiatorTypes.includes('xmlhttprequest')) {
23495
+ return function() {
23496
+ //
23497
+ };
23498
+ }
23499
+ var restorePatch = patch(
23500
+ win.XMLHttpRequest.prototype,
23501
+ 'open',
23502
+ function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
23503
+ return function(
23504
+ /** @type {string} */ method,
23505
+ /** @type {string | URL} */ url,
23506
+ /** @type {boolean} */ async,
23507
+ username, password
23508
+ ) {
23509
+ if (async === undefined) {
23510
+ async = true;
23511
+ }
23512
+ var xhr = /** @type {XMLHttpRequest} */ (this);
23513
+ var req = new Request(url, { method: method });
23514
+ /** @type {Partial<NetworkRequest>} */
23515
+ var networkRequest = {};
23516
+ /** @type {number | undefined} */
23517
+ var after;
23518
+ /** @type {number | undefined} */
23519
+ var before;
23520
+
23521
+ /** @type {Headers} */
23522
+ var requestHeaders = {};
23523
+ var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
23524
+ xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
23525
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23526
+ requestHeaders[header] = value;
23527
+ }
23528
+ return originalSetRequestHeader(header, value);
23529
+ };
23530
+ networkRequest.requestHeaders = requestHeaders;
23531
+
23532
+ var originalSend = xhr.send.bind(xhr);
23533
+ xhr.send = function(/** @type {Body} */ body) {
23534
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23535
+ networkRequest.requestBody = tryReadXHRBody(body);
23536
+ }
23537
+ after = win.performance.now();
23538
+ return originalSend(body);
23539
+ };
23540
+ xhr.addEventListener('readystatechange', function() {
23541
+ if (xhr.readyState !== xhr.DONE) {
23542
+ return;
23543
+ }
23544
+ before = win.performance.now();
23545
+ /** @type {Headers} */
23546
+ var responseHeaders = {};
23547
+ var rawHeaders = xhr.getAllResponseHeaders();
23548
+ if (rawHeaders) {
23549
+ var headers = rawHeaders.trim().split(/[\r\n]+/);
23550
+ headers.forEach(function(line) {
23551
+ if (!line) return;
23552
+ var colonIndex = line.indexOf(': ');
23553
+ if (colonIndex === -1) return;
23554
+ var header = line.substring(0, colonIndex);
23555
+ var value = line.substring(colonIndex + 2);
23556
+ if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
23557
+ responseHeaders[header] = value;
23558
+ }
23559
+ });
23560
+ }
23561
+ networkRequest.responseHeaders = responseHeaders;
23562
+ if (
23563
+ shouldRecordBody('response', options.recordBodyUrls, req.url)
23564
+ ) {
23565
+ networkRequest.responseBody = tryReadXHRBody(xhr.response);
23566
+ }
23567
+ getRequestPerformanceEntry(
23568
+ win,
23569
+ 'xmlhttprequest',
23570
+ req.url,
23571
+ after,
23572
+ before
23573
+ )
23574
+ .then(function(entry) {
23575
+ if (!entry) {
23576
+ logger$4.error('Failed to get performance entry for XHR request to ' + req.url);
23577
+ return;
23578
+ }
23579
+ /** @type {NetworkRequest} */
23580
+ var request = {
23581
+ url: entry.name,
23582
+ method: req.method,
23583
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23584
+ status: xhr.status,
23585
+ startTime: Math.round(entry.startTime),
23586
+ endTime: Math.round(entry.responseEnd),
23587
+ timeOrigin: getTimeOrigin(win),
23588
+ requestHeaders: networkRequest.requestHeaders,
23589
+ requestBody: networkRequest.requestBody,
23590
+ responseHeaders: networkRequest.responseHeaders,
23591
+ responseBody: networkRequest.responseBody,
23592
+ };
23593
+ cb({ requests: [request] });
23594
+ })
23595
+ .catch(function(e) {
23596
+ logger$4.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23597
+ });
23598
+ });
23599
+
23600
+ originalOpen.call(xhr, method, url, async, username, password);
23601
+ };
23602
+ }
23603
+ );
23604
+ return function() {
23605
+ restorePatch();
23606
+ };
23607
+ }
23608
+
23609
+ /**
23610
+ * @param {networkCallback} cb
23611
+ * @param {Window} win
23612
+ * @param {Required<NetworkRecordOptions>} options
23613
+ * @returns {listenerHandler}
23614
+ */
23615
+ function initFetchObserver(cb, win, options) {
23616
+ if (!options.initiatorTypes.includes('fetch')) {
23617
+ return function() {
23618
+ //
23619
+ };
23620
+ }
23621
+
23622
+ var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
23623
+ return function() {
23624
+ var req = new Request(arguments[0], arguments[1]);
23625
+ /** @type {Response | undefined} */
23626
+ var res;
23627
+ /** @type {Partial<NetworkRequest>} */
23628
+ var networkRequest = {};
23629
+ /** @type {number | undefined} */
23630
+ var after;
23631
+ /** @type {number | undefined} */
23632
+ var before;
23633
+
23634
+ var originalFetchPromise;
23635
+ var requestBodyPromise = Promise.resolve(undefined);
23636
+ var responseBodyPromise = Promise.resolve(undefined);
23637
+ try {
23638
+ /** @type {Headers} */
23639
+ var requestHeaders = {};
23640
+ req.headers.forEach(function(value, header) {
23641
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23642
+ requestHeaders[header] = value;
23643
+ }
23644
+ });
23645
+ networkRequest.requestHeaders = requestHeaders;
23646
+
23647
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23648
+ requestBodyPromise = tryReadFetchBody(req)
23649
+ .then(function(body) {
23650
+ networkRequest.requestBody = body;
23651
+ });
23652
+ }
23653
+
23654
+ after = win.performance.now();
23655
+ originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
23656
+ res = response;
23657
+ before = win.performance.now();
23658
+
23659
+ /** @type {Headers} */
23660
+ var responseHeaders = {};
23661
+ res.headers.forEach(function(value, header) {
23662
+ if (shouldRecordHeader('response', options.recordHeaders, header)) {
23663
+ responseHeaders[header] = value;
23664
+ }
23665
+ });
23666
+ networkRequest.responseHeaders = responseHeaders;
23667
+
23668
+ if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
23669
+ responseBodyPromise = tryReadFetchBody(res)
23670
+ .then(function(body) {
23671
+ networkRequest.responseBody = body;
23672
+ });
23673
+ }
23674
+
23675
+ return res;
23676
+ });
23677
+ } catch (e) {
23678
+ originalFetchPromise = Promise.reject(e);
23679
+ }
23680
+
23681
+ // await concurrently so we don't delay the fetch response
23682
+ Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
23683
+ .then(function () {
23684
+ return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
23685
+ })
23686
+ .then(function(entry) {
23687
+ if (!entry) {
23688
+ logger$4.error('Failed to get performance entry for fetch request to ' + req.url);
23689
+ return;
23690
+ }
23691
+ /** @type {NetworkRequest} */
23692
+ var request = {
23693
+ url: entry.name,
23694
+ method: req.method,
23695
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23696
+ status: res ? res.status : undefined,
23697
+ startTime: Math.round(entry.startTime),
23698
+ endTime: Math.round(entry.responseEnd),
23699
+ timeOrigin: getTimeOrigin(win),
23700
+ requestHeaders: networkRequest.requestHeaders,
23701
+ requestBody: networkRequest.requestBody,
23702
+ responseHeaders: networkRequest.responseHeaders,
23703
+ responseBody: networkRequest.responseBody,
23704
+ };
23705
+ cb({ requests: [request] });
23706
+ })
23707
+ .catch(function (e) {
23708
+ logger$4.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23709
+ });
23710
+
23711
+ return originalFetchPromise;
23712
+ };
23713
+ });
23714
+ return function() {
23715
+ restorePatch();
23716
+ };
23717
+ }
23718
+
23719
+ /**
23720
+ * @param {networkCallback} callback
23721
+ * @param {Window} win
23722
+ * @param {NetworkRecordOptions} options
23723
+ * @returns {listenerHandler}
23724
+ */
23725
+ function initNetworkObserver(callback, win, options) {
23726
+ if (!('performance' in win)) {
23727
+ return function() {
23728
+ //
23729
+ };
23730
+ }
23731
+
23732
+ var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
23733
+ var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
23734
+ options = Object.assign({}, options, {
23735
+ recordHeaders: recordHeaders,
23736
+ recordBodyUrls: recordBodyUrls,
23737
+ });
23738
+ var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
23739
+
23740
+ /** @type {networkCallback} */
23741
+ var cb = function(data) {
23742
+ var requests = data.requests.filter(function(request) {
23743
+ var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
23744
+ return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
23745
+ });
23746
+ if (requests.length > 0 || data.isInitial) {
23747
+ callback(Object.assign({}, data, { requests: requests }));
23748
+ }
23749
+ };
23750
+ var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
23751
+ var xhrObserver = initXhrObserver(cb, win, networkOptions);
23752
+ var fetchObserver = initFetchObserver(cb, win, networkOptions);
23753
+ return function() {
23754
+ performanceObserver();
23755
+ xhrObserver();
23756
+ fetchObserver();
23757
+ };
23758
+ }
23759
+
23760
+ // arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
23761
+ // a changed format in the mixpanel product.
23762
+ var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
23763
+
23764
+ /**
23765
+ * @param {NetworkRecordOptions} [options]
23766
+ * @returns {RecordPlugin}
23767
+ */
23768
+ var getRecordNetworkPlugin = function(options) {
23769
+ return {
23770
+ name: NETWORK_PLUGIN_NAME,
23771
+ observer: initNetworkObserver,
23772
+ options: options,
23773
+ };
23774
+ };
23775
+
23776
+ /**
23777
+ * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
23778
+ */
23779
+
23780
+
23781
+ var logger$3 = console_with_prefix('recorder');
23782
+ var CompressionStream = win['CompressionStream'];
23783
+
23784
+ var RECORDER_BATCHER_LIB_CONFIG = {
23785
+ 'batch_size': 1000,
23786
+ 'batch_flush_interval_ms': 10 * 1000,
23787
+ 'batch_request_timeout_ms': 90 * 1000,
23788
+ 'batch_autostart': true
23789
+ };
23790
+
23791
+ var ACTIVE_SOURCES = new Set([
23792
+ IncrementalSource.MouseMove,
23793
+ IncrementalSource.MouseInteraction,
23794
+ IncrementalSource.Scroll,
23795
+ IncrementalSource.ViewportResize,
23796
+ IncrementalSource.Input,
23797
+ IncrementalSource.TouchMove,
23798
+ IncrementalSource.MediaInteraction,
23799
+ IncrementalSource.Drag,
23800
+ IncrementalSource.Selection,
23801
+ ]);
23802
+
23803
+ function isUserEvent(ev) {
23804
+ return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
23805
+ }
23806
+
23807
+ /**
23808
+ * @typedef {Object} SerializedRecording
23809
+ * @property {number} idleExpires
23810
+ * @property {number} maxExpires
23811
+ * @property {number} replayStartTime
23812
+ * @property {number} lastEventTimestamp
23813
+ * @property {number} seqNo
23814
+ * @property {string} batchStartUrl
23815
+ * @property {string} replayId
23816
+ * @property {string} tabId
23817
+ * @property {string} replayStartUrl
23818
+ */
23819
+
23820
+ /**
23821
+ * @typedef {Object} SessionRecordingOptions
23822
+ * @property {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
23823
+ * @property {String} [options.replayId] - unique uuid for a single replay
23824
+ * @property {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
23825
+ * @property {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
23826
+ * @property {import('./rrweb-entrypoint').record} [options.rrwebRecord] - rrweb's `record` function
23827
+ * @property {Function} [options.onBatchSent] - callback when a batch of events is sent to the server
23828
+ * @property {Storage} [options.sharedLockStorage] - optional storage for shared lock, used for test dependency injection
23829
+ * optional properties for deserialization:
23830
+ * @property {number} idleExpires
23831
+ * @property {number} maxExpires
23832
+ * @property {number} replayStartTime
23833
+ * @property {number} lastEventTimestamp - the unix timestamp of the last recorded event from rrweb
23834
+ * @property {number} seqNo
23835
+ * @property {string} batchStartUrl
23836
+ * @property {string} replayStartUrl
23837
+ */
23838
+
23839
+ /**
23840
+ * @typedef {Object} UserIdInfo
23841
+ * @property {string} distinct_id
23842
+ * @property {string} user_id
23843
+ * @property {string} device_id
23844
+ */
23845
+
23846
+
23847
+ /**
23848
+ * This class encapsulates a single session recording and its lifecycle.
23849
+ * @param {SessionRecordingOptions} options
23850
+ */
23851
+ var SessionRecording = function(options) {
23852
+ this._mixpanel = options.mixpanelInstance;
23853
+ this._onIdleTimeout = options.onIdleTimeout || NOOP_FUNC;
23854
+ this._onMaxLengthReached = options.onMaxLengthReached || NOOP_FUNC;
23855
+ this._onBatchSent = options.onBatchSent || NOOP_FUNC;
23856
+ this._rrwebRecord = options.rrwebRecord || null;
23857
+
23858
+ // internal rrweb stopRecording function
23859
+ this._stopRecording = null;
23860
+ this.replayId = options.replayId;
23861
+
23862
+ this.batchStartUrl = options.batchStartUrl || null;
23863
+ this.replayStartUrl = options.replayStartUrl || null;
23864
+ this.idleExpires = options.idleExpires || null;
23865
+ this.maxExpires = options.maxExpires || null;
23866
+ this.replayStartTime = options.replayStartTime || null;
23867
+ this.lastEventTimestamp = options.lastEventTimestamp || null;
23868
+ this.seqNo = options.seqNo || 0;
23869
+
23870
+ this.idleTimeoutId = null;
23871
+ this.maxTimeoutId = null;
23872
+
23873
+ this.recordMaxMs = MAX_RECORDING_MS;
23874
+ this.recordMinMs = 0;
23875
+
23876
+ // disable persistence if localStorage is not supported
23877
+ // request-queue will automatically disable persistence if indexedDB fails to initialize
23878
+ var usePersistence = localStorageSupported(options.sharedLockStorage, true) && !this.getConfig('disable_persistence');
23879
+
23880
+ // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
23881
+ this.batcherKey = '__mprec_' + this.getConfig('name') + '_' + this.getConfig('token') + '_' + this.replayId;
23882
+ this.queueStorage = new IDBStorageWrapper(RECORDING_EVENTS_STORE_NAME);
23883
+ this.batcher = new RequestBatcher(this.batcherKey, {
23884
+ errorReporter: this.reportError.bind(this),
23885
+ flushOnlyOnInterval: true,
23886
+ libConfig: RECORDER_BATCHER_LIB_CONFIG,
23887
+ sendRequestFunc: this.flushEventsWithOptOut.bind(this),
23888
+ queueStorage: this.queueStorage,
23889
+ sharedLockStorage: options.sharedLockStorage,
23890
+ usePersistence: usePersistence,
23891
+ stopAllBatchingFunc: this.stopRecording.bind(this),
23892
+
23893
+ // increased throttle and shared lock timeout because recording events are very high frequency.
23894
+ // this will minimize the amount of lock contention between enqueued events.
23895
+ // for session recordings there is a lock for each tab anyway, so there's no risk of deadlock between tabs.
23896
+ enqueueThrottleMs: RECORD_ENQUEUE_THROTTLE_MS,
23897
+ sharedLockTimeoutMS: 10 * 1000,
23898
+ });
23899
+ };
23900
+
23901
+ /**
23902
+ * @returns {UserIdInfo}
23903
+ */
23904
+ SessionRecording.prototype.getUserIdInfo = function () {
23905
+ if (this.finalFlushUserIdInfo) {
23906
+ return this.finalFlushUserIdInfo;
23907
+ }
23908
+
23909
+ var userIdInfo = {
23910
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
23911
+ };
23912
+
23913
+ // send ID management props if they exist
23914
+ var deviceId = this._mixpanel.get_property('$device_id');
23915
+ if (deviceId) {
23916
+ userIdInfo['$device_id'] = deviceId;
23917
+ }
23918
+ var userId = this._mixpanel.get_property('$user_id');
23919
+ if (userId) {
23920
+ userIdInfo['$user_id'] = userId;
23921
+ }
23922
+ return userIdInfo;
23923
+ };
23924
+
23925
+ SessionRecording.prototype.unloadPersistedData = function () {
23926
+ this.batcher.stop();
23927
+
23928
+ return this.queueStorage.init().catch(function () {
23929
+ this.reportError('Error initializing IndexedDB storage for unloading persisted data.');
23930
+ }.bind(this)).then(function () {
23931
+ // if the recording is too short, just delete any stored events without flushing
23932
+ if (this.getDurationMs() < this._getRecordMinMs()) {
23933
+ return this.queueStorage.removeItem(this.batcherKey);
23934
+ }
23935
+
23936
+ return this.batcher.flush()
23937
+ .then(function () {
23938
+ return this.queueStorage.removeItem(this.batcherKey);
23939
+ }.bind(this));
23940
+ }.bind(this));
23941
+ };
23942
+
23943
+ SessionRecording.prototype.getConfig = function(configVar) {
23944
+ return this._mixpanel.get_config(configVar);
23945
+ };
23946
+
23947
+ // Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
23220
23948
  // reaches into this class instance and expects the snake case version of the function.
23221
23949
  // eslint-disable-next-line camelcase
23222
23950
  SessionRecording.prototype.get_config = function(configVar) {
@@ -23230,14 +23958,14 @@ define((function () { 'use strict';
23230
23958
  }
23231
23959
 
23232
23960
  if (this._stopRecording !== null) {
23233
- logger$2.log('Recording already in progress, skipping startRecording.');
23961
+ logger$3.log('Recording already in progress, skipping startRecording.');
23234
23962
  return;
23235
23963
  }
23236
23964
 
23237
23965
  this.recordMaxMs = this.getConfig('record_max_ms');
23238
23966
  if (this.recordMaxMs > MAX_RECORDING_MS) {
23239
23967
  this.recordMaxMs = MAX_RECORDING_MS;
23240
- logger$2.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23968
+ logger$3.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
23241
23969
  }
23242
23970
 
23243
23971
  if (!this.maxExpires) {
@@ -23278,6 +24006,31 @@ define((function () { 'use strict';
23278
24006
 
23279
24007
  var privacyConfig = getPrivacyConfig(this._mixpanel);
23280
24008
 
24009
+ var plugins = [];
24010
+ if (this.getConfig('record_network')) {
24011
+ var options = this.getConfig('record_network_options') || {};
24012
+ // don't track requests to Mixpanel /record API
24013
+ var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
24014
+ ignoreRequestUrls.push(this._getApiRoute());
24015
+ options.ignoreRequestUrls = ignoreRequestUrls;
24016
+
24017
+ plugins.push(getRecordNetworkPlugin(options));
24018
+ }
24019
+
24020
+ if (this.getConfig('record_console')) {
24021
+ plugins.push(
24022
+ getRecordConsolePlugin({
24023
+ stringifyOptions: {
24024
+ stringLengthLimit: 1000,
24025
+ numOfKeysLimit: 50,
24026
+ depthOfLimit: 2
24027
+ }
24028
+ })
24029
+ );
24030
+ }
24031
+
24032
+ var validatedOrigins = validateAllowedOrigins(this.getConfig('record_allowed_iframe_origins'), logger$3);
24033
+
23281
24034
  try {
23282
24035
  this._stopRecording = this._rrwebRecord({
23283
24036
  'emit': function (ev) {
@@ -23312,19 +24065,13 @@ define((function () { 'use strict';
23312
24065
  'maskTextSelector': '*',
23313
24066
  'maskInputFn': this._getMaskFn(shouldMaskInput, privacyConfig),
23314
24067
  'maskTextFn': this._getMaskFn(shouldMaskText, privacyConfig),
24068
+ 'recordCrossOriginIframes': validatedOrigins.length > 0,
24069
+ 'allowedIframeOrigins': validatedOrigins,
23315
24070
  'recordCanvas': this.getConfig('record_canvas'),
23316
24071
  'sampling': {
23317
24072
  'canvas': 15
23318
24073
  },
23319
- 'plugins': this.getConfig('record_console') ? [
23320
- getRecordConsolePlugin({
23321
- stringifyOptions: {
23322
- stringLengthLimit: 1000,
23323
- numOfKeysLimit: 50,
23324
- depthOfLimit: 2
23325
- }
23326
- })
23327
- ] : []
24074
+ 'plugins': plugins,
23328
24075
  });
23329
24076
  } catch (err) {
23330
24077
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -23439,6 +24186,10 @@ define((function () { 'use strict';
23439
24186
  return recording;
23440
24187
  };
23441
24188
 
24189
+ SessionRecording.prototype._getApiRoute = function () {
24190
+ return this.getConfig('api_routes')['record'];
24191
+ };
24192
+
23442
24193
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
23443
24194
  var onSuccess = function (response, responseBody) {
23444
24195
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -23458,7 +24209,7 @@ define((function () { 'use strict';
23458
24209
  });
23459
24210
  }.bind(this);
23460
24211
  var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
23461
- win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
24212
+ win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
23462
24213
  'method': 'POST',
23463
24214
  'headers': {
23464
24215
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -23540,14 +24291,14 @@ define((function () { 'use strict';
23540
24291
 
23541
24292
 
23542
24293
  SessionRecording.prototype.reportError = function(msg, err) {
23543
- logger$2.error.apply(logger$2.error, arguments);
24294
+ logger$3.error.apply(logger$3.error, arguments);
23544
24295
  try {
23545
24296
  if (!err && !(msg instanceof Error)) {
23546
24297
  msg = new Error(msg);
23547
24298
  }
23548
24299
  this.getConfig('error_reporter')(msg, err);
23549
24300
  } catch(err) {
23550
- logger$2.error(err);
24301
+ logger$3.error(err);
23551
24302
  }
23552
24303
  };
23553
24304
 
@@ -23576,7 +24327,7 @@ define((function () { 'use strict';
23576
24327
  var configValue = this.getConfig('record_min_ms');
23577
24328
 
23578
24329
  if (configValue > MAX_VALUE_FOR_MIN_RECORDING_MS) {
23579
- logger$2.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
24330
+ logger$3.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
23580
24331
  return MAX_VALUE_FOR_MIN_RECORDING_MS;
23581
24332
  }
23582
24333
 
@@ -23739,7 +24490,7 @@ define((function () { 'use strict';
23739
24490
  .catch(this.handleError.bind(this));
23740
24491
  };
23741
24492
 
23742
- var logger$1 = console_with_prefix('recorder');
24493
+ var logger$2 = console_with_prefix('recorder');
23743
24494
 
23744
24495
  /**
23745
24496
  * Recorder API: bundles rrweb and and exposes methods to start and stop recordings.
@@ -23755,7 +24506,7 @@ define((function () { 'use strict';
23755
24506
  */
23756
24507
  this.recordingRegistry = new RecordingRegistry({
23757
24508
  mixpanelInstance: this.mixpanelInstance,
23758
- errorReporter: logger$1.error,
24509
+ errorReporter: logger$2.error,
23759
24510
  sharedLockStorage: sharedLockStorage
23760
24511
  });
23761
24512
  this._flushInactivePromise = this.recordingRegistry.flushInactiveRecordings();
@@ -23767,17 +24518,17 @@ define((function () { 'use strict';
23767
24518
  MixpanelRecorder.prototype.startRecording = function(options) {
23768
24519
  options = options || {};
23769
24520
  if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
23770
- logger$1.log('Recording already in progress, skipping startRecording.');
24521
+ logger$2.log('Recording already in progress, skipping startRecording.');
23771
24522
  return;
23772
24523
  }
23773
24524
 
23774
24525
  var onIdleTimeout = function () {
23775
- logger$1.log('Idle timeout reached, restarting recording.');
24526
+ logger$2.log('Idle timeout reached, restarting recording.');
23776
24527
  this.resetRecording();
23777
24528
  }.bind(this);
23778
24529
 
23779
24530
  var onMaxLengthReached = function () {
23780
- logger$1.log('Max recording length reached, stopping recording.');
24531
+ logger$2.log('Max recording length reached, stopping recording.');
23781
24532
  this.resetRecording();
23782
24533
  }.bind(this);
23783
24534
 
@@ -23847,7 +24598,7 @@ define((function () { 'use strict';
23847
24598
  } else if (startNewIfInactive) {
23848
24599
  return this.startRecording({shouldStopBatcher: false});
23849
24600
  } else {
23850
- logger$1.log('No resumable recording found.');
24601
+ logger$2.log('No resumable recording found.');
23851
24602
  return null;
23852
24603
  }
23853
24604
  }.bind(this));
@@ -23859,8 +24610,12 @@ define((function () { 'use strict';
23859
24610
  this.startRecording({shouldStopBatcher: true});
23860
24611
  };
23861
24612
 
24613
+ MixpanelRecorder.prototype.isRecording = function () {
24614
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
24615
+ };
24616
+
23862
24617
  MixpanelRecorder.prototype.getActiveReplayId = function () {
23863
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24618
+ if (this.isRecording()) {
23864
24619
  return this.activeRecording.replayId;
23865
24620
  } else {
23866
24621
  return null;
@@ -24364,53 +25119,6 @@ define((function () { 'use strict';
24364
25119
  var logicExports = requireLogic();
24365
25120
  var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
24366
25121
 
24367
- /**
24368
- * Shared helper to recursively lowercase strings in nested structures
24369
- * @param {*} obj - Value to process
24370
- * @param {boolean} lowercaseKeys - Whether to lowercase object keys
24371
- * @returns {*} Processed value with lowercased strings
24372
- */
24373
- var lowercaseJson = function(obj, lowercaseKeys) {
24374
- if (obj === null || obj === undefined) {
24375
- return obj;
24376
- } else if (typeof obj === 'string') {
24377
- return obj.toLowerCase();
24378
- } else if (Array.isArray(obj)) {
24379
- return obj.map(function(item) {
24380
- return lowercaseJson(item, lowercaseKeys);
24381
- });
24382
- } else if (obj === Object(obj)) {
24383
- var result = {};
24384
- for (var key in obj) {
24385
- if (obj.hasOwnProperty(key)) {
24386
- var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
24387
- result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
24388
- }
24389
- }
24390
- return result;
24391
- } else {
24392
- return obj;
24393
- }
24394
- };
24395
-
24396
- /**
24397
- * Lowercase all string keys and values in a nested structure
24398
- * @param {*} val - Value to process
24399
- * @returns {*} Processed value with lowercased strings
24400
- */
24401
- var lowercaseKeysAndValues = function(val) {
24402
- return lowercaseJson(val, true);
24403
- };
24404
-
24405
- /**
24406
- * Lowercase only leaf node string values in a nested structure (keys unchanged)
24407
- * @param {*} val - Value to process
24408
- * @returns {*} Processed value with lowercased leaf strings
24409
- */
24410
- var lowercaseOnlyLeafNodes = function(val) {
24411
- return lowercaseJson(val, false);
24412
- };
24413
-
24414
25122
  /**
24415
25123
  * Check if an event matches the given criteria
24416
25124
  * @param {string} eventName - The name of the event being checked
@@ -24434,13 +25142,8 @@ define((function () { 'use strict';
24434
25142
 
24435
25143
  if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
24436
25144
  try {
24437
- // Lowercase all keys and values in event properties for case-insensitive matching
24438
- var lowercasedProperties = lowercaseKeysAndValues(properties || {});
24439
-
24440
- // Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
24441
- var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
24442
-
24443
- filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
25145
+ // Use properties as-is for case-sensitive matching
25146
+ filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
24444
25147
  } catch (error) {
24445
25148
  return {
24446
25149
  matches: false,
@@ -24560,7 +25263,7 @@ define((function () { 'use strict';
24560
25263
  observer.observe(shadowRoot, this.observerConfig);
24561
25264
  this.shadowObservers.push(observer);
24562
25265
  } catch (e) {
24563
- logger$3.critical('Error while observing shadow root', e);
25266
+ logger$5.critical('Error while observing shadow root', e);
24564
25267
  }
24565
25268
  };
24566
25269
 
@@ -24571,7 +25274,7 @@ define((function () { 'use strict';
24571
25274
  }
24572
25275
 
24573
25276
  if (!weakSetSupported()) {
24574
- logger$3.critical('Shadow DOM observation unavailable: WeakSet not supported');
25277
+ logger$5.critical('Shadow DOM observation unavailable: WeakSet not supported');
24575
25278
  return;
24576
25279
  }
24577
25280
 
@@ -24587,7 +25290,7 @@ define((function () { 'use strict';
24587
25290
  try {
24588
25291
  this.shadowObservers[i].disconnect();
24589
25292
  } catch (e) {
24590
- logger$3.critical('Error while disconnecting shadow DOM observer', e);
25293
+ logger$5.critical('Error while disconnecting shadow DOM observer', e);
24591
25294
  }
24592
25295
  }
24593
25296
  this.shadowObservers = [];
@@ -24775,7 +25478,7 @@ define((function () { 'use strict';
24775
25478
 
24776
25479
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24777
25480
  } catch (e) {
24778
- logger$3.critical('Error while setting up mutation observer', e);
25481
+ logger$5.critical('Error while setting up mutation observer', e);
24779
25482
  }
24780
25483
  }
24781
25484
 
@@ -24790,7 +25493,7 @@ define((function () { 'use strict';
24790
25493
  );
24791
25494
  this.shadowDOMObserver.start();
24792
25495
  } catch (e) {
24793
- logger$3.critical('Error while setting up shadow DOM observer', e);
25496
+ logger$5.critical('Error while setting up shadow DOM observer', e);
24794
25497
  this.shadowDOMObserver = null;
24795
25498
  }
24796
25499
  }
@@ -24817,7 +25520,7 @@ define((function () { 'use strict';
24817
25520
  try {
24818
25521
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24819
25522
  } catch (e) {
24820
- logger$3.critical('Error while removing event listener', e);
25523
+ logger$5.critical('Error while removing event listener', e);
24821
25524
  }
24822
25525
  }
24823
25526
  this.eventListeners = [];
@@ -24826,7 +25529,7 @@ define((function () { 'use strict';
24826
25529
  try {
24827
25530
  this.mutationObserver.disconnect();
24828
25531
  } catch (e) {
24829
- logger$3.critical('Error while disconnecting mutation observer', e);
25532
+ logger$5.critical('Error while disconnecting mutation observer', e);
24830
25533
  }
24831
25534
  this.mutationObserver = null;
24832
25535
  }
@@ -24835,7 +25538,7 @@ define((function () { 'use strict';
24835
25538
  try {
24836
25539
  this.shadowDOMObserver.stop();
24837
25540
  } catch (e) {
24838
- logger$3.critical('Error while stopping shadow DOM observer', e);
25541
+ logger$5.critical('Error while stopping shadow DOM observer', e);
24839
25542
  }
24840
25543
  this.shadowDOMObserver = null;
24841
25544
  }
@@ -24913,7 +25616,7 @@ define((function () { 'use strict';
24913
25616
 
24914
25617
  Autocapture.prototype.init = function() {
24915
25618
  if (!minDOMApisSupported()) {
24916
- logger$3.critical('Autocapture unavailable: missing required DOM APIs');
25619
+ logger$5.critical('Autocapture unavailable: missing required DOM APIs');
24917
25620
  return;
24918
25621
  }
24919
25622
  this.initPageListeners();
@@ -24945,27 +25648,15 @@ define((function () { 'use strict';
24945
25648
  };
24946
25649
 
24947
25650
  Autocapture.prototype.currentUrlBlocked = function() {
24948
- var i;
24949
25651
  var currentUrl = _.info.currentUrl();
24950
25652
 
24951
25653
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
24952
25654
  if (allowUrlRegexes.length) {
24953
25655
  // we're using an allowlist, only track if current URL matches
24954
- var allowed = false;
24955
- for (i = 0; i < allowUrlRegexes.length; i++) {
24956
- var allowRegex = allowUrlRegexes[i];
24957
- try {
24958
- if (currentUrl.match(allowRegex)) {
24959
- allowed = true;
24960
- break;
24961
- }
24962
- } catch (err) {
24963
- logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
24964
- return true;
24965
- }
24966
- }
24967
- if (!allowed) {
24968
- // wasn't allowed by any regex
25656
+ try {
25657
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25658
+ } catch (err) {
25659
+ logger$5.critical('Error while checking block URL regexes: ', err);
24969
25660
  return true;
24970
25661
  }
24971
25662
  }
@@ -24975,17 +25666,12 @@ define((function () { 'use strict';
24975
25666
  return false;
24976
25667
  }
24977
25668
 
24978
- for (i = 0; i < blockUrlRegexes.length; i++) {
24979
- try {
24980
- if (currentUrl.match(blockUrlRegexes[i])) {
24981
- return true;
24982
- }
24983
- } catch (err) {
24984
- logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
24985
- return true;
24986
- }
25669
+ try {
25670
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25671
+ } catch (err) {
25672
+ logger$5.critical('Error while checking block URL regexes: ', err);
25673
+ return true;
24987
25674
  }
24988
- return false;
24989
25675
  };
24990
25676
 
24991
25677
  Autocapture.prototype.pageviewTrackingConfig = function() {
@@ -25121,7 +25807,7 @@ define((function () { 'use strict';
25121
25807
  return;
25122
25808
  }
25123
25809
 
25124
- logger$3.log('Initializing scroll depth tracking');
25810
+ logger$5.log('Initializing scroll depth tracking');
25125
25811
 
25126
25812
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25127
25813
 
@@ -25147,7 +25833,7 @@ define((function () { 'use strict';
25147
25833
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25148
25834
  return;
25149
25835
  }
25150
- logger$3.log('Initializing click tracking');
25836
+ logger$5.log('Initializing click tracking');
25151
25837
 
25152
25838
  this.listenerClick = function(ev) {
25153
25839
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25166,7 +25852,7 @@ define((function () { 'use strict';
25166
25852
  return;
25167
25853
  }
25168
25854
 
25169
- logger$3.log('Initializing dead click tracking');
25855
+ logger$5.log('Initializing dead click tracking');
25170
25856
  if (!this._deadClickTracker) {
25171
25857
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25172
25858
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25200,7 +25886,7 @@ define((function () { 'use strict';
25200
25886
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25201
25887
  return;
25202
25888
  }
25203
- logger$3.log('Initializing input tracking');
25889
+ logger$5.log('Initializing input tracking');
25204
25890
 
25205
25891
  this.listenerChange = function(ev) {
25206
25892
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25217,7 +25903,7 @@ define((function () { 'use strict';
25217
25903
  if (!this.pageviewTrackingConfig()) {
25218
25904
  return;
25219
25905
  }
25220
- logger$3.log('Initializing pageview tracking');
25906
+ logger$5.log('Initializing pageview tracking');
25221
25907
 
25222
25908
  var previousTrackedUrl = '';
25223
25909
  var tracked = false;
@@ -25252,7 +25938,7 @@ define((function () { 'use strict';
25252
25938
  }
25253
25939
  if (didPathChange) {
25254
25940
  this.lastScrollCheckpoint = 0;
25255
- logger$3.log('Path change: re-initializing scroll depth checkpoints');
25941
+ logger$5.log('Path change: re-initializing scroll depth checkpoints');
25256
25942
  }
25257
25943
  }
25258
25944
  }.bind(this));
@@ -25267,7 +25953,7 @@ define((function () { 'use strict';
25267
25953
  return;
25268
25954
  }
25269
25955
 
25270
- logger$3.log('Initializing rage click tracking');
25956
+ logger$5.log('Initializing rage click tracking');
25271
25957
  if (!this._rageClickTracker) {
25272
25958
  this._rageClickTracker = new RageClickTracker();
25273
25959
  }
@@ -25297,7 +25983,7 @@ define((function () { 'use strict';
25297
25983
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25298
25984
  return;
25299
25985
  }
25300
- logger$3.log('Initializing scroll tracking');
25986
+ logger$5.log('Initializing scroll tracking');
25301
25987
  this.lastScrollCheckpoint = 0;
25302
25988
 
25303
25989
  var scrollTrackFunction = function() {
@@ -25334,7 +26020,7 @@ define((function () { 'use strict';
25334
26020
  }
25335
26021
  }
25336
26022
  } catch (err) {
25337
- logger$3.critical('Error while calculating scroll percentage', err);
26023
+ logger$5.critical('Error while calculating scroll percentage', err);
25338
26024
  }
25339
26025
  if (shouldTrack) {
25340
26026
  this.mp.track(MP_EV_SCROLL, props);
@@ -25352,7 +26038,7 @@ define((function () { 'use strict';
25352
26038
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
25353
26039
  return;
25354
26040
  }
25355
- logger$3.log('Initializing submit tracking');
26041
+ logger$5.log('Initializing submit tracking');
25356
26042
 
25357
26043
  this.listenerSubmit = function(ev) {
25358
26044
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -25374,7 +26060,7 @@ define((function () { 'use strict';
25374
26060
  return;
25375
26061
  }
25376
26062
 
25377
- logger$3.log('Initializing page visibility tracking.');
26063
+ logger$5.log('Initializing page visibility tracking.');
25378
26064
  this._initScrollDepthTracking();
25379
26065
  var previousTrackedUrl = _.info.currentUrl();
25380
26066
 
@@ -25459,7 +26145,7 @@ define((function () { 'use strict';
25459
26145
  return win[TARGETING_GLOBAL_NAME];
25460
26146
  };
25461
26147
 
25462
- var logger = console_with_prefix('flags');
26148
+ var logger$1 = console_with_prefix('flags');
25463
26149
  var FLAGS_CONFIG_KEY = 'flags';
25464
26150
 
25465
26151
  var CONFIG_CONTEXT = 'context';
@@ -25502,7 +26188,7 @@ define((function () { 'use strict';
25502
26188
 
25503
26189
  FeatureFlagManager.prototype.init = function() {
25504
26190
  if (!this.minApisSupported()) {
25505
- logger.critical('Feature Flags unavailable: missing minimum required APIs');
26191
+ logger$1.critical('Feature Flags unavailable: missing minimum required APIs');
25506
26192
  return;
25507
26193
  }
25508
26194
 
@@ -25537,7 +26223,7 @@ define((function () { 'use strict';
25537
26223
 
25538
26224
  FeatureFlagManager.prototype.updateContext = function(newContext, options) {
25539
26225
  if (!this.isSystemEnabled()) {
25540
- logger.critical('Feature Flags not enabled, cannot update context');
26226
+ logger$1.critical('Feature Flags not enabled, cannot update context');
25541
26227
  return Promise.resolve();
25542
26228
  }
25543
26229
 
@@ -25554,7 +26240,7 @@ define((function () { 'use strict';
25554
26240
 
25555
26241
  FeatureFlagManager.prototype.areFlagsReady = function() {
25556
26242
  if (!this.isSystemEnabled()) {
25557
- logger.error('Feature Flags not enabled');
26243
+ logger$1.error('Feature Flags not enabled');
25558
26244
  }
25559
26245
  return !!this.flags;
25560
26246
  };
@@ -25567,7 +26253,7 @@ define((function () { 'use strict';
25567
26253
  var distinctId = this.getMpProperty('distinct_id');
25568
26254
  var deviceId = this.getMpProperty('$device_id');
25569
26255
  var traceparent = generateTraceparent();
25570
- logger.log('Fetching flags for distinct ID: ' + distinctId);
26256
+ logger$1.log('Fetching flags for distinct ID: ' + distinctId);
25571
26257
 
25572
26258
  var context = _.extend({'distinct_id': distinctId, 'device_id': deviceId}, this.getConfig(CONFIG_CONTEXT));
25573
26259
  var searchParams = new URLSearchParams();
@@ -25666,11 +26352,11 @@ define((function () { 'use strict';
25666
26352
  this._loadTargetingIfNeeded();
25667
26353
  }.bind(this)).catch(function(error) {
25668
26354
  this.markFetchComplete();
25669
- logger.error(error);
26355
+ logger$1.error(error);
25670
26356
  }.bind(this));
25671
26357
  }.bind(this)).catch(function(error) {
25672
26358
  this.markFetchComplete();
25673
- logger.error(error);
26359
+ logger$1.error(error);
25674
26360
  }.bind(this));
25675
26361
 
25676
26362
  return this.fetchPromise;
@@ -25678,7 +26364,7 @@ define((function () { 'use strict';
25678
26364
 
25679
26365
  FeatureFlagManager.prototype.markFetchComplete = function() {
25680
26366
  if (!this._fetchInProgressStartTime) {
25681
- logger.error('Fetch in progress started time not set, cannot mark fetch complete');
26367
+ logger$1.error('Fetch in progress started time not set, cannot mark fetch complete');
25682
26368
  return;
25683
26369
  }
25684
26370
  this._fetchStartTime = this._fetchInProgressStartTime;
@@ -25700,7 +26386,7 @@ define((function () { 'use strict';
25700
26386
 
25701
26387
  if (hasPropertyFilters) {
25702
26388
  this.getTargeting().then(function() {
25703
- logger.log('targeting loaded for property filter evaluation');
26389
+ logger$1.log('targeting loaded for property filter evaluation');
25704
26390
  });
25705
26391
  }
25706
26392
  };
@@ -25715,7 +26401,7 @@ define((function () { 'use strict';
25715
26401
  this.loadExtraBundle.bind(this),
25716
26402
  this.targetingSrc
25717
26403
  ).catch(function(error) {
25718
- logger.error('Failed to load targeting: ' + error);
26404
+ logger$1.error('Failed to load targeting: ' + error);
25719
26405
  }.bind(this));
25720
26406
  };
25721
26407
 
@@ -25769,7 +26455,7 @@ define((function () { 'use strict';
25769
26455
 
25770
26456
  // If no targeting library and event has property filters, skip it
25771
26457
  if (!targeting && pendingEvent['property_filters'] && !_.isEmptyObject(pendingEvent['property_filters'])) {
25772
- logger.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
26458
+ logger$1.warn('Skipping event check for "' + flagKey + '" - property filters require targeting library');
25773
26459
  return;
25774
26460
  }
25775
26461
 
@@ -25792,7 +26478,7 @@ define((function () { 'use strict';
25792
26478
  }
25793
26479
 
25794
26480
  if (matchResult.error) {
25795
- logger.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
26481
+ logger$1.error('Error checking first-time event for flag "' + flagKey + '": ' + matchResult.error);
25796
26482
  return;
25797
26483
  }
25798
26484
 
@@ -25800,7 +26486,7 @@ define((function () { 'use strict';
25800
26486
  return;
25801
26487
  }
25802
26488
 
25803
- logger.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
26489
+ logger$1.log('First-time event matched for flag "' + flagKey + '": ' + eventName);
25804
26490
 
25805
26491
  var newVariant = {
25806
26492
  'key': pendingEvent['pending_variant']['variant_key'],
@@ -25841,7 +26527,7 @@ define((function () { 'use strict';
25841
26527
  'first_time_event_hash': firstTimeEventHash
25842
26528
  };
25843
26529
 
25844
- logger.log('Recording first-time event for flag: ' + flagId);
26530
+ logger$1.log('Recording first-time event for flag: ' + flagId);
25845
26531
 
25846
26532
  // Fire-and-forget POST request
25847
26533
  this.fetch.call(win, url, {
@@ -25854,130 +26540,446 @@ define((function () { 'use strict';
25854
26540
  'body': JSON.stringify(payload)
25855
26541
  }).catch(function(error) {
25856
26542
  // Silent failure - cohort sync will catch up
25857
- logger.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
26543
+ logger$1.error('Failed to record first-time event for flag ' + flagId + ': ' + error);
26544
+ });
26545
+ };
26546
+
26547
+ FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
26548
+ if (!this.fetchPromise) {
26549
+ return new Promise(function(resolve) {
26550
+ logger$1.critical('Feature Flags not initialized');
26551
+ resolve(fallback);
26552
+ });
26553
+ }
26554
+
26555
+ return this.fetchPromise.then(function() {
26556
+ return this.getVariantSync(featureName, fallback);
26557
+ }.bind(this)).catch(function(error) {
26558
+ logger$1.error(error);
26559
+ return fallback;
26560
+ });
26561
+ };
26562
+
26563
+ FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
26564
+ if (!this.areFlagsReady()) {
26565
+ logger$1.log('Flags not loaded yet');
26566
+ return fallback;
26567
+ }
26568
+ var feature = this.flags.get(featureName);
26569
+ if (!feature) {
26570
+ logger$1.log('No flag found: "' + featureName + '"');
26571
+ return fallback;
26572
+ }
26573
+ this.trackFeatureCheck(featureName, feature);
26574
+ return feature;
26575
+ };
26576
+
26577
+ FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
26578
+ return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
26579
+ return feature['value'];
26580
+ }).catch(function(error) {
26581
+ logger$1.error(error);
26582
+ return fallbackValue;
26583
+ });
26584
+ };
26585
+
26586
+ // TODO remove deprecated method
26587
+ FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
26588
+ logger$1.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
26589
+ return this.getVariantValue(featureName, fallbackValue);
26590
+ };
26591
+
26592
+ FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
26593
+ return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
26594
+ };
26595
+
26596
+ FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
26597
+ return this.getVariantValue(featureName).then(function() {
26598
+ return this.isEnabledSync(featureName, fallbackValue);
26599
+ }.bind(this)).catch(function(error) {
26600
+ logger$1.error(error);
26601
+ return fallbackValue;
25858
26602
  });
25859
26603
  };
25860
26604
 
25861
- FeatureFlagManager.prototype.getVariant = function(featureName, fallback) {
25862
- if (!this.fetchPromise) {
25863
- return new Promise(function(resolve) {
25864
- logger.critical('Feature Flags not initialized');
25865
- resolve(fallback);
25866
- });
26605
+ FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
26606
+ fallbackValue = fallbackValue || false;
26607
+ var val = this.getVariantValueSync(featureName, fallbackValue);
26608
+ if (val !== true && val !== false) {
26609
+ logger$1.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
26610
+ val = fallbackValue;
26611
+ }
26612
+ return val;
26613
+ };
26614
+
26615
+ FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
26616
+ if (this.trackedFeatures.has(featureName)) {
26617
+ return;
26618
+ }
26619
+ this.trackedFeatures.add(featureName);
26620
+
26621
+ var trackingProperties = {
26622
+ 'Experiment name': featureName,
26623
+ 'Variant name': feature['key'],
26624
+ '$experiment_type': 'feature_flag',
26625
+ 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
26626
+ 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
26627
+ 'Variant fetch latency (ms)': this._fetchLatency,
26628
+ 'Variant fetch traceparent': this._traceparent,
26629
+ };
26630
+
26631
+ if (feature['experiment_id'] !== 'undefined') {
26632
+ trackingProperties['$experiment_id'] = feature['experiment_id'];
26633
+ }
26634
+ if (feature['is_experiment_active'] !== 'undefined') {
26635
+ trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
26636
+ }
26637
+ if (feature['is_qa_tester'] !== 'undefined') {
26638
+ trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26639
+ }
26640
+
26641
+ this.track('$experiment_started', trackingProperties);
26642
+ };
26643
+
26644
+ FeatureFlagManager.prototype.minApisSupported = function() {
26645
+ return !!this.fetch &&
26646
+ typeof Promise !== 'undefined' &&
26647
+ typeof Map !== 'undefined' &&
26648
+ typeof Set !== 'undefined';
26649
+ };
26650
+
26651
+ safewrapClass(FeatureFlagManager);
26652
+
26653
+ FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
26654
+ FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
26655
+ FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
26656
+ FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
26657
+ FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
26658
+ FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
26659
+ FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
26660
+ FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
26661
+
26662
+ // Deprecated method
26663
+ FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
26664
+
26665
+ // Exports intended only for testing
26666
+ FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
26667
+
26668
+ /* eslint camelcase: "off" */
26669
+
26670
+
26671
+ var logger = console_with_prefix('recorder');
26672
+
26673
+ var IFRAME_HANDSHAKE_REQUEST = 'mp_iframe_handshake_request';
26674
+ var IFRAME_HANDSHAKE_RESPONSE = 'mp_iframe_handshake_response';
26675
+
26676
+
26677
+ /**
26678
+ * RecorderManager: manages session recording initialization, lifecycle and state
26679
+ * @constructor
26680
+ */
26681
+ var RecorderManager = function(initOptions) {
26682
+ // TODO - Passing in mixpanel instance as it is still needed for recorder creation
26683
+ // but ideally we should be able to remove this dependency.
26684
+ this.mixpanelInstance = initOptions.mixpanelInstance;
26685
+
26686
+ this.getMpConfig = initOptions.getConfigFunc;
26687
+ this.getTabId = initOptions.getTabIdFunc;
26688
+ this.reportError = initOptions.reportErrorFunc;
26689
+ this.getDistinctId = initOptions.getDistinctIdFunc;
26690
+ this.loadExtraBundle = initOptions.loadExtraBundle;
26691
+ this.recorderSrc = initOptions.recorderSrc;
26692
+ this.targetingSrc = initOptions.targetingSrc;
26693
+ this.libBasePath = initOptions.libBasePath;
26694
+
26695
+ this._recorder = null;
26696
+ this._parentReplayId = null;
26697
+ this._parentFrameRetryInterval = null;
26698
+ };
26699
+
26700
+ RecorderManager.prototype.shouldLoadRecorder = function() {
26701
+ if (this.getMpConfig('disable_persistence')) {
26702
+ console$1.log('Load recorder check skipped due to disable_persistence config');
26703
+ return PromisePolyfill.resolve(false);
26704
+ }
26705
+
26706
+ var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26707
+ var tab_id = this.getTabId();
26708
+ return recording_registry_idb.init()
26709
+ .then(function () {
26710
+ return recording_registry_idb.getAll();
26711
+ })
26712
+ .then(function (recordings) {
26713
+ for (var i = 0; i < recordings.length; i++) {
26714
+ // if there are expired recordings in the registry, we should load the recorder to flush them
26715
+ // if there's a recording for this tab id, we should load the recorder to continue the recording
26716
+ if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
26717
+ return true;
26718
+ }
26719
+ }
26720
+ return false;
26721
+ })
26722
+ .catch(_.bind(function (err) {
26723
+ this.reportError('Error checking recording registry', err);
26724
+ return false;
26725
+ }, this));
26726
+ };
26727
+
26728
+ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
26729
+ if (!win['MutationObserver']) {
26730
+ console$1.critical('Browser does not support MutationObserver; skipping session recording');
26731
+ return PromisePolyfill.resolve();
26732
+ }
26733
+
26734
+ var loadRecorder = _.bind(function(startNewIfInactive) {
26735
+ return new PromisePolyfill(_.bind(function(resolve) {
26736
+ var handleLoadedRecorder = safewrap(_.bind(function() {
26737
+ this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
26738
+ this._recorder['resumeRecording'](startNewIfInactive);
26739
+ resolve();
26740
+ }, this));
26741
+
26742
+ if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
26743
+ var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
26744
+ this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
26745
+ } else {
26746
+ handleLoadedRecorder();
26747
+ }
26748
+ }, this));
26749
+ }, this);
26750
+
26751
+ // Cross-origin iframe handling
26752
+ var allowedOrigins = validateAllowedOrigins(this.getMpConfig('record_allowed_iframe_origins'), logger);
26753
+ var isCrossOriginRecordingEnabled = allowedOrigins.length > 0;
26754
+
26755
+ if (isCrossOriginRecordingEnabled) {
26756
+ // listen for handshake requests from their own child iframes (including nested)
26757
+ this._setupParentFrameListener(allowedOrigins);
26758
+
26759
+ if (win.parent !== win) {
26760
+ // also wait for parent's replay ID
26761
+ this._setupChildFrameListener(allowedOrigins, loadRecorder);
26762
+ this._sendParentFrameRequestWithRetry(allowedOrigins);
26763
+ return PromisePolyfill.resolve();
26764
+ }
26765
+ }
26766
+
26767
+ /**
26768
+ * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
26769
+ * 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.
26770
+ */
26771
+ var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
26772
+ var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
26773
+ if (force_start || is_sampled) {
26774
+ return loadRecorder(true);
26775
+ } else {
26776
+ return this.shouldLoadRecorder()
26777
+ .then(_.bind(function (shouldLoad) {
26778
+ if (shouldLoad) {
26779
+ return loadRecorder(false);
26780
+ }
26781
+ return PromisePolyfill.resolve();
26782
+ }, this));
26783
+ }
26784
+ };
26785
+
26786
+ RecorderManager.prototype.isRecording = function() {
26787
+ // Safety check: ensure isRecording method exists (older CDN builds may not have it)
26788
+ if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
26789
+ return false;
26790
+ }
26791
+ try {
26792
+ return this._recorder['isRecording']();
26793
+ } catch (e) {
26794
+ this.reportError('Error checking if recording is active', e);
26795
+ return false;
25867
26796
  }
26797
+ };
25868
26798
 
25869
- return this.fetchPromise.then(function() {
25870
- return this.getVariantSync(featureName, fallback);
25871
- }.bind(this)).catch(function(error) {
25872
- logger.error(error);
25873
- return fallback;
25874
- });
26799
+ RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
26800
+ var isRecording = this.isRecording();
26801
+ var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
26802
+
26803
+ if (!isRecording && recordingTriggerEvents) {
26804
+ var trigger = recordingTriggerEvents[event_name];
26805
+ if (trigger && typeof trigger['percentage'] === 'number') {
26806
+ var newRate = trigger['percentage'];
26807
+ var propertyFilters = trigger['property_filters'];
26808
+ if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
26809
+ var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
26810
+ getTargetingPromise(this.loadExtraBundle, targetingSrc)
26811
+ .then(function(targeting) {
26812
+ try {
26813
+ var result = targeting['eventMatchesCriteria'](
26814
+ event_name,
26815
+ properties,
26816
+ {
26817
+ 'event_name': event_name,
26818
+ 'property_filters': propertyFilters
26819
+ }
26820
+ );
26821
+ if (result['matches']) {
26822
+ this.checkAndStartSessionRecording(false, newRate);
26823
+ }
26824
+ } catch (err) {
26825
+ console$1.critical('Could not parse recording event trigger properties logic:', err);
26826
+ }
26827
+ }.bind(this)).catch(function(err) {
26828
+ console$1.critical('Failed to load targeting library:', err);
26829
+ });
26830
+ } else {
26831
+ this.checkAndStartSessionRecording(false, newRate);
26832
+ }
26833
+ }
26834
+ }
25875
26835
  };
25876
26836
 
25877
- FeatureFlagManager.prototype.getVariantSync = function(featureName, fallback) {
25878
- if (!this.areFlagsReady()) {
25879
- logger.log('Flags not loaded yet');
25880
- return fallback;
26837
+ RecorderManager.prototype.stopSessionRecording = function() {
26838
+ if (this._recorder) {
26839
+ return this._recorder['stopRecording']();
25881
26840
  }
25882
- var feature = this.flags.get(featureName);
25883
- if (!feature) {
25884
- logger.log('No flag found: "' + featureName + '"');
25885
- return fallback;
26841
+ return PromisePolyfill.resolve();
26842
+ };
26843
+
26844
+ RecorderManager.prototype.pauseSessionRecording = function() {
26845
+ if (this._recorder) {
26846
+ return this._recorder['pauseRecording']();
25886
26847
  }
25887
- this.trackFeatureCheck(featureName, feature);
25888
- return feature;
26848
+ return PromisePolyfill.resolve();
25889
26849
  };
25890
26850
 
25891
- FeatureFlagManager.prototype.getVariantValue = function(featureName, fallbackValue) {
25892
- return this.getVariant(featureName, {'value': fallbackValue}).then(function(feature) {
25893
- return feature['value'];
25894
- }).catch(function(error) {
25895
- logger.error(error);
25896
- return fallbackValue;
25897
- });
26851
+ RecorderManager.prototype.resumeSessionRecording = function() {
26852
+ if (this._recorder) {
26853
+ return this._recorder['resumeRecording']();
26854
+ }
26855
+ return PromisePolyfill.resolve();
25898
26856
  };
25899
26857
 
25900
- // TODO remove deprecated method
25901
- FeatureFlagManager.prototype.getFeatureData = function(featureName, fallbackValue) {
25902
- logger.critical('mixpanel.flags.get_feature_data() is deprecated and will be removed in a future release. Use mixpanel.flags.get_variant_value() instead.');
25903
- return this.getVariantValue(featureName, fallbackValue);
26858
+ RecorderManager.prototype.isRecordingHeatmapData = function() {
26859
+ return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
25904
26860
  };
25905
26861
 
25906
- FeatureFlagManager.prototype.getVariantValueSync = function(featureName, fallbackValue) {
25907
- return this.getVariantSync(featureName, {'value': fallbackValue})['value'];
26862
+ RecorderManager.prototype.getSessionRecordingProperties = function() {
26863
+ var props = {};
26864
+ var replay_id = this.getSessionReplayId();
26865
+ if (replay_id) {
26866
+ props['$mp_replay_id'] = replay_id;
26867
+ }
26868
+ return props;
25908
26869
  };
25909
26870
 
25910
- FeatureFlagManager.prototype.isEnabled = function(featureName, fallbackValue) {
25911
- return this.getVariantValue(featureName).then(function() {
25912
- return this.isEnabledSync(featureName, fallbackValue);
25913
- }.bind(this)).catch(function(error) {
25914
- logger.error(error);
25915
- return fallbackValue;
25916
- });
26871
+ RecorderManager.prototype.getSessionReplayUrl = function() {
26872
+ var replay_url = null;
26873
+ var replay_id = this.getSessionReplayId();
26874
+ if (replay_id) {
26875
+ var query_params = _.HTTPBuildQuery({
26876
+ 'replay_id': replay_id,
26877
+ 'distinct_id': this.getDistinctId(),
26878
+ 'token': this.getMpConfig('token')
26879
+ });
26880
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
26881
+ }
26882
+ return replay_url;
25917
26883
  };
25918
26884
 
25919
- FeatureFlagManager.prototype.isEnabledSync = function(featureName, fallbackValue) {
25920
- fallbackValue = fallbackValue || false;
25921
- var val = this.getVariantValueSync(featureName, fallbackValue);
25922
- if (val !== true && val !== false) {
25923
- logger.error('Feature flag "' + featureName + '" value: ' + val + ' is not a boolean; returning fallback value: ' + fallbackValue);
25924
- val = fallbackValue;
26885
+ RecorderManager.prototype.getSessionReplayId = function() {
26886
+ // Child iframe uses parent's replay ID
26887
+ if (this._parentReplayId) {
26888
+ return this._parentReplayId;
25925
26889
  }
25926
- return val;
26890
+ var replay_id = null;
26891
+ if (this._recorder) {
26892
+ replay_id = this._recorder['replayId'];
26893
+ }
26894
+ return replay_id || null;
25927
26895
  };
25928
26896
 
25929
- FeatureFlagManager.prototype.trackFeatureCheck = function(featureName, feature) {
25930
- if (this.trackedFeatures.has(featureName)) {
26897
+ // "private" public method to reach into the recorder in test cases
26898
+ RecorderManager.prototype.getRecorder = function() {
26899
+ return this._recorder;
26900
+ };
26901
+
26902
+ RecorderManager.prototype._setupChildFrameListener = function(allowedOrigins, loadRecorder) {
26903
+ if (this._childFrameMessageHandler) {
25931
26904
  return;
25932
26905
  }
25933
- this.trackedFeatures.add(featureName);
25934
-
25935
- var trackingProperties = {
25936
- 'Experiment name': featureName,
25937
- 'Variant name': feature['key'],
25938
- '$experiment_type': 'feature_flag',
25939
- 'Variant fetch start time': new Date(this._fetchStartTime).toISOString(),
25940
- 'Variant fetch complete time': new Date(this._fetchCompleteTime).toISOString(),
25941
- 'Variant fetch latency (ms)': this._fetchLatency,
25942
- 'Variant fetch traceparent': this._traceparent,
26906
+ var self = this;
26907
+ this._childFrameMessageHandler = function(event) {
26908
+ if (allowedOrigins.indexOf(event.origin) === -1) return;
26909
+ var data = event.data;
26910
+ if (data && data['type'] === IFRAME_HANDSHAKE_RESPONSE && data['token'] === self.getMpConfig('token') && data['replayId']) {
26911
+ self._parentReplayId = data['replayId'];
26912
+ if (data['distinctId']) {
26913
+ self.mixpanelInstance['identify'](data['distinctId']);
26914
+ }
26915
+ self._parentFrameRetryActive = false;
26916
+ win.removeEventListener('message', self._childFrameMessageHandler);
26917
+ self._childFrameMessageHandler = null;
26918
+ loadRecorder(true);
26919
+ }
25943
26920
  };
26921
+ win.addEventListener('message', this._childFrameMessageHandler);
26922
+ };
25944
26923
 
25945
- if (feature['experiment_id'] !== 'undefined') {
25946
- trackingProperties['$experiment_id'] = feature['experiment_id'];
25947
- }
25948
- if (feature['is_experiment_active'] !== 'undefined') {
25949
- trackingProperties['$is_experiment_active'] = feature['is_experiment_active'];
25950
- }
25951
- if (feature['is_qa_tester'] !== 'undefined') {
25952
- trackingProperties['$is_qa_tester'] = feature['is_qa_tester'];
26924
+ RecorderManager.prototype._sendParentFrameRequest = function(allowedOrigins) {
26925
+ var message = {};
26926
+ message['type'] = IFRAME_HANDSHAKE_REQUEST;
26927
+ message['token'] = this.getMpConfig('token');
26928
+ for (var i = 0; i < allowedOrigins.length; i++) {
26929
+ try {
26930
+ win.parent.postMessage(message, allowedOrigins[i]);
26931
+ } catch (e) {
26932
+ // origin mismatch - ignore
26933
+ }
25953
26934
  }
25954
-
25955
- this.track('$experiment_started', trackingProperties);
25956
26935
  };
25957
26936
 
25958
- FeatureFlagManager.prototype.minApisSupported = function() {
25959
- return !!this.fetch &&
25960
- typeof Promise !== 'undefined' &&
25961
- typeof Map !== 'undefined' &&
25962
- typeof Set !== 'undefined';
25963
- };
26937
+ RecorderManager.prototype._sendParentFrameRequestWithRetry = function(allowedOrigins) {
26938
+ var self = this;
26939
+ var maxRetries = 10;
26940
+ var retryCount = 0;
26941
+ var delay = 50;
26942
+ this._parentFrameRetryActive = true;
25964
26943
 
25965
- safewrapClass(FeatureFlagManager);
26944
+ this._sendParentFrameRequest(allowedOrigins);
25966
26945
 
25967
- FeatureFlagManager.prototype['are_flags_ready'] = FeatureFlagManager.prototype.areFlagsReady;
25968
- FeatureFlagManager.prototype['get_variant'] = FeatureFlagManager.prototype.getVariant;
25969
- FeatureFlagManager.prototype['get_variant_sync'] = FeatureFlagManager.prototype.getVariantSync;
25970
- FeatureFlagManager.prototype['get_variant_value'] = FeatureFlagManager.prototype.getVariantValue;
25971
- FeatureFlagManager.prototype['get_variant_value_sync'] = FeatureFlagManager.prototype.getVariantValueSync;
25972
- FeatureFlagManager.prototype['is_enabled'] = FeatureFlagManager.prototype.isEnabled;
25973
- FeatureFlagManager.prototype['is_enabled_sync'] = FeatureFlagManager.prototype.isEnabledSync;
25974
- FeatureFlagManager.prototype['update_context'] = FeatureFlagManager.prototype.updateContext;
26946
+ function scheduleRetry() {
26947
+ setTimeout(function() {
26948
+ if (!self._parentFrameRetryActive || self._parentReplayId || ++retryCount >= maxRetries) {
26949
+ return;
26950
+ }
26951
+ self._sendParentFrameRequest(allowedOrigins);
26952
+ delay *= 2;
26953
+ scheduleRetry();
26954
+ }, delay);
26955
+ }
26956
+ scheduleRetry();
26957
+ };
25975
26958
 
25976
- // Deprecated method
25977
- FeatureFlagManager.prototype['get_feature_data'] = FeatureFlagManager.prototype.getFeatureData;
26959
+ RecorderManager.prototype._setupParentFrameListener = function(allowedOrigins) {
26960
+ if (this._parentFrameMessageHandler) {
26961
+ return;
26962
+ }
26963
+ var self = this;
26964
+ this._parentFrameMessageHandler = function(event) {
26965
+ if (allowedOrigins.indexOf(event.origin) === -1) return;
26966
+ var data = event.data;
26967
+ if (data && data['type'] === IFRAME_HANDSHAKE_REQUEST && data['token'] === self.getMpConfig('token')) {
26968
+ var replayId = self.getSessionReplayId();
26969
+ if (replayId) {
26970
+ var response = {};
26971
+ response['type'] = IFRAME_HANDSHAKE_RESPONSE;
26972
+ response['token'] = self.getMpConfig('token');
26973
+ response['replayId'] = replayId;
26974
+ response['distinctId'] = self.getDistinctId();
26975
+ event.source.postMessage(response, event.origin);
26976
+ }
26977
+ }
26978
+ };
26979
+ win.addEventListener('message', this._parentFrameMessageHandler);
26980
+ };
25978
26981
 
25979
- // Exports intended only for testing
25980
- FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getTargeting;
26982
+ safewrapClass(RecorderManager);
25981
26983
 
25982
26984
  /* eslint camelcase: "off" */
25983
26985
 
@@ -27351,7 +28353,6 @@ define((function () { 'use strict';
27351
28353
  /** @const */ var SETTING_FALLBACK = 'fallback';
27352
28354
  /** @const */ var SETTING_DISABLED = 'disabled';
27353
28355
 
27354
-
27355
28356
  /*
27356
28357
  * Dynamic... constants? Is that an oxymoron?
27357
28358
  */
@@ -27436,19 +28437,24 @@ define((function () { 'use strict';
27436
28437
  'batch_request_timeout_ms': 90000,
27437
28438
  'batch_autostart': true,
27438
28439
  'hooks': {},
28440
+ 'record_allowed_iframe_origins': [],
27439
28441
  'record_block_class': new RegExp('^(mp-block|fs-exclude|amp-block|rr-block|ph-no-capture)$'),
27440
28442
  'record_block_selector': 'img, video, audio',
27441
28443
  'record_canvas': false,
27442
28444
  'record_collect_fonts': false,
27443
28445
  'record_console': true,
27444
28446
  'record_heatmap_data': false,
28447
+ 'recording_event_triggers': {},
27445
28448
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
27446
28449
  'record_mask_inputs': true,
27447
28450
  'record_max_ms': MAX_RECORDING_MS,
27448
28451
  'record_min_ms': 0,
28452
+ 'record_network': false,
28453
+ 'record_network_options': {},
27449
28454
  'record_sessions_percent': 0,
27450
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
27451
- 'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
28455
+ 'recorder_src': null,
28456
+ 'targeting_src': null,
28457
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
27452
28458
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
27453
28459
  };
27454
28460
 
@@ -27602,6 +28608,19 @@ define((function () { 'use strict';
27602
28608
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
27603
28609
  }));
27604
28610
 
28611
+ this.recorderManager = new RecorderManager({
28612
+ mixpanelInstance: this,
28613
+ getConfigFunc: _.bind(this.get_config, this),
28614
+ setConfigFunc: _.bind(this.set_config, this),
28615
+ getTabIdFunc: _.bind(this.get_tab_id, this),
28616
+ reportErrorFunc: _.bind(this.report_error, this),
28617
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
28618
+ recorderSrc: this.get_config('recorder_src'),
28619
+ targetingSrc: this.get_config('targeting_src'),
28620
+ libBasePath: this.get_config('lib_base_path'),
28621
+ loadExtraBundle: load_extra_bundle
28622
+ });
28623
+
27605
28624
  this['_jsc'] = NOOP_FUNC;
27606
28625
 
27607
28626
  this.__dom_loaded_queue = [];
@@ -27680,7 +28699,7 @@ define((function () { 'use strict';
27680
28699
  getPropertyFunc: _.bind(this.get_property, this),
27681
28700
  trackingFunc: _.bind(this.track, this),
27682
28701
  loadExtraBundle: load_extra_bundle,
27683
- targetingSrc: this.get_config('targeting_src')
28702
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
27684
28703
  });
27685
28704
  this.flags.init();
27686
28705
  this['flags'] = this.flags;
@@ -27693,11 +28712,11 @@ define((function () { 'use strict';
27693
28712
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
27694
28713
  var mode = this.get_config('remote_settings_mode');
27695
28714
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
27696
- this._fetch_remote_settings(mode).then(_.bind(function() {
27697
- this._check_and_start_session_recording();
28715
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
28716
+ return this._check_and_start_session_recording();
27698
28717
  }, this));
27699
28718
  } else {
27700
- this._check_and_start_session_recording();
28719
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
27701
28720
  }
27702
28721
  };
27703
28722
 
@@ -27741,132 +28760,50 @@ define((function () { 'use strict';
27741
28760
  return this.tab_id || null;
27742
28761
  };
27743
28762
 
27744
- MixpanelLib.prototype._should_load_recorder = function () {
27745
- if (this.get_config('disable_persistence')) {
27746
- console$1.log('Load recorder check skipped due to disable_persistence config');
27747
- return Promise.resolve(false);
27748
- }
27749
-
27750
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27751
- var tab_id = this.get_tab_id();
27752
- return recording_registry_idb.init()
27753
- .then(function () {
27754
- return recording_registry_idb.getAll();
27755
- })
27756
- .then(function (recordings) {
27757
- for (var i = 0; i < recordings.length; i++) {
27758
- // if there are expired recordings in the registry, we should load the recorder to flush them
27759
- // if there's a recording for this tab id, we should load the recorder to continue the recording
27760
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
27761
- return true;
27762
- }
27763
- }
27764
- return false;
27765
- })
27766
- .catch(_.bind(function (err) {
27767
- this.report_error('Error checking recording registry', err);
27768
- }, this));
27769
- };
27770
-
27771
28763
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
27772
- if (!win['MutationObserver']) {
27773
- console$1.critical('Browser does not support MutationObserver; skipping session recording');
27774
- return;
27775
- }
27776
-
27777
- var loadRecorder = _.bind(function(startNewIfInactive) {
27778
- var handleLoadedRecorder = _.bind(function() {
27779
- this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
27780
- this._recorder['resumeRecording'](startNewIfInactive);
27781
- }, this);
27782
-
27783
- if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
27784
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
27785
- } else {
27786
- handleLoadedRecorder();
27787
- }
27788
- }, this);
27789
-
27790
- /**
27791
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
27792
- * 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.
27793
- */
27794
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
27795
- if (force_start || is_sampled) {
27796
- loadRecorder(true);
27797
- } else {
27798
- this._should_load_recorder()
27799
- .then(function (shouldLoad) {
27800
- if (shouldLoad) {
27801
- loadRecorder(false);
27802
- }
27803
- });
27804
- }
28764
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
27805
28765
  });
27806
28766
 
28767
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28768
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28769
+ };
28770
+
27807
28771
  MixpanelLib.prototype.start_session_recording = function () {
27808
- this._check_and_start_session_recording(true);
28772
+ return this._check_and_start_session_recording(true);
27809
28773
  };
27810
28774
 
27811
28775
  MixpanelLib.prototype.stop_session_recording = function () {
27812
- if (this._recorder) {
27813
- return this._recorder['stopRecording']();
27814
- }
27815
- return Promise.resolve();
28776
+ return this.recorderManager.stopSessionRecording();
27816
28777
  };
27817
28778
 
27818
28779
  MixpanelLib.prototype.pause_session_recording = function () {
27819
- if (this._recorder) {
27820
- return this._recorder['pauseRecording']();
27821
- }
27822
- return Promise.resolve();
28780
+ return this.recorderManager.pauseSessionRecording();
27823
28781
  };
27824
28782
 
27825
28783
  MixpanelLib.prototype.resume_session_recording = function () {
27826
- if (this._recorder) {
27827
- return this._recorder['resumeRecording']();
27828
- }
27829
- return Promise.resolve();
28784
+ return this.recorderManager.resumeSessionRecording();
27830
28785
  };
27831
28786
 
27832
28787
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
27833
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
28788
+ return this.recorderManager.isRecordingHeatmapData();
27834
28789
  };
27835
28790
 
27836
28791
  MixpanelLib.prototype.get_session_recording_properties = function () {
27837
- var props = {};
27838
- var replay_id = this._get_session_replay_id();
27839
- if (replay_id) {
27840
- props['$mp_replay_id'] = replay_id;
27841
- }
27842
- return props;
28792
+ return this.recorderManager.getSessionRecordingProperties();
27843
28793
  };
27844
28794
 
27845
28795
  MixpanelLib.prototype.get_session_replay_url = function () {
27846
- var replay_url = null;
27847
- var replay_id = this._get_session_replay_id();
27848
- if (replay_id) {
27849
- var query_params = _.HTTPBuildQuery({
27850
- 'replay_id': replay_id,
27851
- 'distinct_id': this.get_distinct_id(),
27852
- 'token': this.get_config('token')
27853
- });
27854
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
27855
- }
27856
- return replay_url;
27857
- };
27858
-
27859
- MixpanelLib.prototype._get_session_replay_id = function () {
27860
- var replay_id = null;
27861
- if (this._recorder) {
27862
- replay_id = this._recorder['replayId'];
27863
- }
27864
- return replay_id || null;
28796
+ return this.recorderManager.getSessionReplayUrl();
27865
28797
  };
27866
28798
 
27867
28799
  // "private" public method to reach into the recorder in test cases
27868
28800
  MixpanelLib.prototype.__get_recorder = function () {
27869
- return this._recorder;
28801
+ return this.recorderManager.getRecorder();
28802
+ };
28803
+
28804
+ // "private" public method to get session recording init promise in test cases
28805
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
28806
+ return this.__session_recording_init_promise;
27870
28807
  };
27871
28808
 
27872
28809
  // Private methods
@@ -28124,6 +29061,7 @@ define((function () { 'use strict';
28124
29061
  };
28125
29062
 
28126
29063
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
29064
+ var self = this;
28127
29065
  var disableRecordingIfStrict = function() {
28128
29066
  if (mode === 'strict') {
28129
29067
  self.set_config({'record_sessions_percent': 0});
@@ -28144,7 +29082,6 @@ define((function () { 'use strict';
28144
29082
  };
28145
29083
  var query_string = _.HTTPBuildQuery(request_params);
28146
29084
  var full_url = settings_endpoint + '?' + query_string;
28147
- var self = this;
28148
29085
 
28149
29086
  var abortController = new AbortController();
28150
29087
  var timeout_id = setTimeout(function() {
@@ -28336,6 +29273,34 @@ define((function () { 'use strict';
28336
29273
  this._execute_array([item]);
28337
29274
  };
28338
29275
 
29276
+ /**
29277
+ * Enables events on the Mixpanel object. If passed no arguments,
29278
+ * this function enable tracking of all events. If passed an
29279
+ * array of event names, those events will be enabled, but other
29280
+ * existing disabled events will continue to be not tracked.
29281
+ *
29282
+ * @param {Array} [events] An array of event names to enable
29283
+ */
29284
+ MixpanelLib.prototype.enable = function(events) {
29285
+ var keys, new_disabled_events, i, j;
29286
+
29287
+ if (typeof(events) === 'undefined') {
29288
+ this._flags.disable_all_events = false;
29289
+ } else {
29290
+ keys = {};
29291
+ new_disabled_events = [];
29292
+ for (i = 0; i < events.length; i++) {
29293
+ keys[events[i]] = true;
29294
+ }
29295
+ for (j = 0; j < this.__disabled_events.length; j++) {
29296
+ if (!keys[this.__disabled_events[j]]) {
29297
+ new_disabled_events.push(this.__disabled_events[j]);
29298
+ }
29299
+ }
29300
+ this.__disabled_events = new_disabled_events;
29301
+ }
29302
+ };
29303
+
28339
29304
  /**
28340
29305
  * Disable events on the Mixpanel object. If passed no arguments,
28341
29306
  * this function disables tracking of any event. If passed an
@@ -28509,6 +29474,8 @@ define((function () { 'use strict';
28509
29474
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
28510
29475
  }
28511
29476
 
29477
+ this._start_recording_on_event(event_name, properties);
29478
+
28512
29479
  var data = {
28513
29480
  'event': event_name,
28514
29481
  'properties': properties
@@ -29717,6 +30684,7 @@ define((function () { 'use strict';
29717
30684
  // MixpanelLib Exports
29718
30685
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
29719
30686
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
30687
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
29720
30688
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
29721
30689
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
29722
30690
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -29760,6 +30728,7 @@ define((function () { 'use strict';
29760
30728
 
29761
30729
  // Exports intended only for testing
29762
30730
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
30731
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
29763
30732
 
29764
30733
  // MixpanelPersistence Exports
29765
30734
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;