mixpanel-browser 2.55.0 → 2.56.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.
@@ -4509,7 +4509,7 @@ var IncrementalSource = /* @__PURE__ */ ((IncrementalSource2) => {
4509
4509
 
4510
4510
  var Config = {
4511
4511
  DEBUG: false,
4512
- LIB_VERSION: '2.55.0'
4512
+ LIB_VERSION: '2.56.0'
4513
4513
  };
4514
4514
 
4515
4515
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -4521,7 +4521,7 @@ if (typeof(window) === 'undefined') {
4521
4521
  hostname: ''
4522
4522
  };
4523
4523
  win = {
4524
- navigator: { userAgent: '' },
4524
+ navigator: { userAgent: '', onLine: true },
4525
4525
  document: {
4526
4526
  location: loc,
4527
4527
  referrer: ''
@@ -4535,6 +4535,8 @@ if (typeof(window) === 'undefined') {
4535
4535
 
4536
4536
  // Maximum allowed session recording length
4537
4537
  var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
4538
+ // Maximum allowed value for minimum session recording length
4539
+ var MAX_VALUE_FOR_MIN_RECORDING_MS = 8 * 1000; // 8 seconds
4538
4540
 
4539
4541
  /*
4540
4542
  * Saved references to long variable names, so that closure compiler can
@@ -5475,7 +5477,7 @@ _.HTTPBuildQuery = function(formdata, arg_separator) {
5475
5477
  _.getQueryParam = function(url, param) {
5476
5478
  // Expects a raw URL
5477
5479
 
5478
- param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
5480
+ param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
5479
5481
  var regexS = '[\\?&]' + param + '=([^&#]*)',
5480
5482
  regex = new RegExp(regexS),
5481
5483
  results = regex.exec(url);
@@ -5932,8 +5934,8 @@ _.dom_query = (function() {
5932
5934
  };
5933
5935
  })();
5934
5936
 
5935
- var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
5936
- var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
5937
+ var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'utm_id', 'utm_source_platform','utm_campaign_id', 'utm_creative_format', 'utm_marketing_tactic'];
5938
+ var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
5937
5939
 
5938
5940
  _.info = {
5939
5941
  campaignParams: function(default_value) {
@@ -6215,6 +6217,15 @@ var extract_domain = function(hostname) {
6215
6217
  return matches ? matches[0] : '';
6216
6218
  };
6217
6219
 
6220
+ /**
6221
+ * Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
6222
+ * @returns {boolean}
6223
+ */
6224
+ var isOnline = function() {
6225
+ var onLine = win.navigator['onLine'];
6226
+ return _.isUndefined(onLine) || onLine;
6227
+ };
6228
+
6218
6229
  var JSONStringify = null, JSONParse = null;
6219
6230
  if (typeof JSON !== 'undefined') {
6220
6231
  JSONStringify = JSON.stringify;
@@ -6535,7 +6546,7 @@ function _addOptOutCheck(method, getConfigValue) {
6535
6546
  };
6536
6547
  }
6537
6548
 
6538
- var logger$3 = console_with_prefix('lock');
6549
+ var logger$4 = console_with_prefix('lock');
6539
6550
 
6540
6551
  /**
6541
6552
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -6592,7 +6603,7 @@ SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) {
6592
6603
 
6593
6604
  var delay = function(cb) {
6594
6605
  if (new Date().getTime() - startTime > timeoutMS) {
6595
- logger$3.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6606
+ logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6596
6607
  storage.removeItem(keyZ);
6597
6608
  storage.removeItem(keyY);
6598
6609
  loop();
@@ -6681,7 +6692,7 @@ SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) {
6681
6692
  }
6682
6693
  };
6683
6694
 
6684
- var logger$2 = console_with_prefix('batch');
6695
+ var logger$3 = console_with_prefix('batch');
6685
6696
 
6686
6697
  /**
6687
6698
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -6702,11 +6713,13 @@ var logger$2 = console_with_prefix('batch');
6702
6713
  var RequestQueue = function(storageKey, options) {
6703
6714
  options = options || {};
6704
6715
  this.storageKey = storageKey;
6705
- this.storage = options.storage || window.localStorage;
6706
- this.reportError = options.errorReporter || _.bind(logger$2.error, logger$2);
6707
- this.lock = new SharedLock(storageKey, {storage: this.storage});
6708
-
6709
6716
  this.usePersistence = options.usePersistence;
6717
+ if (this.usePersistence) {
6718
+ this.storage = options.storage || window.localStorage;
6719
+ this.lock = new SharedLock(storageKey, {storage: this.storage});
6720
+ }
6721
+ this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
6722
+
6710
6723
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
6711
6724
 
6712
6725
  this.memQueue = [];
@@ -6985,7 +6998,7 @@ RequestQueue.prototype.clear = function() {
6985
6998
  // maximum interval between request retries after exponential backoff
6986
6999
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
6987
7000
 
6988
- var logger$1 = console_with_prefix('batch');
7001
+ var logger$2 = console_with_prefix('batch');
6989
7002
 
6990
7003
  /**
6991
7004
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -7099,7 +7112,7 @@ RequestBatcher.prototype.flush = function(options) {
7099
7112
  try {
7100
7113
 
7101
7114
  if (this.requestInProgress) {
7102
- logger$1.log('Flush: Request already in progress');
7115
+ logger$2.log('Flush: Request already in progress');
7103
7116
  return;
7104
7117
  }
7105
7118
 
@@ -7178,7 +7191,12 @@ RequestBatcher.prototype.flush = function(options) {
7178
7191
  this.flush();
7179
7192
  } else if (
7180
7193
  _.isObject(res) &&
7181
- (res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
7194
+ (
7195
+ res.httpStatusCode >= 500
7196
+ || res.httpStatusCode === 429
7197
+ || (res.httpStatusCode <= 0 && !isOnline())
7198
+ || res.error === 'timeout'
7199
+ )
7182
7200
  ) {
7183
7201
  // network or API error, or 429 Too Many Requests, retry
7184
7202
  var retryMS = this.flushInterval * 2;
@@ -7262,7 +7280,7 @@ RequestBatcher.prototype.flush = function(options) {
7262
7280
  if (options.unloading) {
7263
7281
  requestOptions.transport = 'sendBeacon';
7264
7282
  }
7265
- logger$1.log('MIXPANEL REQUEST:', dataForRequest);
7283
+ logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7266
7284
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
7267
7285
  } catch(err) {
7268
7286
  this.reportError('Error flushing request queue', err);
@@ -7274,7 +7292,7 @@ RequestBatcher.prototype.flush = function(options) {
7274
7292
  * Log error to global logger and optional user-defined logger.
7275
7293
  */
7276
7294
  RequestBatcher.prototype.reportError = function(msg, err) {
7277
- logger$1.error.apply(logger$1.error, arguments);
7295
+ logger$2.error.apply(logger$2.error, arguments);
7278
7296
  if (this.errorReporter) {
7279
7297
  try {
7280
7298
  if (!(err instanceof Error)) {
@@ -7282,12 +7300,12 @@ RequestBatcher.prototype.reportError = function(msg, err) {
7282
7300
  }
7283
7301
  this.errorReporter(msg, err);
7284
7302
  } catch(err) {
7285
- logger$1.error(err);
7303
+ logger$2.error(err);
7286
7304
  }
7287
7305
  }
7288
7306
  };
7289
7307
 
7290
- var logger = console_with_prefix('recorder');
7308
+ var logger$1 = console_with_prefix('recorder');
7291
7309
  var CompressionStream = win['CompressionStream'];
7292
7310
 
7293
7311
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -7313,63 +7331,85 @@ function isUserEvent(ev) {
7313
7331
  return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
7314
7332
  }
7315
7333
 
7316
- var MixpanelRecorder = function(mixpanelInstance) {
7317
- this._mixpanel = mixpanelInstance;
7334
+ /**
7335
+ * This class encapsulates a single session recording and its lifecycle.
7336
+ * @param {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
7337
+ * @param {String} [options.replayId] - unique uuid for a single replay
7338
+ * @param {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
7339
+ * @param {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
7340
+ * @param {Function} [options.rrwebRecord] - rrweb's `record` function
7341
+ */
7342
+ var SessionRecording = function(options) {
7343
+ this._mixpanel = options.mixpanelInstance;
7344
+ this._onIdleTimeout = options.onIdleTimeout;
7345
+ this._onMaxLengthReached = options.onMaxLengthReached;
7346
+ this._rrwebRecord = options.rrwebRecord;
7347
+
7348
+ this.replayId = options.replayId;
7318
7349
 
7319
7350
  // internal rrweb stopRecording function
7320
7351
  this._stopRecording = null;
7321
7352
 
7322
- this.recEvents = [];
7323
7353
  this.seqNo = 0;
7324
- this.replayId = null;
7325
7354
  this.replayStartTime = null;
7326
- this.sendBatchId = null;
7355
+ this.batchStartUrl = null;
7327
7356
 
7328
7357
  this.idleTimeoutId = null;
7329
7358
  this.maxTimeoutId = null;
7330
7359
 
7331
7360
  this.recordMaxMs = MAX_RECORDING_MS;
7332
- this._initBatcher();
7333
- };
7334
-
7361
+ this.recordMinMs = 0;
7335
7362
 
7336
- MixpanelRecorder.prototype._initBatcher = function () {
7337
- this.batcher = new RequestBatcher('__mprec', {
7338
- libConfig: RECORDER_BATCHER_LIB_CONFIG,
7339
- sendRequestFunc: _.bind(this.flushEventsWithOptOut, this),
7363
+ // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
7364
+ // this will be important when persistence is introduced
7365
+ var batcherKey = '__mprec_' + this.getConfig('token') + '_' + this.replayId;
7366
+ this.batcher = new RequestBatcher(batcherKey, {
7340
7367
  errorReporter: _.bind(this.reportError, this),
7341
7368
  flushOnlyOnInterval: true,
7369
+ libConfig: RECORDER_BATCHER_LIB_CONFIG,
7370
+ sendRequestFunc: _.bind(this.flushEventsWithOptOut, this),
7342
7371
  usePersistence: false
7343
7372
  });
7344
7373
  };
7345
7374
 
7346
- // eslint-disable-next-line camelcase
7347
- MixpanelRecorder.prototype.get_config = function(configVar) {
7375
+ SessionRecording.prototype.getConfig = function(configVar) {
7348
7376
  return this._mixpanel.get_config(configVar);
7349
7377
  };
7350
7378
 
7351
- MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
7379
+ // Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
7380
+ // reaches into this class instance and expects the snake case version of the function.
7381
+ // eslint-disable-next-line camelcase
7382
+ SessionRecording.prototype.get_config = function(configVar) {
7383
+ return this.getConfig(configVar);
7384
+ };
7385
+
7386
+ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
7352
7387
  if (this._stopRecording !== null) {
7353
- logger.log('Recording already in progress, skipping startRecording.');
7388
+ logger$1.log('Recording already in progress, skipping startRecording.');
7354
7389
  return;
7355
7390
  }
7356
7391
 
7357
- this.recordMaxMs = this.get_config('record_max_ms');
7392
+ this.recordMaxMs = this.getConfig('record_max_ms');
7358
7393
  if (this.recordMaxMs > MAX_RECORDING_MS) {
7359
7394
  this.recordMaxMs = MAX_RECORDING_MS;
7360
- logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7395
+ logger$1.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7361
7396
  }
7362
7397
 
7363
- this.recEvents = [];
7364
- this.seqNo = 0;
7365
- this.replayStartTime = null;
7398
+ this.recordMinMs = this.getConfig('record_min_ms');
7399
+ if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
7400
+ this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
7401
+ logger$1.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7402
+ }
7366
7403
 
7367
- this.replayId = _.UUID();
7404
+ this.replayStartTime = new Date().getTime();
7405
+ this.batchStartUrl = _.info.currentUrl();
7368
7406
 
7369
- if (shouldStopBatcher) {
7370
- // this is the case when we're starting recording after a reset
7407
+ if (shouldStopBatcher || this.recordMinMs > 0) {
7408
+ // the primary case for shouldStopBatcher is when we're starting recording after a reset
7371
7409
  // and don't want to send anything over the network until there's
7372
7410
  // actual user activity
7411
+ // this also applies if the minimum recording length has not been hit yet
7412
+ // so that we don't send data until we know the recording will be long enough
7373
7413
  this.batcher.stop();
7374
7414
  } else {
7375
7415
  this.batcher.start();
@@ -7377,45 +7417,52 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
7377
7417
 
7378
7418
  var resetIdleTimeout = _.bind(function () {
7379
7419
  clearTimeout(this.idleTimeoutId);
7380
- this.idleTimeoutId = setTimeout(_.bind(function () {
7381
- logger.log('Idle timeout reached, restarting recording.');
7382
- this.resetRecording();
7383
- }, this), this.get_config('record_idle_timeout_ms'));
7420
+ this.idleTimeoutId = setTimeout(this._onIdleTimeout, this.getConfig('record_idle_timeout_ms'));
7384
7421
  }, this);
7385
7422
 
7386
- this._stopRecording = record({
7423
+ var blockSelector = this.getConfig('record_block_selector');
7424
+ if (blockSelector === '' || blockSelector === null) {
7425
+ blockSelector = undefined;
7426
+ }
7427
+
7428
+ this._stopRecording = this._rrwebRecord({
7387
7429
  'emit': _.bind(function (ev) {
7388
7430
  this.batcher.enqueue(ev);
7389
7431
  if (isUserEvent(ev)) {
7390
- if (this.batcher.stopped) {
7432
+ if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
7391
7433
  // start flushing again after user activity
7392
7434
  this.batcher.start();
7393
7435
  }
7394
7436
  resetIdleTimeout();
7395
7437
  }
7396
7438
  }, this),
7397
- 'blockClass': this.get_config('record_block_class'),
7398
- 'blockSelector': this.get_config('record_block_selector'),
7399
- 'collectFonts': this.get_config('record_collect_fonts'),
7400
- 'inlineImages': this.get_config('record_inline_images'),
7439
+ 'blockClass': this.getConfig('record_block_class'),
7440
+ 'blockSelector': blockSelector,
7441
+ 'collectFonts': this.getConfig('record_collect_fonts'),
7401
7442
  'maskAllInputs': true,
7402
- 'maskTextClass': this.get_config('record_mask_text_class'),
7403
- 'maskTextSelector': this.get_config('record_mask_text_selector')
7443
+ 'maskTextClass': this.getConfig('record_mask_text_class'),
7444
+ 'maskTextSelector': this.getConfig('record_mask_text_selector')
7404
7445
  });
7405
7446
 
7406
- resetIdleTimeout();
7447
+ if (typeof this._stopRecording !== 'function') {
7448
+ this.reportError('rrweb failed to start, skipping this recording.');
7449
+ this._stopRecording = null;
7450
+ this.stopRecording(); // stop batcher looping and any timeouts
7451
+ return;
7452
+ }
7407
7453
 
7408
- this.maxTimeoutId = setTimeout(_.bind(this.resetRecording, this), this.recordMaxMs);
7409
- };
7454
+ resetIdleTimeout();
7410
7455
 
7411
- MixpanelRecorder.prototype.resetRecording = function () {
7412
- this.stopRecording();
7413
- this.startRecording(true);
7456
+ this.maxTimeoutId = setTimeout(_.bind(this._onMaxLengthReached, this), this.recordMaxMs);
7414
7457
  };
7415
7458
 
7416
- MixpanelRecorder.prototype.stopRecording = function () {
7417
- if (this._stopRecording !== null) {
7418
- this._stopRecording();
7459
+ SessionRecording.prototype.stopRecording = function () {
7460
+ if (!this.isRrwebStopped()) {
7461
+ try {
7462
+ this._stopRecording();
7463
+ } catch (err) {
7464
+ this.reportError('Error with rrweb stopRecording', err);
7465
+ }
7419
7466
  this._stopRecording = null;
7420
7467
  }
7421
7468
 
@@ -7427,36 +7474,39 @@ MixpanelRecorder.prototype.stopRecording = function () {
7427
7474
  this.batcher.flush();
7428
7475
  this.batcher.stop();
7429
7476
  }
7430
- this.replayId = null;
7431
7477
 
7432
7478
  clearTimeout(this.idleTimeoutId);
7433
7479
  clearTimeout(this.maxTimeoutId);
7434
7480
  };
7435
7481
 
7482
+ SessionRecording.prototype.isRrwebStopped = function () {
7483
+ return this._stopRecording === null;
7484
+ };
7485
+
7436
7486
  /**
7437
7487
  * Flushes the current batch of events to the server, but passes an opt-out callback to make sure
7438
7488
  * we stop recording and dump any queued events if the user has opted out.
7439
7489
  */
7440
- MixpanelRecorder.prototype.flushEventsWithOptOut = function (data, options, cb) {
7490
+ SessionRecording.prototype.flushEventsWithOptOut = function (data, options, cb) {
7441
7491
  this._flushEvents(data, options, cb, _.bind(this._onOptOut, this));
7442
7492
  };
7443
7493
 
7444
- MixpanelRecorder.prototype._onOptOut = function (code) {
7494
+ SessionRecording.prototype._onOptOut = function (code) {
7445
7495
  // addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
7446
7496
  if (code === 0) {
7447
- this.recEvents = [];
7448
7497
  this.stopRecording();
7449
7498
  }
7450
7499
  };
7451
7500
 
7452
- MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
7501
+ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
7453
7502
  var onSuccess = _.bind(function (response, responseBody) {
7454
- // Increment sequence counter only if the request was successful to guarantee ordering.
7503
+ // Update batch specific props only if the request was successful to guarantee ordering.
7455
7504
  // RequestBatcher will always flush the next batch after the previous one succeeds.
7456
- if (response.status === 200) {
7505
+ // extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
7506
+ if (response.status === 200 && this.replayId === currentReplayId) {
7457
7507
  this.seqNo++;
7508
+ this.batchStartUrl = _.info.currentUrl();
7458
7509
  }
7459
-
7460
7510
  callback({
7461
7511
  status: 0,
7462
7512
  httpStatusCode: response.status,
@@ -7465,10 +7515,10 @@ MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback)
7465
7515
  });
7466
7516
  }, this);
7467
7517
 
7468
- win['fetch'](this.get_config('api_host') + '/' + this.get_config('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
7518
+ win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
7469
7519
  'method': 'POST',
7470
7520
  'headers': {
7471
- 'Authorization': 'Basic ' + btoa(this.get_config('token') + ':'),
7521
+ 'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
7472
7522
  'Content-Type': 'application/octet-stream'
7473
7523
  },
7474
7524
  'body': reqBody,
@@ -7479,28 +7529,37 @@ MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback)
7479
7529
  callback({error: error});
7480
7530
  });
7481
7531
  }).catch(function (error) {
7482
- callback({error: error});
7532
+ callback({error: error, httpStatusCode: 0});
7483
7533
  });
7484
7534
  };
7485
7535
 
7486
- MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7536
+ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7487
7537
  const numEvents = data.length;
7488
7538
 
7489
7539
  if (numEvents > 0) {
7540
+ var replayId = this.replayId;
7490
7541
  // each rrweb event has a timestamp - leverage those to get time properties
7491
7542
  var batchStartTime = data[0].timestamp;
7492
- if (this.seqNo === 0) {
7543
+ if (this.seqNo === 0 || !this.replayStartTime) {
7544
+ // extra safety net so that we don't send a null replay start time
7545
+ if (this.seqNo !== 0) {
7546
+ this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
7547
+ }
7548
+
7493
7549
  this.replayStartTime = batchStartTime;
7494
7550
  }
7495
7551
  var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
7496
7552
 
7497
7553
  var reqParams = {
7498
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
7499
- 'seq': this.seqNo,
7554
+ '$current_url': this.batchStartUrl,
7555
+ '$lib_version': Config.LIB_VERSION,
7500
7556
  'batch_start_time': batchStartTime / 1000,
7501
- 'replay_id': this.replayId,
7557
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
7558
+ 'mp_lib': 'web',
7559
+ 'replay_id': replayId,
7502
7560
  'replay_length_ms': replayLengthMs,
7503
- 'replay_start_time': this.replayStartTime / 1000
7561
+ 'replay_start_time': this.replayStartTime / 1000,
7562
+ 'seq': this.seqNo
7504
7563
  };
7505
7564
  var eventsJson = _.JSONEncode(data);
7506
7565
 
@@ -7521,28 +7580,93 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
7521
7580
  .blob()
7522
7581
  .then(_.bind(function(compressedBlob) {
7523
7582
  reqParams['format'] = 'gzip';
7524
- this._sendRequest(reqParams, compressedBlob, callback);
7583
+ this._sendRequest(replayId, reqParams, compressedBlob, callback);
7525
7584
  }, this));
7526
7585
  } else {
7527
7586
  reqParams['format'] = 'body';
7528
- this._sendRequest(reqParams, eventsJson, callback);
7587
+ this._sendRequest(replayId, reqParams, eventsJson, callback);
7529
7588
  }
7530
7589
  }
7531
7590
  });
7532
7591
 
7533
7592
 
7534
- MixpanelRecorder.prototype.reportError = function(msg, err) {
7535
- logger.error.apply(logger.error, arguments);
7593
+ SessionRecording.prototype.reportError = function(msg, err) {
7594
+ logger$1.error.apply(logger$1.error, arguments);
7536
7595
  try {
7537
7596
  if (!err && !(msg instanceof Error)) {
7538
7597
  msg = new Error(msg);
7539
7598
  }
7540
- this.get_config('error_reporter')(msg, err);
7599
+ this.getConfig('error_reporter')(msg, err);
7541
7600
  } catch(err) {
7542
- logger.error(err);
7601
+ logger$1.error(err);
7543
7602
  }
7544
7603
  };
7545
7604
 
7605
+ var logger = console_with_prefix('recorder');
7606
+
7607
+ /**
7608
+ * Recorder API: manages recordings and exposes methods public to the core Mixpanel library.
7609
+ * @param {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
7610
+ */
7611
+ var MixpanelRecorder = function(mixpanelInstance) {
7612
+ this._mixpanel = mixpanelInstance;
7613
+ this.activeRecording = null;
7614
+ };
7615
+
7616
+ MixpanelRecorder.prototype.startRecording = function(shouldStopBatcher) {
7617
+ if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
7618
+ logger.log('Recording already in progress, skipping startRecording.');
7619
+ return;
7620
+ }
7621
+
7622
+ var onIdleTimeout = _.bind(function () {
7623
+ logger.log('Idle timeout reached, restarting recording.');
7624
+ this.resetRecording();
7625
+ }, this);
7626
+
7627
+ var onMaxLengthReached = _.bind(function () {
7628
+ logger.log('Max recording length reached, stopping recording.');
7629
+ this.resetRecording();
7630
+ }, this);
7631
+
7632
+ this.activeRecording = new SessionRecording({
7633
+ mixpanelInstance: this._mixpanel,
7634
+ onIdleTimeout: onIdleTimeout,
7635
+ onMaxLengthReached: onMaxLengthReached,
7636
+ replayId: _.UUID(),
7637
+ rrwebRecord: record
7638
+ });
7639
+
7640
+ this.activeRecording.startRecording(shouldStopBatcher);
7641
+ };
7642
+
7643
+ MixpanelRecorder.prototype.stopRecording = function() {
7644
+ if (this.activeRecording) {
7645
+ this.activeRecording.stopRecording();
7646
+ this.activeRecording = null;
7647
+ }
7648
+ };
7649
+
7650
+ MixpanelRecorder.prototype.resetRecording = function () {
7651
+ this.stopRecording();
7652
+ this.startRecording(true);
7653
+ };
7654
+
7655
+ MixpanelRecorder.prototype.getActiveReplayId = function () {
7656
+ if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
7657
+ return this.activeRecording.replayId;
7658
+ } else {
7659
+ return null;
7660
+ }
7661
+ };
7662
+
7663
+ // getter so that older mixpanel-core versions can still retrieve the replay ID
7664
+ // when pulling the latest recorder bundle from the CDN
7665
+ Object.defineProperty(MixpanelRecorder.prototype, 'replayId', {
7666
+ get: function () {
7667
+ return this.getActiveReplayId();
7668
+ }
7669
+ });
7546
7670
 
7547
7671
  win['__mp_recorder'] = MixpanelRecorder;
7548
7672
 
@@ -9009,10 +9133,10 @@ var DEFAULT_CONFIG = {
9009
9133
  'record_block_selector': 'img, video',
9010
9134
  'record_collect_fonts': false,
9011
9135
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
9012
- 'record_inline_images': false,
9013
9136
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
9014
9137
  'record_mask_text_selector': '*',
9015
9138
  'record_max_ms': MAX_RECORDING_MS,
9139
+ 'record_min_ms': 0,
9016
9140
  'record_sessions_percent': 0,
9017
9141
  'recorder_src': 'https://cdn.mxpnl.com/libs/mixpanel-recorder.min.js'
9018
9142
  };
@@ -9261,15 +9385,35 @@ MixpanelLib.prototype.stop_session_recording = function () {
9261
9385
 
9262
9386
  MixpanelLib.prototype.get_session_recording_properties = function () {
9263
9387
  var props = {};
9264
- if (this._recorder) {
9265
- var replay_id = this._recorder['replayId'];
9266
- if (replay_id) {
9267
- props['$mp_replay_id'] = replay_id;
9268
- }
9388
+ var replay_id = this._get_session_replay_id();
9389
+ if (replay_id) {
9390
+ props['$mp_replay_id'] = replay_id;
9269
9391
  }
9270
9392
  return props;
9271
9393
  };
9272
9394
 
9395
+ MixpanelLib.prototype.get_session_replay_url = function () {
9396
+ var replay_url = null;
9397
+ var replay_id = this._get_session_replay_id();
9398
+ if (replay_id) {
9399
+ var query_params = _.HTTPBuildQuery({
9400
+ 'replay_id': replay_id,
9401
+ 'distinct_id': this.get_distinct_id(),
9402
+ 'token': this.get_config('token')
9403
+ });
9404
+ replay_url = 'https://mixpanel.com/projects/replay-redirect?' + query_params;
9405
+ }
9406
+ return replay_url;
9407
+ };
9408
+
9409
+ MixpanelLib.prototype._get_session_replay_id = function () {
9410
+ var replay_id = null;
9411
+ if (this._recorder) {
9412
+ replay_id = this._recorder['replayId'];
9413
+ }
9414
+ return replay_id || null;
9415
+ };
9416
+
9273
9417
  // Private methods
9274
9418
 
9275
9419
  MixpanelLib.prototype._loaded = function() {
@@ -10996,6 +11140,7 @@ MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.protot
10996
11140
  MixpanelLib.prototype['start_session_recording'] = MixpanelLib.prototype.start_session_recording;
10997
11141
  MixpanelLib.prototype['stop_session_recording'] = MixpanelLib.prototype.stop_session_recording;
10998
11142
  MixpanelLib.prototype['get_session_recording_properties'] = MixpanelLib.prototype.get_session_recording_properties;
11143
+ MixpanelLib.prototype['get_session_replay_url'] = MixpanelLib.prototype.get_session_replay_url;
10999
11144
  MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES;
11000
11145
 
11001
11146
  // MixpanelPersistence Exports