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