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
@@ -29,16 +29,19 @@
29
29
  win = window;
30
30
  }
31
31
 
32
- /**
33
- * Shared global window property names used across modules
34
- */
32
+ var Config = {
33
+ DEBUG: false,
34
+ LIB_VERSION: '2.76.0'
35
+ };
35
36
 
36
- // Targeting library global (used by flags and targeting modules)
37
+ // Window global names for async modules
37
38
  var TARGETING_GLOBAL_NAME = '__mp_targeting';
38
-
39
- // Recorder library global (used by recorder and mixpanel-core)
40
39
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
41
40
 
41
+ // Constants that are injected at build-time for the names of async modules.
42
+ var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
43
+ var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
44
+
42
45
  function _array_like_to_array(arr, len) {
43
46
  if (len == null || len > arr.length) len = arr.length;
44
47
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -18138,7 +18141,7 @@
18138
18141
  var __publicField = function(obj, key, value) {
18139
18142
  return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
18140
18143
  };
18141
- function patch(source, name, replacement) {
18144
+ function patch$3(source, name, replacement) {
18142
18145
  try {
18143
18146
  if (!(name in source)) {
18144
18147
  return function() {};
@@ -18555,7 +18558,7 @@
18555
18558
  if (!_logger[level]) {
18556
18559
  return function() {};
18557
18560
  }
18558
- return patch(_logger, level, function(original) {
18561
+ return patch$3(_logger, level, function(original) {
18559
18562
  var _this1 = _this;
18560
18563
  return function() {
18561
18564
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
@@ -18976,11 +18979,6 @@
18976
18979
  PromisePolyfill = NpoPromise;
18977
18980
  }
18978
18981
 
18979
- var Config = {
18980
- DEBUG: false,
18981
- LIB_VERSION: '2.75.0'
18982
- };
18983
-
18984
18982
  /* eslint camelcase: "off", eqeqeq: "off" */
18985
18983
 
18986
18984
  // Maximum allowed session recording length
@@ -20712,6 +20710,17 @@
20712
20710
 
20713
20711
  var NOOP_FUNC = function () {};
20714
20712
 
20713
+ var urlMatchesRegexList = function (url, regexList) {
20714
+ var matches = false;
20715
+ for (var i = 0; i < regexList.length; i++) {
20716
+ if (url.match(regexList[i])) {
20717
+ matches = true;
20718
+ break;
20719
+ }
20720
+ }
20721
+ return matches;
20722
+ };
20723
+
20715
20724
  var JSONStringify = null, JSONParse = null;
20716
20725
  if (typeof JSON !== 'undefined') {
20717
20726
  JSONStringify = JSON.stringify;
@@ -21183,7 +21192,7 @@
21183
21192
  };
21184
21193
  }
21185
21194
 
21186
- var logger$6 = console_with_prefix('lock');
21195
+ var logger$7 = console_with_prefix('lock');
21187
21196
 
21188
21197
  /**
21189
21198
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21235,7 +21244,7 @@
21235
21244
 
21236
21245
  var delay = function(cb) {
21237
21246
  if (new Date().getTime() - startTime > timeoutMS) {
21238
- logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21247
+ logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21239
21248
  storage.removeItem(keyZ);
21240
21249
  storage.removeItem(keyY);
21241
21250
  loop();
@@ -21382,7 +21391,7 @@
21382
21391
  }, this));
21383
21392
  };
21384
21393
 
21385
- var logger$5 = console_with_prefix('batch');
21394
+ var logger$6 = console_with_prefix('batch');
21386
21395
 
21387
21396
  /**
21388
21397
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21411,7 +21420,7 @@
21411
21420
  timeoutMS: options.sharedLockTimeoutMS,
21412
21421
  });
21413
21422
  }
21414
- this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
21423
+ this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
21415
21424
 
21416
21425
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21417
21426
 
@@ -21744,7 +21753,7 @@
21744
21753
  // maximum interval between request retries after exponential backoff
21745
21754
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21746
21755
 
21747
- var logger$4 = console_with_prefix('batch');
21756
+ var logger$5 = console_with_prefix('batch');
21748
21757
 
21749
21758
  /**
21750
21759
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21872,7 +21881,7 @@
21872
21881
  */
21873
21882
  RequestBatcher.prototype.flush = function(options) {
21874
21883
  if (this.requestInProgress) {
21875
- logger$4.log('Flush: Request already in progress');
21884
+ logger$5.log('Flush: Request already in progress');
21876
21885
  return PromisePolyfill.resolve();
21877
21886
  }
21878
21887
 
@@ -22049,7 +22058,7 @@
22049
22058
  if (options.unloading) {
22050
22059
  requestOptions.transport = 'sendBeacon';
22051
22060
  }
22052
- logger$4.log('MIXPANEL REQUEST:', dataForRequest);
22061
+ logger$5.log('MIXPANEL REQUEST:', dataForRequest);
22053
22062
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22054
22063
  }, this))
22055
22064
  .catch(_.bind(function(err) {
@@ -22062,7 +22071,7 @@
22062
22071
  * Log error to global logger and optional user-defined logger.
22063
22072
  */
22064
22073
  RequestBatcher.prototype.reportError = function(msg, err) {
22065
- logger$4.error.apply(logger$4.error, arguments);
22074
+ logger$5.error.apply(logger$5.error, arguments);
22066
22075
  if (this.errorReporter) {
22067
22076
  try {
22068
22077
  if (!(err instanceof Error)) {
@@ -22070,7 +22079,7 @@
22070
22079
  }
22071
22080
  this.errorReporter(msg, err);
22072
22081
  } catch(err) {
22073
- logger$4.error(err);
22082
+ logger$5.error(err);
22074
22083
  }
22075
22084
  }
22076
22085
  };
@@ -22192,7 +22201,7 @@
22192
22201
 
22193
22202
  var MAX_DEPTH = 5;
22194
22203
 
22195
- var logger$3 = console_with_prefix('autocapture');
22204
+ var logger$4 = console_with_prefix('autocapture');
22196
22205
 
22197
22206
 
22198
22207
  function getClasses(el) {
@@ -22456,7 +22465,7 @@
22456
22465
  return false;
22457
22466
  }
22458
22467
  } catch (err) {
22459
- logger$3.critical('Error while checking element in allowElementCallback', err);
22468
+ logger$4.critical('Error while checking element in allowElementCallback', err);
22460
22469
  return false;
22461
22470
  }
22462
22471
  }
@@ -22473,7 +22482,7 @@
22473
22482
  return true;
22474
22483
  }
22475
22484
  } catch (err) {
22476
- logger$3.critical('Error while checking selector: ' + sel, err);
22485
+ logger$4.critical('Error while checking selector: ' + sel, err);
22477
22486
  }
22478
22487
  }
22479
22488
  return false;
@@ -22488,7 +22497,7 @@
22488
22497
  return true;
22489
22498
  }
22490
22499
  } catch (err) {
22491
- logger$3.critical('Error while checking element in blockElementCallback', err);
22500
+ logger$4.critical('Error while checking element in blockElementCallback', err);
22492
22501
  return true;
22493
22502
  }
22494
22503
  }
@@ -22502,7 +22511,7 @@
22502
22511
  return true;
22503
22512
  }
22504
22513
  } catch (err) {
22505
- logger$3.critical('Error while checking selector: ' + sel, err);
22514
+ logger$4.critical('Error while checking selector: ' + sel, err);
22506
22515
  }
22507
22516
  }
22508
22517
  }
@@ -23049,6 +23058,655 @@
23049
23058
  }
23050
23059
  }
23051
23060
 
23061
+ /**
23062
+ * This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
23063
+ * the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
23064
+ *
23065
+ * This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
23066
+ * header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
23067
+ *
23068
+ */
23069
+
23070
+ var logger$3 = console_with_prefix('network-plugin');
23071
+
23072
+ /**
23073
+ * Get the time origin for converting performance timestamps to absolute timestamps.
23074
+ * Uses Date.now() - performance.now() instead of performance.timeOrigin because
23075
+ * browsers can report timeOrigin values that are skewed from actual time, and some
23076
+ * browsers (notably older Safari versions) don't implement timeOrigin at all.
23077
+ * See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
23078
+ * @param {Window} win
23079
+ * @returns {number}
23080
+ */
23081
+ function getTimeOrigin(win) {
23082
+ return Math.round(Date.now() - win.performance.now());
23083
+ }
23084
+
23085
+ /**
23086
+ * @typedef {import('../index.d.ts').InitiatorType} InitiatorType
23087
+ * @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
23088
+ * @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
23089
+ * @typedef {import('../index.d.ts').NetworkData} NetworkData
23090
+ */
23091
+
23092
+ /**
23093
+ * @typedef {Record<string, string>} Headers
23094
+ */
23095
+
23096
+ /**
23097
+ * @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
23098
+ */
23099
+
23100
+ /**
23101
+ * @callback networkCallback
23102
+ * @param {NetworkData} data
23103
+ * @returns {void}
23104
+ */
23105
+
23106
+ /**
23107
+ * @callback listenerHandler
23108
+ * @returns {void}
23109
+ */
23110
+
23111
+ /**
23112
+ * @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
23113
+ */
23114
+
23115
+ /**
23116
+ * @typedef {Object} RecordPlugin
23117
+ * @property {string} name
23118
+ * @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
23119
+ * @property {NetworkRecordOptions} [options]
23120
+ */
23121
+
23122
+ /** @type {Required<NetworkRecordOptions>} */
23123
+ var defaultNetworkOptions = {
23124
+ initiatorTypes: [
23125
+ 'audio',
23126
+ 'beacon',
23127
+ 'body',
23128
+ 'css',
23129
+ 'early-hint',
23130
+ 'embed',
23131
+ 'fetch',
23132
+ 'frame',
23133
+ 'iframe',
23134
+ 'icon',
23135
+ 'image',
23136
+ 'img',
23137
+ 'input',
23138
+ 'link',
23139
+ 'navigation',
23140
+ 'object',
23141
+ 'ping',
23142
+ 'script',
23143
+ 'track',
23144
+ 'video',
23145
+ 'xmlhttprequest',
23146
+ ],
23147
+ ignoreRequestFn: function() { return false; },
23148
+ recordHeaders: {
23149
+ request: [],
23150
+ response: [],
23151
+ },
23152
+ recordBodyUrls: {
23153
+ request: [],
23154
+ response: [],
23155
+ },
23156
+ recordInitialRequests: false,
23157
+ };
23158
+
23159
+ /**
23160
+ * @param {PerformanceEntry} entry
23161
+ * @returns {entry is PerformanceNavigationTiming}
23162
+ */
23163
+ function isNavigationTiming(entry) {
23164
+ return entry.entryType === 'navigation';
23165
+ }
23166
+
23167
+ /**
23168
+ * @param {PerformanceEntry} entry
23169
+ * @returns {entry is PerformanceResourceTiming}
23170
+ */
23171
+ function isResourceTiming (entry) {
23172
+ return entry.entryType === 'resource';
23173
+ }
23174
+
23175
+ function findLast(array, predicate) {
23176
+ var length = array.length;
23177
+ for (var i = length - 1; i >= 0; i -= 1) {
23178
+ if (predicate(array[i])) {
23179
+ return array[i];
23180
+ }
23181
+ }
23182
+ }
23183
+
23184
+ /**
23185
+ * Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
23186
+ * Adapted from Sentry's `fill` utility:
23187
+ * https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
23188
+ *
23189
+ * @param {object} source - The object containing the method to patch
23190
+ * @param {string} name - The method name to patch
23191
+ * @param {function} replacementFactory - A function that receives the original method and returns the replacement
23192
+ * @returns {function} A function that restores the original method
23193
+ */
23194
+ function patch(source, name, replacementFactory) {
23195
+ if (!(name in source) || typeof source[name] !== 'function') {
23196
+ return function() {};
23197
+ }
23198
+ var original = source[name];
23199
+ var wrapped = replacementFactory(original);
23200
+ source[name] = wrapped;
23201
+ return function() {
23202
+ source[name] = original;
23203
+ };
23204
+ }
23205
+
23206
+
23207
+ /**
23208
+ * Maximum body size to record (1MB)
23209
+ */
23210
+ var MAX_BODY_SIZE = 1024 * 1024;
23211
+
23212
+ /**
23213
+ * Truncate string if it exceeds max size
23214
+ * @param {string} str
23215
+ * @returns {string}
23216
+ */
23217
+ function truncateBody(str) {
23218
+ if (!str || typeof str !== 'string') {
23219
+ return str;
23220
+ }
23221
+ if (str.length > MAX_BODY_SIZE) {
23222
+ logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23223
+ return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23224
+ }
23225
+ return str;
23226
+ }
23227
+
23228
+ /**
23229
+ * @param {networkCallback} cb
23230
+ * @param {Window} win
23231
+ * @param {Required<NetworkRecordOptions>} options
23232
+ * @returns {listenerHandler}
23233
+ */
23234
+ function initPerformanceObserver(cb, win, options) {
23235
+ if (!win.PerformanceObserver) {
23236
+ logger$3.error('PerformanceObserver not supported');
23237
+ return function() {
23238
+ //
23239
+ };
23240
+ }
23241
+ if (options.recordInitialRequests) {
23242
+ var initialPerformanceEntries = win.performance
23243
+ .getEntries()
23244
+ .filter(function(entry) {
23245
+ return isNavigationTiming(entry) ||
23246
+ (isResourceTiming(entry) &&
23247
+ options.initiatorTypes.includes(entry.initiatorType));
23248
+ });
23249
+ cb({
23250
+ requests: initialPerformanceEntries.map(function(entry) {
23251
+ return {
23252
+ url: entry.name,
23253
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23254
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23255
+ startTime: Math.round(entry.startTime),
23256
+ endTime: Math.round(entry.responseEnd),
23257
+ timeOrigin: getTimeOrigin(win),
23258
+ };
23259
+ }),
23260
+ isInitial: true,
23261
+ });
23262
+ }
23263
+ var observer = new win.PerformanceObserver(function(entries) {
23264
+ var performanceEntries = entries
23265
+ .getEntries()
23266
+ .filter(function(entry) {
23267
+ return isResourceTiming(entry) &&
23268
+ options.initiatorTypes.includes(entry.initiatorType) &&
23269
+ entry.initiatorType !== 'xmlhttprequest' &&
23270
+ entry.initiatorType !== 'fetch';
23271
+ });
23272
+ cb({
23273
+ requests: performanceEntries.map(function(entry) {
23274
+ return {
23275
+ url: entry.name,
23276
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23277
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23278
+ startTime: Math.round(entry.startTime),
23279
+ endTime: Math.round(entry.responseEnd),
23280
+ timeOrigin: getTimeOrigin(win),
23281
+ };
23282
+ }),
23283
+ });
23284
+ });
23285
+ observer.observe({ entryTypes: ['navigation', 'resource'] });
23286
+ return function() {
23287
+ observer.disconnect();
23288
+ };
23289
+ }
23290
+
23291
+ /**
23292
+ * Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
23293
+ * @param {'request' | 'response'} type
23294
+ * @param {NetworkRecordOptions['recordHeaders']} recordHeaders
23295
+ * @param {string} headerName
23296
+ * @returns {boolean}
23297
+ */
23298
+ function shouldRecordHeader(type, recordHeaders, headerName) {
23299
+ if (!recordHeaders[type] || recordHeaders[type].length === 0) {
23300
+ return false;
23301
+ }
23302
+
23303
+ return recordHeaders[type].includes(headerName.toLowerCase());
23304
+ }
23305
+
23306
+ /**
23307
+ * Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
23308
+ * @param {'request' | 'response'} type
23309
+ * @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
23310
+ * @param {string} url
23311
+ * @returns {boolean}
23312
+ */
23313
+ function shouldRecordBody(type, recordBodyUrls, url) {
23314
+ if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
23315
+ return false;
23316
+ }
23317
+
23318
+ return urlMatchesRegexList(url, recordBodyUrls[type]);
23319
+ }
23320
+
23321
+ function tryReadXHRBody(body) {
23322
+ if (body === null || body === undefined) {
23323
+ return null;
23324
+ }
23325
+
23326
+ var result;
23327
+ if (typeof body === 'string') {
23328
+ result = body;
23329
+ } else if (body instanceof Document) {
23330
+ result = body.textContent;
23331
+ } else if (body instanceof FormData) {
23332
+ result = _.HTTPBuildQuery(body);
23333
+ } else if (_.isObject(body)) {
23334
+ try {
23335
+ result = JSON.stringify(body);
23336
+ } catch (e) {
23337
+ return 'Failed to stringify response object';
23338
+ }
23339
+ } else {
23340
+ return 'Cannot read body of type ' + typeof body;
23341
+ }
23342
+
23343
+ return truncateBody(result);
23344
+ }
23345
+
23346
+ /**
23347
+ * @param {Request | Response} r
23348
+ * @returns {Promise<string>}
23349
+ */
23350
+ function tryReadFetchBody(r) {
23351
+ return new Promise(function(resolve) {
23352
+ var timeout = setTimeout(function() {
23353
+ resolve('Timeout while trying to read body');
23354
+ }, 500);
23355
+ try {
23356
+ r.clone()
23357
+ .text()
23358
+ .then(
23359
+ function(txt) {
23360
+ clearTimeout(timeout);
23361
+ resolve(truncateBody(txt));
23362
+ },
23363
+ function(reason) {
23364
+ clearTimeout(timeout);
23365
+ resolve('Failed to read body: ' + String(reason));
23366
+ }
23367
+ );
23368
+ } catch (e) {
23369
+ clearTimeout(timeout);
23370
+ resolve('Failed to read body: ' + String(e));
23371
+ }
23372
+ });
23373
+ }
23374
+
23375
+ /**
23376
+ * @param {Window} win
23377
+ * @param {string} initiatorType
23378
+ * @param {string} url
23379
+ * @param {number} [after]
23380
+ * @param {number} [before]
23381
+ * @param {number} [attempt]
23382
+ * @returns {Promise<PerformanceResourceTiming>}
23383
+ */
23384
+ function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
23385
+ if (attempt === undefined) {
23386
+ attempt = 0;
23387
+ }
23388
+ if (attempt > 10) {
23389
+ logger$3.error('Cannot find performance entry');
23390
+ return Promise.resolve(null);
23391
+ }
23392
+ var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
23393
+ win.performance.getEntriesByName(url)
23394
+ );
23395
+ var performanceEntry = findLast(
23396
+ urlPerformanceEntries,
23397
+ function(entry) {
23398
+ return isResourceTiming(entry) &&
23399
+ entry.initiatorType === initiatorType &&
23400
+ (!after || entry.startTime >= after) &&
23401
+ (!before || entry.startTime <= before);
23402
+ }
23403
+ );
23404
+ if (!performanceEntry) {
23405
+ return new Promise(function(resolve) {
23406
+ setTimeout(resolve, 50 * attempt);
23407
+ }).then(function() {
23408
+ return getRequestPerformanceEntry(
23409
+ win,
23410
+ initiatorType,
23411
+ url,
23412
+ after,
23413
+ before,
23414
+ attempt + 1
23415
+ );
23416
+ });
23417
+ }
23418
+ return Promise.resolve(performanceEntry);
23419
+ }
23420
+
23421
+ /**
23422
+ * @param {networkCallback} cb
23423
+ * @param {Window} win
23424
+ * @param {Required<NetworkRecordOptions>} options
23425
+ * @returns {listenerHandler}
23426
+ */
23427
+ function initXhrObserver(cb, win, options) {
23428
+ if (!options.initiatorTypes.includes('xmlhttprequest')) {
23429
+ return function() {
23430
+ //
23431
+ };
23432
+ }
23433
+ var restorePatch = patch(
23434
+ win.XMLHttpRequest.prototype,
23435
+ 'open',
23436
+ function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
23437
+ return function(
23438
+ /** @type {string} */ method,
23439
+ /** @type {string | URL} */ url,
23440
+ /** @type {boolean} */ async,
23441
+ username, password
23442
+ ) {
23443
+ if (async === undefined) {
23444
+ async = true;
23445
+ }
23446
+ var xhr = /** @type {XMLHttpRequest} */ (this);
23447
+ var req = new Request(url, { method: method });
23448
+ /** @type {Partial<NetworkRequest>} */
23449
+ var networkRequest = {};
23450
+ /** @type {number | undefined} */
23451
+ var after;
23452
+ /** @type {number | undefined} */
23453
+ var before;
23454
+
23455
+ /** @type {Headers} */
23456
+ var requestHeaders = {};
23457
+ var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
23458
+ xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
23459
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23460
+ requestHeaders[header] = value;
23461
+ }
23462
+ return originalSetRequestHeader(header, value);
23463
+ };
23464
+ networkRequest.requestHeaders = requestHeaders;
23465
+
23466
+ var originalSend = xhr.send.bind(xhr);
23467
+ xhr.send = function(/** @type {Body} */ body) {
23468
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23469
+ networkRequest.requestBody = tryReadXHRBody(body);
23470
+ }
23471
+ after = win.performance.now();
23472
+ return originalSend(body);
23473
+ };
23474
+ xhr.addEventListener('readystatechange', function() {
23475
+ if (xhr.readyState !== xhr.DONE) {
23476
+ return;
23477
+ }
23478
+ before = win.performance.now();
23479
+ /** @type {Headers} */
23480
+ var responseHeaders = {};
23481
+ var rawHeaders = xhr.getAllResponseHeaders();
23482
+ if (rawHeaders) {
23483
+ var headers = rawHeaders.trim().split(/[\r\n]+/);
23484
+ headers.forEach(function(line) {
23485
+ if (!line) return;
23486
+ var colonIndex = line.indexOf(': ');
23487
+ if (colonIndex === -1) return;
23488
+ var header = line.substring(0, colonIndex);
23489
+ var value = line.substring(colonIndex + 2);
23490
+ if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
23491
+ responseHeaders[header] = value;
23492
+ }
23493
+ });
23494
+ }
23495
+ networkRequest.responseHeaders = responseHeaders;
23496
+ if (
23497
+ shouldRecordBody('response', options.recordBodyUrls, req.url)
23498
+ ) {
23499
+ networkRequest.responseBody = tryReadXHRBody(xhr.response);
23500
+ }
23501
+ getRequestPerformanceEntry(
23502
+ win,
23503
+ 'xmlhttprequest',
23504
+ req.url,
23505
+ after,
23506
+ before
23507
+ )
23508
+ .then(function(entry) {
23509
+ if (!entry) {
23510
+ logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
23511
+ return;
23512
+ }
23513
+ /** @type {NetworkRequest} */
23514
+ var request = {
23515
+ url: entry.name,
23516
+ method: req.method,
23517
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23518
+ status: xhr.status,
23519
+ startTime: Math.round(entry.startTime),
23520
+ endTime: Math.round(entry.responseEnd),
23521
+ timeOrigin: getTimeOrigin(win),
23522
+ requestHeaders: networkRequest.requestHeaders,
23523
+ requestBody: networkRequest.requestBody,
23524
+ responseHeaders: networkRequest.responseHeaders,
23525
+ responseBody: networkRequest.responseBody,
23526
+ };
23527
+ cb({ requests: [request] });
23528
+ })
23529
+ .catch(function(e) {
23530
+ logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23531
+ });
23532
+ });
23533
+
23534
+ originalOpen.call(xhr, method, url, async, username, password);
23535
+ };
23536
+ }
23537
+ );
23538
+ return function() {
23539
+ restorePatch();
23540
+ };
23541
+ }
23542
+
23543
+ /**
23544
+ * @param {networkCallback} cb
23545
+ * @param {Window} win
23546
+ * @param {Required<NetworkRecordOptions>} options
23547
+ * @returns {listenerHandler}
23548
+ */
23549
+ function initFetchObserver(cb, win, options) {
23550
+ if (!options.initiatorTypes.includes('fetch')) {
23551
+ return function() {
23552
+ //
23553
+ };
23554
+ }
23555
+
23556
+ var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
23557
+ return function() {
23558
+ var req = new Request(arguments[0], arguments[1]);
23559
+ /** @type {Response | undefined} */
23560
+ var res;
23561
+ /** @type {Partial<NetworkRequest>} */
23562
+ var networkRequest = {};
23563
+ /** @type {number | undefined} */
23564
+ var after;
23565
+ /** @type {number | undefined} */
23566
+ var before;
23567
+
23568
+ var originalFetchPromise;
23569
+ var requestBodyPromise = Promise.resolve(undefined);
23570
+ var responseBodyPromise = Promise.resolve(undefined);
23571
+ try {
23572
+ /** @type {Headers} */
23573
+ var requestHeaders = {};
23574
+ req.headers.forEach(function(value, header) {
23575
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23576
+ requestHeaders[header] = value;
23577
+ }
23578
+ });
23579
+ networkRequest.requestHeaders = requestHeaders;
23580
+
23581
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23582
+ requestBodyPromise = tryReadFetchBody(req)
23583
+ .then(function(body) {
23584
+ networkRequest.requestBody = body;
23585
+ });
23586
+ }
23587
+
23588
+ after = win.performance.now();
23589
+ originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
23590
+ res = response;
23591
+ before = win.performance.now();
23592
+
23593
+ /** @type {Headers} */
23594
+ var responseHeaders = {};
23595
+ res.headers.forEach(function(value, header) {
23596
+ if (shouldRecordHeader('response', options.recordHeaders, header)) {
23597
+ responseHeaders[header] = value;
23598
+ }
23599
+ });
23600
+ networkRequest.responseHeaders = responseHeaders;
23601
+
23602
+ if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
23603
+ responseBodyPromise = tryReadFetchBody(res)
23604
+ .then(function(body) {
23605
+ networkRequest.responseBody = body;
23606
+ });
23607
+ }
23608
+
23609
+ return res;
23610
+ });
23611
+ } catch (e) {
23612
+ originalFetchPromise = Promise.reject(e);
23613
+ }
23614
+
23615
+ // await concurrently so we don't delay the fetch response
23616
+ Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
23617
+ .then(function () {
23618
+ return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
23619
+ })
23620
+ .then(function(entry) {
23621
+ if (!entry) {
23622
+ logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
23623
+ return;
23624
+ }
23625
+ /** @type {NetworkRequest} */
23626
+ var request = {
23627
+ url: entry.name,
23628
+ method: req.method,
23629
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23630
+ status: res ? res.status : undefined,
23631
+ startTime: Math.round(entry.startTime),
23632
+ endTime: Math.round(entry.responseEnd),
23633
+ timeOrigin: getTimeOrigin(win),
23634
+ requestHeaders: networkRequest.requestHeaders,
23635
+ requestBody: networkRequest.requestBody,
23636
+ responseHeaders: networkRequest.responseHeaders,
23637
+ responseBody: networkRequest.responseBody,
23638
+ };
23639
+ cb({ requests: [request] });
23640
+ })
23641
+ .catch(function (e) {
23642
+ logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23643
+ });
23644
+
23645
+ return originalFetchPromise;
23646
+ };
23647
+ });
23648
+ return function() {
23649
+ restorePatch();
23650
+ };
23651
+ }
23652
+
23653
+ /**
23654
+ * @param {networkCallback} callback
23655
+ * @param {Window} win
23656
+ * @param {NetworkRecordOptions} options
23657
+ * @returns {listenerHandler}
23658
+ */
23659
+ function initNetworkObserver(callback, win, options) {
23660
+ if (!('performance' in win)) {
23661
+ return function() {
23662
+ //
23663
+ };
23664
+ }
23665
+
23666
+ var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
23667
+ var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
23668
+ options = Object.assign({}, options, {
23669
+ recordHeaders: recordHeaders,
23670
+ recordBodyUrls: recordBodyUrls,
23671
+ });
23672
+ var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
23673
+
23674
+ /** @type {networkCallback} */
23675
+ var cb = function(data) {
23676
+ var requests = data.requests.filter(function(request) {
23677
+ var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
23678
+ return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
23679
+ });
23680
+ if (requests.length > 0 || data.isInitial) {
23681
+ callback(Object.assign({}, data, { requests: requests }));
23682
+ }
23683
+ };
23684
+ var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
23685
+ var xhrObserver = initXhrObserver(cb, win, networkOptions);
23686
+ var fetchObserver = initFetchObserver(cb, win, networkOptions);
23687
+ return function() {
23688
+ performanceObserver();
23689
+ xhrObserver();
23690
+ fetchObserver();
23691
+ };
23692
+ }
23693
+
23694
+ // arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
23695
+ // a changed format in the mixpanel product.
23696
+ var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
23697
+
23698
+ /**
23699
+ * @param {NetworkRecordOptions} [options]
23700
+ * @returns {RecordPlugin}
23701
+ */
23702
+ var getRecordNetworkPlugin = function(options) {
23703
+ return {
23704
+ name: NETWORK_PLUGIN_NAME,
23705
+ observer: initNetworkObserver,
23706
+ options: options,
23707
+ };
23708
+ };
23709
+
23052
23710
  /**
23053
23711
  * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
23054
23712
  */
@@ -23282,12 +23940,35 @@
23282
23940
 
23283
23941
  var privacyConfig = getPrivacyConfig(this._mixpanel);
23284
23942
 
23285
- try {
23286
- this._stopRecording = this._rrwebRecord({
23287
- 'emit': function (ev) {
23288
- if (this.idleExpires && this.idleExpires < ev.timestamp) {
23289
- this._onIdleTimeout();
23290
- return;
23943
+ var plugins = [];
23944
+ if (this.getConfig('record_network')) {
23945
+ var options = this.getConfig('record_network_options') || {};
23946
+ // don't track requests to Mixpanel /record API
23947
+ var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
23948
+ ignoreRequestUrls.push(this._getApiRoute());
23949
+ options.ignoreRequestUrls = ignoreRequestUrls;
23950
+
23951
+ plugins.push(getRecordNetworkPlugin(options));
23952
+ }
23953
+
23954
+ if (this.getConfig('record_console')) {
23955
+ plugins.push(
23956
+ getRecordConsolePlugin({
23957
+ stringifyOptions: {
23958
+ stringLengthLimit: 1000,
23959
+ numOfKeysLimit: 50,
23960
+ depthOfLimit: 2
23961
+ }
23962
+ })
23963
+ );
23964
+ }
23965
+
23966
+ try {
23967
+ this._stopRecording = this._rrwebRecord({
23968
+ 'emit': function (ev) {
23969
+ if (this.idleExpires && this.idleExpires < ev.timestamp) {
23970
+ this._onIdleTimeout();
23971
+ return;
23291
23972
  }
23292
23973
  if (isUserEvent(ev)) {
23293
23974
  if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
@@ -23320,15 +24001,7 @@
23320
24001
  'sampling': {
23321
24002
  'canvas': 15
23322
24003
  },
23323
- 'plugins': this.getConfig('record_console') ? [
23324
- getRecordConsolePlugin({
23325
- stringifyOptions: {
23326
- stringLengthLimit: 1000,
23327
- numOfKeysLimit: 50,
23328
- depthOfLimit: 2
23329
- }
23330
- })
23331
- ] : []
24004
+ 'plugins': plugins,
23332
24005
  });
23333
24006
  } catch (err) {
23334
24007
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -23443,6 +24116,10 @@
23443
24116
  return recording;
23444
24117
  };
23445
24118
 
24119
+ SessionRecording.prototype._getApiRoute = function () {
24120
+ return this.getConfig('api_routes')['record'];
24121
+ };
24122
+
23446
24123
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
23447
24124
  var onSuccess = function (response, responseBody) {
23448
24125
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -23462,7 +24139,7 @@
23462
24139
  });
23463
24140
  }.bind(this);
23464
24141
  var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
23465
- win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
24142
+ win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
23466
24143
  'method': 'POST',
23467
24144
  'headers': {
23468
24145
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -23863,8 +24540,12 @@
23863
24540
  this.startRecording({shouldStopBatcher: true});
23864
24541
  };
23865
24542
 
24543
+ MixpanelRecorder.prototype.isRecording = function () {
24544
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
24545
+ };
24546
+
23866
24547
  MixpanelRecorder.prototype.getActiveReplayId = function () {
23867
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24548
+ if (this.isRecording()) {
23868
24549
  return this.activeRecording.replayId;
23869
24550
  } else {
23870
24551
  return null;
@@ -24368,53 +25049,6 @@
24368
25049
  var logicExports = requireLogic();
24369
25050
  var jsonLogic = /*@__PURE__*/getDefaultExportFromCjs(logicExports);
24370
25051
 
24371
- /**
24372
- * Shared helper to recursively lowercase strings in nested structures
24373
- * @param {*} obj - Value to process
24374
- * @param {boolean} lowercaseKeys - Whether to lowercase object keys
24375
- * @returns {*} Processed value with lowercased strings
24376
- */
24377
- var lowercaseJson = function(obj, lowercaseKeys) {
24378
- if (obj === null || obj === undefined) {
24379
- return obj;
24380
- } else if (typeof obj === 'string') {
24381
- return obj.toLowerCase();
24382
- } else if (Array.isArray(obj)) {
24383
- return obj.map(function(item) {
24384
- return lowercaseJson(item, lowercaseKeys);
24385
- });
24386
- } else if (obj === Object(obj)) {
24387
- var result = {};
24388
- for (var key in obj) {
24389
- if (obj.hasOwnProperty(key)) {
24390
- var newKey = lowercaseKeys && typeof key === 'string' ? key.toLowerCase() : key;
24391
- result[newKey] = lowercaseJson(obj[key], lowercaseKeys);
24392
- }
24393
- }
24394
- return result;
24395
- } else {
24396
- return obj;
24397
- }
24398
- };
24399
-
24400
- /**
24401
- * Lowercase all string keys and values in a nested structure
24402
- * @param {*} val - Value to process
24403
- * @returns {*} Processed value with lowercased strings
24404
- */
24405
- var lowercaseKeysAndValues = function(val) {
24406
- return lowercaseJson(val, true);
24407
- };
24408
-
24409
- /**
24410
- * Lowercase only leaf node string values in a nested structure (keys unchanged)
24411
- * @param {*} val - Value to process
24412
- * @returns {*} Processed value with lowercased leaf strings
24413
- */
24414
- var lowercaseOnlyLeafNodes = function(val) {
24415
- return lowercaseJson(val, false);
24416
- };
24417
-
24418
25052
  /**
24419
25053
  * Check if an event matches the given criteria
24420
25054
  * @param {string} eventName - The name of the event being checked
@@ -24438,13 +25072,8 @@
24438
25072
 
24439
25073
  if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
24440
25074
  try {
24441
- // Lowercase all keys and values in event properties for case-insensitive matching
24442
- var lowercasedProperties = lowercaseKeysAndValues(properties || {});
24443
-
24444
- // Lowercase only leaf nodes in JsonLogic filters (keep operators intact)
24445
- var lowercasedFilters = lowercaseOnlyLeafNodes(propertyFilters);
24446
-
24447
- filtersMatch = jsonLogic.apply(lowercasedFilters, lowercasedProperties);
25075
+ // Use properties as-is for case-sensitive matching
25076
+ filtersMatch = jsonLogic.apply(propertyFilters, properties || {});
24448
25077
  } catch (error) {
24449
25078
  return {
24450
25079
  matches: false,
@@ -24564,7 +25193,7 @@
24564
25193
  observer.observe(shadowRoot, this.observerConfig);
24565
25194
  this.shadowObservers.push(observer);
24566
25195
  } catch (e) {
24567
- logger$3.critical('Error while observing shadow root', e);
25196
+ logger$4.critical('Error while observing shadow root', e);
24568
25197
  }
24569
25198
  };
24570
25199
 
@@ -24575,7 +25204,7 @@
24575
25204
  }
24576
25205
 
24577
25206
  if (!weakSetSupported()) {
24578
- logger$3.critical('Shadow DOM observation unavailable: WeakSet not supported');
25207
+ logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
24579
25208
  return;
24580
25209
  }
24581
25210
 
@@ -24591,7 +25220,7 @@
24591
25220
  try {
24592
25221
  this.shadowObservers[i].disconnect();
24593
25222
  } catch (e) {
24594
- logger$3.critical('Error while disconnecting shadow DOM observer', e);
25223
+ logger$4.critical('Error while disconnecting shadow DOM observer', e);
24595
25224
  }
24596
25225
  }
24597
25226
  this.shadowObservers = [];
@@ -24779,7 +25408,7 @@
24779
25408
 
24780
25409
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24781
25410
  } catch (e) {
24782
- logger$3.critical('Error while setting up mutation observer', e);
25411
+ logger$4.critical('Error while setting up mutation observer', e);
24783
25412
  }
24784
25413
  }
24785
25414
 
@@ -24794,7 +25423,7 @@
24794
25423
  );
24795
25424
  this.shadowDOMObserver.start();
24796
25425
  } catch (e) {
24797
- logger$3.critical('Error while setting up shadow DOM observer', e);
25426
+ logger$4.critical('Error while setting up shadow DOM observer', e);
24798
25427
  this.shadowDOMObserver = null;
24799
25428
  }
24800
25429
  }
@@ -24821,7 +25450,7 @@
24821
25450
  try {
24822
25451
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24823
25452
  } catch (e) {
24824
- logger$3.critical('Error while removing event listener', e);
25453
+ logger$4.critical('Error while removing event listener', e);
24825
25454
  }
24826
25455
  }
24827
25456
  this.eventListeners = [];
@@ -24830,7 +25459,7 @@
24830
25459
  try {
24831
25460
  this.mutationObserver.disconnect();
24832
25461
  } catch (e) {
24833
- logger$3.critical('Error while disconnecting mutation observer', e);
25462
+ logger$4.critical('Error while disconnecting mutation observer', e);
24834
25463
  }
24835
25464
  this.mutationObserver = null;
24836
25465
  }
@@ -24839,7 +25468,7 @@
24839
25468
  try {
24840
25469
  this.shadowDOMObserver.stop();
24841
25470
  } catch (e) {
24842
- logger$3.critical('Error while stopping shadow DOM observer', e);
25471
+ logger$4.critical('Error while stopping shadow DOM observer', e);
24843
25472
  }
24844
25473
  this.shadowDOMObserver = null;
24845
25474
  }
@@ -24917,7 +25546,7 @@
24917
25546
 
24918
25547
  Autocapture.prototype.init = function() {
24919
25548
  if (!minDOMApisSupported()) {
24920
- logger$3.critical('Autocapture unavailable: missing required DOM APIs');
25549
+ logger$4.critical('Autocapture unavailable: missing required DOM APIs');
24921
25550
  return;
24922
25551
  }
24923
25552
  this.initPageListeners();
@@ -24949,27 +25578,15 @@
24949
25578
  };
24950
25579
 
24951
25580
  Autocapture.prototype.currentUrlBlocked = function() {
24952
- var i;
24953
25581
  var currentUrl = _.info.currentUrl();
24954
25582
 
24955
25583
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
24956
25584
  if (allowUrlRegexes.length) {
24957
25585
  // we're using an allowlist, only track if current URL matches
24958
- var allowed = false;
24959
- for (i = 0; i < allowUrlRegexes.length; i++) {
24960
- var allowRegex = allowUrlRegexes[i];
24961
- try {
24962
- if (currentUrl.match(allowRegex)) {
24963
- allowed = true;
24964
- break;
24965
- }
24966
- } catch (err) {
24967
- logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
24968
- return true;
24969
- }
24970
- }
24971
- if (!allowed) {
24972
- // wasn't allowed by any regex
25586
+ try {
25587
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25588
+ } catch (err) {
25589
+ logger$4.critical('Error while checking block URL regexes: ', err);
24973
25590
  return true;
24974
25591
  }
24975
25592
  }
@@ -24979,17 +25596,12 @@
24979
25596
  return false;
24980
25597
  }
24981
25598
 
24982
- for (i = 0; i < blockUrlRegexes.length; i++) {
24983
- try {
24984
- if (currentUrl.match(blockUrlRegexes[i])) {
24985
- return true;
24986
- }
24987
- } catch (err) {
24988
- logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
24989
- return true;
24990
- }
25599
+ try {
25600
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25601
+ } catch (err) {
25602
+ logger$4.critical('Error while checking block URL regexes: ', err);
25603
+ return true;
24991
25604
  }
24992
- return false;
24993
25605
  };
24994
25606
 
24995
25607
  Autocapture.prototype.pageviewTrackingConfig = function() {
@@ -25125,7 +25737,7 @@
25125
25737
  return;
25126
25738
  }
25127
25739
 
25128
- logger$3.log('Initializing scroll depth tracking');
25740
+ logger$4.log('Initializing scroll depth tracking');
25129
25741
 
25130
25742
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
25131
25743
 
@@ -25151,7 +25763,7 @@
25151
25763
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
25152
25764
  return;
25153
25765
  }
25154
- logger$3.log('Initializing click tracking');
25766
+ logger$4.log('Initializing click tracking');
25155
25767
 
25156
25768
  this.listenerClick = function(ev) {
25157
25769
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -25170,7 +25782,7 @@
25170
25782
  return;
25171
25783
  }
25172
25784
 
25173
- logger$3.log('Initializing dead click tracking');
25785
+ logger$4.log('Initializing dead click tracking');
25174
25786
  if (!this._deadClickTracker) {
25175
25787
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
25176
25788
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -25204,7 +25816,7 @@
25204
25816
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
25205
25817
  return;
25206
25818
  }
25207
- logger$3.log('Initializing input tracking');
25819
+ logger$4.log('Initializing input tracking');
25208
25820
 
25209
25821
  this.listenerChange = function(ev) {
25210
25822
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -25221,7 +25833,7 @@
25221
25833
  if (!this.pageviewTrackingConfig()) {
25222
25834
  return;
25223
25835
  }
25224
- logger$3.log('Initializing pageview tracking');
25836
+ logger$4.log('Initializing pageview tracking');
25225
25837
 
25226
25838
  var previousTrackedUrl = '';
25227
25839
  var tracked = false;
@@ -25256,7 +25868,7 @@
25256
25868
  }
25257
25869
  if (didPathChange) {
25258
25870
  this.lastScrollCheckpoint = 0;
25259
- logger$3.log('Path change: re-initializing scroll depth checkpoints');
25871
+ logger$4.log('Path change: re-initializing scroll depth checkpoints');
25260
25872
  }
25261
25873
  }
25262
25874
  }.bind(this));
@@ -25271,7 +25883,7 @@
25271
25883
  return;
25272
25884
  }
25273
25885
 
25274
- logger$3.log('Initializing rage click tracking');
25886
+ logger$4.log('Initializing rage click tracking');
25275
25887
  if (!this._rageClickTracker) {
25276
25888
  this._rageClickTracker = new RageClickTracker();
25277
25889
  }
@@ -25301,7 +25913,7 @@
25301
25913
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
25302
25914
  return;
25303
25915
  }
25304
- logger$3.log('Initializing scroll tracking');
25916
+ logger$4.log('Initializing scroll tracking');
25305
25917
  this.lastScrollCheckpoint = 0;
25306
25918
 
25307
25919
  var scrollTrackFunction = function() {
@@ -25338,7 +25950,7 @@
25338
25950
  }
25339
25951
  }
25340
25952
  } catch (err) {
25341
- logger$3.critical('Error while calculating scroll percentage', err);
25953
+ logger$4.critical('Error while calculating scroll percentage', err);
25342
25954
  }
25343
25955
  if (shouldTrack) {
25344
25956
  this.mp.track(MP_EV_SCROLL, props);
@@ -25356,7 +25968,7 @@
25356
25968
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
25357
25969
  return;
25358
25970
  }
25359
- logger$3.log('Initializing submit tracking');
25971
+ logger$4.log('Initializing submit tracking');
25360
25972
 
25361
25973
  this.listenerSubmit = function(ev) {
25362
25974
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -25378,7 +25990,7 @@
25378
25990
  return;
25379
25991
  }
25380
25992
 
25381
- logger$3.log('Initializing page visibility tracking.');
25993
+ logger$4.log('Initializing page visibility tracking.');
25382
25994
  this._initScrollDepthTracking();
25383
25995
  var previousTrackedUrl = _.info.currentUrl();
25384
25996
 
@@ -25986,6 +26598,214 @@
25986
26598
  /* eslint camelcase: "off" */
25987
26599
 
25988
26600
 
26601
+ /**
26602
+ * RecorderManager: manages session recording initialization, lifecycle and state
26603
+ * @constructor
26604
+ */
26605
+ var RecorderManager = function(initOptions) {
26606
+ // TODO - Passing in mixpanel instance as it is still needed for recorder creation
26607
+ // but ideally we should be able to remove this dependency.
26608
+ this.mixpanelInstance = initOptions.mixpanelInstance;
26609
+
26610
+ this.getMpConfig = initOptions.getConfigFunc;
26611
+ this.getTabId = initOptions.getTabIdFunc;
26612
+ this.reportError = initOptions.reportErrorFunc;
26613
+ this.getDistinctId = initOptions.getDistinctIdFunc;
26614
+ this.loadExtraBundle = initOptions.loadExtraBundle;
26615
+ this.recorderSrc = initOptions.recorderSrc;
26616
+ this.targetingSrc = initOptions.targetingSrc;
26617
+ this.libBasePath = initOptions.libBasePath;
26618
+
26619
+ this._recorder = null;
26620
+ };
26621
+
26622
+ RecorderManager.prototype.shouldLoadRecorder = function() {
26623
+ if (this.getMpConfig('disable_persistence')) {
26624
+ console$1.log('Load recorder check skipped due to disable_persistence config');
26625
+ return PromisePolyfill.resolve(false);
26626
+ }
26627
+
26628
+ var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26629
+ var tab_id = this.getTabId();
26630
+ return recording_registry_idb.init()
26631
+ .then(function () {
26632
+ return recording_registry_idb.getAll();
26633
+ })
26634
+ .then(function (recordings) {
26635
+ for (var i = 0; i < recordings.length; i++) {
26636
+ // if there are expired recordings in the registry, we should load the recorder to flush them
26637
+ // if there's a recording for this tab id, we should load the recorder to continue the recording
26638
+ if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
26639
+ return true;
26640
+ }
26641
+ }
26642
+ return false;
26643
+ })
26644
+ .catch(_.bind(function (err) {
26645
+ this.reportError('Error checking recording registry', err);
26646
+ return false;
26647
+ }, this));
26648
+ };
26649
+
26650
+ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
26651
+ if (!win['MutationObserver']) {
26652
+ console$1.critical('Browser does not support MutationObserver; skipping session recording');
26653
+ return PromisePolyfill.resolve();
26654
+ }
26655
+
26656
+ var loadRecorder = _.bind(function(startNewIfInactive) {
26657
+ return new PromisePolyfill(_.bind(function(resolve) {
26658
+ var handleLoadedRecorder = safewrap(_.bind(function() {
26659
+ this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
26660
+ this._recorder['resumeRecording'](startNewIfInactive);
26661
+ resolve();
26662
+ }, this));
26663
+
26664
+ if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
26665
+ var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
26666
+ this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
26667
+ } else {
26668
+ handleLoadedRecorder();
26669
+ }
26670
+ }, this));
26671
+ }, this);
26672
+
26673
+ /**
26674
+ * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
26675
+ * Otherwise, if the recording registry has any records then it's likely there's a recording in progress or orphaned data that needs to be flushed.
26676
+ */
26677
+ var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
26678
+ var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
26679
+ if (force_start || is_sampled) {
26680
+ return loadRecorder(true);
26681
+ } else {
26682
+ return this.shouldLoadRecorder()
26683
+ .then(_.bind(function (shouldLoad) {
26684
+ if (shouldLoad) {
26685
+ return loadRecorder(false);
26686
+ }
26687
+ return PromisePolyfill.resolve();
26688
+ }, this));
26689
+ }
26690
+ };
26691
+
26692
+ RecorderManager.prototype.isRecording = function() {
26693
+ // Safety check: ensure isRecording method exists (older CDN builds may not have it)
26694
+ if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
26695
+ return false;
26696
+ }
26697
+ try {
26698
+ return this._recorder['isRecording']();
26699
+ } catch (e) {
26700
+ this.reportError('Error checking if recording is active', e);
26701
+ return false;
26702
+ }
26703
+ };
26704
+
26705
+ RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
26706
+ var isRecording = this.isRecording();
26707
+ var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
26708
+
26709
+ if (!isRecording && recordingTriggerEvents) {
26710
+ var trigger = recordingTriggerEvents[event_name];
26711
+ if (trigger && typeof trigger['percentage'] === 'number') {
26712
+ var newRate = trigger['percentage'];
26713
+ var propertyFilters = trigger['property_filters'];
26714
+ if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
26715
+ var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
26716
+ getTargetingPromise(this.loadExtraBundle, targetingSrc)
26717
+ .then(function(targeting) {
26718
+ try {
26719
+ var result = targeting['eventMatchesCriteria'](
26720
+ event_name,
26721
+ properties,
26722
+ {
26723
+ 'event_name': event_name,
26724
+ 'property_filters': propertyFilters
26725
+ }
26726
+ );
26727
+ if (result['matches']) {
26728
+ this.checkAndStartSessionRecording(false, newRate);
26729
+ }
26730
+ } catch (err) {
26731
+ console$1.critical('Could not parse recording event trigger properties logic:', err);
26732
+ }
26733
+ }.bind(this)).catch(function(err) {
26734
+ console$1.critical('Failed to load targeting library:', err);
26735
+ });
26736
+ } else {
26737
+ this.checkAndStartSessionRecording(false, newRate);
26738
+ }
26739
+ }
26740
+ }
26741
+ };
26742
+
26743
+ RecorderManager.prototype.stopSessionRecording = function() {
26744
+ if (this._recorder) {
26745
+ return this._recorder['stopRecording']();
26746
+ }
26747
+ return PromisePolyfill.resolve();
26748
+ };
26749
+
26750
+ RecorderManager.prototype.pauseSessionRecording = function() {
26751
+ if (this._recorder) {
26752
+ return this._recorder['pauseRecording']();
26753
+ }
26754
+ return PromisePolyfill.resolve();
26755
+ };
26756
+
26757
+ RecorderManager.prototype.resumeSessionRecording = function() {
26758
+ if (this._recorder) {
26759
+ return this._recorder['resumeRecording']();
26760
+ }
26761
+ return PromisePolyfill.resolve();
26762
+ };
26763
+
26764
+ RecorderManager.prototype.isRecordingHeatmapData = function() {
26765
+ return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
26766
+ };
26767
+
26768
+ RecorderManager.prototype.getSessionRecordingProperties = function() {
26769
+ var props = {};
26770
+ var replay_id = this.getSessionReplayId();
26771
+ if (replay_id) {
26772
+ props['$mp_replay_id'] = replay_id;
26773
+ }
26774
+ return props;
26775
+ };
26776
+
26777
+ RecorderManager.prototype.getSessionReplayUrl = function() {
26778
+ var replay_url = null;
26779
+ var replay_id = this.getSessionReplayId();
26780
+ if (replay_id) {
26781
+ var query_params = _.HTTPBuildQuery({
26782
+ 'replay_id': replay_id,
26783
+ 'distinct_id': this.getDistinctId(),
26784
+ 'token': this.getMpConfig('token')
26785
+ });
26786
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
26787
+ }
26788
+ return replay_url;
26789
+ };
26790
+
26791
+ RecorderManager.prototype.getSessionReplayId = function() {
26792
+ var replay_id = null;
26793
+ if (this._recorder) {
26794
+ replay_id = this._recorder['replayId'];
26795
+ }
26796
+ return replay_id || null;
26797
+ };
26798
+
26799
+ // "private" public method to reach into the recorder in test cases
26800
+ RecorderManager.prototype.getRecorder = function() {
26801
+ return this._recorder;
26802
+ };
26803
+
26804
+ safewrapClass(RecorderManager);
26805
+
26806
+ /* eslint camelcase: "off" */
26807
+
26808
+
25989
26809
  /**
25990
26810
  * DomTracker Object
25991
26811
  * @constructor
@@ -27446,13 +28266,17 @@
27446
28266
  'record_collect_fonts': false,
27447
28267
  'record_console': true,
27448
28268
  'record_heatmap_data': false,
28269
+ 'recording_event_triggers': {},
27449
28270
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
27450
28271
  'record_mask_inputs': true,
27451
28272
  'record_max_ms': MAX_RECORDING_MS,
27452
28273
  'record_min_ms': 0,
28274
+ 'record_network': false,
28275
+ 'record_network_options': {},
27453
28276
  'record_sessions_percent': 0,
27454
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
27455
- 'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
28277
+ 'recorder_src': null,
28278
+ 'targeting_src': null,
28279
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
27456
28280
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
27457
28281
  };
27458
28282
 
@@ -27606,6 +28430,19 @@
27606
28430
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
27607
28431
  }));
27608
28432
 
28433
+ this.recorderManager = new RecorderManager({
28434
+ mixpanelInstance: this,
28435
+ getConfigFunc: _.bind(this.get_config, this),
28436
+ setConfigFunc: _.bind(this.set_config, this),
28437
+ getTabIdFunc: _.bind(this.get_tab_id, this),
28438
+ reportErrorFunc: _.bind(this.report_error, this),
28439
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
28440
+ recorderSrc: this.get_config('recorder_src'),
28441
+ targetingSrc: this.get_config('targeting_src'),
28442
+ libBasePath: this.get_config('lib_base_path'),
28443
+ loadExtraBundle: load_extra_bundle
28444
+ });
28445
+
27609
28446
  this['_jsc'] = NOOP_FUNC;
27610
28447
 
27611
28448
  this.__dom_loaded_queue = [];
@@ -27684,7 +28521,7 @@
27684
28521
  getPropertyFunc: _.bind(this.get_property, this),
27685
28522
  trackingFunc: _.bind(this.track, this),
27686
28523
  loadExtraBundle: load_extra_bundle,
27687
- targetingSrc: this.get_config('targeting_src')
28524
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
27688
28525
  });
27689
28526
  this.flags.init();
27690
28527
  this['flags'] = this.flags;
@@ -27697,11 +28534,11 @@
27697
28534
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
27698
28535
  var mode = this.get_config('remote_settings_mode');
27699
28536
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
27700
- this._fetch_remote_settings(mode).then(_.bind(function() {
27701
- this._check_and_start_session_recording();
28537
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
28538
+ return this._check_and_start_session_recording();
27702
28539
  }, this));
27703
28540
  } else {
27704
- this._check_and_start_session_recording();
28541
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
27705
28542
  }
27706
28543
  };
27707
28544
 
@@ -27745,132 +28582,50 @@
27745
28582
  return this.tab_id || null;
27746
28583
  };
27747
28584
 
27748
- MixpanelLib.prototype._should_load_recorder = function () {
27749
- if (this.get_config('disable_persistence')) {
27750
- console$1.log('Load recorder check skipped due to disable_persistence config');
27751
- return Promise.resolve(false);
27752
- }
27753
-
27754
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27755
- var tab_id = this.get_tab_id();
27756
- return recording_registry_idb.init()
27757
- .then(function () {
27758
- return recording_registry_idb.getAll();
27759
- })
27760
- .then(function (recordings) {
27761
- for (var i = 0; i < recordings.length; i++) {
27762
- // if there are expired recordings in the registry, we should load the recorder to flush them
27763
- // if there's a recording for this tab id, we should load the recorder to continue the recording
27764
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
27765
- return true;
27766
- }
27767
- }
27768
- return false;
27769
- })
27770
- .catch(_.bind(function (err) {
27771
- this.report_error('Error checking recording registry', err);
27772
- }, this));
27773
- };
27774
-
27775
28585
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
27776
- if (!win['MutationObserver']) {
27777
- console$1.critical('Browser does not support MutationObserver; skipping session recording');
27778
- return;
27779
- }
27780
-
27781
- var loadRecorder = _.bind(function(startNewIfInactive) {
27782
- var handleLoadedRecorder = _.bind(function() {
27783
- this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
27784
- this._recorder['resumeRecording'](startNewIfInactive);
27785
- }, this);
27786
-
27787
- if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
27788
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
27789
- } else {
27790
- handleLoadedRecorder();
27791
- }
27792
- }, this);
27793
-
27794
- /**
27795
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
27796
- * 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.
27797
- */
27798
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
27799
- if (force_start || is_sampled) {
27800
- loadRecorder(true);
27801
- } else {
27802
- this._should_load_recorder()
27803
- .then(function (shouldLoad) {
27804
- if (shouldLoad) {
27805
- loadRecorder(false);
27806
- }
27807
- });
27808
- }
28586
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
27809
28587
  });
27810
28588
 
28589
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28590
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28591
+ };
28592
+
27811
28593
  MixpanelLib.prototype.start_session_recording = function () {
27812
- this._check_and_start_session_recording(true);
28594
+ return this._check_and_start_session_recording(true);
27813
28595
  };
27814
28596
 
27815
28597
  MixpanelLib.prototype.stop_session_recording = function () {
27816
- if (this._recorder) {
27817
- return this._recorder['stopRecording']();
27818
- }
27819
- return Promise.resolve();
28598
+ return this.recorderManager.stopSessionRecording();
27820
28599
  };
27821
28600
 
27822
28601
  MixpanelLib.prototype.pause_session_recording = function () {
27823
- if (this._recorder) {
27824
- return this._recorder['pauseRecording']();
27825
- }
27826
- return Promise.resolve();
28602
+ return this.recorderManager.pauseSessionRecording();
27827
28603
  };
27828
28604
 
27829
28605
  MixpanelLib.prototype.resume_session_recording = function () {
27830
- if (this._recorder) {
27831
- return this._recorder['resumeRecording']();
27832
- }
27833
- return Promise.resolve();
28606
+ return this.recorderManager.resumeSessionRecording();
27834
28607
  };
27835
28608
 
27836
28609
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
27837
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
28610
+ return this.recorderManager.isRecordingHeatmapData();
27838
28611
  };
27839
28612
 
27840
28613
  MixpanelLib.prototype.get_session_recording_properties = function () {
27841
- var props = {};
27842
- var replay_id = this._get_session_replay_id();
27843
- if (replay_id) {
27844
- props['$mp_replay_id'] = replay_id;
27845
- }
27846
- return props;
28614
+ return this.recorderManager.getSessionRecordingProperties();
27847
28615
  };
27848
28616
 
27849
28617
  MixpanelLib.prototype.get_session_replay_url = function () {
27850
- var replay_url = null;
27851
- var replay_id = this._get_session_replay_id();
27852
- if (replay_id) {
27853
- var query_params = _.HTTPBuildQuery({
27854
- 'replay_id': replay_id,
27855
- 'distinct_id': this.get_distinct_id(),
27856
- 'token': this.get_config('token')
27857
- });
27858
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
27859
- }
27860
- return replay_url;
27861
- };
27862
-
27863
- MixpanelLib.prototype._get_session_replay_id = function () {
27864
- var replay_id = null;
27865
- if (this._recorder) {
27866
- replay_id = this._recorder['replayId'];
27867
- }
27868
- return replay_id || null;
28618
+ return this.recorderManager.getSessionReplayUrl();
27869
28619
  };
27870
28620
 
27871
28621
  // "private" public method to reach into the recorder in test cases
27872
28622
  MixpanelLib.prototype.__get_recorder = function () {
27873
- return this._recorder;
28623
+ return this.recorderManager.getRecorder();
28624
+ };
28625
+
28626
+ // "private" public method to get session recording init promise in test cases
28627
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
28628
+ return this.__session_recording_init_promise;
27874
28629
  };
27875
28630
 
27876
28631
  // Private methods
@@ -28128,6 +28883,7 @@
28128
28883
  };
28129
28884
 
28130
28885
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
28886
+ var self = this;
28131
28887
  var disableRecordingIfStrict = function() {
28132
28888
  if (mode === 'strict') {
28133
28889
  self.set_config({'record_sessions_percent': 0});
@@ -28148,7 +28904,6 @@
28148
28904
  };
28149
28905
  var query_string = _.HTTPBuildQuery(request_params);
28150
28906
  var full_url = settings_endpoint + '?' + query_string;
28151
- var self = this;
28152
28907
 
28153
28908
  var abortController = new AbortController();
28154
28909
  var timeout_id = setTimeout(function() {
@@ -28340,6 +29095,34 @@
28340
29095
  this._execute_array([item]);
28341
29096
  };
28342
29097
 
29098
+ /**
29099
+ * Enables events on the Mixpanel object. If passed no arguments,
29100
+ * this function enable tracking of all events. If passed an
29101
+ * array of event names, those events will be enabled, but other
29102
+ * existing disabled events will continue to be not tracked.
29103
+ *
29104
+ * @param {Array} [events] An array of event names to enable
29105
+ */
29106
+ MixpanelLib.prototype.enable = function(events) {
29107
+ var keys, new_disabled_events, i, j;
29108
+
29109
+ if (typeof(events) === 'undefined') {
29110
+ this._flags.disable_all_events = false;
29111
+ } else {
29112
+ keys = {};
29113
+ new_disabled_events = [];
29114
+ for (i = 0; i < events.length; i++) {
29115
+ keys[events[i]] = true;
29116
+ }
29117
+ for (j = 0; j < this.__disabled_events.length; j++) {
29118
+ if (!keys[this.__disabled_events[j]]) {
29119
+ new_disabled_events.push(this.__disabled_events[j]);
29120
+ }
29121
+ }
29122
+ this.__disabled_events = new_disabled_events;
29123
+ }
29124
+ };
29125
+
28343
29126
  /**
28344
29127
  * Disable events on the Mixpanel object. If passed no arguments,
28345
29128
  * this function disables tracking of any event. If passed an
@@ -28513,6 +29296,8 @@
28513
29296
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
28514
29297
  }
28515
29298
 
29299
+ this._start_recording_on_event(event_name, properties);
29300
+
28516
29301
  var data = {
28517
29302
  'event': event_name,
28518
29303
  'properties': properties
@@ -29721,6 +30506,7 @@
29721
30506
  // MixpanelLib Exports
29722
30507
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
29723
30508
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
30509
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
29724
30510
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
29725
30511
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
29726
30512
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -29764,6 +30550,7 @@
29764
30550
 
29765
30551
  // Exports intended only for testing
29766
30552
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
30553
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
29767
30554
 
29768
30555
  // MixpanelPersistence Exports
29769
30556
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;