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.
@@ -4510,7 +4510,7 @@
4510
4510
 
4511
4511
  var Config = {
4512
4512
  DEBUG: false,
4513
- LIB_VERSION: '2.55.0'
4513
+ LIB_VERSION: '2.56.0'
4514
4514
  };
4515
4515
 
4516
4516
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -4522,7 +4522,7 @@
4522
4522
  hostname: ''
4523
4523
  };
4524
4524
  win = {
4525
- navigator: { userAgent: '' },
4525
+ navigator: { userAgent: '', onLine: true },
4526
4526
  document: {
4527
4527
  location: loc,
4528
4528
  referrer: ''
@@ -4536,6 +4536,8 @@
4536
4536
 
4537
4537
  // Maximum allowed session recording length
4538
4538
  var MAX_RECORDING_MS = 24 * 60 * 60 * 1000; // 24 hours
4539
+ // Maximum allowed value for minimum session recording length
4540
+ var MAX_VALUE_FOR_MIN_RECORDING_MS = 8 * 1000; // 8 seconds
4539
4541
 
4540
4542
  /*
4541
4543
  * Saved references to long variable names, so that closure compiler can
@@ -5447,7 +5449,7 @@
5447
5449
  _.getQueryParam = function(url, param) {
5448
5450
  // Expects a raw URL
5449
5451
 
5450
- param = param.replace(/[[]/, '\\[').replace(/[\]]/, '\\]');
5452
+ param = param.replace(/[[]/g, '\\[').replace(/[\]]/g, '\\]');
5451
5453
  var regexS = '[\\?&]' + param + '=([^&#]*)',
5452
5454
  regex = new RegExp(regexS),
5453
5455
  results = regex.exec(url);
@@ -5904,8 +5906,8 @@
5904
5906
  };
5905
5907
  })();
5906
5908
 
5907
- var CAMPAIGN_KEYWORDS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'];
5908
- var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'ttclid', 'twclid', 'wbraid'];
5909
+ 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'];
5910
+ var CLICK_IDS = ['dclid', 'fbclid', 'gclid', 'ko_click_id', 'li_fat_id', 'msclkid', 'sccid', 'ttclid', 'twclid', 'wbraid'];
5909
5911
 
5910
5912
  _.info = {
5911
5913
  campaignParams: function(default_value) {
@@ -6187,6 +6189,15 @@
6187
6189
  return matches ? matches[0] : '';
6188
6190
  };
6189
6191
 
6192
+ /**
6193
+ * Check whether we have network connection. default to true for browsers that don't support navigator.onLine (IE)
6194
+ * @returns {boolean}
6195
+ */
6196
+ var isOnline = function() {
6197
+ var onLine = win.navigator['onLine'];
6198
+ return _.isUndefined(onLine) || onLine;
6199
+ };
6200
+
6190
6201
  var JSONStringify = null, JSONParse = null;
6191
6202
  if (typeof JSON !== 'undefined') {
6192
6203
  JSONStringify = JSON.stringify;
@@ -6375,7 +6386,7 @@
6375
6386
  };
6376
6387
  }
6377
6388
 
6378
- var logger$3 = console_with_prefix('lock');
6389
+ var logger$4 = console_with_prefix('lock');
6379
6390
 
6380
6391
  /**
6381
6392
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -6432,7 +6443,7 @@
6432
6443
 
6433
6444
  var delay = function(cb) {
6434
6445
  if (new Date().getTime() - startTime > timeoutMS) {
6435
- logger$3.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6446
+ logger$4.error('Timeout waiting for mutex on ' + key + '; clearing lock. [' + i + ']');
6436
6447
  storage.removeItem(keyZ);
6437
6448
  storage.removeItem(keyY);
6438
6449
  loop();
@@ -6521,7 +6532,7 @@
6521
6532
  }
6522
6533
  };
6523
6534
 
6524
- var logger$2 = console_with_prefix('batch');
6535
+ var logger$3 = console_with_prefix('batch');
6525
6536
 
6526
6537
  /**
6527
6538
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -6542,11 +6553,13 @@
6542
6553
  var RequestQueue = function(storageKey, options) {
6543
6554
  options = options || {};
6544
6555
  this.storageKey = storageKey;
6545
- this.storage = options.storage || window.localStorage;
6546
- this.reportError = options.errorReporter || _.bind(logger$2.error, logger$2);
6547
- this.lock = new SharedLock(storageKey, {storage: this.storage});
6548
-
6549
6556
  this.usePersistence = options.usePersistence;
6557
+ if (this.usePersistence) {
6558
+ this.storage = options.storage || window.localStorage;
6559
+ this.lock = new SharedLock(storageKey, {storage: this.storage});
6560
+ }
6561
+ this.reportError = options.errorReporter || _.bind(logger$3.error, logger$3);
6562
+
6550
6563
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
6551
6564
 
6552
6565
  this.memQueue = [];
@@ -6825,7 +6838,7 @@
6825
6838
  // maximum interval between request retries after exponential backoff
6826
6839
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
6827
6840
 
6828
- var logger$1 = console_with_prefix('batch');
6841
+ var logger$2 = console_with_prefix('batch');
6829
6842
 
6830
6843
  /**
6831
6844
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -6939,7 +6952,7 @@
6939
6952
  try {
6940
6953
 
6941
6954
  if (this.requestInProgress) {
6942
- logger$1.log('Flush: Request already in progress');
6955
+ logger$2.log('Flush: Request already in progress');
6943
6956
  return;
6944
6957
  }
6945
6958
 
@@ -7018,7 +7031,12 @@
7018
7031
  this.flush();
7019
7032
  } else if (
7020
7033
  _.isObject(res) &&
7021
- (res.httpStatusCode >= 500 || res.httpStatusCode === 429 || res.error === 'timeout')
7034
+ (
7035
+ res.httpStatusCode >= 500
7036
+ || res.httpStatusCode === 429
7037
+ || (res.httpStatusCode <= 0 && !isOnline())
7038
+ || res.error === 'timeout'
7039
+ )
7022
7040
  ) {
7023
7041
  // network or API error, or 429 Too Many Requests, retry
7024
7042
  var retryMS = this.flushInterval * 2;
@@ -7102,7 +7120,7 @@
7102
7120
  if (options.unloading) {
7103
7121
  requestOptions.transport = 'sendBeacon';
7104
7122
  }
7105
- logger$1.log('MIXPANEL REQUEST:', dataForRequest);
7123
+ logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7106
7124
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
7107
7125
  } catch(err) {
7108
7126
  this.reportError('Error flushing request queue', err);
@@ -7114,7 +7132,7 @@
7114
7132
  * Log error to global logger and optional user-defined logger.
7115
7133
  */
7116
7134
  RequestBatcher.prototype.reportError = function(msg, err) {
7117
- logger$1.error.apply(logger$1.error, arguments);
7135
+ logger$2.error.apply(logger$2.error, arguments);
7118
7136
  if (this.errorReporter) {
7119
7137
  try {
7120
7138
  if (!(err instanceof Error)) {
@@ -7122,12 +7140,12 @@
7122
7140
  }
7123
7141
  this.errorReporter(msg, err);
7124
7142
  } catch(err) {
7125
- logger$1.error(err);
7143
+ logger$2.error(err);
7126
7144
  }
7127
7145
  }
7128
7146
  };
7129
7147
 
7130
- var logger = console_with_prefix('recorder');
7148
+ var logger$1 = console_with_prefix('recorder');
7131
7149
  var CompressionStream = win['CompressionStream'];
7132
7150
 
7133
7151
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -7153,63 +7171,85 @@
7153
7171
  return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
7154
7172
  }
7155
7173
 
7156
- var MixpanelRecorder = function(mixpanelInstance) {
7157
- this._mixpanel = mixpanelInstance;
7174
+ /**
7175
+ * This class encapsulates a single session recording and its lifecycle.
7176
+ * @param {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
7177
+ * @param {String} [options.replayId] - unique uuid for a single replay
7178
+ * @param {Function} [options.onIdleTimeout] - callback when a recording reaches idle timeout
7179
+ * @param {Function} [options.onMaxLengthReached] - callback when a recording reaches its maximum length
7180
+ * @param {Function} [options.rrwebRecord] - rrweb's `record` function
7181
+ */
7182
+ var SessionRecording = function(options) {
7183
+ this._mixpanel = options.mixpanelInstance;
7184
+ this._onIdleTimeout = options.onIdleTimeout;
7185
+ this._onMaxLengthReached = options.onMaxLengthReached;
7186
+ this._rrwebRecord = options.rrwebRecord;
7187
+
7188
+ this.replayId = options.replayId;
7158
7189
 
7159
7190
  // internal rrweb stopRecording function
7160
7191
  this._stopRecording = null;
7161
7192
 
7162
- this.recEvents = [];
7163
7193
  this.seqNo = 0;
7164
- this.replayId = null;
7165
7194
  this.replayStartTime = null;
7166
- this.sendBatchId = null;
7195
+ this.batchStartUrl = null;
7167
7196
 
7168
7197
  this.idleTimeoutId = null;
7169
7198
  this.maxTimeoutId = null;
7170
7199
 
7171
7200
  this.recordMaxMs = MAX_RECORDING_MS;
7172
- this._initBatcher();
7173
- };
7174
-
7201
+ this.recordMinMs = 0;
7175
7202
 
7176
- MixpanelRecorder.prototype._initBatcher = function () {
7177
- this.batcher = new RequestBatcher('__mprec', {
7178
- libConfig: RECORDER_BATCHER_LIB_CONFIG,
7179
- sendRequestFunc: _.bind(this.flushEventsWithOptOut, this),
7203
+ // each replay has its own batcher key to avoid conflicts between rrweb events of different recordings
7204
+ // this will be important when persistence is introduced
7205
+ var batcherKey = '__mprec_' + this.getConfig('token') + '_' + this.replayId;
7206
+ this.batcher = new RequestBatcher(batcherKey, {
7180
7207
  errorReporter: _.bind(this.reportError, this),
7181
7208
  flushOnlyOnInterval: true,
7209
+ libConfig: RECORDER_BATCHER_LIB_CONFIG,
7210
+ sendRequestFunc: _.bind(this.flushEventsWithOptOut, this),
7182
7211
  usePersistence: false
7183
7212
  });
7184
7213
  };
7185
7214
 
7186
- // eslint-disable-next-line camelcase
7187
- MixpanelRecorder.prototype.get_config = function(configVar) {
7215
+ SessionRecording.prototype.getConfig = function(configVar) {
7188
7216
  return this._mixpanel.get_config(configVar);
7189
7217
  };
7190
7218
 
7191
- MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
7219
+ // Alias for getConfig, used by the common addOptOutCheckMixpanelLib function which
7220
+ // reaches into this class instance and expects the snake case version of the function.
7221
+ // eslint-disable-next-line camelcase
7222
+ SessionRecording.prototype.get_config = function(configVar) {
7223
+ return this.getConfig(configVar);
7224
+ };
7225
+
7226
+ SessionRecording.prototype.startRecording = function (shouldStopBatcher) {
7192
7227
  if (this._stopRecording !== null) {
7193
- logger.log('Recording already in progress, skipping startRecording.');
7228
+ logger$1.log('Recording already in progress, skipping startRecording.');
7194
7229
  return;
7195
7230
  }
7196
7231
 
7197
- this.recordMaxMs = this.get_config('record_max_ms');
7232
+ this.recordMaxMs = this.getConfig('record_max_ms');
7198
7233
  if (this.recordMaxMs > MAX_RECORDING_MS) {
7199
7234
  this.recordMaxMs = MAX_RECORDING_MS;
7200
- logger.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7235
+ logger$1.critical('record_max_ms cannot be greater than ' + MAX_RECORDING_MS + 'ms. Capping value.');
7201
7236
  }
7202
7237
 
7203
- this.recEvents = [];
7204
- this.seqNo = 0;
7205
- this.replayStartTime = null;
7238
+ this.recordMinMs = this.getConfig('record_min_ms');
7239
+ if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
7240
+ this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
7241
+ logger$1.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7242
+ }
7206
7243
 
7207
- this.replayId = _.UUID();
7244
+ this.replayStartTime = new Date().getTime();
7245
+ this.batchStartUrl = _.info.currentUrl();
7208
7246
 
7209
- if (shouldStopBatcher) {
7210
- // this is the case when we're starting recording after a reset
7247
+ if (shouldStopBatcher || this.recordMinMs > 0) {
7248
+ // the primary case for shouldStopBatcher is when we're starting recording after a reset
7211
7249
  // and don't want to send anything over the network until there's
7212
7250
  // actual user activity
7251
+ // this also applies if the minimum recording length has not been hit yet
7252
+ // so that we don't send data until we know the recording will be long enough
7213
7253
  this.batcher.stop();
7214
7254
  } else {
7215
7255
  this.batcher.start();
@@ -7217,45 +7257,52 @@
7217
7257
 
7218
7258
  var resetIdleTimeout = _.bind(function () {
7219
7259
  clearTimeout(this.idleTimeoutId);
7220
- this.idleTimeoutId = setTimeout(_.bind(function () {
7221
- logger.log('Idle timeout reached, restarting recording.');
7222
- this.resetRecording();
7223
- }, this), this.get_config('record_idle_timeout_ms'));
7260
+ this.idleTimeoutId = setTimeout(this._onIdleTimeout, this.getConfig('record_idle_timeout_ms'));
7224
7261
  }, this);
7225
7262
 
7226
- this._stopRecording = record({
7263
+ var blockSelector = this.getConfig('record_block_selector');
7264
+ if (blockSelector === '' || blockSelector === null) {
7265
+ blockSelector = undefined;
7266
+ }
7267
+
7268
+ this._stopRecording = this._rrwebRecord({
7227
7269
  'emit': _.bind(function (ev) {
7228
7270
  this.batcher.enqueue(ev);
7229
7271
  if (isUserEvent(ev)) {
7230
- if (this.batcher.stopped) {
7272
+ if (this.batcher.stopped && new Date().getTime() - this.replayStartTime >= this.recordMinMs) {
7231
7273
  // start flushing again after user activity
7232
7274
  this.batcher.start();
7233
7275
  }
7234
7276
  resetIdleTimeout();
7235
7277
  }
7236
7278
  }, this),
7237
- 'blockClass': this.get_config('record_block_class'),
7238
- 'blockSelector': this.get_config('record_block_selector'),
7239
- 'collectFonts': this.get_config('record_collect_fonts'),
7240
- 'inlineImages': this.get_config('record_inline_images'),
7279
+ 'blockClass': this.getConfig('record_block_class'),
7280
+ 'blockSelector': blockSelector,
7281
+ 'collectFonts': this.getConfig('record_collect_fonts'),
7241
7282
  'maskAllInputs': true,
7242
- 'maskTextClass': this.get_config('record_mask_text_class'),
7243
- 'maskTextSelector': this.get_config('record_mask_text_selector')
7283
+ 'maskTextClass': this.getConfig('record_mask_text_class'),
7284
+ 'maskTextSelector': this.getConfig('record_mask_text_selector')
7244
7285
  });
7245
7286
 
7246
- resetIdleTimeout();
7287
+ if (typeof this._stopRecording !== 'function') {
7288
+ this.reportError('rrweb failed to start, skipping this recording.');
7289
+ this._stopRecording = null;
7290
+ this.stopRecording(); // stop batcher looping and any timeouts
7291
+ return;
7292
+ }
7247
7293
 
7248
- this.maxTimeoutId = setTimeout(_.bind(this.resetRecording, this), this.recordMaxMs);
7249
- };
7294
+ resetIdleTimeout();
7250
7295
 
7251
- MixpanelRecorder.prototype.resetRecording = function () {
7252
- this.stopRecording();
7253
- this.startRecording(true);
7296
+ this.maxTimeoutId = setTimeout(_.bind(this._onMaxLengthReached, this), this.recordMaxMs);
7254
7297
  };
7255
7298
 
7256
- MixpanelRecorder.prototype.stopRecording = function () {
7257
- if (this._stopRecording !== null) {
7258
- this._stopRecording();
7299
+ SessionRecording.prototype.stopRecording = function () {
7300
+ if (!this.isRrwebStopped()) {
7301
+ try {
7302
+ this._stopRecording();
7303
+ } catch (err) {
7304
+ this.reportError('Error with rrweb stopRecording', err);
7305
+ }
7259
7306
  this._stopRecording = null;
7260
7307
  }
7261
7308
 
@@ -7267,36 +7314,39 @@
7267
7314
  this.batcher.flush();
7268
7315
  this.batcher.stop();
7269
7316
  }
7270
- this.replayId = null;
7271
7317
 
7272
7318
  clearTimeout(this.idleTimeoutId);
7273
7319
  clearTimeout(this.maxTimeoutId);
7274
7320
  };
7275
7321
 
7322
+ SessionRecording.prototype.isRrwebStopped = function () {
7323
+ return this._stopRecording === null;
7324
+ };
7325
+
7276
7326
  /**
7277
7327
  * Flushes the current batch of events to the server, but passes an opt-out callback to make sure
7278
7328
  * we stop recording and dump any queued events if the user has opted out.
7279
7329
  */
7280
- MixpanelRecorder.prototype.flushEventsWithOptOut = function (data, options, cb) {
7330
+ SessionRecording.prototype.flushEventsWithOptOut = function (data, options, cb) {
7281
7331
  this._flushEvents(data, options, cb, _.bind(this._onOptOut, this));
7282
7332
  };
7283
7333
 
7284
- MixpanelRecorder.prototype._onOptOut = function (code) {
7334
+ SessionRecording.prototype._onOptOut = function (code) {
7285
7335
  // addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
7286
7336
  if (code === 0) {
7287
- this.recEvents = [];
7288
7337
  this.stopRecording();
7289
7338
  }
7290
7339
  };
7291
7340
 
7292
- MixpanelRecorder.prototype._sendRequest = function(reqParams, reqBody, callback) {
7341
+ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
7293
7342
  var onSuccess = _.bind(function (response, responseBody) {
7294
- // Increment sequence counter only if the request was successful to guarantee ordering.
7343
+ // Update batch specific props only if the request was successful to guarantee ordering.
7295
7344
  // RequestBatcher will always flush the next batch after the previous one succeeds.
7296
- if (response.status === 200) {
7345
+ // extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
7346
+ if (response.status === 200 && this.replayId === currentReplayId) {
7297
7347
  this.seqNo++;
7348
+ this.batchStartUrl = _.info.currentUrl();
7298
7349
  }
7299
-
7300
7350
  callback({
7301
7351
  status: 0,
7302
7352
  httpStatusCode: response.status,
@@ -7305,10 +7355,10 @@
7305
7355
  });
7306
7356
  }, this);
7307
7357
 
7308
- win['fetch'](this.get_config('api_host') + '/' + this.get_config('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
7358
+ win['fetch'](this.getConfig('api_host') + '/' + this.getConfig('api_routes')['record'] + '?' + new URLSearchParams(reqParams), {
7309
7359
  'method': 'POST',
7310
7360
  'headers': {
7311
- 'Authorization': 'Basic ' + btoa(this.get_config('token') + ':'),
7361
+ 'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
7312
7362
  'Content-Type': 'application/octet-stream'
7313
7363
  },
7314
7364
  'body': reqBody,
@@ -7319,28 +7369,37 @@
7319
7369
  callback({error: error});
7320
7370
  });
7321
7371
  }).catch(function (error) {
7322
- callback({error: error});
7372
+ callback({error: error, httpStatusCode: 0});
7323
7373
  });
7324
7374
  };
7325
7375
 
7326
- MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7376
+ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7327
7377
  const numEvents = data.length;
7328
7378
 
7329
7379
  if (numEvents > 0) {
7380
+ var replayId = this.replayId;
7330
7381
  // each rrweb event has a timestamp - leverage those to get time properties
7331
7382
  var batchStartTime = data[0].timestamp;
7332
- if (this.seqNo === 0) {
7383
+ if (this.seqNo === 0 || !this.replayStartTime) {
7384
+ // extra safety net so that we don't send a null replay start time
7385
+ if (this.seqNo !== 0) {
7386
+ this.reportError('Replay start time not set but seqNo is not 0. Using current batch start time as a fallback.');
7387
+ }
7388
+
7333
7389
  this.replayStartTime = batchStartTime;
7334
7390
  }
7335
7391
  var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
7336
7392
 
7337
7393
  var reqParams = {
7338
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
7339
- 'seq': this.seqNo,
7394
+ '$current_url': this.batchStartUrl,
7395
+ '$lib_version': Config.LIB_VERSION,
7340
7396
  'batch_start_time': batchStartTime / 1000,
7341
- 'replay_id': this.replayId,
7397
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
7398
+ 'mp_lib': 'web',
7399
+ 'replay_id': replayId,
7342
7400
  'replay_length_ms': replayLengthMs,
7343
- 'replay_start_time': this.replayStartTime / 1000
7401
+ 'replay_start_time': this.replayStartTime / 1000,
7402
+ 'seq': this.seqNo
7344
7403
  };
7345
7404
  var eventsJson = _.JSONEncode(data);
7346
7405
 
@@ -7361,28 +7420,93 @@
7361
7420
  .blob()
7362
7421
  .then(_.bind(function(compressedBlob) {
7363
7422
  reqParams['format'] = 'gzip';
7364
- this._sendRequest(reqParams, compressedBlob, callback);
7423
+ this._sendRequest(replayId, reqParams, compressedBlob, callback);
7365
7424
  }, this));
7366
7425
  } else {
7367
7426
  reqParams['format'] = 'body';
7368
- this._sendRequest(reqParams, eventsJson, callback);
7427
+ this._sendRequest(replayId, reqParams, eventsJson, callback);
7369
7428
  }
7370
7429
  }
7371
7430
  });
7372
7431
 
7373
7432
 
7374
- MixpanelRecorder.prototype.reportError = function(msg, err) {
7375
- logger.error.apply(logger.error, arguments);
7433
+ SessionRecording.prototype.reportError = function(msg, err) {
7434
+ logger$1.error.apply(logger$1.error, arguments);
7376
7435
  try {
7377
7436
  if (!err && !(msg instanceof Error)) {
7378
7437
  msg = new Error(msg);
7379
7438
  }
7380
- this.get_config('error_reporter')(msg, err);
7439
+ this.getConfig('error_reporter')(msg, err);
7381
7440
  } catch(err) {
7382
- logger.error(err);
7441
+ logger$1.error(err);
7442
+ }
7443
+ };
7444
+
7445
+ var logger = console_with_prefix('recorder');
7446
+
7447
+ /**
7448
+ * Recorder API: manages recordings and exposes methods public to the core Mixpanel library.
7449
+ * @param {Object} [options.mixpanelInstance] - reference to the core MixpanelLib
7450
+ */
7451
+ var MixpanelRecorder = function(mixpanelInstance) {
7452
+ this._mixpanel = mixpanelInstance;
7453
+ this.activeRecording = null;
7454
+ };
7455
+
7456
+ MixpanelRecorder.prototype.startRecording = function(shouldStopBatcher) {
7457
+ if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
7458
+ logger.log('Recording already in progress, skipping startRecording.');
7459
+ return;
7460
+ }
7461
+
7462
+ var onIdleTimeout = _.bind(function () {
7463
+ logger.log('Idle timeout reached, restarting recording.');
7464
+ this.resetRecording();
7465
+ }, this);
7466
+
7467
+ var onMaxLengthReached = _.bind(function () {
7468
+ logger.log('Max recording length reached, stopping recording.');
7469
+ this.resetRecording();
7470
+ }, this);
7471
+
7472
+ this.activeRecording = new SessionRecording({
7473
+ mixpanelInstance: this._mixpanel,
7474
+ onIdleTimeout: onIdleTimeout,
7475
+ onMaxLengthReached: onMaxLengthReached,
7476
+ replayId: _.UUID(),
7477
+ rrwebRecord: record
7478
+ });
7479
+
7480
+ this.activeRecording.startRecording(shouldStopBatcher);
7481
+ };
7482
+
7483
+ MixpanelRecorder.prototype.stopRecording = function() {
7484
+ if (this.activeRecording) {
7485
+ this.activeRecording.stopRecording();
7486
+ this.activeRecording = null;
7383
7487
  }
7384
7488
  };
7385
7489
 
7490
+ MixpanelRecorder.prototype.resetRecording = function () {
7491
+ this.stopRecording();
7492
+ this.startRecording(true);
7493
+ };
7494
+
7495
+ MixpanelRecorder.prototype.getActiveReplayId = function () {
7496
+ if (this.activeRecording && !this.activeRecording.isRrwebStopped()) {
7497
+ return this.activeRecording.replayId;
7498
+ } else {
7499
+ return null;
7500
+ }
7501
+ };
7502
+
7503
+ // getter so that older mixpanel-core versions can still retrieve the replay ID
7504
+ // when pulling the latest recorder bundle from the CDN
7505
+ Object.defineProperty(MixpanelRecorder.prototype, 'replayId', {
7506
+ get: function () {
7507
+ return this.getActiveReplayId();
7508
+ }
7509
+ });
7386
7510
 
7387
7511
  win['__mp_recorder'] = MixpanelRecorder;
7388
7512