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
@@ -26,16 +26,19 @@
26
26
  win = window;
27
27
  }
28
28
 
29
- /**
30
- * Shared global window property names used across modules
31
- */
29
+ var Config = {
30
+ DEBUG: false,
31
+ LIB_VERSION: '2.76.0'
32
+ };
32
33
 
33
- // Targeting library global (used by flags and targeting modules)
34
+ // Window global names for async modules
34
35
  var TARGETING_GLOBAL_NAME = '__mp_targeting';
35
-
36
- // Recorder library global (used by recorder and mixpanel-core)
37
36
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
38
37
 
38
+ // Constants that are injected at build-time for the names of async modules.
39
+ var RECORDER_FILENAME = '__MP_RECORDER_FILENAME__';
40
+ var TARGETING_FILENAME = '__MP_TARGETING_FILENAME__';
41
+
39
42
  function _array_like_to_array(arr, len) {
40
43
  if (len == null || len > arr.length) len = arr.length;
41
44
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -18135,7 +18138,7 @@
18135
18138
  var __publicField = function(obj, key, value) {
18136
18139
  return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
18137
18140
  };
18138
- function patch(source, name, replacement) {
18141
+ function patch$3(source, name, replacement) {
18139
18142
  try {
18140
18143
  if (!(name in source)) {
18141
18144
  return function() {};
@@ -18552,7 +18555,7 @@
18552
18555
  if (!_logger[level]) {
18553
18556
  return function() {};
18554
18557
  }
18555
- return patch(_logger, level, function(original) {
18558
+ return patch$3(_logger, level, function(original) {
18556
18559
  var _this1 = _this;
18557
18560
  return function() {
18558
18561
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
@@ -18973,11 +18976,6 @@
18973
18976
  PromisePolyfill = NpoPromise;
18974
18977
  }
18975
18978
 
18976
- var Config = {
18977
- DEBUG: false,
18978
- LIB_VERSION: '2.75.0'
18979
- };
18980
-
18981
18979
  /* eslint camelcase: "off", eqeqeq: "off" */
18982
18980
 
18983
18981
  // Maximum allowed session recording length
@@ -20709,6 +20707,17 @@
20709
20707
 
20710
20708
  var NOOP_FUNC = function () {};
20711
20709
 
20710
+ var urlMatchesRegexList = function (url, regexList) {
20711
+ var matches = false;
20712
+ for (var i = 0; i < regexList.length; i++) {
20713
+ if (url.match(regexList[i])) {
20714
+ matches = true;
20715
+ break;
20716
+ }
20717
+ }
20718
+ return matches;
20719
+ };
20720
+
20712
20721
  var JSONStringify = null, JSONParse = null;
20713
20722
  if (typeof JSON !== 'undefined') {
20714
20723
  JSONStringify = JSON.stringify;
@@ -21180,7 +21189,7 @@
21180
21189
  };
21181
21190
  }
21182
21191
 
21183
- var logger$6 = console_with_prefix('lock');
21192
+ var logger$7 = console_with_prefix('lock');
21184
21193
 
21185
21194
  /**
21186
21195
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21232,7 +21241,7 @@
21232
21241
 
21233
21242
  var delay = function(cb) {
21234
21243
  if (new Date().getTime() - startTime > timeoutMS) {
21235
- logger$6.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21244
+ logger$7.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21236
21245
  storage.removeItem(keyZ);
21237
21246
  storage.removeItem(keyY);
21238
21247
  loop();
@@ -21379,7 +21388,7 @@
21379
21388
  }, this));
21380
21389
  };
21381
21390
 
21382
- var logger$5 = console_with_prefix('batch');
21391
+ var logger$6 = console_with_prefix('batch');
21383
21392
 
21384
21393
  /**
21385
21394
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21408,7 +21417,7 @@
21408
21417
  timeoutMS: options.sharedLockTimeoutMS,
21409
21418
  });
21410
21419
  }
21411
- this.reportError = options.errorReporter || _.bind(logger$5.error, logger$5);
21420
+ this.reportError = options.errorReporter || _.bind(logger$6.error, logger$6);
21412
21421
 
21413
21422
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21414
21423
 
@@ -21741,7 +21750,7 @@
21741
21750
  // maximum interval between request retries after exponential backoff
21742
21751
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21743
21752
 
21744
- var logger$4 = console_with_prefix('batch');
21753
+ var logger$5 = console_with_prefix('batch');
21745
21754
 
21746
21755
  /**
21747
21756
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21869,7 +21878,7 @@
21869
21878
  */
21870
21879
  RequestBatcher.prototype.flush = function(options) {
21871
21880
  if (this.requestInProgress) {
21872
- logger$4.log('Flush: Request already in progress');
21881
+ logger$5.log('Flush: Request already in progress');
21873
21882
  return PromisePolyfill.resolve();
21874
21883
  }
21875
21884
 
@@ -22046,7 +22055,7 @@
22046
22055
  if (options.unloading) {
22047
22056
  requestOptions.transport = 'sendBeacon';
22048
22057
  }
22049
- logger$4.log('MIXPANEL REQUEST:', dataForRequest);
22058
+ logger$5.log('MIXPANEL REQUEST:', dataForRequest);
22050
22059
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
22051
22060
  }, this))
22052
22061
  .catch(_.bind(function(err) {
@@ -22059,7 +22068,7 @@
22059
22068
  * Log error to global logger and optional user-defined logger.
22060
22069
  */
22061
22070
  RequestBatcher.prototype.reportError = function(msg, err) {
22062
- logger$4.error.apply(logger$4.error, arguments);
22071
+ logger$5.error.apply(logger$5.error, arguments);
22063
22072
  if (this.errorReporter) {
22064
22073
  try {
22065
22074
  if (!(err instanceof Error)) {
@@ -22067,7 +22076,7 @@
22067
22076
  }
22068
22077
  this.errorReporter(msg, err);
22069
22078
  } catch(err) {
22070
- logger$4.error(err);
22079
+ logger$5.error(err);
22071
22080
  }
22072
22081
  }
22073
22082
  };
@@ -22189,7 +22198,7 @@
22189
22198
 
22190
22199
  var MAX_DEPTH = 5;
22191
22200
 
22192
- var logger$3 = console_with_prefix('autocapture');
22201
+ var logger$4 = console_with_prefix('autocapture');
22193
22202
 
22194
22203
 
22195
22204
  function getClasses(el) {
@@ -22453,7 +22462,7 @@
22453
22462
  return false;
22454
22463
  }
22455
22464
  } catch (err) {
22456
- logger$3.critical('Error while checking element in allowElementCallback', err);
22465
+ logger$4.critical('Error while checking element in allowElementCallback', err);
22457
22466
  return false;
22458
22467
  }
22459
22468
  }
@@ -22470,7 +22479,7 @@
22470
22479
  return true;
22471
22480
  }
22472
22481
  } catch (err) {
22473
- logger$3.critical('Error while checking selector: ' + sel, err);
22482
+ logger$4.critical('Error while checking selector: ' + sel, err);
22474
22483
  }
22475
22484
  }
22476
22485
  return false;
@@ -22485,7 +22494,7 @@
22485
22494
  return true;
22486
22495
  }
22487
22496
  } catch (err) {
22488
- logger$3.critical('Error while checking element in blockElementCallback', err);
22497
+ logger$4.critical('Error while checking element in blockElementCallback', err);
22489
22498
  return true;
22490
22499
  }
22491
22500
  }
@@ -22499,7 +22508,7 @@
22499
22508
  return true;
22500
22509
  }
22501
22510
  } catch (err) {
22502
- logger$3.critical('Error while checking selector: ' + sel, err);
22511
+ logger$4.critical('Error while checking selector: ' + sel, err);
22503
22512
  }
22504
22513
  }
22505
22514
  }
@@ -23046,6 +23055,655 @@
23046
23055
  }
23047
23056
  }
23048
23057
 
23058
+ /**
23059
+ * This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
23060
+ * the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
23061
+ *
23062
+ * This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
23063
+ * header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
23064
+ *
23065
+ */
23066
+
23067
+ var logger$3 = console_with_prefix('network-plugin');
23068
+
23069
+ /**
23070
+ * Get the time origin for converting performance timestamps to absolute timestamps.
23071
+ * Uses Date.now() - performance.now() instead of performance.timeOrigin because
23072
+ * browsers can report timeOrigin values that are skewed from actual time, and some
23073
+ * browsers (notably older Safari versions) don't implement timeOrigin at all.
23074
+ * See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
23075
+ * @param {Window} win
23076
+ * @returns {number}
23077
+ */
23078
+ function getTimeOrigin(win) {
23079
+ return Math.round(Date.now() - win.performance.now());
23080
+ }
23081
+
23082
+ /**
23083
+ * @typedef {import('../index.d.ts').InitiatorType} InitiatorType
23084
+ * @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
23085
+ * @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
23086
+ * @typedef {import('../index.d.ts').NetworkData} NetworkData
23087
+ */
23088
+
23089
+ /**
23090
+ * @typedef {Record<string, string>} Headers
23091
+ */
23092
+
23093
+ /**
23094
+ * @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
23095
+ */
23096
+
23097
+ /**
23098
+ * @callback networkCallback
23099
+ * @param {NetworkData} data
23100
+ * @returns {void}
23101
+ */
23102
+
23103
+ /**
23104
+ * @callback listenerHandler
23105
+ * @returns {void}
23106
+ */
23107
+
23108
+ /**
23109
+ * @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
23110
+ */
23111
+
23112
+ /**
23113
+ * @typedef {Object} RecordPlugin
23114
+ * @property {string} name
23115
+ * @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
23116
+ * @property {NetworkRecordOptions} [options]
23117
+ */
23118
+
23119
+ /** @type {Required<NetworkRecordOptions>} */
23120
+ var defaultNetworkOptions = {
23121
+ initiatorTypes: [
23122
+ 'audio',
23123
+ 'beacon',
23124
+ 'body',
23125
+ 'css',
23126
+ 'early-hint',
23127
+ 'embed',
23128
+ 'fetch',
23129
+ 'frame',
23130
+ 'iframe',
23131
+ 'icon',
23132
+ 'image',
23133
+ 'img',
23134
+ 'input',
23135
+ 'link',
23136
+ 'navigation',
23137
+ 'object',
23138
+ 'ping',
23139
+ 'script',
23140
+ 'track',
23141
+ 'video',
23142
+ 'xmlhttprequest',
23143
+ ],
23144
+ ignoreRequestFn: function() { return false; },
23145
+ recordHeaders: {
23146
+ request: [],
23147
+ response: [],
23148
+ },
23149
+ recordBodyUrls: {
23150
+ request: [],
23151
+ response: [],
23152
+ },
23153
+ recordInitialRequests: false,
23154
+ };
23155
+
23156
+ /**
23157
+ * @param {PerformanceEntry} entry
23158
+ * @returns {entry is PerformanceNavigationTiming}
23159
+ */
23160
+ function isNavigationTiming(entry) {
23161
+ return entry.entryType === 'navigation';
23162
+ }
23163
+
23164
+ /**
23165
+ * @param {PerformanceEntry} entry
23166
+ * @returns {entry is PerformanceResourceTiming}
23167
+ */
23168
+ function isResourceTiming (entry) {
23169
+ return entry.entryType === 'resource';
23170
+ }
23171
+
23172
+ function findLast(array, predicate) {
23173
+ var length = array.length;
23174
+ for (var i = length - 1; i >= 0; i -= 1) {
23175
+ if (predicate(array[i])) {
23176
+ return array[i];
23177
+ }
23178
+ }
23179
+ }
23180
+
23181
+ /**
23182
+ * Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
23183
+ * Adapted from Sentry's `fill` utility:
23184
+ * https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
23185
+ *
23186
+ * @param {object} source - The object containing the method to patch
23187
+ * @param {string} name - The method name to patch
23188
+ * @param {function} replacementFactory - A function that receives the original method and returns the replacement
23189
+ * @returns {function} A function that restores the original method
23190
+ */
23191
+ function patch(source, name, replacementFactory) {
23192
+ if (!(name in source) || typeof source[name] !== 'function') {
23193
+ return function() {};
23194
+ }
23195
+ var original = source[name];
23196
+ var wrapped = replacementFactory(original);
23197
+ source[name] = wrapped;
23198
+ return function() {
23199
+ source[name] = original;
23200
+ };
23201
+ }
23202
+
23203
+
23204
+ /**
23205
+ * Maximum body size to record (1MB)
23206
+ */
23207
+ var MAX_BODY_SIZE = 1024 * 1024;
23208
+
23209
+ /**
23210
+ * Truncate string if it exceeds max size
23211
+ * @param {string} str
23212
+ * @returns {string}
23213
+ */
23214
+ function truncateBody(str) {
23215
+ if (!str || typeof str !== 'string') {
23216
+ return str;
23217
+ }
23218
+ if (str.length > MAX_BODY_SIZE) {
23219
+ logger$3.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
23220
+ return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
23221
+ }
23222
+ return str;
23223
+ }
23224
+
23225
+ /**
23226
+ * @param {networkCallback} cb
23227
+ * @param {Window} win
23228
+ * @param {Required<NetworkRecordOptions>} options
23229
+ * @returns {listenerHandler}
23230
+ */
23231
+ function initPerformanceObserver(cb, win, options) {
23232
+ if (!win.PerformanceObserver) {
23233
+ logger$3.error('PerformanceObserver not supported');
23234
+ return function() {
23235
+ //
23236
+ };
23237
+ }
23238
+ if (options.recordInitialRequests) {
23239
+ var initialPerformanceEntries = win.performance
23240
+ .getEntries()
23241
+ .filter(function(entry) {
23242
+ return isNavigationTiming(entry) ||
23243
+ (isResourceTiming(entry) &&
23244
+ options.initiatorTypes.includes(entry.initiatorType));
23245
+ });
23246
+ cb({
23247
+ requests: initialPerformanceEntries.map(function(entry) {
23248
+ return {
23249
+ url: entry.name,
23250
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23251
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23252
+ startTime: Math.round(entry.startTime),
23253
+ endTime: Math.round(entry.responseEnd),
23254
+ timeOrigin: getTimeOrigin(win),
23255
+ };
23256
+ }),
23257
+ isInitial: true,
23258
+ });
23259
+ }
23260
+ var observer = new win.PerformanceObserver(function(entries) {
23261
+ var performanceEntries = entries
23262
+ .getEntries()
23263
+ .filter(function(entry) {
23264
+ return isResourceTiming(entry) &&
23265
+ options.initiatorTypes.includes(entry.initiatorType) &&
23266
+ entry.initiatorType !== 'xmlhttprequest' &&
23267
+ entry.initiatorType !== 'fetch';
23268
+ });
23269
+ cb({
23270
+ requests: performanceEntries.map(function(entry) {
23271
+ return {
23272
+ url: entry.name,
23273
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23274
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
23275
+ startTime: Math.round(entry.startTime),
23276
+ endTime: Math.round(entry.responseEnd),
23277
+ timeOrigin: getTimeOrigin(win),
23278
+ };
23279
+ }),
23280
+ });
23281
+ });
23282
+ observer.observe({ entryTypes: ['navigation', 'resource'] });
23283
+ return function() {
23284
+ observer.disconnect();
23285
+ };
23286
+ }
23287
+
23288
+ /**
23289
+ * Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
23290
+ * @param {'request' | 'response'} type
23291
+ * @param {NetworkRecordOptions['recordHeaders']} recordHeaders
23292
+ * @param {string} headerName
23293
+ * @returns {boolean}
23294
+ */
23295
+ function shouldRecordHeader(type, recordHeaders, headerName) {
23296
+ if (!recordHeaders[type] || recordHeaders[type].length === 0) {
23297
+ return false;
23298
+ }
23299
+
23300
+ return recordHeaders[type].includes(headerName.toLowerCase());
23301
+ }
23302
+
23303
+ /**
23304
+ * Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
23305
+ * @param {'request' | 'response'} type
23306
+ * @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
23307
+ * @param {string} url
23308
+ * @returns {boolean}
23309
+ */
23310
+ function shouldRecordBody(type, recordBodyUrls, url) {
23311
+ if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
23312
+ return false;
23313
+ }
23314
+
23315
+ return urlMatchesRegexList(url, recordBodyUrls[type]);
23316
+ }
23317
+
23318
+ function tryReadXHRBody(body) {
23319
+ if (body === null || body === undefined) {
23320
+ return null;
23321
+ }
23322
+
23323
+ var result;
23324
+ if (typeof body === 'string') {
23325
+ result = body;
23326
+ } else if (body instanceof Document) {
23327
+ result = body.textContent;
23328
+ } else if (body instanceof FormData) {
23329
+ result = _.HTTPBuildQuery(body);
23330
+ } else if (_.isObject(body)) {
23331
+ try {
23332
+ result = JSON.stringify(body);
23333
+ } catch (e) {
23334
+ return 'Failed to stringify response object';
23335
+ }
23336
+ } else {
23337
+ return 'Cannot read body of type ' + typeof body;
23338
+ }
23339
+
23340
+ return truncateBody(result);
23341
+ }
23342
+
23343
+ /**
23344
+ * @param {Request | Response} r
23345
+ * @returns {Promise<string>}
23346
+ */
23347
+ function tryReadFetchBody(r) {
23348
+ return new Promise(function(resolve) {
23349
+ var timeout = setTimeout(function() {
23350
+ resolve('Timeout while trying to read body');
23351
+ }, 500);
23352
+ try {
23353
+ r.clone()
23354
+ .text()
23355
+ .then(
23356
+ function(txt) {
23357
+ clearTimeout(timeout);
23358
+ resolve(truncateBody(txt));
23359
+ },
23360
+ function(reason) {
23361
+ clearTimeout(timeout);
23362
+ resolve('Failed to read body: ' + String(reason));
23363
+ }
23364
+ );
23365
+ } catch (e) {
23366
+ clearTimeout(timeout);
23367
+ resolve('Failed to read body: ' + String(e));
23368
+ }
23369
+ });
23370
+ }
23371
+
23372
+ /**
23373
+ * @param {Window} win
23374
+ * @param {string} initiatorType
23375
+ * @param {string} url
23376
+ * @param {number} [after]
23377
+ * @param {number} [before]
23378
+ * @param {number} [attempt]
23379
+ * @returns {Promise<PerformanceResourceTiming>}
23380
+ */
23381
+ function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
23382
+ if (attempt === undefined) {
23383
+ attempt = 0;
23384
+ }
23385
+ if (attempt > 10) {
23386
+ logger$3.error('Cannot find performance entry');
23387
+ return Promise.resolve(null);
23388
+ }
23389
+ var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
23390
+ win.performance.getEntriesByName(url)
23391
+ );
23392
+ var performanceEntry = findLast(
23393
+ urlPerformanceEntries,
23394
+ function(entry) {
23395
+ return isResourceTiming(entry) &&
23396
+ entry.initiatorType === initiatorType &&
23397
+ (!after || entry.startTime >= after) &&
23398
+ (!before || entry.startTime <= before);
23399
+ }
23400
+ );
23401
+ if (!performanceEntry) {
23402
+ return new Promise(function(resolve) {
23403
+ setTimeout(resolve, 50 * attempt);
23404
+ }).then(function() {
23405
+ return getRequestPerformanceEntry(
23406
+ win,
23407
+ initiatorType,
23408
+ url,
23409
+ after,
23410
+ before,
23411
+ attempt + 1
23412
+ );
23413
+ });
23414
+ }
23415
+ return Promise.resolve(performanceEntry);
23416
+ }
23417
+
23418
+ /**
23419
+ * @param {networkCallback} cb
23420
+ * @param {Window} win
23421
+ * @param {Required<NetworkRecordOptions>} options
23422
+ * @returns {listenerHandler}
23423
+ */
23424
+ function initXhrObserver(cb, win, options) {
23425
+ if (!options.initiatorTypes.includes('xmlhttprequest')) {
23426
+ return function() {
23427
+ //
23428
+ };
23429
+ }
23430
+ var restorePatch = patch(
23431
+ win.XMLHttpRequest.prototype,
23432
+ 'open',
23433
+ function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
23434
+ return function(
23435
+ /** @type {string} */ method,
23436
+ /** @type {string | URL} */ url,
23437
+ /** @type {boolean} */ async,
23438
+ username, password
23439
+ ) {
23440
+ if (async === undefined) {
23441
+ async = true;
23442
+ }
23443
+ var xhr = /** @type {XMLHttpRequest} */ (this);
23444
+ var req = new Request(url, { method: method });
23445
+ /** @type {Partial<NetworkRequest>} */
23446
+ var networkRequest = {};
23447
+ /** @type {number | undefined} */
23448
+ var after;
23449
+ /** @type {number | undefined} */
23450
+ var before;
23451
+
23452
+ /** @type {Headers} */
23453
+ var requestHeaders = {};
23454
+ var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
23455
+ xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
23456
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23457
+ requestHeaders[header] = value;
23458
+ }
23459
+ return originalSetRequestHeader(header, value);
23460
+ };
23461
+ networkRequest.requestHeaders = requestHeaders;
23462
+
23463
+ var originalSend = xhr.send.bind(xhr);
23464
+ xhr.send = function(/** @type {Body} */ body) {
23465
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23466
+ networkRequest.requestBody = tryReadXHRBody(body);
23467
+ }
23468
+ after = win.performance.now();
23469
+ return originalSend(body);
23470
+ };
23471
+ xhr.addEventListener('readystatechange', function() {
23472
+ if (xhr.readyState !== xhr.DONE) {
23473
+ return;
23474
+ }
23475
+ before = win.performance.now();
23476
+ /** @type {Headers} */
23477
+ var responseHeaders = {};
23478
+ var rawHeaders = xhr.getAllResponseHeaders();
23479
+ if (rawHeaders) {
23480
+ var headers = rawHeaders.trim().split(/[\r\n]+/);
23481
+ headers.forEach(function(line) {
23482
+ if (!line) return;
23483
+ var colonIndex = line.indexOf(': ');
23484
+ if (colonIndex === -1) return;
23485
+ var header = line.substring(0, colonIndex);
23486
+ var value = line.substring(colonIndex + 2);
23487
+ if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
23488
+ responseHeaders[header] = value;
23489
+ }
23490
+ });
23491
+ }
23492
+ networkRequest.responseHeaders = responseHeaders;
23493
+ if (
23494
+ shouldRecordBody('response', options.recordBodyUrls, req.url)
23495
+ ) {
23496
+ networkRequest.responseBody = tryReadXHRBody(xhr.response);
23497
+ }
23498
+ getRequestPerformanceEntry(
23499
+ win,
23500
+ 'xmlhttprequest',
23501
+ req.url,
23502
+ after,
23503
+ before
23504
+ )
23505
+ .then(function(entry) {
23506
+ if (!entry) {
23507
+ logger$3.error('Failed to get performance entry for XHR request to ' + req.url);
23508
+ return;
23509
+ }
23510
+ /** @type {NetworkRequest} */
23511
+ var request = {
23512
+ url: entry.name,
23513
+ method: req.method,
23514
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23515
+ status: xhr.status,
23516
+ startTime: Math.round(entry.startTime),
23517
+ endTime: Math.round(entry.responseEnd),
23518
+ timeOrigin: getTimeOrigin(win),
23519
+ requestHeaders: networkRequest.requestHeaders,
23520
+ requestBody: networkRequest.requestBody,
23521
+ responseHeaders: networkRequest.responseHeaders,
23522
+ responseBody: networkRequest.responseBody,
23523
+ };
23524
+ cb({ requests: [request] });
23525
+ })
23526
+ .catch(function(e) {
23527
+ logger$3.error('Error recording XHR request to ' + req.url + ': ' + String(e));
23528
+ });
23529
+ });
23530
+
23531
+ originalOpen.call(xhr, method, url, async, username, password);
23532
+ };
23533
+ }
23534
+ );
23535
+ return function() {
23536
+ restorePatch();
23537
+ };
23538
+ }
23539
+
23540
+ /**
23541
+ * @param {networkCallback} cb
23542
+ * @param {Window} win
23543
+ * @param {Required<NetworkRecordOptions>} options
23544
+ * @returns {listenerHandler}
23545
+ */
23546
+ function initFetchObserver(cb, win, options) {
23547
+ if (!options.initiatorTypes.includes('fetch')) {
23548
+ return function() {
23549
+ //
23550
+ };
23551
+ }
23552
+
23553
+ var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
23554
+ return function() {
23555
+ var req = new Request(arguments[0], arguments[1]);
23556
+ /** @type {Response | undefined} */
23557
+ var res;
23558
+ /** @type {Partial<NetworkRequest>} */
23559
+ var networkRequest = {};
23560
+ /** @type {number | undefined} */
23561
+ var after;
23562
+ /** @type {number | undefined} */
23563
+ var before;
23564
+
23565
+ var originalFetchPromise;
23566
+ var requestBodyPromise = Promise.resolve(undefined);
23567
+ var responseBodyPromise = Promise.resolve(undefined);
23568
+ try {
23569
+ /** @type {Headers} */
23570
+ var requestHeaders = {};
23571
+ req.headers.forEach(function(value, header) {
23572
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
23573
+ requestHeaders[header] = value;
23574
+ }
23575
+ });
23576
+ networkRequest.requestHeaders = requestHeaders;
23577
+
23578
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
23579
+ requestBodyPromise = tryReadFetchBody(req)
23580
+ .then(function(body) {
23581
+ networkRequest.requestBody = body;
23582
+ });
23583
+ }
23584
+
23585
+ after = win.performance.now();
23586
+ originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
23587
+ res = response;
23588
+ before = win.performance.now();
23589
+
23590
+ /** @type {Headers} */
23591
+ var responseHeaders = {};
23592
+ res.headers.forEach(function(value, header) {
23593
+ if (shouldRecordHeader('response', options.recordHeaders, header)) {
23594
+ responseHeaders[header] = value;
23595
+ }
23596
+ });
23597
+ networkRequest.responseHeaders = responseHeaders;
23598
+
23599
+ if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
23600
+ responseBodyPromise = tryReadFetchBody(res)
23601
+ .then(function(body) {
23602
+ networkRequest.responseBody = body;
23603
+ });
23604
+ }
23605
+
23606
+ return res;
23607
+ });
23608
+ } catch (e) {
23609
+ originalFetchPromise = Promise.reject(e);
23610
+ }
23611
+
23612
+ // await concurrently so we don't delay the fetch response
23613
+ Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
23614
+ .then(function () {
23615
+ return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
23616
+ })
23617
+ .then(function(entry) {
23618
+ if (!entry) {
23619
+ logger$3.error('Failed to get performance entry for fetch request to ' + req.url);
23620
+ return;
23621
+ }
23622
+ /** @type {NetworkRequest} */
23623
+ var request = {
23624
+ url: entry.name,
23625
+ method: req.method,
23626
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
23627
+ status: res ? res.status : undefined,
23628
+ startTime: Math.round(entry.startTime),
23629
+ endTime: Math.round(entry.responseEnd),
23630
+ timeOrigin: getTimeOrigin(win),
23631
+ requestHeaders: networkRequest.requestHeaders,
23632
+ requestBody: networkRequest.requestBody,
23633
+ responseHeaders: networkRequest.responseHeaders,
23634
+ responseBody: networkRequest.responseBody,
23635
+ };
23636
+ cb({ requests: [request] });
23637
+ })
23638
+ .catch(function (e) {
23639
+ logger$3.error('Error recording fetch request to ' + req.url + ': ' + String(e));
23640
+ });
23641
+
23642
+ return originalFetchPromise;
23643
+ };
23644
+ });
23645
+ return function() {
23646
+ restorePatch();
23647
+ };
23648
+ }
23649
+
23650
+ /**
23651
+ * @param {networkCallback} callback
23652
+ * @param {Window} win
23653
+ * @param {NetworkRecordOptions} options
23654
+ * @returns {listenerHandler}
23655
+ */
23656
+ function initNetworkObserver(callback, win, options) {
23657
+ if (!('performance' in win)) {
23658
+ return function() {
23659
+ //
23660
+ };
23661
+ }
23662
+
23663
+ var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
23664
+ var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
23665
+ options = Object.assign({}, options, {
23666
+ recordHeaders: recordHeaders,
23667
+ recordBodyUrls: recordBodyUrls,
23668
+ });
23669
+ var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
23670
+
23671
+ /** @type {networkCallback} */
23672
+ var cb = function(data) {
23673
+ var requests = data.requests.filter(function(request) {
23674
+ var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
23675
+ return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
23676
+ });
23677
+ if (requests.length > 0 || data.isInitial) {
23678
+ callback(Object.assign({}, data, { requests: requests }));
23679
+ }
23680
+ };
23681
+ var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
23682
+ var xhrObserver = initXhrObserver(cb, win, networkOptions);
23683
+ var fetchObserver = initFetchObserver(cb, win, networkOptions);
23684
+ return function() {
23685
+ performanceObserver();
23686
+ xhrObserver();
23687
+ fetchObserver();
23688
+ };
23689
+ }
23690
+
23691
+ // arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
23692
+ // a changed format in the mixpanel product.
23693
+ var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
23694
+
23695
+ /**
23696
+ * @param {NetworkRecordOptions} [options]
23697
+ * @returns {RecordPlugin}
23698
+ */
23699
+ var getRecordNetworkPlugin = function(options) {
23700
+ return {
23701
+ name: NETWORK_PLUGIN_NAME,
23702
+ observer: initNetworkObserver,
23703
+ options: options,
23704
+ };
23705
+ };
23706
+
23049
23707
  /**
23050
23708
  * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
23051
23709
  */
@@ -23279,12 +23937,35 @@
23279
23937
 
23280
23938
  var privacyConfig = getPrivacyConfig(this._mixpanel);
23281
23939
 
23282
- try {
23283
- this._stopRecording = this._rrwebRecord({
23284
- 'emit': function (ev) {
23285
- if (this.idleExpires && this.idleExpires < ev.timestamp) {
23286
- this._onIdleTimeout();
23287
- return;
23940
+ var plugins = [];
23941
+ if (this.getConfig('record_network')) {
23942
+ var options = this.getConfig('record_network_options') || {};
23943
+ // don't track requests to Mixpanel /record API
23944
+ var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
23945
+ ignoreRequestUrls.push(this._getApiRoute());
23946
+ options.ignoreRequestUrls = ignoreRequestUrls;
23947
+
23948
+ plugins.push(getRecordNetworkPlugin(options));
23949
+ }
23950
+
23951
+ if (this.getConfig('record_console')) {
23952
+ plugins.push(
23953
+ getRecordConsolePlugin({
23954
+ stringifyOptions: {
23955
+ stringLengthLimit: 1000,
23956
+ numOfKeysLimit: 50,
23957
+ depthOfLimit: 2
23958
+ }
23959
+ })
23960
+ );
23961
+ }
23962
+
23963
+ try {
23964
+ this._stopRecording = this._rrwebRecord({
23965
+ 'emit': function (ev) {
23966
+ if (this.idleExpires && this.idleExpires < ev.timestamp) {
23967
+ this._onIdleTimeout();
23968
+ return;
23288
23969
  }
23289
23970
  if (isUserEvent(ev)) {
23290
23971
  if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
@@ -23317,15 +23998,7 @@
23317
23998
  'sampling': {
23318
23999
  'canvas': 15
23319
24000
  },
23320
- 'plugins': this.getConfig('record_console') ? [
23321
- getRecordConsolePlugin({
23322
- stringifyOptions: {
23323
- stringLengthLimit: 1000,
23324
- numOfKeysLimit: 50,
23325
- depthOfLimit: 2
23326
- }
23327
- })
23328
- ] : []
24001
+ 'plugins': plugins,
23329
24002
  });
23330
24003
  } catch (err) {
23331
24004
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -23440,6 +24113,10 @@
23440
24113
  return recording;
23441
24114
  };
23442
24115
 
24116
+ SessionRecording.prototype._getApiRoute = function () {
24117
+ return this.getConfig('api_routes')['record'];
24118
+ };
24119
+
23443
24120
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
23444
24121
  var onSuccess = function (response, responseBody) {
23445
24122
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -23459,7 +24136,7 @@
23459
24136
  });
23460
24137
  }.bind(this);
23461
24138
  var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
23462
- win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
24139
+ win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
23463
24140
  'method': 'POST',
23464
24141
  'headers': {
23465
24142
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -23860,8 +24537,12 @@
23860
24537
  this.startRecording({shouldStopBatcher: true});
23861
24538
  };
23862
24539
 
24540
+ MixpanelRecorder.prototype.isRecording = function () {
24541
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
24542
+ };
24543
+
23863
24544
  MixpanelRecorder.prototype.getActiveReplayId = function () {
23864
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
24545
+ if (this.isRecording()) {
23865
24546
  return this.activeRecording.replayId;
23866
24547
  } else {
23867
24548
  return null;
@@ -23978,7 +24659,7 @@
23978
24659
  observer.observe(shadowRoot, this.observerConfig);
23979
24660
  this.shadowObservers.push(observer);
23980
24661
  } catch (e) {
23981
- logger$3.critical('Error while observing shadow root', e);
24662
+ logger$4.critical('Error while observing shadow root', e);
23982
24663
  }
23983
24664
  };
23984
24665
 
@@ -23989,7 +24670,7 @@
23989
24670
  }
23990
24671
 
23991
24672
  if (!weakSetSupported()) {
23992
- logger$3.critical('Shadow DOM observation unavailable: WeakSet not supported');
24673
+ logger$4.critical('Shadow DOM observation unavailable: WeakSet not supported');
23993
24674
  return;
23994
24675
  }
23995
24676
 
@@ -24005,7 +24686,7 @@
24005
24686
  try {
24006
24687
  this.shadowObservers[i].disconnect();
24007
24688
  } catch (e) {
24008
- logger$3.critical('Error while disconnecting shadow DOM observer', e);
24689
+ logger$4.critical('Error while disconnecting shadow DOM observer', e);
24009
24690
  }
24010
24691
  }
24011
24692
  this.shadowObservers = [];
@@ -24193,7 +24874,7 @@
24193
24874
 
24194
24875
  this.mutationObserver.observe(document.body || document.documentElement, MUTATION_OBSERVER_CONFIG);
24195
24876
  } catch (e) {
24196
- logger$3.critical('Error while setting up mutation observer', e);
24877
+ logger$4.critical('Error while setting up mutation observer', e);
24197
24878
  }
24198
24879
  }
24199
24880
 
@@ -24208,7 +24889,7 @@
24208
24889
  );
24209
24890
  this.shadowDOMObserver.start();
24210
24891
  } catch (e) {
24211
- logger$3.critical('Error while setting up shadow DOM observer', e);
24892
+ logger$4.critical('Error while setting up shadow DOM observer', e);
24212
24893
  this.shadowDOMObserver = null;
24213
24894
  }
24214
24895
  }
@@ -24235,7 +24916,7 @@
24235
24916
  try {
24236
24917
  listener.target.removeEventListener(listener.event, listener.handler, listener.options);
24237
24918
  } catch (e) {
24238
- logger$3.critical('Error while removing event listener', e);
24919
+ logger$4.critical('Error while removing event listener', e);
24239
24920
  }
24240
24921
  }
24241
24922
  this.eventListeners = [];
@@ -24244,7 +24925,7 @@
24244
24925
  try {
24245
24926
  this.mutationObserver.disconnect();
24246
24927
  } catch (e) {
24247
- logger$3.critical('Error while disconnecting mutation observer', e);
24928
+ logger$4.critical('Error while disconnecting mutation observer', e);
24248
24929
  }
24249
24930
  this.mutationObserver = null;
24250
24931
  }
@@ -24253,7 +24934,7 @@
24253
24934
  try {
24254
24935
  this.shadowDOMObserver.stop();
24255
24936
  } catch (e) {
24256
- logger$3.critical('Error while stopping shadow DOM observer', e);
24937
+ logger$4.critical('Error while stopping shadow DOM observer', e);
24257
24938
  }
24258
24939
  this.shadowDOMObserver = null;
24259
24940
  }
@@ -24331,7 +25012,7 @@
24331
25012
 
24332
25013
  Autocapture.prototype.init = function() {
24333
25014
  if (!minDOMApisSupported()) {
24334
- logger$3.critical('Autocapture unavailable: missing required DOM APIs');
25015
+ logger$4.critical('Autocapture unavailable: missing required DOM APIs');
24335
25016
  return;
24336
25017
  }
24337
25018
  this.initPageListeners();
@@ -24363,27 +25044,15 @@
24363
25044
  };
24364
25045
 
24365
25046
  Autocapture.prototype.currentUrlBlocked = function() {
24366
- var i;
24367
25047
  var currentUrl = _.info.currentUrl();
24368
25048
 
24369
25049
  var allowUrlRegexes = this.getConfig(CONFIG_ALLOW_URL_REGEXES) || [];
24370
25050
  if (allowUrlRegexes.length) {
24371
25051
  // we're using an allowlist, only track if current URL matches
24372
- var allowed = false;
24373
- for (i = 0; i < allowUrlRegexes.length; i++) {
24374
- var allowRegex = allowUrlRegexes[i];
24375
- try {
24376
- if (currentUrl.match(allowRegex)) {
24377
- allowed = true;
24378
- break;
24379
- }
24380
- } catch (err) {
24381
- logger$3.critical('Error while checking block URL regex: ' + allowRegex, err);
24382
- return true;
24383
- }
24384
- }
24385
- if (!allowed) {
24386
- // wasn't allowed by any regex
25052
+ try {
25053
+ return !urlMatchesRegexList(currentUrl, allowUrlRegexes);
25054
+ } catch (err) {
25055
+ logger$4.critical('Error while checking block URL regexes: ', err);
24387
25056
  return true;
24388
25057
  }
24389
25058
  }
@@ -24393,17 +25062,12 @@
24393
25062
  return false;
24394
25063
  }
24395
25064
 
24396
- for (i = 0; i < blockUrlRegexes.length; i++) {
24397
- try {
24398
- if (currentUrl.match(blockUrlRegexes[i])) {
24399
- return true;
24400
- }
24401
- } catch (err) {
24402
- logger$3.critical('Error while checking block URL regex: ' + blockUrlRegexes[i], err);
24403
- return true;
24404
- }
25065
+ try {
25066
+ return urlMatchesRegexList(currentUrl, blockUrlRegexes);
25067
+ } catch (err) {
25068
+ logger$4.critical('Error while checking block URL regexes: ', err);
25069
+ return true;
24405
25070
  }
24406
- return false;
24407
25071
  };
24408
25072
 
24409
25073
  Autocapture.prototype.pageviewTrackingConfig = function() {
@@ -24539,7 +25203,7 @@
24539
25203
  return;
24540
25204
  }
24541
25205
 
24542
- logger$3.log('Initializing scroll depth tracking');
25206
+ logger$4.log('Initializing scroll depth tracking');
24543
25207
 
24544
25208
  this.maxScrollViewDepth = Math.max(document$1.documentElement.clientHeight, win.innerHeight || 0);
24545
25209
 
@@ -24565,7 +25229,7 @@
24565
25229
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.get_config('record_heatmap_data')) {
24566
25230
  return;
24567
25231
  }
24568
- logger$3.log('Initializing click tracking');
25232
+ logger$4.log('Initializing click tracking');
24569
25233
 
24570
25234
  this.listenerClick = function(ev) {
24571
25235
  if (!this.getConfig(CONFIG_TRACK_CLICK) && !this.mp.is_recording_heatmap_data()) {
@@ -24584,7 +25248,7 @@
24584
25248
  return;
24585
25249
  }
24586
25250
 
24587
- logger$3.log('Initializing dead click tracking');
25251
+ logger$4.log('Initializing dead click tracking');
24588
25252
  if (!this._deadClickTracker) {
24589
25253
  this._deadClickTracker = new DeadClickTracker(function(deadClickEvent) {
24590
25254
  this.trackDomEvent(deadClickEvent, MP_EV_DEAD_CLICK);
@@ -24618,7 +25282,7 @@
24618
25282
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
24619
25283
  return;
24620
25284
  }
24621
- logger$3.log('Initializing input tracking');
25285
+ logger$4.log('Initializing input tracking');
24622
25286
 
24623
25287
  this.listenerChange = function(ev) {
24624
25288
  if (!this.getConfig(CONFIG_TRACK_INPUT)) {
@@ -24635,7 +25299,7 @@
24635
25299
  if (!this.pageviewTrackingConfig()) {
24636
25300
  return;
24637
25301
  }
24638
- logger$3.log('Initializing pageview tracking');
25302
+ logger$4.log('Initializing pageview tracking');
24639
25303
 
24640
25304
  var previousTrackedUrl = '';
24641
25305
  var tracked = false;
@@ -24670,7 +25334,7 @@
24670
25334
  }
24671
25335
  if (didPathChange) {
24672
25336
  this.lastScrollCheckpoint = 0;
24673
- logger$3.log('Path change: re-initializing scroll depth checkpoints');
25337
+ logger$4.log('Path change: re-initializing scroll depth checkpoints');
24674
25338
  }
24675
25339
  }
24676
25340
  }.bind(this));
@@ -24685,7 +25349,7 @@
24685
25349
  return;
24686
25350
  }
24687
25351
 
24688
- logger$3.log('Initializing rage click tracking');
25352
+ logger$4.log('Initializing rage click tracking');
24689
25353
  if (!this._rageClickTracker) {
24690
25354
  this._rageClickTracker = new RageClickTracker();
24691
25355
  }
@@ -24715,7 +25379,7 @@
24715
25379
  if (!this.getConfig(CONFIG_TRACK_SCROLL)) {
24716
25380
  return;
24717
25381
  }
24718
- logger$3.log('Initializing scroll tracking');
25382
+ logger$4.log('Initializing scroll tracking');
24719
25383
  this.lastScrollCheckpoint = 0;
24720
25384
 
24721
25385
  var scrollTrackFunction = function() {
@@ -24752,7 +25416,7 @@
24752
25416
  }
24753
25417
  }
24754
25418
  } catch (err) {
24755
- logger$3.critical('Error while calculating scroll percentage', err);
25419
+ logger$4.critical('Error while calculating scroll percentage', err);
24756
25420
  }
24757
25421
  if (shouldTrack) {
24758
25422
  this.mp.track(MP_EV_SCROLL, props);
@@ -24770,7 +25434,7 @@
24770
25434
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
24771
25435
  return;
24772
25436
  }
24773
- logger$3.log('Initializing submit tracking');
25437
+ logger$4.log('Initializing submit tracking');
24774
25438
 
24775
25439
  this.listenerSubmit = function(ev) {
24776
25440
  if (!this.getConfig(CONFIG_TRACK_SUBMIT)) {
@@ -24792,7 +25456,7 @@
24792
25456
  return;
24793
25457
  }
24794
25458
 
24795
- logger$3.log('Initializing page visibility tracking.');
25459
+ logger$4.log('Initializing page visibility tracking.');
24796
25460
  this._initScrollDepthTracking();
24797
25461
  var previousTrackedUrl = _.info.currentUrl();
24798
25462
 
@@ -25400,6 +26064,214 @@
25400
26064
  /* eslint camelcase: "off" */
25401
26065
 
25402
26066
 
26067
+ /**
26068
+ * RecorderManager: manages session recording initialization, lifecycle and state
26069
+ * @constructor
26070
+ */
26071
+ var RecorderManager = function(initOptions) {
26072
+ // TODO - Passing in mixpanel instance as it is still needed for recorder creation
26073
+ // but ideally we should be able to remove this dependency.
26074
+ this.mixpanelInstance = initOptions.mixpanelInstance;
26075
+
26076
+ this.getMpConfig = initOptions.getConfigFunc;
26077
+ this.getTabId = initOptions.getTabIdFunc;
26078
+ this.reportError = initOptions.reportErrorFunc;
26079
+ this.getDistinctId = initOptions.getDistinctIdFunc;
26080
+ this.loadExtraBundle = initOptions.loadExtraBundle;
26081
+ this.recorderSrc = initOptions.recorderSrc;
26082
+ this.targetingSrc = initOptions.targetingSrc;
26083
+ this.libBasePath = initOptions.libBasePath;
26084
+
26085
+ this._recorder = null;
26086
+ };
26087
+
26088
+ RecorderManager.prototype.shouldLoadRecorder = function() {
26089
+ if (this.getMpConfig('disable_persistence')) {
26090
+ console$1.log('Load recorder check skipped due to disable_persistence config');
26091
+ return PromisePolyfill.resolve(false);
26092
+ }
26093
+
26094
+ var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
26095
+ var tab_id = this.getTabId();
26096
+ return recording_registry_idb.init()
26097
+ .then(function () {
26098
+ return recording_registry_idb.getAll();
26099
+ })
26100
+ .then(function (recordings) {
26101
+ for (var i = 0; i < recordings.length; i++) {
26102
+ // if there are expired recordings in the registry, we should load the recorder to flush them
26103
+ // if there's a recording for this tab id, we should load the recorder to continue the recording
26104
+ if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
26105
+ return true;
26106
+ }
26107
+ }
26108
+ return false;
26109
+ })
26110
+ .catch(_.bind(function (err) {
26111
+ this.reportError('Error checking recording registry', err);
26112
+ return false;
26113
+ }, this));
26114
+ };
26115
+
26116
+ RecorderManager.prototype.checkAndStartSessionRecording = function(force_start, rate) {
26117
+ if (!win['MutationObserver']) {
26118
+ console$1.critical('Browser does not support MutationObserver; skipping session recording');
26119
+ return PromisePolyfill.resolve();
26120
+ }
26121
+
26122
+ var loadRecorder = _.bind(function(startNewIfInactive) {
26123
+ return new PromisePolyfill(_.bind(function(resolve) {
26124
+ var handleLoadedRecorder = safewrap(_.bind(function() {
26125
+ this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this.mixpanelInstance);
26126
+ this._recorder['resumeRecording'](startNewIfInactive);
26127
+ resolve();
26128
+ }, this));
26129
+
26130
+ if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
26131
+ var recorderSrc = this.recorderSrc || (this.libBasePath + RECORDER_FILENAME);
26132
+ this.loadExtraBundle(recorderSrc, handleLoadedRecorder);
26133
+ } else {
26134
+ handleLoadedRecorder();
26135
+ }
26136
+ }, this));
26137
+ }, this);
26138
+
26139
+ /**
26140
+ * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
26141
+ * 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.
26142
+ */
26143
+ var effective_rate = _.isUndefined(rate) ? this.getMpConfig('record_sessions_percent') : rate;
26144
+ var is_sampled = effective_rate > 0 && Math.random() * 100 <= effective_rate;
26145
+ if (force_start || is_sampled) {
26146
+ return loadRecorder(true);
26147
+ } else {
26148
+ return this.shouldLoadRecorder()
26149
+ .then(_.bind(function (shouldLoad) {
26150
+ if (shouldLoad) {
26151
+ return loadRecorder(false);
26152
+ }
26153
+ return PromisePolyfill.resolve();
26154
+ }, this));
26155
+ }
26156
+ };
26157
+
26158
+ RecorderManager.prototype.isRecording = function() {
26159
+ // Safety check: ensure isRecording method exists (older CDN builds may not have it)
26160
+ if (!this._recorder || !_.isFunction(this._recorder['isRecording'])) {
26161
+ return false;
26162
+ }
26163
+ try {
26164
+ return this._recorder['isRecording']();
26165
+ } catch (e) {
26166
+ this.reportError('Error checking if recording is active', e);
26167
+ return false;
26168
+ }
26169
+ };
26170
+
26171
+ RecorderManager.prototype.startRecordingOnEvent = function(event_name, properties) {
26172
+ var isRecording = this.isRecording();
26173
+ var recordingTriggerEvents = this.getMpConfig('recording_event_triggers');
26174
+
26175
+ if (!isRecording && recordingTriggerEvents) {
26176
+ var trigger = recordingTriggerEvents[event_name];
26177
+ if (trigger && typeof trigger['percentage'] === 'number') {
26178
+ var newRate = trigger['percentage'];
26179
+ var propertyFilters = trigger['property_filters'];
26180
+ if (propertyFilters && !_.isEmptyObject(propertyFilters)) {
26181
+ var targetingSrc = this.targetingSrc || (this.libBasePath + TARGETING_FILENAME);
26182
+ getTargetingPromise(this.loadExtraBundle, targetingSrc)
26183
+ .then(function(targeting) {
26184
+ try {
26185
+ var result = targeting['eventMatchesCriteria'](
26186
+ event_name,
26187
+ properties,
26188
+ {
26189
+ 'event_name': event_name,
26190
+ 'property_filters': propertyFilters
26191
+ }
26192
+ );
26193
+ if (result['matches']) {
26194
+ this.checkAndStartSessionRecording(false, newRate);
26195
+ }
26196
+ } catch (err) {
26197
+ console$1.critical('Could not parse recording event trigger properties logic:', err);
26198
+ }
26199
+ }.bind(this)).catch(function(err) {
26200
+ console$1.critical('Failed to load targeting library:', err);
26201
+ });
26202
+ } else {
26203
+ this.checkAndStartSessionRecording(false, newRate);
26204
+ }
26205
+ }
26206
+ }
26207
+ };
26208
+
26209
+ RecorderManager.prototype.stopSessionRecording = function() {
26210
+ if (this._recorder) {
26211
+ return this._recorder['stopRecording']();
26212
+ }
26213
+ return PromisePolyfill.resolve();
26214
+ };
26215
+
26216
+ RecorderManager.prototype.pauseSessionRecording = function() {
26217
+ if (this._recorder) {
26218
+ return this._recorder['pauseRecording']();
26219
+ }
26220
+ return PromisePolyfill.resolve();
26221
+ };
26222
+
26223
+ RecorderManager.prototype.resumeSessionRecording = function() {
26224
+ if (this._recorder) {
26225
+ return this._recorder['resumeRecording']();
26226
+ }
26227
+ return PromisePolyfill.resolve();
26228
+ };
26229
+
26230
+ RecorderManager.prototype.isRecordingHeatmapData = function() {
26231
+ return this.getSessionReplayId() && this.getMpConfig('record_heatmap_data');
26232
+ };
26233
+
26234
+ RecorderManager.prototype.getSessionRecordingProperties = function() {
26235
+ var props = {};
26236
+ var replay_id = this.getSessionReplayId();
26237
+ if (replay_id) {
26238
+ props['$mp_replay_id'] = replay_id;
26239
+ }
26240
+ return props;
26241
+ };
26242
+
26243
+ RecorderManager.prototype.getSessionReplayUrl = function() {
26244
+ var replay_url = null;
26245
+ var replay_id = this.getSessionReplayId();
26246
+ if (replay_id) {
26247
+ var query_params = _.HTTPBuildQuery({
26248
+ 'replay_id': replay_id,
26249
+ 'distinct_id': this.getDistinctId(),
26250
+ 'token': this.getMpConfig('token')
26251
+ });
26252
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
26253
+ }
26254
+ return replay_url;
26255
+ };
26256
+
26257
+ RecorderManager.prototype.getSessionReplayId = function() {
26258
+ var replay_id = null;
26259
+ if (this._recorder) {
26260
+ replay_id = this._recorder['replayId'];
26261
+ }
26262
+ return replay_id || null;
26263
+ };
26264
+
26265
+ // "private" public method to reach into the recorder in test cases
26266
+ RecorderManager.prototype.getRecorder = function() {
26267
+ return this._recorder;
26268
+ };
26269
+
26270
+ safewrapClass(RecorderManager);
26271
+
26272
+ /* eslint camelcase: "off" */
26273
+
26274
+
25403
26275
  /**
25404
26276
  * DomTracker Object
25405
26277
  * @constructor
@@ -26860,13 +27732,17 @@
26860
27732
  'record_collect_fonts': false,
26861
27733
  'record_console': true,
26862
27734
  'record_heatmap_data': false,
27735
+ 'recording_event_triggers': {},
26863
27736
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
26864
27737
  'record_mask_inputs': true,
26865
27738
  'record_max_ms': MAX_RECORDING_MS,
26866
27739
  'record_min_ms': 0,
27740
+ 'record_network': false,
27741
+ 'record_network_options': {},
26867
27742
  'record_sessions_percent': 0,
26868
- 'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js',
26869
- 'targeting_src': 'https://cdn.mxpnl.com/libs/mixpanel-targeting.min.js',
27743
+ 'recorder_src': null,
27744
+ 'targeting_src': null,
27745
+ 'lib_base_path': 'https://cdn.mxpnl.com/libs/',
26870
27746
  'remote_settings_mode': SETTING_DISABLED // 'strict', 'fallback', 'disabled'
26871
27747
  };
26872
27748
 
@@ -27020,6 +27896,19 @@
27020
27896
  'callback_fn': ((name === PRIMARY_INSTANCE_NAME) ? name : PRIMARY_INSTANCE_NAME + '.' + name) + '._jsc'
27021
27897
  }));
27022
27898
 
27899
+ this.recorderManager = new RecorderManager({
27900
+ mixpanelInstance: this,
27901
+ getConfigFunc: _.bind(this.get_config, this),
27902
+ setConfigFunc: _.bind(this.set_config, this),
27903
+ getTabIdFunc: _.bind(this.get_tab_id, this),
27904
+ reportErrorFunc: _.bind(this.report_error, this),
27905
+ getDistinctIdFunc: _.bind(this.get_distinct_id, this),
27906
+ recorderSrc: this.get_config('recorder_src'),
27907
+ targetingSrc: this.get_config('targeting_src'),
27908
+ libBasePath: this.get_config('lib_base_path'),
27909
+ loadExtraBundle: load_extra_bundle
27910
+ });
27911
+
27023
27912
  this['_jsc'] = NOOP_FUNC;
27024
27913
 
27025
27914
  this.__dom_loaded_queue = [];
@@ -27098,7 +27987,7 @@
27098
27987
  getPropertyFunc: _.bind(this.get_property, this),
27099
27988
  trackingFunc: _.bind(this.track, this),
27100
27989
  loadExtraBundle: load_extra_bundle,
27101
- targetingSrc: this.get_config('targeting_src')
27990
+ targetingSrc: this.get_config('targeting_src') || (this.get_config('lib_base_path') + TARGETING_FILENAME)
27102
27991
  });
27103
27992
  this.flags.init();
27104
27993
  this['flags'] = this.flags;
@@ -27111,11 +28000,11 @@
27111
28000
  // Based on remote_settings_mode, fetch remote settings and then start session recording if applicable
27112
28001
  var mode = this.get_config('remote_settings_mode');
27113
28002
  if (mode === SETTING_STRICT || mode === SETTING_FALLBACK) {
27114
- this._fetch_remote_settings(mode).then(_.bind(function() {
27115
- this._check_and_start_session_recording();
28003
+ this.__session_recording_init_promise = this._fetch_remote_settings(mode).then(_.bind(function() {
28004
+ return this._check_and_start_session_recording();
27116
28005
  }, this));
27117
28006
  } else {
27118
- this._check_and_start_session_recording();
28007
+ this.__session_recording_init_promise = this._check_and_start_session_recording();
27119
28008
  }
27120
28009
  };
27121
28010
 
@@ -27159,132 +28048,50 @@
27159
28048
  return this.tab_id || null;
27160
28049
  };
27161
28050
 
27162
- MixpanelLib.prototype._should_load_recorder = function () {
27163
- if (this.get_config('disable_persistence')) {
27164
- console$1.log('Load recorder check skipped due to disable_persistence config');
27165
- return Promise.resolve(false);
27166
- }
27167
-
27168
- var recording_registry_idb = new IDBStorageWrapper(RECORDING_REGISTRY_STORE_NAME);
27169
- var tab_id = this.get_tab_id();
27170
- return recording_registry_idb.init()
27171
- .then(function () {
27172
- return recording_registry_idb.getAll();
27173
- })
27174
- .then(function (recordings) {
27175
- for (var i = 0; i < recordings.length; i++) {
27176
- // if there are expired recordings in the registry, we should load the recorder to flush them
27177
- // if there's a recording for this tab id, we should load the recorder to continue the recording
27178
- if (isRecordingExpired(recordings[i]) || recordings[i]['tabId'] === tab_id) {
27179
- return true;
27180
- }
27181
- }
27182
- return false;
27183
- })
27184
- .catch(_.bind(function (err) {
27185
- this.report_error('Error checking recording registry', err);
27186
- }, this));
27187
- };
27188
-
27189
28051
  MixpanelLib.prototype._check_and_start_session_recording = addOptOutCheckMixpanelLib(function(force_start) {
27190
- if (!win['MutationObserver']) {
27191
- console$1.critical('Browser does not support MutationObserver; skipping session recording');
27192
- return;
27193
- }
27194
-
27195
- var loadRecorder = _.bind(function(startNewIfInactive) {
27196
- var handleLoadedRecorder = _.bind(function() {
27197
- this._recorder = this._recorder || new win[RECORDER_GLOBAL_NAME](this);
27198
- this._recorder['resumeRecording'](startNewIfInactive);
27199
- }, this);
27200
-
27201
- if (_.isUndefined(win[RECORDER_GLOBAL_NAME])) {
27202
- load_extra_bundle(this.get_config('recorder_src'), handleLoadedRecorder);
27203
- } else {
27204
- handleLoadedRecorder();
27205
- }
27206
- }, this);
27207
-
27208
- /**
27209
- * If the user is sampled or start_session_recording is called, we always load the recorder since it's guaranteed a recording should start.
27210
- * 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.
27211
- */
27212
- var is_sampled = this.get_config('record_sessions_percent') > 0 && Math.random() * 100 <= this.get_config('record_sessions_percent');
27213
- if (force_start || is_sampled) {
27214
- loadRecorder(true);
27215
- } else {
27216
- this._should_load_recorder()
27217
- .then(function (shouldLoad) {
27218
- if (shouldLoad) {
27219
- loadRecorder(false);
27220
- }
27221
- });
27222
- }
28052
+ return this.recorderManager.checkAndStartSessionRecording(force_start);
27223
28053
  });
27224
28054
 
28055
+ MixpanelLib.prototype._start_recording_on_event = function(event_name, properties) {
28056
+ return this.recorderManager.startRecordingOnEvent(event_name, properties);
28057
+ };
28058
+
27225
28059
  MixpanelLib.prototype.start_session_recording = function () {
27226
- this._check_and_start_session_recording(true);
28060
+ return this._check_and_start_session_recording(true);
27227
28061
  };
27228
28062
 
27229
28063
  MixpanelLib.prototype.stop_session_recording = function () {
27230
- if (this._recorder) {
27231
- return this._recorder['stopRecording']();
27232
- }
27233
- return Promise.resolve();
28064
+ return this.recorderManager.stopSessionRecording();
27234
28065
  };
27235
28066
 
27236
28067
  MixpanelLib.prototype.pause_session_recording = function () {
27237
- if (this._recorder) {
27238
- return this._recorder['pauseRecording']();
27239
- }
27240
- return Promise.resolve();
28068
+ return this.recorderManager.pauseSessionRecording();
27241
28069
  };
27242
28070
 
27243
28071
  MixpanelLib.prototype.resume_session_recording = function () {
27244
- if (this._recorder) {
27245
- return this._recorder['resumeRecording']();
27246
- }
27247
- return Promise.resolve();
28072
+ return this.recorderManager.resumeSessionRecording();
27248
28073
  };
27249
28074
 
27250
28075
  MixpanelLib.prototype.is_recording_heatmap_data = function () {
27251
- return this._get_session_replay_id() && this.get_config('record_heatmap_data');
28076
+ return this.recorderManager.isRecordingHeatmapData();
27252
28077
  };
27253
28078
 
27254
28079
  MixpanelLib.prototype.get_session_recording_properties = function () {
27255
- var props = {};
27256
- var replay_id = this._get_session_replay_id();
27257
- if (replay_id) {
27258
- props['$mp_replay_id'] = replay_id;
27259
- }
27260
- return props;
28080
+ return this.recorderManager.getSessionRecordingProperties();
27261
28081
  };
27262
28082
 
27263
28083
  MixpanelLib.prototype.get_session_replay_url = function () {
27264
- var replay_url = null;
27265
- var replay_id = this._get_session_replay_id();
27266
- if (replay_id) {
27267
- var query_params = _.HTTPBuildQuery({
27268
- 'replay_id': replay_id,
27269
- 'distinct_id': this.get_distinct_id(),
27270
- 'token': this.get_config('token')
27271
- });
27272
- replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
27273
- }
27274
- return replay_url;
27275
- };
27276
-
27277
- MixpanelLib.prototype._get_session_replay_id = function () {
27278
- var replay_id = null;
27279
- if (this._recorder) {
27280
- replay_id = this._recorder['replayId'];
27281
- }
27282
- return replay_id || null;
28084
+ return this.recorderManager.getSessionReplayUrl();
27283
28085
  };
27284
28086
 
27285
28087
  // "private" public method to reach into the recorder in test cases
27286
28088
  MixpanelLib.prototype.__get_recorder = function () {
27287
- return this._recorder;
28089
+ return this.recorderManager.getRecorder();
28090
+ };
28091
+
28092
+ // "private" public method to get session recording init promise in test cases
28093
+ MixpanelLib.prototype.__get_recording_init_promise = function () {
28094
+ return this.__session_recording_init_promise;
27288
28095
  };
27289
28096
 
27290
28097
  // Private methods
@@ -27542,6 +28349,7 @@
27542
28349
  };
27543
28350
 
27544
28351
  MixpanelLib.prototype._fetch_remote_settings = function(mode) {
28352
+ var self = this;
27545
28353
  var disableRecordingIfStrict = function() {
27546
28354
  if (mode === 'strict') {
27547
28355
  self.set_config({'record_sessions_percent': 0});
@@ -27562,7 +28370,6 @@
27562
28370
  };
27563
28371
  var query_string = _.HTTPBuildQuery(request_params);
27564
28372
  var full_url = settings_endpoint + '?' + query_string;
27565
- var self = this;
27566
28373
 
27567
28374
  var abortController = new AbortController();
27568
28375
  var timeout_id = setTimeout(function() {
@@ -27754,6 +28561,34 @@
27754
28561
  this._execute_array([item]);
27755
28562
  };
27756
28563
 
28564
+ /**
28565
+ * Enables events on the Mixpanel object. If passed no arguments,
28566
+ * this function enable tracking of all events. If passed an
28567
+ * array of event names, those events will be enabled, but other
28568
+ * existing disabled events will continue to be not tracked.
28569
+ *
28570
+ * @param {Array} [events] An array of event names to enable
28571
+ */
28572
+ MixpanelLib.prototype.enable = function(events) {
28573
+ var keys, new_disabled_events, i, j;
28574
+
28575
+ if (typeof(events) === 'undefined') {
28576
+ this._flags.disable_all_events = false;
28577
+ } else {
28578
+ keys = {};
28579
+ new_disabled_events = [];
28580
+ for (i = 0; i < events.length; i++) {
28581
+ keys[events[i]] = true;
28582
+ }
28583
+ for (j = 0; j < this.__disabled_events.length; j++) {
28584
+ if (!keys[this.__disabled_events[j]]) {
28585
+ new_disabled_events.push(this.__disabled_events[j]);
28586
+ }
28587
+ }
28588
+ this.__disabled_events = new_disabled_events;
28589
+ }
28590
+ };
28591
+
27757
28592
  /**
27758
28593
  * Disable events on the Mixpanel object. If passed no arguments,
27759
28594
  * this function disables tracking of any event. If passed an
@@ -27927,6 +28762,8 @@
27927
28762
  this.report_error('Invalid value for property_blacklist config: ' + property_blacklist);
27928
28763
  }
27929
28764
 
28765
+ this._start_recording_on_event(event_name, properties);
28766
+
27930
28767
  var data = {
27931
28768
  'event': event_name,
27932
28769
  'properties': properties
@@ -29135,6 +29972,7 @@
29135
29972
  // MixpanelLib Exports
29136
29973
  MixpanelLib.prototype['init'] = MixpanelLib.prototype.init;
29137
29974
  MixpanelLib.prototype['reset'] = MixpanelLib.prototype.reset;
29975
+ MixpanelLib.prototype['enable'] = MixpanelLib.prototype.enable;
29138
29976
  MixpanelLib.prototype['disable'] = MixpanelLib.prototype.disable;
29139
29977
  MixpanelLib.prototype['time_event'] = MixpanelLib.prototype.time_event;
29140
29978
  MixpanelLib.prototype['track'] = MixpanelLib.prototype.track;
@@ -29178,6 +30016,7 @@
29178
30016
 
29179
30017
  // Exports intended only for testing
29180
30018
  MixpanelLib.prototype['__get_recorder'] = MixpanelLib.prototype.__get_recorder;
30019
+ MixpanelLib.prototype['__get_recording_init_promise'] = MixpanelLib.prototype.__get_recording_init_promise;
29181
30020
 
29182
30021
  // MixpanelPersistence Exports
29183
30022
  MixpanelPersistence.prototype['properties'] = MixpanelPersistence.prototype.properties;