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,12 +26,9 @@
26
26
  win = window;
27
27
  }
28
28
 
29
- /**
30
- * Shared global window property names used across modules
31
- */
32
-
33
-
34
- // Recorder library global (used by recorder and mixpanel-core)
29
+ var Config = {
30
+ LIB_VERSION: '2.76.0'
31
+ };
35
32
  var RECORDER_GLOBAL_NAME = '__mp_recorder';
36
33
 
37
34
  function _array_like_to_array(arr, len) {
@@ -18133,7 +18130,7 @@
18133
18130
  var __publicField = function(obj, key, value) {
18134
18131
  return __defNormalProp(obj, (typeof key === "undefined" ? "undefined" : _type_of(key)) !== "symbol" ? key + "" : key, value);
18135
18132
  };
18136
- function patch(source, name, replacement) {
18133
+ function patch$3(source, name, replacement) {
18137
18134
  try {
18138
18135
  if (!(name in source)) {
18139
18136
  return function() {};
@@ -18550,7 +18547,7 @@
18550
18547
  if (!_logger[level]) {
18551
18548
  return function() {};
18552
18549
  }
18553
- return patch(_logger, level, function(original) {
18550
+ return patch$3(_logger, level, function(original) {
18554
18551
  var _this1 = _this;
18555
18552
  return function() {
18556
18553
  for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++){
@@ -18971,10 +18968,6 @@
18971
18968
  PromisePolyfill = NpoPromise;
18972
18969
  }
18973
18970
 
18974
- var Config = {
18975
- LIB_VERSION: '2.75.0'
18976
- };
18977
-
18978
18971
  /* eslint camelcase: "off", eqeqeq: "off" */
18979
18972
 
18980
18973
  // Maximum allowed session recording length
@@ -20640,6 +20633,17 @@
20640
20633
 
20641
20634
  var NOOP_FUNC = function () {};
20642
20635
 
20636
+ var urlMatchesRegexList = function (url, regexList) {
20637
+ var matches = false;
20638
+ for (var i = 0; i < regexList.length; i++) {
20639
+ if (url.match(regexList[i])) {
20640
+ matches = true;
20641
+ break;
20642
+ }
20643
+ }
20644
+ return matches;
20645
+ };
20646
+
20643
20647
  var JSONStringify = null, JSONParse = null;
20644
20648
  if (typeof JSON !== 'undefined') {
20645
20649
  JSONStringify = JSON.stringify;
@@ -20979,7 +20983,7 @@
20979
20983
  };
20980
20984
  }
20981
20985
 
20982
- var logger$4 = console_with_prefix('lock');
20986
+ var logger$5 = console_with_prefix('lock');
20983
20987
 
20984
20988
  /**
20985
20989
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -21031,7 +21035,7 @@
21031
21035
 
21032
21036
  var delay = function(cb) {
21033
21037
  if (new Date().getTime() - startTime > timeoutMS) {
21034
- logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21038
+ logger$5.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
21035
21039
  storage.removeItem(keyZ);
21036
21040
  storage.removeItem(keyY);
21037
21041
  loop();
@@ -21178,7 +21182,7 @@
21178
21182
  }, this));
21179
21183
  };
21180
21184
 
21181
- var logger$3 = console_with_prefix('batch');
21185
+ var logger$4 = console_with_prefix('batch');
21182
21186
 
21183
21187
  /**
21184
21188
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -21207,7 +21211,7 @@
21207
21211
  timeoutMS: options.sharedLockTimeoutMS,
21208
21212
  });
21209
21213
  }
21210
- this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
21214
+ this.reportError = options.errorReporter || _.bind(logger$4.error, logger$4);
21211
21215
 
21212
21216
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
21213
21217
 
@@ -21540,7 +21544,7 @@
21540
21544
  // maximum interval between request retries after exponential backoff
21541
21545
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
21542
21546
 
21543
- var logger$2 = console_with_prefix('batch');
21547
+ var logger$3 = console_with_prefix('batch');
21544
21548
 
21545
21549
  /**
21546
21550
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -21668,7 +21672,7 @@
21668
21672
  */
21669
21673
  RequestBatcher.prototype.flush = function(options) {
21670
21674
  if (this.requestInProgress) {
21671
- logger$2.log('Flush: Request already in progress');
21675
+ logger$3.log('Flush: Request already in progress');
21672
21676
  return PromisePolyfill.resolve();
21673
21677
  }
21674
21678
 
@@ -21845,7 +21849,7 @@
21845
21849
  if (options.unloading) {
21846
21850
  requestOptions.transport = 'sendBeacon';
21847
21851
  }
21848
- logger$2.log('MIXPANEL REQUEST:', dataForRequest);
21852
+ logger$3.log('MIXPANEL REQUEST:', dataForRequest);
21849
21853
  return this.sendRequestPromise(dataForRequest, requestOptions).then(batchSendCallback);
21850
21854
  }, this))
21851
21855
  .catch(_.bind(function(err) {
@@ -21858,7 +21862,7 @@
21858
21862
  * Log error to global logger and optional user-defined logger.
21859
21863
  */
21860
21864
  RequestBatcher.prototype.reportError = function(msg, err) {
21861
- logger$2.error.apply(logger$2.error, arguments);
21865
+ logger$3.error.apply(logger$3.error, arguments);
21862
21866
  if (this.errorReporter) {
21863
21867
  try {
21864
21868
  if (!(err instanceof Error)) {
@@ -21866,7 +21870,7 @@
21866
21870
  }
21867
21871
  this.errorReporter(msg, err);
21868
21872
  } catch(err) {
21869
- logger$2.error(err);
21873
+ logger$3.error(err);
21870
21874
  }
21871
21875
  }
21872
21876
  };
@@ -22084,6 +22088,655 @@
22084
22088
  }
22085
22089
  }
22086
22090
 
22091
+ /**
22092
+ * This is a port of the open rrweb network plugin in this PR https://github.com/rrweb-io/rrweb/pull/1105
22093
+ * the hope is that eventually this can be replaced with the official plugin once it's published (and we sync the mixpanel rrweb fork)
22094
+ *
22095
+ * This plugin incorporates some important fixes for fetch/XHR body recording that are not yet in the main rrweb repo, as well as makes
22096
+ * header and body recording more restrictive by requiring an allowlist instead of content type / blocklist.
22097
+ *
22098
+ */
22099
+
22100
+ var logger$2 = console_with_prefix('network-plugin');
22101
+
22102
+ /**
22103
+ * Get the time origin for converting performance timestamps to absolute timestamps.
22104
+ * Uses Date.now() - performance.now() instead of performance.timeOrigin because
22105
+ * browsers can report timeOrigin values that are skewed from actual time, and some
22106
+ * browsers (notably older Safari versions) don't implement timeOrigin at all.
22107
+ * See: https://github.com/getsentry/sentry-javascript/blob/e856e40b6e71a73252e788cd42b5260f81c9c88e/packages/utils/src/time.ts#L49-L70
22108
+ * @param {Window} win
22109
+ * @returns {number}
22110
+ */
22111
+ function getTimeOrigin(win) {
22112
+ return Math.round(Date.now() - win.performance.now());
22113
+ }
22114
+
22115
+ /**
22116
+ * @typedef {import('../index.d.ts').InitiatorType} InitiatorType
22117
+ * @typedef {import('../index.d.ts').NetworkRequest} NetworkRequest
22118
+ * @typedef {import('../index.d.ts').NetworkRecordOptions} NetworkRecordOptions
22119
+ * @typedef {import('../index.d.ts').NetworkData} NetworkData
22120
+ */
22121
+
22122
+ /**
22123
+ * @typedef {Record<string, string>} Headers
22124
+ */
22125
+
22126
+ /**
22127
+ * @typedef {string | Document | Blob | ArrayBufferView | ArrayBuffer | FormData | URLSearchParams | ReadableStream<Uint8Array> | null} Body
22128
+ */
22129
+
22130
+ /**
22131
+ * @callback networkCallback
22132
+ * @param {NetworkData} data
22133
+ * @returns {void}
22134
+ */
22135
+
22136
+ /**
22137
+ * @callback listenerHandler
22138
+ * @returns {void}
22139
+ */
22140
+
22141
+ /**
22142
+ * @typedef {(PerformanceNavigationTiming | PerformanceResourceTiming) & { responseStatus?: number }} ObservedPerformanceEntry
22143
+ */
22144
+
22145
+ /**
22146
+ * @typedef {Object} RecordPlugin
22147
+ * @property {string} name
22148
+ * @property {(callback: networkCallback, win: Window, options: NetworkRecordOptions) => listenerHandler} observer
22149
+ * @property {NetworkRecordOptions} [options]
22150
+ */
22151
+
22152
+ /** @type {Required<NetworkRecordOptions>} */
22153
+ var defaultNetworkOptions = {
22154
+ initiatorTypes: [
22155
+ 'audio',
22156
+ 'beacon',
22157
+ 'body',
22158
+ 'css',
22159
+ 'early-hint',
22160
+ 'embed',
22161
+ 'fetch',
22162
+ 'frame',
22163
+ 'iframe',
22164
+ 'icon',
22165
+ 'image',
22166
+ 'img',
22167
+ 'input',
22168
+ 'link',
22169
+ 'navigation',
22170
+ 'object',
22171
+ 'ping',
22172
+ 'script',
22173
+ 'track',
22174
+ 'video',
22175
+ 'xmlhttprequest',
22176
+ ],
22177
+ ignoreRequestFn: function() { return false; },
22178
+ recordHeaders: {
22179
+ request: [],
22180
+ response: [],
22181
+ },
22182
+ recordBodyUrls: {
22183
+ request: [],
22184
+ response: [],
22185
+ },
22186
+ recordInitialRequests: false,
22187
+ };
22188
+
22189
+ /**
22190
+ * @param {PerformanceEntry} entry
22191
+ * @returns {entry is PerformanceNavigationTiming}
22192
+ */
22193
+ function isNavigationTiming(entry) {
22194
+ return entry.entryType === 'navigation';
22195
+ }
22196
+
22197
+ /**
22198
+ * @param {PerformanceEntry} entry
22199
+ * @returns {entry is PerformanceResourceTiming}
22200
+ */
22201
+ function isResourceTiming (entry) {
22202
+ return entry.entryType === 'resource';
22203
+ }
22204
+
22205
+ function findLast(array, predicate) {
22206
+ var length = array.length;
22207
+ for (var i = length - 1; i >= 0; i -= 1) {
22208
+ if (predicate(array[i])) {
22209
+ return array[i];
22210
+ }
22211
+ }
22212
+ }
22213
+
22214
+ /**
22215
+ * Monkey-patches a method on an object with a wrapped version, returning a function that restores the original.
22216
+ * Adapted from Sentry's `fill` utility:
22217
+ * https://github.com/getsentry/sentry-javascript/blob/de5c5cbe177b4334386e747857225eec36a91ea1/packages/core/src/utils/object.ts#L67-L95
22218
+ *
22219
+ * @param {object} source - The object containing the method to patch
22220
+ * @param {string} name - The method name to patch
22221
+ * @param {function} replacementFactory - A function that receives the original method and returns the replacement
22222
+ * @returns {function} A function that restores the original method
22223
+ */
22224
+ function patch(source, name, replacementFactory) {
22225
+ if (!(name in source) || typeof source[name] !== 'function') {
22226
+ return function() {};
22227
+ }
22228
+ var original = source[name];
22229
+ var wrapped = replacementFactory(original);
22230
+ source[name] = wrapped;
22231
+ return function() {
22232
+ source[name] = original;
22233
+ };
22234
+ }
22235
+
22236
+
22237
+ /**
22238
+ * Maximum body size to record (1MB)
22239
+ */
22240
+ var MAX_BODY_SIZE = 1024 * 1024;
22241
+
22242
+ /**
22243
+ * Truncate string if it exceeds max size
22244
+ * @param {string} str
22245
+ * @returns {string}
22246
+ */
22247
+ function truncateBody(str) {
22248
+ if (!str || typeof str !== 'string') {
22249
+ return str;
22250
+ }
22251
+ if (str.length > MAX_BODY_SIZE) {
22252
+ logger$2.error('Body truncated from ' + str.length + ' to ' + MAX_BODY_SIZE + ' characters');
22253
+ return str.substring(0, MAX_BODY_SIZE) + '... [truncated]';
22254
+ }
22255
+ return str;
22256
+ }
22257
+
22258
+ /**
22259
+ * @param {networkCallback} cb
22260
+ * @param {Window} win
22261
+ * @param {Required<NetworkRecordOptions>} options
22262
+ * @returns {listenerHandler}
22263
+ */
22264
+ function initPerformanceObserver(cb, win, options) {
22265
+ if (!win.PerformanceObserver) {
22266
+ logger$2.error('PerformanceObserver not supported');
22267
+ return function() {
22268
+ //
22269
+ };
22270
+ }
22271
+ if (options.recordInitialRequests) {
22272
+ var initialPerformanceEntries = win.performance
22273
+ .getEntries()
22274
+ .filter(function(entry) {
22275
+ return isNavigationTiming(entry) ||
22276
+ (isResourceTiming(entry) &&
22277
+ options.initiatorTypes.includes(entry.initiatorType));
22278
+ });
22279
+ cb({
22280
+ requests: initialPerformanceEntries.map(function(entry) {
22281
+ return {
22282
+ url: entry.name,
22283
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
22284
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
22285
+ startTime: Math.round(entry.startTime),
22286
+ endTime: Math.round(entry.responseEnd),
22287
+ timeOrigin: getTimeOrigin(win),
22288
+ };
22289
+ }),
22290
+ isInitial: true,
22291
+ });
22292
+ }
22293
+ var observer = new win.PerformanceObserver(function(entries) {
22294
+ var performanceEntries = entries
22295
+ .getEntries()
22296
+ .filter(function(entry) {
22297
+ return isResourceTiming(entry) &&
22298
+ options.initiatorTypes.includes(entry.initiatorType) &&
22299
+ entry.initiatorType !== 'xmlhttprequest' &&
22300
+ entry.initiatorType !== 'fetch';
22301
+ });
22302
+ cb({
22303
+ requests: performanceEntries.map(function(entry) {
22304
+ return {
22305
+ url: entry.name,
22306
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
22307
+ status: 'responseStatus' in entry ? entry.responseStatus : undefined,
22308
+ startTime: Math.round(entry.startTime),
22309
+ endTime: Math.round(entry.responseEnd),
22310
+ timeOrigin: getTimeOrigin(win),
22311
+ };
22312
+ }),
22313
+ });
22314
+ });
22315
+ observer.observe({ entryTypes: ['navigation', 'resource'] });
22316
+ return function() {
22317
+ observer.disconnect();
22318
+ };
22319
+ }
22320
+
22321
+ /**
22322
+ * Variation of the original rrweb function that requires an allowlist for headers instead of supporting boolean options
22323
+ * @param {'request' | 'response'} type
22324
+ * @param {NetworkRecordOptions['recordHeaders']} recordHeaders
22325
+ * @param {string} headerName
22326
+ * @returns {boolean}
22327
+ */
22328
+ function shouldRecordHeader(type, recordHeaders, headerName) {
22329
+ if (!recordHeaders[type] || recordHeaders[type].length === 0) {
22330
+ return false;
22331
+ }
22332
+
22333
+ return recordHeaders[type].includes(headerName.toLowerCase());
22334
+ }
22335
+
22336
+ /**
22337
+ * Variation of the original rrweb function that requires an allowlist for URLs instead of supporting boolean options or by content type
22338
+ * @param {'request' | 'response'} type
22339
+ * @param {NetworkRecordOptions['recordBodyUrls']} recordBodyUrls
22340
+ * @param {string} url
22341
+ * @returns {boolean}
22342
+ */
22343
+ function shouldRecordBody(type, recordBodyUrls, url) {
22344
+ if (!recordBodyUrls[type] || recordBodyUrls[type].length === 0) {
22345
+ return false;
22346
+ }
22347
+
22348
+ return urlMatchesRegexList(url, recordBodyUrls[type]);
22349
+ }
22350
+
22351
+ function tryReadXHRBody(body) {
22352
+ if (body === null || body === undefined) {
22353
+ return null;
22354
+ }
22355
+
22356
+ var result;
22357
+ if (typeof body === 'string') {
22358
+ result = body;
22359
+ } else if (body instanceof Document) {
22360
+ result = body.textContent;
22361
+ } else if (body instanceof FormData) {
22362
+ result = _.HTTPBuildQuery(body);
22363
+ } else if (_.isObject(body)) {
22364
+ try {
22365
+ result = JSON.stringify(body);
22366
+ } catch (e) {
22367
+ return 'Failed to stringify response object';
22368
+ }
22369
+ } else {
22370
+ return 'Cannot read body of type ' + typeof body;
22371
+ }
22372
+
22373
+ return truncateBody(result);
22374
+ }
22375
+
22376
+ /**
22377
+ * @param {Request | Response} r
22378
+ * @returns {Promise<string>}
22379
+ */
22380
+ function tryReadFetchBody(r) {
22381
+ return new Promise(function(resolve) {
22382
+ var timeout = setTimeout(function() {
22383
+ resolve('Timeout while trying to read body');
22384
+ }, 500);
22385
+ try {
22386
+ r.clone()
22387
+ .text()
22388
+ .then(
22389
+ function(txt) {
22390
+ clearTimeout(timeout);
22391
+ resolve(truncateBody(txt));
22392
+ },
22393
+ function(reason) {
22394
+ clearTimeout(timeout);
22395
+ resolve('Failed to read body: ' + String(reason));
22396
+ }
22397
+ );
22398
+ } catch (e) {
22399
+ clearTimeout(timeout);
22400
+ resolve('Failed to read body: ' + String(e));
22401
+ }
22402
+ });
22403
+ }
22404
+
22405
+ /**
22406
+ * @param {Window} win
22407
+ * @param {string} initiatorType
22408
+ * @param {string} url
22409
+ * @param {number} [after]
22410
+ * @param {number} [before]
22411
+ * @param {number} [attempt]
22412
+ * @returns {Promise<PerformanceResourceTiming>}
22413
+ */
22414
+ function getRequestPerformanceEntry(win, initiatorType, url, after, before, attempt) {
22415
+ if (attempt === undefined) {
22416
+ attempt = 0;
22417
+ }
22418
+ if (attempt > 10) {
22419
+ logger$2.error('Cannot find performance entry');
22420
+ return Promise.resolve(null);
22421
+ }
22422
+ var urlPerformanceEntries = /** @type {PerformanceResourceTiming[]} */ (
22423
+ win.performance.getEntriesByName(url)
22424
+ );
22425
+ var performanceEntry = findLast(
22426
+ urlPerformanceEntries,
22427
+ function(entry) {
22428
+ return isResourceTiming(entry) &&
22429
+ entry.initiatorType === initiatorType &&
22430
+ (!after || entry.startTime >= after) &&
22431
+ (!before || entry.startTime <= before);
22432
+ }
22433
+ );
22434
+ if (!performanceEntry) {
22435
+ return new Promise(function(resolve) {
22436
+ setTimeout(resolve, 50 * attempt);
22437
+ }).then(function() {
22438
+ return getRequestPerformanceEntry(
22439
+ win,
22440
+ initiatorType,
22441
+ url,
22442
+ after,
22443
+ before,
22444
+ attempt + 1
22445
+ );
22446
+ });
22447
+ }
22448
+ return Promise.resolve(performanceEntry);
22449
+ }
22450
+
22451
+ /**
22452
+ * @param {networkCallback} cb
22453
+ * @param {Window} win
22454
+ * @param {Required<NetworkRecordOptions>} options
22455
+ * @returns {listenerHandler}
22456
+ */
22457
+ function initXhrObserver(cb, win, options) {
22458
+ if (!options.initiatorTypes.includes('xmlhttprequest')) {
22459
+ return function() {
22460
+ //
22461
+ };
22462
+ }
22463
+ var restorePatch = patch(
22464
+ win.XMLHttpRequest.prototype,
22465
+ 'open',
22466
+ function(/** @type {typeof XMLHttpRequest.prototype.open} */ originalOpen) {
22467
+ return function(
22468
+ /** @type {string} */ method,
22469
+ /** @type {string | URL} */ url,
22470
+ /** @type {boolean} */ async,
22471
+ username, password
22472
+ ) {
22473
+ if (async === undefined) {
22474
+ async = true;
22475
+ }
22476
+ var xhr = /** @type {XMLHttpRequest} */ (this);
22477
+ var req = new Request(url, { method: method });
22478
+ /** @type {Partial<NetworkRequest>} */
22479
+ var networkRequest = {};
22480
+ /** @type {number | undefined} */
22481
+ var after;
22482
+ /** @type {number | undefined} */
22483
+ var before;
22484
+
22485
+ /** @type {Headers} */
22486
+ var requestHeaders = {};
22487
+ var originalSetRequestHeader = xhr.setRequestHeader.bind(xhr);
22488
+ xhr.setRequestHeader = function(/** @type {string} */ header, /** @type {string} */ value) {
22489
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
22490
+ requestHeaders[header] = value;
22491
+ }
22492
+ return originalSetRequestHeader(header, value);
22493
+ };
22494
+ networkRequest.requestHeaders = requestHeaders;
22495
+
22496
+ var originalSend = xhr.send.bind(xhr);
22497
+ xhr.send = function(/** @type {Body} */ body) {
22498
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
22499
+ networkRequest.requestBody = tryReadXHRBody(body);
22500
+ }
22501
+ after = win.performance.now();
22502
+ return originalSend(body);
22503
+ };
22504
+ xhr.addEventListener('readystatechange', function() {
22505
+ if (xhr.readyState !== xhr.DONE) {
22506
+ return;
22507
+ }
22508
+ before = win.performance.now();
22509
+ /** @type {Headers} */
22510
+ var responseHeaders = {};
22511
+ var rawHeaders = xhr.getAllResponseHeaders();
22512
+ if (rawHeaders) {
22513
+ var headers = rawHeaders.trim().split(/[\r\n]+/);
22514
+ headers.forEach(function(line) {
22515
+ if (!line) return;
22516
+ var colonIndex = line.indexOf(': ');
22517
+ if (colonIndex === -1) return;
22518
+ var header = line.substring(0, colonIndex);
22519
+ var value = line.substring(colonIndex + 2);
22520
+ if (header && shouldRecordHeader('response', options.recordHeaders, header)) {
22521
+ responseHeaders[header] = value;
22522
+ }
22523
+ });
22524
+ }
22525
+ networkRequest.responseHeaders = responseHeaders;
22526
+ if (
22527
+ shouldRecordBody('response', options.recordBodyUrls, req.url)
22528
+ ) {
22529
+ networkRequest.responseBody = tryReadXHRBody(xhr.response);
22530
+ }
22531
+ getRequestPerformanceEntry(
22532
+ win,
22533
+ 'xmlhttprequest',
22534
+ req.url,
22535
+ after,
22536
+ before
22537
+ )
22538
+ .then(function(entry) {
22539
+ if (!entry) {
22540
+ logger$2.error('Failed to get performance entry for XHR request to ' + req.url);
22541
+ return;
22542
+ }
22543
+ /** @type {NetworkRequest} */
22544
+ var request = {
22545
+ url: entry.name,
22546
+ method: req.method,
22547
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
22548
+ status: xhr.status,
22549
+ startTime: Math.round(entry.startTime),
22550
+ endTime: Math.round(entry.responseEnd),
22551
+ timeOrigin: getTimeOrigin(win),
22552
+ requestHeaders: networkRequest.requestHeaders,
22553
+ requestBody: networkRequest.requestBody,
22554
+ responseHeaders: networkRequest.responseHeaders,
22555
+ responseBody: networkRequest.responseBody,
22556
+ };
22557
+ cb({ requests: [request] });
22558
+ })
22559
+ .catch(function(e) {
22560
+ logger$2.error('Error recording XHR request to ' + req.url + ': ' + String(e));
22561
+ });
22562
+ });
22563
+
22564
+ originalOpen.call(xhr, method, url, async, username, password);
22565
+ };
22566
+ }
22567
+ );
22568
+ return function() {
22569
+ restorePatch();
22570
+ };
22571
+ }
22572
+
22573
+ /**
22574
+ * @param {networkCallback} cb
22575
+ * @param {Window} win
22576
+ * @param {Required<NetworkRecordOptions>} options
22577
+ * @returns {listenerHandler}
22578
+ */
22579
+ function initFetchObserver(cb, win, options) {
22580
+ if (!options.initiatorTypes.includes('fetch')) {
22581
+ return function() {
22582
+ //
22583
+ };
22584
+ }
22585
+
22586
+ var restorePatch = patch(win, 'fetch', function(/** @type {typeof fetch} */ originalFetch) {
22587
+ return function() {
22588
+ var req = new Request(arguments[0], arguments[1]);
22589
+ /** @type {Response | undefined} */
22590
+ var res;
22591
+ /** @type {Partial<NetworkRequest>} */
22592
+ var networkRequest = {};
22593
+ /** @type {number | undefined} */
22594
+ var after;
22595
+ /** @type {number | undefined} */
22596
+ var before;
22597
+
22598
+ var originalFetchPromise;
22599
+ var requestBodyPromise = Promise.resolve(undefined);
22600
+ var responseBodyPromise = Promise.resolve(undefined);
22601
+ try {
22602
+ /** @type {Headers} */
22603
+ var requestHeaders = {};
22604
+ req.headers.forEach(function(value, header) {
22605
+ if (shouldRecordHeader('request', options.recordHeaders, header)) {
22606
+ requestHeaders[header] = value;
22607
+ }
22608
+ });
22609
+ networkRequest.requestHeaders = requestHeaders;
22610
+
22611
+ if (shouldRecordBody('request', options.recordBodyUrls, req.url)) {
22612
+ requestBodyPromise = tryReadFetchBody(req)
22613
+ .then(function(body) {
22614
+ networkRequest.requestBody = body;
22615
+ });
22616
+ }
22617
+
22618
+ after = win.performance.now();
22619
+ originalFetchPromise = originalFetch.apply(win, arguments).then(function(response) {
22620
+ res = response;
22621
+ before = win.performance.now();
22622
+
22623
+ /** @type {Headers} */
22624
+ var responseHeaders = {};
22625
+ res.headers.forEach(function(value, header) {
22626
+ if (shouldRecordHeader('response', options.recordHeaders, header)) {
22627
+ responseHeaders[header] = value;
22628
+ }
22629
+ });
22630
+ networkRequest.responseHeaders = responseHeaders;
22631
+
22632
+ if (shouldRecordBody('response', options.recordBodyUrls, req.url)) {
22633
+ responseBodyPromise = tryReadFetchBody(res)
22634
+ .then(function(body) {
22635
+ networkRequest.responseBody = body;
22636
+ });
22637
+ }
22638
+
22639
+ return res;
22640
+ });
22641
+ } catch (e) {
22642
+ originalFetchPromise = Promise.reject(e);
22643
+ }
22644
+
22645
+ // await concurrently so we don't delay the fetch response
22646
+ Promise.all([requestBodyPromise, responseBodyPromise, originalFetchPromise])
22647
+ .then(function () {
22648
+ return getRequestPerformanceEntry(win, 'fetch', req.url, after, before);
22649
+ })
22650
+ .then(function(entry) {
22651
+ if (!entry) {
22652
+ logger$2.error('Failed to get performance entry for fetch request to ' + req.url);
22653
+ return;
22654
+ }
22655
+ /** @type {NetworkRequest} */
22656
+ var request = {
22657
+ url: entry.name,
22658
+ method: req.method,
22659
+ initiatorType: /** @type {InitiatorType} */ (entry.initiatorType),
22660
+ status: res ? res.status : undefined,
22661
+ startTime: Math.round(entry.startTime),
22662
+ endTime: Math.round(entry.responseEnd),
22663
+ timeOrigin: getTimeOrigin(win),
22664
+ requestHeaders: networkRequest.requestHeaders,
22665
+ requestBody: networkRequest.requestBody,
22666
+ responseHeaders: networkRequest.responseHeaders,
22667
+ responseBody: networkRequest.responseBody,
22668
+ };
22669
+ cb({ requests: [request] });
22670
+ })
22671
+ .catch(function (e) {
22672
+ logger$2.error('Error recording fetch request to ' + req.url + ': ' + String(e));
22673
+ });
22674
+
22675
+ return originalFetchPromise;
22676
+ };
22677
+ });
22678
+ return function() {
22679
+ restorePatch();
22680
+ };
22681
+ }
22682
+
22683
+ /**
22684
+ * @param {networkCallback} callback
22685
+ * @param {Window} win
22686
+ * @param {NetworkRecordOptions} options
22687
+ * @returns {listenerHandler}
22688
+ */
22689
+ function initNetworkObserver(callback, win, options) {
22690
+ if (!('performance' in win)) {
22691
+ return function() {
22692
+ //
22693
+ };
22694
+ }
22695
+
22696
+ var recordHeaders = Object.assign({}, defaultNetworkOptions.recordHeaders, options.recordHeaders || {});
22697
+ var recordBodyUrls = Object.assign({}, defaultNetworkOptions.recordBodyUrls, options.recordBodyUrls || {});
22698
+ options = Object.assign({}, options, {
22699
+ recordHeaders: recordHeaders,
22700
+ recordBodyUrls: recordBodyUrls,
22701
+ });
22702
+ var networkOptions = /** @type {Required<NetworkRecordOptions>} */ Object.assign({}, defaultNetworkOptions, options);
22703
+
22704
+ /** @type {networkCallback} */
22705
+ var cb = function(data) {
22706
+ var requests = data.requests.filter(function(request) {
22707
+ var shouldIgnoreUrl = urlMatchesRegexList(request.url, networkOptions.ignoreRequestUrls || []);
22708
+ return !shouldIgnoreUrl && !networkOptions.ignoreRequestFn(request);
22709
+ });
22710
+ if (requests.length > 0 || data.isInitial) {
22711
+ callback(Object.assign({}, data, { requests: requests }));
22712
+ }
22713
+ };
22714
+ var performanceObserver = initPerformanceObserver(cb, win, networkOptions);
22715
+ var xhrObserver = initXhrObserver(cb, win, networkOptions);
22716
+ var fetchObserver = initFetchObserver(cb, win, networkOptions);
22717
+ return function() {
22718
+ performanceObserver();
22719
+ xhrObserver();
22720
+ fetchObserver();
22721
+ };
22722
+ }
22723
+
22724
+ // arbitrary .mp suffix in case rrweb does publish this plugin later and we use it but need to handle
22725
+ // a changed format in the mixpanel product.
22726
+ var NETWORK_PLUGIN_NAME = 'rrweb/network@1.mp';
22727
+
22728
+ /**
22729
+ * @param {NetworkRecordOptions} [options]
22730
+ * @returns {RecordPlugin}
22731
+ */
22732
+ var getRecordNetworkPlugin = function(options) {
22733
+ return {
22734
+ name: NETWORK_PLUGIN_NAME,
22735
+ observer: initNetworkObserver,
22736
+ options: options,
22737
+ };
22738
+ };
22739
+
22087
22740
  /**
22088
22741
  * @typedef {import('../index').RecordPrivacyConfig} RecordPrivacyConfig
22089
22742
  */
@@ -22317,6 +22970,29 @@
22317
22970
 
22318
22971
  var privacyConfig = getPrivacyConfig(this._mixpanel);
22319
22972
 
22973
+ var plugins = [];
22974
+ if (this.getConfig('record_network')) {
22975
+ var options = this.getConfig('record_network_options') || {};
22976
+ // don't track requests to Mixpanel /record API
22977
+ var ignoreRequestUrls = (options.ignoreRequestUrls || []).slice();
22978
+ ignoreRequestUrls.push(this._getApiRoute());
22979
+ options.ignoreRequestUrls = ignoreRequestUrls;
22980
+
22981
+ plugins.push(getRecordNetworkPlugin(options));
22982
+ }
22983
+
22984
+ if (this.getConfig('record_console')) {
22985
+ plugins.push(
22986
+ getRecordConsolePlugin({
22987
+ stringifyOptions: {
22988
+ stringLengthLimit: 1000,
22989
+ numOfKeysLimit: 50,
22990
+ depthOfLimit: 2
22991
+ }
22992
+ })
22993
+ );
22994
+ }
22995
+
22320
22996
  try {
22321
22997
  this._stopRecording = this._rrwebRecord({
22322
22998
  'emit': function (ev) {
@@ -22355,15 +23031,7 @@
22355
23031
  'sampling': {
22356
23032
  'canvas': 15
22357
23033
  },
22358
- 'plugins': this.getConfig('record_console') ? [
22359
- getRecordConsolePlugin({
22360
- stringifyOptions: {
22361
- stringLengthLimit: 1000,
22362
- numOfKeysLimit: 50,
22363
- depthOfLimit: 2
22364
- }
22365
- })
22366
- ] : []
23034
+ 'plugins': plugins,
22367
23035
  });
22368
23036
  } catch (err) {
22369
23037
  this.reportError('Unexpected error when starting rrweb recording.', err);
@@ -22478,6 +23146,10 @@
22478
23146
  return recording;
22479
23147
  };
22480
23148
 
23149
+ SessionRecording.prototype._getApiRoute = function () {
23150
+ return this.getConfig('api_routes')['record'];
23151
+ };
23152
+
22481
23153
  SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
22482
23154
  var onSuccess = function (response, responseBody) {
22483
23155
  // Update batch specific props only if the request was successful to guarantee ordering.
@@ -22497,7 +23169,7 @@
22497
23169
  });
22498
23170
  }.bind(this);
22499
23171
  var apiHost = (this._mixpanel.get_api_host && this._mixpanel.get_api_host('record')) || this.getConfig('api_host');
22500
- win['fetch'](apiHost + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
23172
+ win['fetch'](apiHost + '/' + this._getApiRoute() + '?' + new URLSearchParams(reqParams), {
22501
23173
  'method': 'POST',
22502
23174
  'headers': {
22503
23175
  'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
@@ -22898,8 +23570,12 @@
22898
23570
  this.startRecording({shouldStopBatcher: true});
22899
23571
  };
22900
23572
 
23573
+ MixpanelRecorder.prototype.isRecording = function () {
23574
+ return this.activeRecording && !this.activeRecording.isRrwebStopped();
23575
+ };
23576
+
22901
23577
  MixpanelRecorder.prototype.getActiveReplayId = function () {
22902
- if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
23578
+ if (this.isRecording()) {
22903
23579
  return this.activeRecording.replayId;
22904
23580
  } else {
22905
23581
  return null;