mixpanel-browser 2.55.1 → 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.1'
4510
+ LIB_VERSION: '2.56.0'
4511
4511
  };
4512
4512
 
4513
4513
  /* eslint camelcase: "off", eqeqeq: "off" */
@@ -6544,7 +6544,7 @@ function _addOptOutCheck(method, getConfigValue) {
6544
6544
  };
6545
6545
  }
6546
6546
 
6547
- var logger$3 = console_with_prefix('lock');
6547
+ var logger$4 = console_with_prefix('lock');
6548
6548
 
6549
6549
  /**
6550
6550
  * SharedLock: a mutex built on HTML5 localStorage, to ensure that only one browser
@@ -6601,7 +6601,7 @@ SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) {
6601
6601
 
6602
6602
  var delay = function(cb) {
6603
6603
  if (new Date().getTime() - startTime > timeoutMS) {
6604
- 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 + ']');
6605
6605
  storage.removeItem(keyZ);
6606
6606
  storage.removeItem(keyY);
6607
6607
  loop();
@@ -6690,7 +6690,7 @@ SharedLock.prototype.withLock = function(lockedCB, errorCB, pid) {
6690
6690
  }
6691
6691
  };
6692
6692
 
6693
- var logger$2 = console_with_prefix('batch');
6693
+ var logger$3 = console_with_prefix('batch');
6694
6694
 
6695
6695
  /**
6696
6696
  * RequestQueue: queue for batching API requests with localStorage backup for retries.
@@ -6711,11 +6711,13 @@ var logger$2 = console_with_prefix('batch');
6711
6711
  var RequestQueue = function(storageKey, options) {
6712
6712
  options = options || {};
6713
6713
  this.storageKey = storageKey;
6714
- this.storage = options.storage || window.localStorage;
6715
- this.reportError = options.errorReporter || _.bind(logger$2.error, logger$2);
6716
- this.lock = new SharedLock(storageKey, {storage: this.storage});
6717
-
6718
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
+
6719
6721
  this.pid = options.pid || null; // pass pid to test out storage lock contention scenarios
6720
6722
 
6721
6723
  this.memQueue = [];
@@ -6994,7 +6996,7 @@ RequestQueue.prototype.clear = function() {
6994
6996
  // maximum interval between request retries after exponential backoff
6995
6997
  var MAX_RETRY_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes
6996
6998
 
6997
- var logger$1 = console_with_prefix('batch');
6999
+ var logger$2 = console_with_prefix('batch');
6998
7000
 
6999
7001
  /**
7000
7002
  * RequestBatcher: manages the queueing, flushing, retry etc of requests of one
@@ -7108,7 +7110,7 @@ RequestBatcher.prototype.flush = function(options) {
7108
7110
  try {
7109
7111
 
7110
7112
  if (this.requestInProgress) {
7111
- logger$1.log('Flush: Request already in progress');
7113
+ logger$2.log('Flush: Request already in progress');
7112
7114
  return;
7113
7115
  }
7114
7116
 
@@ -7276,7 +7278,7 @@ RequestBatcher.prototype.flush = function(options) {
7276
7278
  if (options.unloading) {
7277
7279
  requestOptions.transport = 'sendBeacon';
7278
7280
  }
7279
- logger$1.log('MIXPANEL REQUEST:', dataForRequest);
7281
+ logger$2.log('MIXPANEL REQUEST:', dataForRequest);
7280
7282
  this.sendRequest(dataForRequest, requestOptions, batchSendCallback);
7281
7283
  } catch(err) {
7282
7284
  this.reportError('Error flushing request queue', err);
@@ -7288,7 +7290,7 @@ RequestBatcher.prototype.flush = function(options) {
7288
7290
  * Log error to global logger and optional user-defined logger.
7289
7291
  */
7290
7292
  RequestBatcher.prototype.reportError = function(msg, err) {
7291
- logger$1.error.apply(logger$1.error, arguments);
7293
+ logger$2.error.apply(logger$2.error, arguments);
7292
7294
  if (this.errorReporter) {
7293
7295
  try {
7294
7296
  if (!(err instanceof Error)) {
@@ -7296,12 +7298,12 @@ RequestBatcher.prototype.reportError = function(msg, err) {
7296
7298
  }
7297
7299
  this.errorReporter(msg, err);
7298
7300
  } catch(err) {
7299
- logger$1.error(err);
7301
+ logger$2.error(err);
7300
7302
  }
7301
7303
  }
7302
7304
  };
7303
7305
 
7304
- var logger = console_with_prefix('recorder');
7306
+ var logger$1 = console_with_prefix('recorder');
7305
7307
  var CompressionStream = win['CompressionStream'];
7306
7308
 
7307
7309
  var RECORDER_BATCHER_LIB_CONFIG = {
@@ -7327,65 +7329,78 @@ function isUserEvent(ev) {
7327
7329
  return ev.type === EventType.IncrementalSnapshot && ACTIVE_SOURCES.has(ev.data.source);
7328
7330
  }
7329
7331
 
7330
- var MixpanelRecorder = function(mixpanelInstance) {
7331
- 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;
7332
7347
 
7333
7348
  // internal rrweb stopRecording function
7334
7349
  this._stopRecording = null;
7335
7350
 
7336
- this.recEvents = [];
7337
7351
  this.seqNo = 0;
7338
- this.replayId = null;
7339
7352
  this.replayStartTime = null;
7340
- this.sendBatchId = null;
7353
+ this.batchStartUrl = null;
7341
7354
 
7342
7355
  this.idleTimeoutId = null;
7343
7356
  this.maxTimeoutId = null;
7344
7357
 
7345
7358
  this.recordMaxMs = MAX_RECORDING_MS;
7346
7359
  this.recordMinMs = 0;
7347
- this._initBatcher();
7348
- };
7349
7360
 
7350
-
7351
- MixpanelRecorder.prototype._initBatcher = function () {
7352
- this.batcher = new RequestBatcher('__mprec', {
7353
- libConfig: RECORDER_BATCHER_LIB_CONFIG,
7354
- 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, {
7355
7365
  errorReporter: _.bind(this.reportError, this),
7356
7366
  flushOnlyOnInterval: true,
7367
+ libConfig: RECORDER_BATCHER_LIB_CONFIG,
7368
+ sendRequestFunc: _.bind(this.flushEventsWithOptOut, this),
7357
7369
  usePersistence: false
7358
7370
  });
7359
7371
  };
7360
7372
 
7361
- // eslint-disable-next-line camelcase
7362
- MixpanelRecorder.prototype.get_config = function(configVar) {
7373
+ SessionRecording.prototype.getConfig = function(configVar) {
7363
7374
  return this._mixpanel.get_config(configVar);
7364
7375
  };
7365
7376
 
7366
- 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) {
7367
7385
  if (this._stopRecording !== null) {
7368
- logger.log('Recording already in progress, skipping startRecording.');
7386
+ logger$1.log('Recording already in progress, skipping startRecording.');
7369
7387
  return;
7370
7388
  }
7371
7389
 
7372
- this.recordMaxMs = this.get_config('record_max_ms');
7390
+ this.recordMaxMs = this.getConfig('record_max_ms');
7373
7391
  if (this.recordMaxMs > MAX_RECORDING_MS) {
7374
7392
  this.recordMaxMs = MAX_RECORDING_MS;
7375
- 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.');
7376
7394
  }
7377
7395
 
7378
- this.recordMinMs = this.get_config('record_min_ms');
7396
+ this.recordMinMs = this.getConfig('record_min_ms');
7379
7397
  if (this.recordMinMs > MAX_VALUE_FOR_MIN_RECORDING_MS) {
7380
7398
  this.recordMinMs = MAX_VALUE_FOR_MIN_RECORDING_MS;
7381
- logger.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7399
+ logger$1.critical('record_min_ms cannot be greater than ' + MAX_VALUE_FOR_MIN_RECORDING_MS + 'ms. Capping value.');
7382
7400
  }
7383
7401
 
7384
- this.recEvents = [];
7385
- this.seqNo = 0;
7386
7402
  this.replayStartTime = new Date().getTime();
7387
-
7388
- this.replayId = _.UUID();
7403
+ this.batchStartUrl = _.info.currentUrl();
7389
7404
 
7390
7405
  if (shouldStopBatcher || this.recordMinMs > 0) {
7391
7406
  // the primary case for shouldStopBatcher is when we're starting recording after a reset
@@ -7400,18 +7415,15 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
7400
7415
 
7401
7416
  var resetIdleTimeout = _.bind(function () {
7402
7417
  clearTimeout(this.idleTimeoutId);
7403
- this.idleTimeoutId = setTimeout(_.bind(function () {
7404
- logger.log('Idle timeout reached, restarting recording.');
7405
- this.resetRecording();
7406
- }, this), this.get_config('record_idle_timeout_ms'));
7418
+ this.idleTimeoutId = setTimeout(this._onIdleTimeout, this.getConfig('record_idle_timeout_ms'));
7407
7419
  }, this);
7408
7420
 
7409
- var blockSelector = this.get_config('record_block_selector');
7421
+ var blockSelector = this.getConfig('record_block_selector');
7410
7422
  if (blockSelector === '' || blockSelector === null) {
7411
7423
  blockSelector = undefined;
7412
7424
  }
7413
7425
 
7414
- this._stopRecording = record({
7426
+ this._stopRecording = this._rrwebRecord({
7415
7427
  'emit': _.bind(function (ev) {
7416
7428
  this.batcher.enqueue(ev);
7417
7429
  if (isUserEvent(ev)) {
@@ -7422,28 +7434,33 @@ MixpanelRecorder.prototype.startRecording = function (shouldStopBatcher) {
7422
7434
  resetIdleTimeout();
7423
7435
  }
7424
7436
  }, this),
7425
- 'blockClass': this.get_config('record_block_class'),
7437
+ 'blockClass': this.getConfig('record_block_class'),
7426
7438
  'blockSelector': blockSelector,
7427
- 'collectFonts': this.get_config('record_collect_fonts'),
7428
- 'inlineImages': this.get_config('record_inline_images'),
7439
+ 'collectFonts': this.getConfig('record_collect_fonts'),
7429
7440
  'maskAllInputs': true,
7430
- 'maskTextClass': this.get_config('record_mask_text_class'),
7431
- 'maskTextSelector': this.get_config('record_mask_text_selector')
7441
+ 'maskTextClass': this.getConfig('record_mask_text_class'),
7442
+ 'maskTextSelector': this.getConfig('record_mask_text_selector')
7432
7443
  });
7433
7444
 
7434
- 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
+ }
7435
7451
 
7436
- this.maxTimeoutId = setTimeout(_.bind(this.resetRecording, this), this.recordMaxMs);
7437
- };
7452
+ resetIdleTimeout();
7438
7453
 
7439
- MixpanelRecorder.prototype.resetRecording = function () {
7440
- this.stopRecording();
7441
- this.startRecording(true);
7454
+ this.maxTimeoutId = setTimeout(_.bind(this._onMaxLengthReached, this), this.recordMaxMs);
7442
7455
  };
7443
7456
 
7444
- MixpanelRecorder.prototype.stopRecording = function () {
7445
- if (this._stopRecording !== null) {
7446
- 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
+ }
7447
7464
  this._stopRecording = null;
7448
7465
  }
7449
7466
 
@@ -7455,35 +7472,38 @@ MixpanelRecorder.prototype.stopRecording = function () {
7455
7472
  this.batcher.flush();
7456
7473
  this.batcher.stop();
7457
7474
  }
7458
- this.replayId = null;
7459
7475
 
7460
7476
  clearTimeout(this.idleTimeoutId);
7461
7477
  clearTimeout(this.maxTimeoutId);
7462
7478
  };
7463
7479
 
7480
+ SessionRecording.prototype.isRrwebStopped = function () {
7481
+ return this._stopRecording === null;
7482
+ };
7483
+
7464
7484
  /**
7465
7485
  * Flushes the current batch of events to the server, but passes an opt-out callback to make sure
7466
7486
  * we stop recording and dump any queued events if the user has opted out.
7467
7487
  */
7468
- MixpanelRecorder.prototype.flushEventsWithOptOut = function (data, options, cb) {
7488
+ SessionRecording.prototype.flushEventsWithOptOut = function (data, options, cb) {
7469
7489
  this._flushEvents(data, options, cb, _.bind(this._onOptOut, this));
7470
7490
  };
7471
7491
 
7472
- MixpanelRecorder.prototype._onOptOut = function (code) {
7492
+ SessionRecording.prototype._onOptOut = function (code) {
7473
7493
  // addOptOutCheckMixpanelLib invokes this function with code=0 when the user has opted out
7474
7494
  if (code === 0) {
7475
- this.recEvents = [];
7476
7495
  this.stopRecording();
7477
7496
  }
7478
7497
  };
7479
7498
 
7480
- MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
7499
+ SessionRecording.prototype._sendRequest = function(currentReplayId, reqParams, reqBody, callback) {
7481
7500
  var onSuccess = _.bind(function (response, responseBody) {
7482
- // 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.
7483
7502
  // RequestBatcher will always flush the next batch after the previous one succeeds.
7484
7503
  // extra check to see if the replay ID has changed so that we don't increment the seqNo on the wrong replay
7485
7504
  if (response.status === 200 && this.replayId === currentReplayId) {
7486
7505
  this.seqNo++;
7506
+ this.batchStartUrl = _.info.currentUrl();
7487
7507
  }
7488
7508
  callback({
7489
7509
  status: 0,
@@ -7493,10 +7513,10 @@ MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, r
7493
7513
  });
7494
7514
  }, this);
7495
7515
 
7496
- 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), {
7497
7517
  'method': 'POST',
7498
7518
  'headers': {
7499
- 'Authorization': 'Basic ' + btoa(this.get_config('token') + ':'),
7519
+ 'Authorization': 'Basic ' + btoa(this.getConfig('token') + ':'),
7500
7520
  'Content-Type': 'application/octet-stream'
7501
7521
  },
7502
7522
  'body': reqBody,
@@ -7511,7 +7531,7 @@ MixpanelRecorder.prototype._sendRequest = function(currentReplayId, reqParams, r
7511
7531
  });
7512
7532
  };
7513
7533
 
7514
- MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7534
+ SessionRecording.prototype._flushEvents = addOptOutCheckMixpanelLib(function (data, options, callback) {
7515
7535
  const numEvents = data.length;
7516
7536
 
7517
7537
  if (numEvents > 0) {
@@ -7529,12 +7549,15 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
7529
7549
  var replayLengthMs = data[numEvents - 1].timestamp - this.replayStartTime;
7530
7550
 
7531
7551
  var reqParams = {
7532
- 'distinct_id': String(this._mixpanel.get_distinct_id()),
7533
- 'seq': this.seqNo,
7552
+ '$current_url': this.batchStartUrl,
7553
+ '$lib_version': Config.LIB_VERSION,
7534
7554
  'batch_start_time': batchStartTime / 1000,
7555
+ 'distinct_id': String(this._mixpanel.get_distinct_id()),
7556
+ 'mp_lib': 'web',
7535
7557
  'replay_id': replayId,
7536
7558
  'replay_length_ms': replayLengthMs,
7537
- 'replay_start_time': this.replayStartTime / 1000
7559
+ 'replay_start_time': this.replayStartTime / 1000,
7560
+ 'seq': this.seqNo
7538
7561
  };
7539
7562
  var eventsJson = _.JSONEncode(data);
7540
7563
 
@@ -7565,18 +7588,83 @@ MixpanelRecorder.prototype._flushEvents = addOptOutCheckMixpanelLib(function (da
7565
7588
  });
7566
7589
 
7567
7590
 
7568
- MixpanelRecorder.prototype.reportError = function(msg, err) {
7569
- logger.error.apply(logger.error, arguments);
7591
+ SessionRecording.prototype.reportError = function(msg, err) {
7592
+ logger$1.error.apply(logger$1.error, arguments);
7570
7593
  try {
7571
7594
  if (!err && !(msg instanceof Error)) {
7572
7595
  msg = new Error(msg);
7573
7596
  }
7574
- this.get_config('error_reporter')(msg, err);
7597
+ this.getConfig('error_reporter')(msg, err);
7575
7598
  } catch(err) {
7576
- logger.error(err);
7599
+ logger$1.error(err);
7600
+ }
7601
+ };
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;
7577
7645
  }
7578
7646
  };
7579
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
+ });
7580
7668
 
7581
7669
  win['__mp_recorder'] = MixpanelRecorder;
7582
7670
 
@@ -9043,7 +9131,6 @@ var DEFAULT_CONFIG = {
9043
9131
  'record_block_selector': 'img, video',
9044
9132
  'record_collect_fonts': false,
9045
9133
  'record_idle_timeout_ms': 30 * 60 * 1000, // 30 minutes
9046
- 'record_inline_images': false,
9047
9134
  'record_mask_text_class': new RegExp('^(mp-mask|fs-mask|amp-mask|rr-mask|ph-mask)$'),
9048
9135
  'record_mask_text_selector': '*',
9049
9136
  'record_max_ms': MAX_RECORDING_MS,
@@ -9296,15 +9383,35 @@ MixpanelLib.prototype.stop_session_recording = function () {
9296
9383
 
9297
9384
  MixpanelLib.prototype.get_session_recording_properties = function () {
9298
9385
  var props = {};
9299
- if (this._recorder) {
9300
- var replay_id = this._recorder['replayId'];
9301
- if (replay_id) {
9302
- props['$mp_replay_id'] = replay_id;
9303
- }
9386
+ var replay_id = this._get_session_replay_id();
9387
+ if (replay_id) {
9388
+ props['$mp_replay_id'] = replay_id;
9304
9389
  }
9305
9390
  return props;
9306
9391
  };
9307
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
+
9308
9415
  // Private methods
9309
9416
 
9310
9417
  MixpanelLib.prototype._loaded = function() {
@@ -11031,6 +11138,7 @@ MixpanelLib.prototype['stop_batch_senders'] = MixpanelLib.protot
11031
11138
  MixpanelLib.prototype['start_session_recording'] = MixpanelLib.prototype.start_session_recording;
11032
11139
  MixpanelLib.prototype['stop_session_recording'] = MixpanelLib.prototype.stop_session_recording;
11033
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;
11034
11142
  MixpanelLib.prototype['DEFAULT_API_ROUTES'] = DEFAULT_API_ROUTES;
11035
11143
 
11036
11144
  // MixpanelPersistence Exports