mixpanel-browser 2.75.0 → 2.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.github/workflows/integration-tests.yml +2 -2
  3. package/.github/workflows/unit-tests.yml +2 -2
  4. package/CHANGELOG.md +10 -0
  5. package/build.sh +10 -8
  6. package/dist/async-modules/mixpanel-recorder-bIS4LMGd.js +23595 -0
  7. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js +2 -0
  8. package/dist/async-modules/mixpanel-recorder-hFoTniVR.min.js.map +1 -0
  9. package/dist/async-modules/mixpanel-targeting-BcAPS-Mz.js +2520 -0
  10. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js +2 -0
  11. package/dist/async-modules/mixpanel-targeting-VOeN7RWY.min.js.map +1 -0
  12. package/dist/mixpanel-core.cjs.d.ts +68 -0
  13. package/dist/mixpanel-core.cjs.js +550 -383
  14. package/dist/mixpanel-recorder.js +708 -32
  15. package/dist/mixpanel-recorder.min.js +1 -1
  16. package/dist/mixpanel-recorder.min.js.map +1 -1
  17. package/dist/mixpanel-targeting.js +6 -62
  18. package/dist/mixpanel-targeting.min.js +1 -1
  19. package/dist/mixpanel-targeting.min.js.map +1 -1
  20. package/dist/mixpanel-with-async-modules.cjs.d.ts +68 -0
  21. package/dist/mixpanel-with-async-modules.cjs.js +550 -383
  22. package/dist/mixpanel-with-async-recorder.cjs.d.ts +68 -0
  23. package/dist/mixpanel-with-async-recorder.cjs.js +550 -383
  24. package/dist/mixpanel-with-recorder.d.ts +68 -0
  25. package/dist/mixpanel-with-recorder.js +1036 -197
  26. package/dist/mixpanel-with-recorder.min.d.ts +68 -0
  27. package/dist/mixpanel-with-recorder.min.js +1 -1
  28. package/dist/mixpanel.amd.d.ts +68 -0
  29. package/dist/mixpanel.amd.js +1038 -251
  30. package/dist/mixpanel.cjs.d.ts +68 -0
  31. package/dist/mixpanel.cjs.js +1038 -251
  32. package/dist/mixpanel.globals.js +550 -383
  33. package/dist/mixpanel.min.js +184 -181
  34. package/dist/mixpanel.module.d.ts +68 -0
  35. package/dist/mixpanel.module.js +1038 -251
  36. package/dist/mixpanel.umd.d.ts +68 -0
  37. package/dist/mixpanel.umd.js +1038 -251
  38. package/logo.svg +5 -0
  39. package/package.json +2 -1
  40. package/rollup.config.mjs +163 -46
  41. package/src/autocapture/index.js +10 -27
  42. package/src/config.js +9 -3
  43. package/src/flags/index.js +1 -2
  44. package/src/index.d.ts +68 -0
  45. package/src/mixpanel-core.js +76 -111
  46. package/src/recorder/index.js +1 -1
  47. package/src/recorder/recorder.js +5 -1
  48. package/src/recorder/rrweb-network-plugin.js +649 -0
  49. package/src/recorder/session-recording.js +31 -11
  50. package/src/recorder-manager.js +216 -0
  51. package/src/request-batcher.js +1 -1
  52. package/src/targeting/event-matcher.js +2 -57
  53. package/src/targeting/index.js +1 -1
  54. package/src/targeting/loader.js +1 -1
  55. package/src/utils.js +13 -1
  56. package/testServer.js +55 -0
  57. package/src/globals.js +0 -14
@@ -23,16 +23,19 @@ if (typeof(window) === 'undefined') {
23
23
  win = window;
24
24
  }
25
25
 
26
- /**
27
- * Shared global window property names used across modules
28
- */
26
+ var Config = {
27
+ DEBUG: false,
28
+ LIB_VERSION: '2.76.0'
29
+ };
29
30
 
30
- // Targeting library global (used by flags and targeting modules)
31
+ // Window global names for async modules
31
32
  var TARGETING_GLOBAL_NAME = '__mp_targeting';
32
-
33
- // Recorder library global (used by recorder and mixpanel-core)
34
33
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
35
34
 
35
+ // Constants that are injected at build-time for the names of async modules.
36
+ var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
37
+ var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
38
+
36
39
  function _array_like_to_array(arr, len) {
37
40
  if (len == null || len > arr.length) len = arr.length;
38
41
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -18132,7 +18135,7 @@ var __defNormalProp = function(obj, key, value) {
18132
18135
  var __publicField = function(obj, key, value) {
18133
18136
  return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
18134
18137
  };
18135
- function patch(source, name, replacement) {
18138
+ function patch$3(source, name, replacement) {
18136
18139
  try {
18137
18140
  if (!(name in source)) {
18138
18141
  return function() {};
@@ -18549,7 +18552,7 @@ function initLogObserver(cb, win, options) {
18549
18552
  if (!_logger[level]) {
18550
18553
  return function() {};
18551
18554
  }
18552
- return patch(_logger, level, function(original) {
18555
+ return patch$3(_logger, level, function(original) {
18553
18556
  var _this1 = _this;
18554
18557
  return function() {
18555
18558
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
@@ -18970,11 +18973,6 @@ if (typeof Promise !== 'undefined' && Promise.toString().indexOf('[native code]'
18970
18973
  PromisePolyfill = NpoPromise;
18971
18974
  }
18972
18975
 
18973
- var Config = {
18974
- DEBUG: false,
18975
- LIB_VERSION: '2.75.0'
18976
- };
18977
-
18978
18976
  /* eslint camelcase: "off", eqeqeq: "off" */
18979
18977
 
18980
18978
  // Maximum allowed session recording length
@@ -20706,6 +20704,17 @@ var isOnline = function() {
20706
20704
 
20707
20705
  var NOOP_FUNC = function () {};
20708
20706
 
20707
+ var urlMatchesRegexList = function (url, regexList) {
20708
+ var matches = false;
20709
+ for (var i = 0; i < regexList.length; i++) {
20710
+ if (url.match(regexList[i])) {
20711
+ matches = true;
20712
+ break;
20713
+ }
20714
+ }
20715
+ return matches;
20716
+ };
20717
+
20709
20718
  var JSONStringify = null, JSONParse = null;
20710
20719
  if (typeof JSON !== 'undefined') {
20711
20720
  JSONStringify = JSON.stringify;
@@ -21177,7 +21186,7 @@ function _addOptOutCheck(method, getConfigValue) {
21177
21186
  };
21178
21187
  }
21179
21188
 
21180
- var logger$6 = console_with_prefix('lock');
21189
+ var logger$7 = console_with_prefix('lock');
21181
21190
 
21182
21191
  /**
21183
21192
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21229,7 +21238,7 @@ SharedLock.prototype.withLock = function(lockedCB, pid) {
21229
21238
 
21230
21239
  var delay = function(cb) {
21231
21240
  if (new Date().getTime() - startTime > timeoutMS) {
21232
- logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21241
+ logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21233
21242
  storage.removeItem(keyZ);
21234
21243
  storage.removeItem(keyY);
21235
21244
  loop();
@@ -21376,7 +21385,7 @@ LocalStorageWrapper.prototype.removeItem = function (key) {
21376
21385
  }, this));
21377
21386
  };
21378
21387
 
21379
- var logger$5 = console_with_prefix('batch');
21388
+ var logger$6 = console_with_prefix('batch');
21380
21389
 
21381
21390
  /**
21382
21391
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21405,7 +21414,7 @@ var RequestQueue = function (storageKey, options) {
21405
21414
  timeoutMS: options.sharedLockTimeoutMS,
21406
21415
  });
21407
21416
  }
21408
- this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
21417
+ this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
21409
21418
 
21410
21419
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21411
21420
 
@@ -21738,7 +21747,7 @@ RequestQueue.prototype.clear = function () {
21738
21747
  // maximum interval between request retries after exponential backoff
21739
21748
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21740
21749
 
21741
- var logger$4 = console_with_prefix('batch');
21750
+ var logger$5 = console_with_prefix('batch');
21742
21751
 
21743
21752
  /**
21744
21753
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21866,7 +21875,7 @@ RequestBatcher.prototype.sendRequestPromise = function(data, options) {
21866
21875
  */
21867
21876
  RequestBatcher.prototype.flush = function(options) {
21868
21877
  if (this.requestInProgress) {
21869
- logger$4.log('Flush: Request already in progress');
21878
+ logger$5.log('Flush: Request already in progress');
21870
21879
  return PromisePolyfill.resolve();
21871
21880
  }
21872
21881
 
@@ -22043,7 +22052,7 @@ RequestBatcher.prototype.flush = function(options) {
22043
22052
  if (options.unloading) {
22044
22053
  requestOptions.transport = 'sendBeacon';
22045
22054
  }
22046
- logger$4.log('MIXPANEL REQUEST:', dataForRequest);
22055
+ logger$5.log('MIXPANEL REQUEST:', dataForRequest);
22047
22056
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22048
22057
  }, this))
22049
22058
  .catch(_.bind(function(err) {
@@ -22056,7 +22065,7 @@ RequestBatcher.prototype.flush = function(options) {
22056
22065
  * Log error to global logger and optional user-defined logger.
22057
22066
  */
22058
22067
  RequestBatcher.prototype.reportError = function(msg, err) {
22059
- logger$4.error.apply(logger$4.error, arguments);
22068
+ logger$5.error.apply(logger$5.error, arguments);
22060
22069
  if (this.errorReporter) {
22061
22070
  try {
22062
22071
  if (!(err instanceof Error)) {
@@ -22064,7 +22073,7 @@ RequestBatcher.prototype.reportError = function(msg, err) {
22064
22073
  }
22065
22074
  this.errorReporter(msg, err);
22066
22075
  } catch(err) {
22067
- logger$4.error(err);
22076
+ logger$5.error(err);
22068
22077
  }
22069
22078
  }
22070
22079
  };
@@ -22186,7 +22195,7 @@ var EVENT_HANDLER_ATTRIBUTES = [
22186
22195
 
22187
22196
  var MAX_DEPTH = 5;
22188
22197
 
22189
- var logger$3 = console_with_prefix('autocapture');
22198
+ var logger$4 = console_with_prefix('autocapture');
22190
22199
 
22191
22200
 
22192
22201
  function getClasses(el) {
@@ -22450,7 +22459,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22450
22459
  return false;
22451
22460
  }
22452
22461
  } catch (err) {
22453
- logger$3.critical('Error while checking element in allowElementCallback', err);
22462
+ logger$4.critical('Error while checking element in allowElementCallback', err);
22454
22463
  return false;
22455
22464
  }
22456
22465
  }
@@ -22467,7 +22476,7 @@ function isElementAllowed(el, ev, allowElementCallback, allowSelectors) {
22467
22476
  return true;
22468
22477
  }
22469
22478
  } catch (err) {
22470
- logger$3.critical('Error while checking selector: ' + sel, err);
22479
+ logger$4.critical('Error while checking selector: ' + sel, err);
22471
22480
  }
22472
22481
  }
22473
22482
  return false;
@@ -22482,7 +22491,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22482
22491
  return true;
22483
22492
  }
22484
22493
  } catch (err) {
22485
- logger$3.critical('Error while checking element in blockElementCallback', err);
22494
+ logger$4.critical('Error while checking element in blockElementCallback', err);
22486
22495
  return true;
22487
22496
  }
22488
22497
  }
@@ -22496,7 +22505,7 @@ function isElementBlocked(el, ev, blockElementCallback, blockSelectors) {
22496
22505
  return true;
22497
22506
  }
22498
22507
  } catch (err) {
22499
- logger$3.critical('Error while checking selector: ' + sel, err);
22508
+ logger$4.critical('Error while checking selector: ' + sel, err);
22500
22509
  }
22501
22510
  }
22502
22511
  }
@@ -23043,6 +23052,655 @@ function shouldMaskText(element, privacyConfig) {
23043
23052
  }
23044
23053
  }
23045
23054
 
23055
+ /**
23056
+ * This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
23057
+ * the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
23058
+ *
23059
+ * This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
23060
+ * header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
23061
+ *
23062
+ */
23063
+
23064
+ var logger$3 = console_with_prefix('network-plugin');
23065
+
23066
+ /**
23067
+ * Get the time origin for converting performance timestamps to absolute timestamps.
23068
+ * Uses Date.now() - performance.now() instead of performance.timeOrigin because
23069
+ * browsers can report timeOrigin values that are skewed from actual time, and some
23070
+ * browsers (notably older Safari versions) don't implement timeOrigin at all.
23071
+ * See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
23072
+ * @param {Window} win
23073
+ * @returns {number}
23074
+ */
23075
+ function getTimeOrigin(win) {
23076
+ return Math.round(Date.now() - win.performance.now());
23077
+ }
23078
+
23079
+ /**
23080
+ * @typedef {import('../index.d.ts').InitiatorType} InitiatorType
23081
+ * @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
23082
+ * @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
23083
+ * @typedef {import('../index.d.ts').NetworkData} NetworkData
23084
+ */
23085
+
23086
+ /**
23087
+ * @typedef {Record<string, string>} Headers
23088
+ */
23089
+
23090
+ /**
23091
+ * @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
23092
+ */
23093
+
23094
+ /**
23095
+ * @callback networkCallback
23096
+ * @param {NetworkData} data
23097
+ * @returns {void}
23098
+ */
23099
+
23100
+ /**
23101
+ * @callback listenerHandler
23102
+ * @returns {void}
23103
+ */
23104
+
23105
+ /**
23106
+ * @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
23107
+ */
23108
+
23109
+ /**
23110
+ * @typedef {Object} RecordPlugin
23111
+ * @property {string} name
23112
+ * @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
23113
+ * @property {NetworkRecordOptions} [options]
23114
+ */
23115
+
23116
+ /** @type {Required<NetworkRecordOptions>} */
23117
+ var defaultNetworkOptions = {
23118
+ initiatorTypes: [
23119
+ 'audio',
23120
+ 'beacon',
23121
+ 'body',
23122
+ 'css',
23123
+ 'early-hint',
23124
+ 'embed',
23125
+ 'fetch',
23126
+ 'frame',
23127
+ 'iframe',
23128
+ 'icon',
23129
+ 'image',
23130
+ 'img',
23131
+ 'input',
23132
+ 'link',
23133
+ 'navigation',
23134
+ 'object',
23135
+ 'ping',
23136
+ 'script',
23137
+ 'track',
23138
+ 'video',
23139
+ 'xmlhttprequest',
23140
+ ],
23141
+ ignoreRequestFn: function() { return false; },
23142
+ recordHeaders: {
23143
+ request: [],
23144
+ response: [],
23145
+ },
23146
+ recordBodyUrls: {
23147
+ request: [],
23148
+ response: [],
23149
+ },
23150
+ recordInitialRequests: false,
23151
+ };
23152
+
23153
+ /**
23154
+ * @param {PerformanceEntry} entry
23155
+ * @returns {entry is PerformanceNavigationTiming}
23156
+ */
23157
+ function isNavigationTiming(entry) {
23158
+ return entry.entryType === 'navigation';
23159
+ }
23160
+
23161
+ /**
23162
+ * @param {PerformanceEntry} entry
23163
+ * @returns {entry is PerformanceResourceTiming}
23164
+ */
23165
+ function isResourceTiming (entry) {
23166
+ return entry.entryType === 'resource';
23167
+ }
23168
+
23169
+ function findLast(array, predicate) {
23170
+ var length = array.length;
23171
+ for (var i = length - 1; i >= 0; i -= 1) {
23172
+ if (predicate(array[i])) {
23173
+ return array[i];
23174
+ }
23175
+ }
23176
+ }
23177
+
23178
+ /**
23179
+ * Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
23180
+ * Adapted from Sentry's `fill` utility:
23181
+ * https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
23182
+ *
23183
+ * @param {object} source - The object containing the method to patch
23184
+ * @param {string} name - The method name to patch
23185
+ * @param {function} replacementFactory - A function that receives the original method and returns the replacement
23186
+ * @returns {function} A function that restores the original method
23187
+ */
23188
+ function patch(source, name, replacementFactory) {
23189
+ if (!(name in source) || typeof source[name] !== 'function') {
23190
+ return function() {};
23191
+ }
23192
+ var original = source[name];
23193
+ var wrapped = replacementFactory(original);
23194
+ source[name] = wrapped;
23195
+ return function() {
23196
+ source[name] = original;
23197
+ };
23198
+ }
23199
+
23200
+
23201
+ /**
23202
+ * Maximum body size to record (1MB)
23203
+ */
23204
+ var MAX_BODY_SIZE = 1024 * 1024;
23205
+
23206
+ /**
23207
+ * Truncate string if it exceeds max size
23208
+ * @param {string} str
23209
+ * @returns {string}
23210
+ */
23211
+ function truncateBody(str) {
23212
+ if (!str || typeof str !== 'string') {
23213
+ return str;
23214
+ }
23215
+ if (str.length > MAX_BODY_SIZE) {
23216
+ logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23217
+ return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23218
+ }
23219
+ return str;
23220
+ }
23221
+
23222
+ /**
23223
+ * @param {networkCallback} cb
23224
+ * @param {Window} win
23225
+ * @param {Required<NetworkRecordOptions>} options
23226
+ * @returns {listenerHandler}
23227
+ */
23228
+ function initPerformanceObserver(cb, win, options) {
23229
+ if (!win.PerformanceObserver) {
23230
+ logger$3.error('PerformanceObserver not supported');
23231
+ return function() {
23232
+ //
23233
+ };
23234
+ }
23235
+ if (options.recordInitialRequests) {
23236
+ var initialPerformanceEntries = win.performance
23237
+ .getEntries()
23238
+ .filter(function(entry) {
23239
+ return isNavigationTiming(entry) ||
23240
+ (isResourceTiming(entry) &&
23241
+ options.initiatorTypes.includes(entry.initiatorType));
23242
+ });
23243
+ cb({
23244
+ requests: initialPerformanceEntries.map(function(entry) {
23245
+ return {
23246
+ url: entry.name,
23247
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23248
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23249
+ startTime: Math.round(entry.startTime),
23250
+ endTime: Math.round(entry.responseEnd),
23251
+ timeOrigin: getTimeOrigin(win),
23252
+ };
23253
+ }),
23254
+ isInitial: true,
23255
+ });
23256
+ }
23257
+ var observer = new win.PerformanceObserver(function(entries) {
23258
+ var performanceEntries = entries
23259
+ .getEntries()
23260
+ .filter(function(entry) {
23261
+ return isResourceTiming(entry) &&
23262
+ options.initiatorTypes.includes(entry.initiatorType) &&
23263
+ entry.initiatorType !== 'xmlhttprequest' &&
23264
+ entry.initiatorType !== 'fetch';
23265
+ });
23266
+ cb({
23267
+ requests: performanceEntries.map(function(entry) {
23268
+ return {
23269
+ url: entry.name,
23270
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23271
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23272
+ startTime: Math.round(entry.startTime),
23273
+ endTime: Math.round(entry.responseEnd),
23274
+ timeOrigin: getTimeOrigin(win),
23275
+ };
23276
+ }),
23277
+ });
23278
+ });
23279
+ observer.observe({ entryTypes: ['navigation', 'resource'] });
23280
+ return function() {
23281
+ observer.disconnect();
23282
+ };
23283
+ }
23284
+
23285
+ /**
23286
+ * Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
23287
+ * @param {'request' | 'response'} type
23288
+ * @param {NetworkRecordOptions['recordHeaders']} recordHeaders
23289
+ * @param {string} headerName
23290
+ * @returns {boolean}
23291
+ */
23292
+ function shouldRecordHeader(type, recordHeaders, headerName) {
23293
+ if (!recordHeaders[type] || recordHeaders[type].length === 0) {
23294
+ return false;
23295
+ }
23296
+
23297
+ return recordHeaders[type].includes(headerName.toLowerCase());
23298
+ }
23299
+
23300
+ /**
23301
+ * Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
23302
+ * @param {'request' | 'response'} type
23303
+ * @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
23304
+ * @param {string} url
23305
+ * @returns {boolean}
23306
+ */
23307
+ function shouldRecordBody(type, recordBodyUrls, url) {
23308
+ if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
23309
+ return false;
23310
+ }
23311
+
23312
+ return urlMatchesRegexList(url, recordBodyUrls[type]);
23313
+ }
23314
+
23315
+ function tryReadXHRBody(body) {
23316
+ if (body === null || body === undefined) {
23317
+ return null;
23318
+ }
23319
+
23320
+ var result;
23321
+ if (typeof body === 'string') {
23322
+ result = body;
23323
+ } else if (body instanceof Document) {
23324
+ result = body.textContent;
23325
+ } else if (body instanceof FormData) {
23326
+ result = _.HTTPBuildQuery(body);
23327
+ } else if (_.isObject(body)) {
23328
+ try {
23329
+ result = JSON.stringify(body);
23330
+ } catch (e) {
23331
+ return 'Failed to stringify response object';
23332
+ }
23333
+ } else {
23334
+ return 'Cannot read body of type ' + typeof body;
23335
+ }
23336
+
23337
+ return truncateBody(result);
23338
+ }
23339
+
23340
+ /**
23341
+ * @param {Request | Response} r
23342
+ * @returns {Promise<string>}
23343
+ */
23344
+ function tryReadFetchBody(r) {
23345
+ return new Promise(function(resolve) {
23346
+ var timeout = setTimeout(function() {
23347
+ resolve('Timeout while trying to read body');
23348
+ }, 500);
23349
+ try {
23350
+ r.clone()
23351
+ .text()
23352
+ .then(
23353
+ function(txt) {
23354
+ clearTimeout(timeout);
23355
+ resolve(truncateBody(txt));
23356
+ },
23357
+ function(reason) {
23358
+ clearTimeout(timeout);
23359
+ resolve('Failed to read body: ' + String(reason));
23360
+ }
23361
+ );
23362
+ } catch (e) {
23363
+ clearTimeout(timeout);
23364
+ resolve('Failed to read body: ' + String(e));
23365
+ }
23366
+ });
23367
+ }
23368
+
23369
+ /**
23370
+ * @param {Window} win
23371
+ * @param {string} initiatorType
23372
+ * @param {string} url
23373
+ * @param {number} [after]
23374
+ * @param {number} [before]
23375
+ * @param {number} [attempt]
23376
+ * @returns {Promise<PerformanceResourceTiming>}
23377
+ */
23378
+ function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
23379
+ if (attempt === undefined) {
23380
+ attempt = 0;
23381
+ }
23382
+ if (attempt > 10) {
23383
+ logger$3.error('Cannot find performance entry');
23384
+ return Promise.resolve(null);
23385
+ }
23386
+ var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
23387
+ win.performance.getEntriesByName(url)
23388
+ );
23389
+ var performanceEntry = findLast(
23390
+ urlPerformanceEntries,
23391
+ function(entry) {
23392
+ return isResourceTiming(entry) &&
23393
+ entry.initiatorType === initiatorType &&
23394
+ (!after || entry.startTime >= after) &&
23395
+ (!before || entry.startTime <= before);
23396
+ }
23397
+ );
23398
+ if (!performanceEntry) {
23399
+ return new Promise(function(resolve) {
23400
+ setTimeout(resolve, 50 * attempt);
23401
+ }).then(function() {
23402
+ return getRequestPerformanceEntry(
23403
+ win,
23404
+ initiatorType,
23405
+ url,
23406
+ after,
23407
+ before,
23408
+ attempt + 1
23409
+ );
23410
+ });
23411
+ }
23412
+ return Promise.resolve(performanceEntry);
23413
+ }
23414
+
23415
+ /**
23416
+ * @param {networkCallback} cb
23417
+ * @param {Window} win
23418
+ * @param {Required<NetworkRecordOptions>} options
23419
+ * @returns {listenerHandler}
23420
+ */
23421
+ function initXhrObserver(cb, win, options) {
23422
+ if (!options.initiatorTypes.includes('xmlhttprequest')) {
23423
+ return function() {
23424
+ //
23425
+ };
23426
+ }
23427
+ var restorePatch = patch(
23428
+ win.XMLHttpRequest.prototype,
23429
+ 'open',
23430
+ function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
23431
+ return function(
23432
+ /** @type {string} */ method,
23433
+ /** @type {string | URL} */ url,
23434
+ /** @type {boolean} */ async,
23435
+ username, password
23436
+ ) {
23437
+ if (async === undefined) {
23438
+ async = true;
23439
+ }
23440
+ var xhr = /** @type {XMLHttpRequest} */ (this);
23441
+ var req = new Request(url, { method: method });
23442
+ /** @type {Partial<NetworkRequest>} */
23443
+ var networkRequest = {};
23444
+ /** @type {number | undefined} */
23445
+ var after;
23446
+ /** @type {number | undefined} */
23447
+ var before;
23448
+
23449
+ /** @type {Headers} */
23450
+ var requestHeaders = {};
23451
+ var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
23452
+ xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
23453
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23454
+ requestHeaders[header] = value;
23455
+ }
23456
+ return originalSetRequestHeader(header, value);
23457
+ };
23458
+ networkRequest.requestHeaders = requestHeaders;
23459
+
23460
+ var originalSend = xhr.send.bind(xhr);
23461
+ xhr.send = function(/** @type {Body} */ body) {
23462
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23463
+ networkRequest.requestBody = tryReadXHRBody(body);
23464
+ }
23465
+ after = win.performance.now();
23466
+ return originalSend(body);
23467
+ };
23468
+ xhr.addEventListener('readystatechange', function() {
23469
+ if (xhr.readyState !== xhr.DONE) {
23470
+ return;
23471
+ }
23472
+ before = win.performance.now();
23473
+ /** @type {Headers} */
23474
+ var responseHeaders = {};
23475
+ var rawHeaders = xhr.getAllResponseHeaders();
23476
+ if (rawHeaders) {
23477
+ var headers = rawHeaders.trim().split(/[\r\n]+/);
23478
+ headers.forEach(function(line) {
23479
+ if (!line) return;
23480
+ var colonIndex = line.indexOf(': ');
23481
+ if (colonIndex === -1) return;
23482
+ var header = line.substring(0, colonIndex);
23483
+ var value = line.substring(colonIndex + 2);
23484
+ if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
23485
+ responseHeaders[header] = value;
23486
+ }
23487
+ });
23488
+ }
23489
+ networkRequest.responseHeaders = responseHeaders;
23490
+ if (
23491
+ shouldRecordBody('response', options.recordBodyUrls, req.url)
23492
+ ) {
23493
+ networkRequest.responseBody = tryReadXHRBody(xhr.response);
23494
+ }
23495
+ getRequestPerformanceEntry(
23496
+ win,
23497
+ 'xmlhttprequest',
23498
+ req.url,
23499
+ after,
23500
+ before
23501
+ )
23502
+ .then(function(entry) {
23503
+ if (!entry) {
23504
+ logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
23505
+ return;
23506
+ }
23507
+ /** @type {NetworkRequest} */
23508
+ var request = {
23509
+ url: entry.name,
23510
+ method: req.method,
23511
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23512
+ status: xhr.status,
23513
+ startTime: Math.round(entry.startTime),
23514
+ endTime: Math.round(entry.responseEnd),
23515
+ timeOrigin: getTimeOrigin(win),
23516
+ requestHeaders: networkRequest.requestHeaders,
23517
+ requestBody: networkRequest.requestBody,
23518
+ responseHeaders: networkRequest.responseHeaders,
23519
+ responseBody: networkRequest.responseBody,
23520
+ };
23521
+ cb({ requests: [request] });
23522
+ })
23523
+ .catch(function(e) {
23524
+ logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23525
+ });
23526
+ });
23527
+
23528
+ originalOpen.call(xhr, method, url, async, username, password);
23529
+ };
23530
+ }
23531
+ );
23532
+ return function() {
23533
+ restorePatch();
23534
+ };
23535
+ }
23536
+
23537
+ /**
23538
+ * @param {networkCallback} cb
23539
+ * @param {Window} win
23540
+ * @param {Required<NetworkRecordOptions>} options
23541
+ * @returns {listenerHandler}
23542
+ */
23543
+ function initFetchObserver(cb, win, options) {
23544
+ if (!options.initiatorTypes.includes('fetch')) {
23545
+ return function() {
23546
+ //
23547
+ };
23548
+ }
23549
+
23550
+ var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
23551
+ return function() {
23552
+ var req = new Request(arguments[0], arguments[1]);
23553
+ /** @type {Response | undefined} */
23554
+ var res;
23555
+ /** @type {Partial<NetworkRequest>} */
23556
+ var networkRequest = {};
23557
+ /** @type {number | undefined} */
23558
+ var after;
23559
+ /** @type {number | undefined} */
23560
+ var before;
23561
+
23562
+ var originalFetchPromise;
23563
+ var requestBodyPromise = Promise.resolve(undefined);
23564
+ var responseBodyPromise = Promise.resolve(undefined);
23565
+ try {
23566
+ /** @type {Headers} */
23567
+ var requestHeaders = {};
23568
+ req.headers.forEach(function(value, header) {
23569
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23570
+ requestHeaders[header] = value;
23571
+ }
23572
+ });
23573
+ networkRequest.requestHeaders = requestHeaders;
23574
+
23575
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23576
+ requestBodyPromise = tryReadFetchBody(req)
23577
+ .then(function(body) {
23578
+ networkRequest.requestBody = body;
23579
+ });
23580
+ }
23581
+
23582
+ after = win.performance.now();
23583
+ originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
23584
+ res = response;
23585
+ before = win.performance.now();
23586
+
23587
+ /** @type {Headers} */
23588
+ var responseHeaders = {};
23589
+ res.headers.forEach(function(value, header) {
23590
+ if (shouldRecordHeader('response', options.recordHeaders, header)) {
23591
+ responseHeaders[header] = value;
23592
+ }
23593
+ });
23594
+ networkRequest.responseHeaders = responseHeaders;
23595
+
23596
+ if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
23597
+ responseBodyPromise = tryReadFetchBody(res)
23598
+ .then(function(body) {
23599
+ networkRequest.responseBody = body;
23600
+ });
23601
+ }
23602
+
23603
+ return res;
23604
+ });
23605
+ } catch (e) {
23606
+ originalFetchPromise = Promise.reject(e);
23607
+ }
23608
+
23609
+ // await concurrently so we don't delay the fetch response
23610
+ Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
23611
+ .then(function () {
23612
+ return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
23613
+ })
23614
+ .then(function(entry) {
23615
+ if (!entry) {
23616
+ logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
23617
+ return;
23618
+ }
23619
+ /** @type {NetworkRequest} */
23620
+ var request = {
23621
+ url: entry.name,
23622
+ method: req.method,
23623
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23624
+ status: res ? res.status : undefined,
23625
+ startTime: Math.round(entry.startTime),
23626
+ endTime: Math.round(entry.responseEnd),
23627
+ timeOrigin: getTimeOrigin(win),
23628
+ requestHeaders: networkRequest.requestHeaders,
23629
+ requestBody: networkRequest.requestBody,
23630
+ responseHeaders: networkRequest.responseHeaders,
23631
+ responseBody: networkRequest.responseBody,
23632
+ };
23633
+ cb({ requests: [request] });
23634
+ })
23635
+ .catch(function (e) {
23636
+ logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23637
+ });
23638
+
23639
+ return originalFetchPromise;
23640
+ };
23641
+ });
23642
+ return function() {
23643
+ restorePatch();
23644
+ };
23645
+ }
23646
+
23647
+ /**
23648
+ * @param {networkCallback} callback
23649
+ * @param {Window} win
23650
+ * @param {NetworkRecordOptions} options
23651
+ * @returns {listenerHandler}
23652
+ */
23653
+ function initNetworkObserver(callback, win, options) {
23654
+ if (!('performance' in win)) {
23655
+ return function() {
23656
+ //
23657
+ };
23658
+ }
23659
+
23660
+ var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
23661
+ var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
23662
+ options = Object.assign({}, options, {
23663
+ recordHeaders: recordHeaders,
23664
+ recordBodyUrls: recordBodyUrls,
23665
+ });
23666
+ var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
23667
+
23668
+ /** @type {networkCallback} */
23669
+ var cb = function(data) {
23670
+ var requests = data.requests.filter(function(request) {
23671
+ var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
23672
+ return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
23673
+ });
23674
+ if (requests.length > 0 || data.isInitial) {
23675
+ callback(Object.assign({}, data, { requests: requests }));
23676
+ }
23677
+ };
23678
+ var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
23679
+ var xhrObserver = initXhrObserver(cb, win, networkOptions);
23680
+ var fetchObserver = initFetchObserver(cb, win, networkOptions);
23681
+ return function() {
23682
+ performanceObserver();
23683
+ xhrObserver();
23684
+ fetchObserver();
23685
+ };
23686
+ }
23687
+
23688
+ // arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
23689
+ // a changed format in the mixpanel product.
23690
+ var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
23691
+
23692
+ /**
23693
+ * @param {NetworkRecordOptions} [options]
23694
+ * @returns {RecordPlugin}
23695
+ */
23696
+ var getRecordNetworkPlugin = function(options) {
23697
+ return {
23698
+ name: NETWORK_PLUGIN_NAME,
23699
+ observer: initNetworkObserver,
23700
+ options: options,
23701
+ };
23702
+ };
23703
+
23046
23704
  /**
23047
23705
  * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
23048
23706
  */
@@ -23276,12 +23934,35 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
23276
23934
 
23277
23935
  var privacyConfig = getPrivacyConfig(this._mixpanel);
23278
23936
 
23279
- try {
23280
- this._stopRecording = this._rrwebRecord({
23281
- 'emit': function (ev) {
23282
- if (this.idleExpires && this.idleExpires < ev.timestamp) {
23283
- this._onIdleTimeout();
23284
- return;
23937
+ var plugins = [];
23938
+ if (this.getConfig('record_network')) {
23939
+ var options = this.getConfig('record_network_options') || {};
23940
+ // don't track requests to Mixpanel /record API
23941
+ var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
23942
+ ignoreRequestUrls.push(this._getApiRoute());
23943
+ options.ignoreRequestUrls = ignoreRequestUrls;
23944
+
23945
+ plugins.push(getRecordNetworkPlugin(options));
23946
+ }
23947
+
23948
+ if (this.getConfig('record_console')) {
23949
+ plugins.push(
23950
+ getRecordConsolePlugin({
23951
+ stringifyOptions: {
23952
+ stringLengthLimit: 1000,
23953
+ numOfKeysLimit: 50,
23954
+ depthOfLimit: 2
23955
+ }
23956
+ })
23957
+ );
23958
+ }
23959
+
23960
+ try {
23961
+ this._stopRecording = this._rrwebRecord({
23962
+ 'emit': function (ev) {
23963
+ if (this.idleExpires && this.idleExpires < ev.timestamp) {
23964
+ this._onIdleTimeout();
23965
+ return;
23285
23966
  }
23286
23967
  if (isUserEvent(ev)) {
23287
23968
  if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
@@ -23314,15 +23995,7 @@ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
23314
23995
  'sampling': {
23315
23996
  'canvas': 15
23316
23997
  },
23317
- 'plugins': this.getConfig('record_console') ? [
23318
- getRecordConsolePlugin({
23319
- stringifyOptions: {
23320
- stringLengthLimit: 1000,
23321
- numOfKeysLimit: 50,
23322
- depthOfLimit: 2
23323
- }
23324
- })
23325
- ] : []
23998
+ 'plugins': plugins,
23326
23999
  });
23327
24000
  } catch (err) {
23328
24001
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -23437,6 +24110,10 @@ SessionRecording.deserialize = function (serializedRecording, options) {
23437
24110
  return recording;
23438
24111
  };
23439
24112
 
24113
+ SessionRecording.prototype._getApiRoute = function () {
24114
+ return this.getConfig('api_routes')['record'];
24115
+ };
24116
+
23440
24117
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
23441
24118
  var onSuccess = function (response, responseBody) {
23442
24119
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -23456,7 +24133,7 @@ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, r
23456
24133
  });
23457
24134
  }.bind(this);
23458
24135
  var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
23459
- win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
24136
+ win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
23460
24137
  'method': 'POST',
23461
24138
  'headers': {
23462
24139
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -23857,8 +24534,12 @@ MixpanelRecorder.prototype.resetRecording = function () {
23857
24534
  this.startRecording({shouldStopBatcher: true});
23858
24535
  };
23859
24536
 
24537
+ MixpanelRecorder.prototype.isRecording = function () {
24538
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
24539
+ };
24540
+
23860
24541
  MixpanelRecorder.prototype.getActiveReplayId = function () {
23861
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24542
+ if (this.isRecording()) {
23862
24543
  return this.activeRecording.replayId;
23863
24544
  } else {
23864
24545
  return null;
@@ -24362,53 +25043,6 @@ function requireLogic () {
24362
25043
  var logicExports = requireLogic();
24363
25044
  var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
24364
25045
 
24365
- /**
24366
- * Shared helper to recursively lowercase strings in nested structures
24367
- * @param {*} obj - Value to process
24368
- * @param {boolean} lowercaseKeys - Whether to lowercase object keys
24369
- * @returns {*} Processed value with lowercased strings
24370
- */
24371
- var lowercaseJson = function(obj, lowercaseKeys) {
24372
- if (obj === null || obj === undefined) {
24373
- return obj;
24374
- } else if (typeof obj === 'string') {
24375
- return obj.toLowerCase();
24376
- } else if (Array.isArray(obj)) {
24377
- return obj.map(function(item) {
24378
- return lowercaseJson(item, lowercaseKeys);
24379
- });
24380
- } else if (obj === Object(obj)) {
24381
- var result = {};
24382
- for (var key in obj) {
24383
- if (obj.hasOwnProperty(key)) {
24384
- var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
24385
- result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
24386
- }
24387
- }
24388
- return result;
24389
- } else {
24390
- return obj;
24391
- }
24392
- };
24393
-
24394
- /**
24395
- * Lowercase all string keys and values in a nested structure
24396
- * @param {*} val - Value to process
24397
- * @returns {*} Processed value with lowercased strings
24398
- */
24399
- var lowercaseKeysAndValues = function(val) {
24400
- return lowercaseJson(val, true);
24401
- };
24402
-
24403
- /**
24404
- * Lowercase only leaf node string values in a nested structure (keys unchanged)
24405
- * @param {*} val - Value to process
24406
- * @returns {*} Processed value with lowercased leaf strings
24407
- */
24408
- var lowercaseOnlyLeafNodes = function(val) {
24409
- return lowercaseJson(val, false);
24410
- };
24411
-
24412
25046
  /**
24413
25047
  * Check if an event matches the given criteria
24414
25048
  * @param {string} eventName - The name of the event being checked
@@ -24432,13 +25066,8 @@ var eventMatchesCriteria = function(eventName, properties, criteria) {
24432
25066
 
24433
25067
  if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
24434
25068
  try {
24435
- // Lowercase all keys and values in event properties for case-insensitive matching
24436
- var lowercasedProperties = lowercaseKeysAndValues(properties || {});
24437
-
24438
- // Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
24439
- var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
24440
-
24441
- filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
25069
+ // Use properties as-is for case-sensitive matching
25070
+ filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
24442
25071
  } catch (error) {
24443
25072
  return {
24444
25073
  matches: false,
@@ -24558,7 +25187,7 @@ ShadowDOMObserver.prototype.observeShadowRoot = function(shadowRoot) {
24558
25187
  observer.observe(shadowRoot, this.observerConfig);
24559
25188
  this.shadowObservers.push(observer);
24560
25189
  } catch (e) {
24561
- logger$3.critical('Error while observing shadow root', e);
25190
+ logger$4.critical('Error while observing shadow root', e);
24562
25191
  }
24563
25192
  };
24564
25193
 
@@ -24569,7 +25198,7 @@ ShadowDOMObserver.prototype.start = function() {
24569
25198
  }
24570
25199
 
24571
25200
  if (!weakSetSupported()) {
24572
- logger$3.critical('Shadow DOM observation unavailable: WeakSet not supported');
25201
+ logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
24573
25202
  return;
24574
25203
  }
24575
25204
 
@@ -24585,7 +25214,7 @@ ShadowDOMObserver.prototype.stop = function() {
24585
25214
  try {
24586
25215
  this.shadowObservers[i].disconnect();
24587
25216
  } catch (e) {
24588
- logger$3.critical('Error while disconnecting shadow DOM observer', e);
25217
+ logger$4.critical('Error while disconnecting shadow DOM observer', e);
24589
25218
  }
24590
25219
  }
24591
25220
  this.shadowObservers = [];
@@ -24773,7 +25402,7 @@ DeadClickTracker.prototype.startTracking = function() {
24773
25402
 
24774
25403
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24775
25404
  } catch (e) {
24776
- logger$3.critical('Error while setting up mutation observer', e);
25405
+ logger$4.critical('Error while setting up mutation observer', e);
24777
25406
  }
24778
25407
  }
24779
25408
 
@@ -24788,7 +25417,7 @@ DeadClickTracker.prototype.startTracking = function() {
24788
25417
  );
24789
25418
  this.shadowDOMObserver.start();
24790
25419
  } catch (e) {
24791
- logger$3.critical('Error while setting up shadow DOM observer', e);
25420
+ logger$4.critical('Error while setting up shadow DOM observer', e);
24792
25421
  this.shadowDOMObserver = null;
24793
25422
  }
24794
25423
  }
@@ -24815,7 +25444,7 @@ DeadClickTracker.prototype.stopTracking = function() {
24815
25444
  try {
24816
25445
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24817
25446
  } catch (e) {
24818
- logger$3.critical('Error while removing event listener', e);
25447
+ logger$4.critical('Error while removing event listener', e);
24819
25448
  }
24820
25449
  }
24821
25450
  this.eventListeners = [];
@@ -24824,7 +25453,7 @@ DeadClickTracker.prototype.stopTracking = function() {
24824
25453
  try {
24825
25454
  this.mutationObserver.disconnect();
24826
25455
  } catch (e) {
24827
- logger$3.critical('Error while disconnecting mutation observer', e);
25456
+ logger$4.critical('Error while disconnecting mutation observer', e);
24828
25457
  }
24829
25458
  this.mutationObserver = null;
24830
25459
  }
@@ -24833,7 +25462,7 @@ DeadClickTracker.prototype.stopTracking = function() {
24833
25462
  try {
24834
25463
  this.shadowDOMObserver.stop();
24835
25464
  } catch (e) {
24836
- logger$3.critical('Error while stopping shadow DOM observer', e);
25465
+ logger$4.critical('Error while stopping shadow DOM observer', e);
24837
25466
  }
24838
25467
  this.shadowDOMObserver = null;
24839
25468
  }
@@ -24911,7 +25540,7 @@ var Autocapture = function(mp) {
24911
25540
 
24912
25541
  Autocapture.prototype.init = function() {
24913
25542
  if (!minDOMApisSupported()) {
24914
- logger$3.critical('Autocapture unavailable: missing required DOM APIs');
25543
+ logger$4.critical('Autocapture unavailable: missing required DOM APIs');
24915
25544
  return;
24916
25545
  }
24917
25546
  this.initPageListeners();
@@ -24943,27 +25572,15 @@ Autocapture.prototype.getConfig = function(key) {
24943
25572
  };
24944
25573
 
24945
25574
  Autocapture.prototype.currentUrlBlocked = function() {
24946
- var i;
24947
25575
  var currentUrl = _.info.currentUrl();
24948
25576
 
24949
25577
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
24950
25578
  if (allowUrlRegexes.length) {
24951
25579
  // we're using an allowlist, only track if current URL matches
24952
- var allowed = false;
24953
- for (i = 0; i < allowUrlRegexes.length; i++) {
24954
- var allowRegex = allowUrlRegexes[i];
24955
- try {
24956
- if (currentUrl.match(allowRegex)) {
24957
- allowed = true;
24958
- break;
24959
- }
24960
- } catch (err) {
24961
- logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
24962
- return true;
24963
- }
24964
- }
24965
- if (!allowed) {
24966
- // wasn't allowed by any regex
25580
+ try {
25581
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25582
+ } catch (err) {
25583
+ logger$4.critical('Error while checking block URL regexes: ', err);
24967
25584
  return true;
24968
25585
  }
24969
25586
  }
@@ -24973,17 +25590,12 @@ Autocapture.prototype.currentUrlBlocked = function() {
24973
25590
  return false;
24974
25591
  }
24975
25592
 
24976
- for (i = 0; i < blockUrlRegexes.length; i++) {
24977
- try {
24978
- if (currentUrl.match(blockUrlRegexes[i])) {
24979
- return true;
24980
- }
24981
- } catch (err) {
24982
- logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
24983
- return true;
24984
- }
25593
+ try {
25594
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25595
+ } catch (err) {
25596
+ logger$4.critical('Error while checking block URL regexes: ', err);
25597
+ return true;
24985
25598
  }
24986
- return false;
24987
25599
  };
24988
25600
 
24989
25601
  Autocapture.prototype.pageviewTrackingConfig = function() {
@@ -25119,7 +25731,7 @@ Autocapture.prototype._initScrollDepthTracking = function() {
25119
25731
  return;
25120
25732
  }
25121
25733
 
25122
- logger$3.log('Initializing scroll depth tracking');
25734
+ logger$4.log('Initializing scroll depth tracking');
25123
25735
 
25124
25736
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25125
25737
 
@@ -25145,7 +25757,7 @@ Autocapture.prototype.initClickTracking = function() {
25145
25757
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25146
25758
  return;
25147
25759
  }
25148
- logger$3.log('Initializing click tracking');
25760
+ logger$4.log('Initializing click tracking');
25149
25761
 
25150
25762
  this.listenerClick = function(ev) {
25151
25763
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25164,7 +25776,7 @@ Autocapture.prototype.initDeadClickTracking = function() {
25164
25776
  return;
25165
25777
  }
25166
25778
 
25167
- logger$3.log('Initializing dead click tracking');
25779
+ logger$4.log('Initializing dead click tracking');
25168
25780
  if (!this._deadClickTracker) {
25169
25781
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25170
25782
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25198,7 +25810,7 @@ Autocapture.prototype.initInputTracking = function() {
25198
25810
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25199
25811
  return;
25200
25812
  }
25201
- logger$3.log('Initializing input tracking');
25813
+ logger$4.log('Initializing input tracking');
25202
25814
 
25203
25815
  this.listenerChange = function(ev) {
25204
25816
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25215,7 +25827,7 @@ Autocapture.prototype.initPageviewTracking = function() {
25215
25827
  if (!this.pageviewTrackingConfig()) {
25216
25828
  return;
25217
25829
  }
25218
- logger$3.log('Initializing pageview tracking');
25830
+ logger$4.log('Initializing pageview tracking');
25219
25831
 
25220
25832
  var previousTrackedUrl = '';
25221
25833
  var tracked = false;
@@ -25250,7 +25862,7 @@ Autocapture.prototype.initPageviewTracking = function() {
25250
25862
  }
25251
25863
  if (didPathChange) {
25252
25864
  this.lastScrollCheckpoint = 0;
25253
- logger$3.log('Path change: re-initializing scroll depth checkpoints');
25865
+ logger$4.log('Path change: re-initializing scroll depth checkpoints');
25254
25866
  }
25255
25867
  }
25256
25868
  }.bind(this));
@@ -25265,7 +25877,7 @@ Autocapture.prototype.initRageClickTracking = function() {
25265
25877
  return;
25266
25878
  }
25267
25879
 
25268
- logger$3.log('Initializing rage click tracking');
25880
+ logger$4.log('Initializing rage click tracking');
25269
25881
  if (!this._rageClickTracker) {
25270
25882
  this._rageClickTracker = new RageClickTracker();
25271
25883
  }
@@ -25295,7 +25907,7 @@ Autocapture.prototype.initScrollTracking = function() {
25295
25907
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25296
25908
  return;
25297
25909
  }
25298
- logger$3.log('Initializing scroll tracking');
25910
+ logger$4.log('Initializing scroll tracking');
25299
25911
  this.lastScrollCheckpoint = 0;
25300
25912
 
25301
25913
  var scrollTrackFunction = function() {
@@ -25332,7 +25944,7 @@ Autocapture.prototype.initScrollTracking = function() {
25332
25944
  }
25333
25945
  }
25334
25946
  } catch (err) {
25335
- logger$3.critical('Error while calculating scroll percentage', err);
25947
+ logger$4.critical('Error while calculating scroll percentage', err);
25336
25948
  }
25337
25949
  if (shouldTrack) {
25338
25950
  this.mp.track(MP_EV_SCROLL, props);
@@ -25350,7 +25962,7 @@ Autocapture.prototype.initSubmitTracking = function() {
25350
25962
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
25351
25963
  return;
25352
25964
  }
25353
- logger$3.log('Initializing submit tracking');
25965
+ logger$4.log('Initializing submit tracking');
25354
25966
 
25355
25967
  this.listenerSubmit = function(ev) {
25356
25968
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -25372,7 +25984,7 @@ Autocapture.prototype.initPageLeaveTracking = function() {
25372
25984
  return;
25373
25985
  }
25374
25986
 
25375
- logger$3.log('Initializing page visibility tracking.');
25987
+ logger$4.log('Initializing page visibility tracking.');
25376
25988
  this._initScrollDepthTracking();
25377
25989
  var previousTrackedUrl = _.info.currentUrl();
25378
25990
 
@@ -25980,6 +26592,214 @@ FeatureFlagManager.prototype['getTargeting'] = FeatureFlagManager.prototype.getT
25980
26592
  /* eslint camelcase: "off" */
25981
26593
 
25982
26594
 
26595
+ /**
26596
+ * RecorderManager: manages session recording initialization, lifecycle and state
26597
+ * @constructor
26598
+ */
26599
+ var RecorderManager = function(initOptions) {
26600
+ // TODO - Passing in mixpanel instance as it is still needed for recorder creation
26601
+ // but ideally we should be able to remove this dependency.
26602
+ this.mixpanelInstance = initOptions.mixpanelInstance;
26603
+
26604
+ this.getMpConfig = initOptions.getConfigFunc;
26605
+ this.getTabId = initOptions.getTabIdFunc;
26606
+ this.reportError = initOptions.reportErrorFunc;
26607
+ this.getDistinctId = initOptions.getDistinctIdFunc;
26608
+ this.loadExtraBundle = initOptions.loadExtraBundle;
26609
+ this.recorderSrc = initOptions.recorderSrc;
26610
+ this.targetingSrc = initOptions.targetingSrc;
26611
+ this.libBasePath = initOptions.libBasePath;
26612
+
26613
+ this._recorder = null;
26614
+ };
26615
+
26616
+ RecorderManager.prototype.shouldLoadRecorder = function() {
26617
+ if (this.getMpConfig('disable_persistence')) {
26618
+ console$1.log('Load recorder check skipped due to disable_persistence config');
26619
+ return PromisePolyfill.resolve(false);
26620
+ }
26621
+
26622
+ var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26623
+ var tab_id = this.getTabId();
26624
+ return recording_registry_idb.init()
26625
+ .then(function () {
26626
+ return recording_registry_idb.getAll();
26627
+ })
26628
+ .then(function (recordings) {
26629
+ for (var i = 0; i < recordings.length; i++) {
26630
+ // if there are expired recordings in the registry, we should load the recorder to flush them
26631
+ // if there's a recording for this tab id, we should load the recorder to continue the recording
26632
+ if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
26633
+ return true;
26634
+ }
26635
+ }
26636
+ return false;
26637
+ })
26638
+ .catch(_.bind(function (err) {
26639
+ this.reportError('Error checking recording registry', err);
26640
+ return false;
26641
+ }, this));
26642
+ };
26643
+
26644
+ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
26645
+ if (!win['MutationObserver']) {
26646
+ console$1.critical('Browser does not support MutationObserver; skipping session recording');
26647
+ return PromisePolyfill.resolve();
26648
+ }
26649
+
26650
+ var loadRecorder = _.bind(function(startNewIfInactive) {
26651
+ return new PromisePolyfill(_.bind(function(resolve) {
26652
+ var handleLoadedRecorder = safewrap(_.bind(function() {
26653
+ this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
26654
+ this._recorder['resumeRecording'](startNewIfInactive);
26655
+ resolve();
26656
+ }, this));
26657
+
26658
+ if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
26659
+ var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
26660
+ this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
26661
+ } else {
26662
+ handleLoadedRecorder();
26663
+ }
26664
+ }, this));
26665
+ }, this);
26666
+
26667
+ /**
26668
+ * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
26669
+ * Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
26670
+ */
26671
+ var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
26672
+ var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
26673
+ if (force_start || is_sampled) {
26674
+ return loadRecorder(true);
26675
+ } else {
26676
+ return this.shouldLoadRecorder()
26677
+ .then(_.bind(function (shouldLoad) {
26678
+ if (shouldLoad) {
26679
+ return loadRecorder(false);
26680
+ }
26681
+ return PromisePolyfill.resolve();
26682
+ }, this));
26683
+ }
26684
+ };
26685
+
26686
+ RecorderManager.prototype.isRecording = function() {
26687
+ // Safety check: ensure isRecording method exists (older CDN builds may not have it)
26688
+ if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
26689
+ return false;
26690
+ }
26691
+ try {
26692
+ return this._recorder['isRecording']();
26693
+ } catch (e) {
26694
+ this.reportError('Error checking if recording is active', e);
26695
+ return false;
26696
+ }
26697
+ };
26698
+
26699
+ RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
26700
+ var isRecording = this.isRecording();
26701
+ var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
26702
+
26703
+ if (!isRecording && recordingTriggerEvents) {
26704
+ var trigger = recordingTriggerEvents[event_name];
26705
+ if (trigger && typeof trigger['percentage'] === 'number') {
26706
+ var newRate = trigger['percentage'];
26707
+ var propertyFilters = trigger['property_filters'];
26708
+ if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
26709
+ var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
26710
+ getTargetingPromise(this.loadExtraBundle, targetingSrc)
26711
+ .then(function(targeting) {
26712
+ try {
26713
+ var result = targeting['eventMatchesCriteria'](
26714
+ event_name,
26715
+ properties,
26716
+ {
26717
+ 'event_name': event_name,
26718
+ 'property_filters': propertyFilters
26719
+ }
26720
+ );
26721
+ if (result['matches']) {
26722
+ this.checkAndStartSessionRecording(false, newRate);
26723
+ }
26724
+ } catch (err) {
26725
+ console$1.critical('Could not parse recording event trigger properties logic:', err);
26726
+ }
26727
+ }.bind(this)).catch(function(err) {
26728
+ console$1.critical('Failed to load targeting library:', err);
26729
+ });
26730
+ } else {
26731
+ this.checkAndStartSessionRecording(false, newRate);
26732
+ }
26733
+ }
26734
+ }
26735
+ };
26736
+
26737
+ RecorderManager.prototype.stopSessionRecording = function() {
26738
+ if (this._recorder) {
26739
+ return this._recorder['stopRecording']();
26740
+ }
26741
+ return PromisePolyfill.resolve();
26742
+ };
26743
+
26744
+ RecorderManager.prototype.pauseSessionRecording = function() {
26745
+ if (this._recorder) {
26746
+ return this._recorder['pauseRecording']();
26747
+ }
26748
+ return PromisePolyfill.resolve();
26749
+ };
26750
+
26751
+ RecorderManager.prototype.resumeSessionRecording = function() {
26752
+ if (this._recorder) {
26753
+ return this._recorder['resumeRecording']();
26754
+ }
26755
+ return PromisePolyfill.resolve();
26756
+ };
26757
+
26758
+ RecorderManager.prototype.isRecordingHeatmapData = function() {
26759
+ return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
26760
+ };
26761
+
26762
+ RecorderManager.prototype.getSessionRecordingProperties = function() {
26763
+ var props = {};
26764
+ var replay_id = this.getSessionReplayId();
26765
+ if (replay_id) {
26766
+ props['$mp_replay_id'] = replay_id;
26767
+ }
26768
+ return props;
26769
+ };
26770
+
26771
+ RecorderManager.prototype.getSessionReplayUrl = function() {
26772
+ var replay_url = null;
26773
+ var replay_id = this.getSessionReplayId();
26774
+ if (replay_id) {
26775
+ var query_params = _.HTTPBuildQuery({
26776
+ 'replay_id': replay_id,
26777
+ 'distinct_id': this.getDistinctId(),
26778
+ 'token': this.getMpConfig('token')
26779
+ });
26780
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
26781
+ }
26782
+ return replay_url;
26783
+ };
26784
+
26785
+ RecorderManager.prototype.getSessionReplayId = function() {
26786
+ var replay_id = null;
26787
+ if (this._recorder) {
26788
+ replay_id = this._recorder['replayId'];
26789
+ }
26790
+ return replay_id || null;
26791
+ };
26792
+
26793
+ // "private" public method to reach into the recorder in test cases
26794
+ RecorderManager.prototype.getRecorder = function() {
26795
+ return this._recorder;
26796
+ };
26797
+
26798
+ safewrapClass(RecorderManager);
26799
+
26800
+ /* eslint camelcase: "off" */
26801
+
26802
+
25983
26803
  /**
25984
26804
  * DomTracker Object
25985
26805
  * @constructor
@@ -27440,13 +28260,17 @@ var DEFAULT_CONFIG = {
27440
28260
  'record_collect_fonts': false,
27441
28261
  'record_console': true,
27442
28262
  'record_heatmap_data': false,
28263
+ 'recording_event_triggers': {},
27443
28264
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
27444
28265
  'record_mask_inputs': true,
27445
28266
  'record_max_ms': MAX_RECORDING_MS,
27446
28267
  'record_min_ms': 0,
28268
+ 'record_network': false,
28269
+ 'record_network_options': {},
27447
28270
  'record_sessions_percent': 0,
27448
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
27449
- 'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
28271
+ 'recorder_src': null,
28272
+ 'targeting_src': null,
28273
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
27450
28274
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
27451
28275
  };
27452
28276
 
@@ -27600,6 +28424,19 @@ MixpanelLib.prototype._init = function(token, config, name) {
27600
28424
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
27601
28425
  }));
27602
28426
 
28427
+ this.recorderManager = new RecorderManager({
28428
+ mixpanelInstance: this,
28429
+ getConfigFunc: _.bind(this.get_config, this),
28430
+ setConfigFunc: _.bind(this.set_config, this),
28431
+ getTabIdFunc: _.bind(this.get_tab_id, this),
28432
+ reportErrorFunc: _.bind(this.report_error, this),
28433
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
28434
+ recorderSrc: this.get_config('recorder_src'),
28435
+ targetingSrc: this.get_config('targeting_src'),
28436
+ libBasePath: this.get_config('lib_base_path'),
28437
+ loadExtraBundle: load_extra_bundle
28438
+ });
28439
+
27603
28440
  this['_jsc'] = NOOP_FUNC;
27604
28441
 
27605
28442
  this.__dom_loaded_queue = [];
@@ -27678,7 +28515,7 @@ MixpanelLib.prototype._init = function(token, config, name) {
27678
28515
  getPropertyFunc: _.bind(this.get_property, this),
27679
28516
  trackingFunc: _.bind(this.track, this),
27680
28517
  loadExtraBundle: load_extra_bundle,
27681
- targetingSrc: this.get_config('targeting_src')
28518
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
27682
28519
  });
27683
28520
  this.flags.init();
27684
28521
  this['flags'] = this.flags;
@@ -27691,11 +28528,11 @@ MixpanelLib.prototype._init = function(token, config, name) {
27691
28528
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
27692
28529
  var mode = this.get_config('remote_settings_mode');
27693
28530
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
27694
- this._fetch_remote_settings(mode).then(_.bind(function() {
27695
- this._check_and_start_session_recording();
28531
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
28532
+ return this._check_and_start_session_recording();
27696
28533
  }, this));
27697
28534
  } else {
27698
- this._check_and_start_session_recording();
28535
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
27699
28536
  }
27700
28537
  };
27701
28538
 
@@ -27739,132 +28576,50 @@ MixpanelLib.prototype.get_tab_id = function () {
27739
28576
  return this.tab_id || null;
27740
28577
  };
27741
28578
 
27742
- MixpanelLib.prototype._should_load_recorder = function () {
27743
- if (this.get_config('disable_persistence')) {
27744
- console$1.log('Load recorder check skipped due to disable_persistence config');
27745
- return Promise.resolve(false);
27746
- }
27747
-
27748
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27749
- var tab_id = this.get_tab_id();
27750
- return recording_registry_idb.init()
27751
- .then(function () {
27752
- return recording_registry_idb.getAll();
27753
- })
27754
- .then(function (recordings) {
27755
- for (var i = 0; i < recordings.length; i++) {
27756
- // if there are expired recordings in the registry, we should load the recorder to flush them
27757
- // if there's a recording for this tab id, we should load the recorder to continue the recording
27758
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
27759
- return true;
27760
- }
27761
- }
27762
- return false;
27763
- })
27764
- .catch(_.bind(function (err) {
27765
- this.report_error('Error checking recording registry', err);
27766
- }, this));
27767
- };
27768
-
27769
28579
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
27770
- if (!win['MutationObserver']) {
27771
- console$1.critical('Browser does not support MutationObserver; skipping session recording');
27772
- return;
27773
- }
27774
-
27775
- var loadRecorder = _.bind(function(startNewIfInactive) {
27776
- var handleLoadedRecorder = _.bind(function() {
27777
- this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
27778
- this._recorder['resumeRecording'](startNewIfInactive);
27779
- }, this);
27780
-
27781
- if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
27782
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
27783
- } else {
27784
- handleLoadedRecorder();
27785
- }
27786
- }, this);
27787
-
27788
- /**
27789
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
27790
- * 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.
27791
- */
27792
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
27793
- if (force_start || is_sampled) {
27794
- loadRecorder(true);
27795
- } else {
27796
- this._should_load_recorder()
27797
- .then(function (shouldLoad) {
27798
- if (shouldLoad) {
27799
- loadRecorder(false);
27800
- }
27801
- });
27802
- }
28580
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
27803
28581
  });
27804
28582
 
28583
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28584
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28585
+ };
28586
+
27805
28587
  MixpanelLib.prototype.start_session_recording = function () {
27806
- this._check_and_start_session_recording(true);
28588
+ return this._check_and_start_session_recording(true);
27807
28589
  };
27808
28590
 
27809
28591
  MixpanelLib.prototype.stop_session_recording = function () {
27810
- if (this._recorder) {
27811
- return this._recorder['stopRecording']();
27812
- }
27813
- return Promise.resolve();
28592
+ return this.recorderManager.stopSessionRecording();
27814
28593
  };
27815
28594
 
27816
28595
  MixpanelLib.prototype.pause_session_recording = function () {
27817
- if (this._recorder) {
27818
- return this._recorder['pauseRecording']();
27819
- }
27820
- return Promise.resolve();
28596
+ return this.recorderManager.pauseSessionRecording();
27821
28597
  };
27822
28598
 
27823
28599
  MixpanelLib.prototype.resume_session_recording = function () {
27824
- if (this._recorder) {
27825
- return this._recorder['resumeRecording']();
27826
- }
27827
- return Promise.resolve();
28600
+ return this.recorderManager.resumeSessionRecording();
27828
28601
  };
27829
28602
 
27830
28603
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
27831
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
28604
+ return this.recorderManager.isRecordingHeatmapData();
27832
28605
  };
27833
28606
 
27834
28607
  MixpanelLib.prototype.get_session_recording_properties = function () {
27835
- var props = {};
27836
- var replay_id = this._get_session_replay_id();
27837
- if (replay_id) {
27838
- props['$mp_replay_id'] = replay_id;
27839
- }
27840
- return props;
28608
+ return this.recorderManager.getSessionRecordingProperties();
27841
28609
  };
27842
28610
 
27843
28611
  MixpanelLib.prototype.get_session_replay_url = function () {
27844
- var replay_url = null;
27845
- var replay_id = this._get_session_replay_id();
27846
- if (replay_id) {
27847
- var query_params = _.HTTPBuildQuery({
27848
- 'replay_id': replay_id,
27849
- 'distinct_id': this.get_distinct_id(),
27850
- 'token': this.get_config('token')
27851
- });
27852
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
27853
- }
27854
- return replay_url;
27855
- };
27856
-
27857
- MixpanelLib.prototype._get_session_replay_id = function () {
27858
- var replay_id = null;
27859
- if (this._recorder) {
27860
- replay_id = this._recorder['replayId'];
27861
- }
27862
- return replay_id || null;
28612
+ return this.recorderManager.getSessionReplayUrl();
27863
28613
  };
27864
28614
 
27865
28615
  // "private" public method to reach into the recorder in test cases
27866
28616
  MixpanelLib.prototype.__get_recorder = function () {
27867
- return this._recorder;
28617
+ return this.recorderManager.getRecorder();
28618
+ };
28619
+
28620
+ // "private" public method to get session recording init promise in test cases
28621
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
28622
+ return this.__session_recording_init_promise;
27868
28623
  };
27869
28624
 
27870
28625
  // Private methods
@@ -28122,6 +28877,7 @@ MixpanelLib.prototype._send_request = function(url, data, options, callback) {
28122
28877
  };
28123
28878
 
28124
28879
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
28880
+ var self = this;
28125
28881
  var disableRecordingIfStrict = function() {
28126
28882
  if (mode === 'strict') {
28127
28883
  self.set_config({'record_sessions_percent': 0});
@@ -28142,7 +28898,6 @@ MixpanelLib.prototype._fetch_remote_settings = function(mode) {
28142
28898
  };
28143
28899
  var query_string = _.HTTPBuildQuery(request_params);
28144
28900
  var full_url = settings_endpoint + '?' + query_string;
28145
- var self = this;
28146
28901
 
28147
28902
  var abortController = new AbortController();
28148
28903
  var timeout_id = setTimeout(function() {
@@ -28334,6 +29089,34 @@ MixpanelLib.prototype.push = function(item) {
28334
29089
  this._execute_array([item]);
28335
29090
  };
28336
29091
 
29092
+ /**
29093
+ * Enables events on the Mixpanel object. If passed no arguments,
29094
+ * this function enable tracking of all events. If passed an
29095
+ * array of event names, those events will be enabled, but other
29096
+ * existing disabled events will continue to be not tracked.
29097
+ *
29098
+ * @param {Array} [events] An array of event names to enable
29099
+ */
29100
+ MixpanelLib.prototype.enable = function(events) {
29101
+ var keys, new_disabled_events, i, j;
29102
+
29103
+ if (typeof(events) === 'undefined') {
29104
+ this._flags.disable_all_events = false;
29105
+ } else {
29106
+ keys = {};
29107
+ new_disabled_events = [];
29108
+ for (i = 0; i < events.length; i++) {
29109
+ keys[events[i]] = true;
29110
+ }
29111
+ for (j = 0; j < this.__disabled_events.length; j++) {
29112
+ if (!keys[this.__disabled_events[j]]) {
29113
+ new_disabled_events.push(this.__disabled_events[j]);
29114
+ }
29115
+ }
29116
+ this.__disabled_events = new_disabled_events;
29117
+ }
29118
+ };
29119
+
28337
29120
  /**
28338
29121
  * Disable events on the Mixpanel object. If passed no arguments,
28339
29122
  * this function disables tracking of any event. If passed an
@@ -28507,6 +29290,8 @@ MixpanelLib.prototype.track = addOptOutCheckMixpanelLib(function(event_name, pro
28507
29290
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
28508
29291
  }
28509
29292
 
29293
+ this._start_recording_on_event(event_name, properties);
29294
+
28510
29295
  var data = {
28511
29296
  'event': event_name,
28512
29297
  'properties': properties
@@ -29715,6 +30500,7 @@ MixpanelLib.prototype.remove_hook = function(hook_name, hook_fn) {
29715
30500
  // MixpanelLib Exports
29716
30501
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
29717
30502
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
30503
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
29718
30504
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
29719
30505
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
29720
30506
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -29758,6 +30544,7 @@ MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES
29758
30544
 
29759
30545
  // Exports intended only for testing
29760
30546
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
30547
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
29761
30548
 
29762
30549
  // MixpanelPersistence Exports
29763
30550
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;