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