@sentry/replay 7.60.1 → 7.61.1

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.
package/esm/index.js CHANGED
@@ -3402,17 +3402,17 @@ function handleClick(clickDetector, clickBreadcrumb, node) {
3402
3402
  /** A click detector class that can be used to detect slow or rage clicks on elements. */
3403
3403
  class ClickDetector {
3404
3404
  // protected for testing
3405
- __init() {this._lastMutation = 0;}
3406
- __init2() {this._lastScroll = 0;}
3407
-
3408
- __init3() {this._clicks = [];}
3409
3405
 
3410
3406
  constructor(
3411
3407
  replay,
3412
3408
  slowClickConfig,
3413
3409
  // Just for easier testing
3414
3410
  _addBreadcrumbEvent = addBreadcrumbEvent,
3415
- ) {ClickDetector.prototype.__init.call(this);ClickDetector.prototype.__init2.call(this);ClickDetector.prototype.__init3.call(this);
3411
+ ) {
3412
+ this._lastMutation = 0;
3413
+ this._lastScroll = 0;
3414
+ this._clicks = [];
3415
+
3416
3416
  // We want everything in s, but options are in ms
3417
3417
  this._timeout = slowClickConfig.timeout / 1000;
3418
3418
  this._threshold = slowClickConfig.threshold / 1000;
@@ -4014,6 +4014,32 @@ function t(t){let e=t.length;for(;--e>=0;)t[e]=0}const e=new Uint8Array([0,0,0,0
4014
4014
 
4015
4015
  function e(){const e=new Blob([r]);return URL.createObjectURL(e)}
4016
4016
 
4017
+ /**
4018
+ * Log a message in debug mode, and add a breadcrumb when _experiment.traceInternals is enabled.
4019
+ */
4020
+ function logInfo(message, shouldAddBreadcrumb) {
4021
+ if (!(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
4022
+ return;
4023
+ }
4024
+
4025
+ logger.info(message);
4026
+
4027
+ if (shouldAddBreadcrumb) {
4028
+ const hub = getCurrentHub();
4029
+ hub.addBreadcrumb(
4030
+ {
4031
+ category: 'console',
4032
+ data: {
4033
+ logger: 'replay',
4034
+ },
4035
+ level: 'info',
4036
+ message,
4037
+ },
4038
+ { level: 'info' },
4039
+ );
4040
+ }
4041
+ }
4042
+
4017
4043
  /** This error indicates that the event buffer size exceeded the limit.. */
4018
4044
  class EventBufferSizeExceededError extends Error {
4019
4045
  constructor() {
@@ -4028,10 +4054,9 @@ class EventBufferSizeExceededError extends Error {
4028
4054
  class EventBufferArray {
4029
4055
  /** All the events that are buffered to be sent. */
4030
4056
 
4031
- __init() {this._totalSize = 0;}
4032
-
4033
- constructor() {EventBufferArray.prototype.__init.call(this);
4057
+ constructor() {
4034
4058
  this.events = [];
4059
+ this._totalSize = 0;
4035
4060
  }
4036
4061
 
4037
4062
  /** @inheritdoc */
@@ -4140,7 +4165,7 @@ class WorkerHandler {
4140
4165
  * Destroy the worker.
4141
4166
  */
4142
4167
  destroy() {
4143
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Destroying compression worker');
4168
+ logInfo('[Replay] Destroying compression worker');
4144
4169
  this._worker.terminate();
4145
4170
  }
4146
4171
 
@@ -4196,11 +4221,10 @@ class WorkerHandler {
4196
4221
  */
4197
4222
  class EventBufferCompressionWorker {
4198
4223
 
4199
- __init() {this._totalSize = 0;}
4200
-
4201
- constructor(worker) {EventBufferCompressionWorker.prototype.__init.call(this);
4224
+ constructor(worker) {
4202
4225
  this._worker = new WorkerHandler(worker);
4203
4226
  this._earliestTimestamp = null;
4227
+ this._totalSize = 0;
4204
4228
  }
4205
4229
 
4206
4230
  /** @inheritdoc */
@@ -4359,7 +4383,7 @@ class EventBufferProxy {
4359
4383
  } catch (error) {
4360
4384
  // If the worker fails to load, we fall back to the simple buffer.
4361
4385
  // Nothing more to do from our side here
4362
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Failed to load the compression worker, falling back to simple buffer');
4386
+ logInfo('[Replay] Failed to load the compression worker, falling back to simple buffer');
4363
4387
  return;
4364
4388
  }
4365
4389
 
@@ -4398,16 +4422,16 @@ function createEventBuffer({ useCompression }) {
4398
4422
  try {
4399
4423
  const workerUrl = e();
4400
4424
 
4401
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using compression worker');
4425
+ logInfo('[Replay] Using compression worker');
4402
4426
  const worker = new Worker(workerUrl);
4403
4427
  return new EventBufferProxy(worker);
4404
4428
  } catch (error) {
4405
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Failed to create compression worker');
4429
+ logInfo('[Replay] Failed to create compression worker');
4406
4430
  // Fall back to use simple event buffer array
4407
4431
  }
4408
4432
  }
4409
4433
 
4410
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using simple buffer');
4434
+ logInfo('[Replay] Using simple buffer');
4411
4435
  return new EventBufferArray();
4412
4436
  }
4413
4437
 
@@ -4520,6 +4544,7 @@ function makeSession(session) {
4520
4544
  const lastActivity = session.lastActivity || now;
4521
4545
  const segmentId = session.segmentId || 0;
4522
4546
  const sampled = session.sampled;
4547
+ const shouldRefresh = typeof session.shouldRefresh === 'boolean' ? session.shouldRefresh : true;
4523
4548
 
4524
4549
  return {
4525
4550
  id,
@@ -4527,7 +4552,7 @@ function makeSession(session) {
4527
4552
  lastActivity,
4528
4553
  segmentId,
4529
4554
  sampled,
4530
- shouldRefresh: true,
4555
+ shouldRefresh,
4531
4556
  };
4532
4557
  }
4533
4558
 
@@ -4549,8 +4574,6 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4549
4574
  sampled,
4550
4575
  });
4551
4576
 
4552
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Replay] Creating new session: ${session.id}`);
4553
-
4554
4577
  if (stickySession) {
4555
4578
  saveSession(session);
4556
4579
  }
@@ -4561,7 +4584,7 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4561
4584
  /**
4562
4585
  * Fetches a session from storage
4563
4586
  */
4564
- function fetchSession() {
4587
+ function fetchSession(traceInternals) {
4565
4588
  if (!hasSessionStorage()) {
4566
4589
  return null;
4567
4590
  }
@@ -4576,6 +4599,8 @@ function fetchSession() {
4576
4599
 
4577
4600
  const sessionObj = JSON.parse(sessionStringFromStorage) ;
4578
4601
 
4602
+ logInfo('[Replay] Loading existing session', traceInternals);
4603
+
4579
4604
  return makeSession(sessionObj);
4580
4605
  } catch (e) {
4581
4606
  return null;
@@ -4591,9 +4616,10 @@ function getSession({
4591
4616
  stickySession,
4592
4617
  sessionSampleRate,
4593
4618
  allowBuffering,
4619
+ traceInternals,
4594
4620
  }) {
4595
4621
  // If session exists and is passed, use it instead of always hitting session storage
4596
- const session = currentSession || (stickySession && fetchSession());
4622
+ const session = currentSession || (stickySession && fetchSession(traceInternals));
4597
4623
 
4598
4624
  if (session) {
4599
4625
  // If there is a session, check if it is valid (e.g. "last activity" time
@@ -4609,9 +4635,10 @@ function getSession({
4609
4635
  // and when this session is expired, it will not be renewed until user
4610
4636
  // reloads.
4611
4637
  const discardedSession = makeSession({ sampled: false });
4638
+ logInfo('[Replay] Session should not be refreshed', traceInternals);
4612
4639
  return { type: 'new', session: discardedSession };
4613
4640
  } else {
4614
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Session has expired');
4641
+ logInfo('[Replay] Session has expired', traceInternals);
4615
4642
  }
4616
4643
  // Otherwise continue to create a new session
4617
4644
  }
@@ -4621,6 +4648,7 @@ function getSession({
4621
4648
  sessionSampleRate,
4622
4649
  allowBuffering,
4623
4650
  });
4651
+ logInfo('[Replay] Created new session', traceInternals);
4624
4652
 
4625
4653
  return { type: 'new', session: newSession };
4626
4654
  }
@@ -4727,7 +4755,7 @@ function handleAfterSendEvent(replay) {
4727
4755
  const enforceStatusCode = isBaseTransportSend();
4728
4756
 
4729
4757
  return (event, sendResponse) => {
4730
- if (!isErrorEvent(event) && !isTransactionEvent(event)) {
4758
+ if (!replay.isEnabled() || (!isErrorEvent(event) && !isTransactionEvent(event))) {
4731
4759
  return;
4732
4760
  }
4733
4761
 
@@ -4740,36 +4768,47 @@ function handleAfterSendEvent(replay) {
4740
4768
  return;
4741
4769
  }
4742
4770
 
4743
- // Collect traceIds in _context regardless of `recordingMode`
4744
- // In error mode, _context gets cleared on every checkout
4745
- if (isTransactionEvent(event) && event.contexts && event.contexts.trace && event.contexts.trace.trace_id) {
4746
- replay.getContext().traceIds.add(event.contexts.trace.trace_id );
4771
+ if (isTransactionEvent(event)) {
4772
+ handleTransactionEvent(replay, event);
4747
4773
  return;
4748
4774
  }
4749
4775
 
4750
- // Everything below is just for error events
4751
- if (!isErrorEvent(event)) {
4752
- return;
4753
- }
4776
+ handleErrorEvent(replay, event);
4777
+ };
4778
+ }
4754
4779
 
4755
- // Add error to list of errorIds of replay. This is ok to do even if not
4756
- // sampled because context will get reset at next checkout.
4757
- // XXX: There is also a race condition where it's possible to capture an
4758
- // error to Sentry before Replay SDK has loaded, but response returns after
4759
- // it was loaded, and this gets called.
4760
- if (event.event_id) {
4761
- replay.getContext().errorIds.add(event.event_id);
4762
- }
4780
+ function handleTransactionEvent(replay, event) {
4781
+ const replayContext = replay.getContext();
4763
4782
 
4764
- // If error event is tagged with replay id it means it was sampled (when in buffer mode)
4765
- // Need to be very careful that this does not cause an infinite loop
4766
- if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
4767
- setTimeout(() => {
4768
- // Capture current event buffer as new replay
4769
- void replay.sendBufferedReplayOrFlush();
4770
- });
4771
- }
4772
- };
4783
+ // Collect traceIds in _context regardless of `recordingMode`
4784
+ // In error mode, _context gets cleared on every checkout
4785
+ // We limit to max. 100 transactions linked
4786
+ if (event.contexts && event.contexts.trace && event.contexts.trace.trace_id && replayContext.traceIds.size < 100) {
4787
+ replayContext.traceIds.add(event.contexts.trace.trace_id );
4788
+ }
4789
+ }
4790
+
4791
+ function handleErrorEvent(replay, event) {
4792
+ const replayContext = replay.getContext();
4793
+
4794
+ // Add error to list of errorIds of replay. This is ok to do even if not
4795
+ // sampled because context will get reset at next checkout.
4796
+ // XXX: There is also a race condition where it's possible to capture an
4797
+ // error to Sentry before Replay SDK has loaded, but response returns after
4798
+ // it was loaded, and this gets called.
4799
+ // We limit to max. 100 errors linked
4800
+ if (event.event_id && replayContext.errorIds.size < 100) {
4801
+ replayContext.errorIds.add(event.event_id);
4802
+ }
4803
+
4804
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
4805
+ // Need to be very careful that this does not cause an infinite loop
4806
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
4807
+ setTimeout(() => {
4808
+ // Capture current event buffer as new replay
4809
+ void replay.sendBufferedReplayOrFlush();
4810
+ });
4811
+ }
4773
4812
  }
4774
4813
 
4775
4814
  function isBaseTransportSend() {
@@ -4845,6 +4884,11 @@ function handleGlobalEventListener(
4845
4884
  const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined;
4846
4885
 
4847
4886
  return (event, hint) => {
4887
+ // Do nothing if replay has been disabled
4888
+ if (!replay.isEnabled()) {
4889
+ return event;
4890
+ }
4891
+
4848
4892
  if (isReplayEvent(event)) {
4849
4893
  // Replays have separate set of breadcrumbs, do not include breadcrumbs
4850
4894
  // from core SDK
@@ -6626,10 +6670,10 @@ function getHandleRecordingEmit(replay) {
6626
6670
  if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
6627
6671
  const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
6628
6672
  if (earliestEvent) {
6629
- // eslint-disable-next-line no-console
6630
- const log = replay.getOptions()._experiments.traceInternals ? console.info : logger.info;
6631
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
6632
- log(`[Replay] Updating session start time to earliest event in buffer at ${earliestEvent}`);
6673
+ logInfo(
6674
+ `[Replay] Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`,
6675
+ replay.getOptions()._experiments.traceInternals,
6676
+ );
6633
6677
 
6634
6678
  replay.session.started = earliestEvent;
6635
6679
 
@@ -6843,7 +6887,7 @@ async function sendReplayRequest({
6843
6887
  if (!replayEvent) {
6844
6888
  // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
6845
6889
  client.recordDroppedEvent('event_processor', 'replay', baseEvent);
6846
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('An event processor returned `null`, will not send event.');
6890
+ logInfo('An event processor returned `null`, will not send event.');
6847
6891
  return;
6848
6892
  }
6849
6893
 
@@ -7056,12 +7100,10 @@ function throttle(
7056
7100
  * The main replay container class, which holds all the state and methods for recording and sending replays.
7057
7101
  */
7058
7102
  class ReplayContainer {
7059
- __init() {this.eventBuffer = null;}
7060
7103
 
7061
7104
  /**
7062
7105
  * List of PerformanceEntry from PerformanceObserver
7063
7106
  */
7064
- __init2() {this.performanceEvents = [];}
7065
7107
 
7066
7108
  /**
7067
7109
  * Recording can happen in one of three modes:
@@ -7070,7 +7112,6 @@ class ReplayContainer {
7070
7112
  * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
7071
7113
  * - or calling `flush()` to send the replay
7072
7114
  */
7073
- __init3() {this.recordingMode = 'session';}
7074
7115
 
7075
7116
  /**
7076
7117
  * The current or last active transcation.
@@ -7081,62 +7122,60 @@ class ReplayContainer {
7081
7122
  * These are here so we can overwrite them in tests etc.
7082
7123
  * @hidden
7083
7124
  */
7084
- __init4() {this.timeouts = {
7085
- sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7086
- sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7087
- maxSessionLife: MAX_SESSION_LIFE,
7088
- }; }
7089
7125
 
7090
7126
  /**
7091
7127
  * Options to pass to `rrweb.record()`
7092
7128
  */
7093
7129
 
7094
- __init5() {this._performanceObserver = null;}
7095
-
7096
- __init6() {this._flushLock = null;}
7097
-
7098
7130
  /**
7099
7131
  * Timestamp of the last user activity. This lives across sessions.
7100
7132
  */
7101
- __init7() {this._lastActivity = Date.now();}
7102
7133
 
7103
7134
  /**
7104
7135
  * Is the integration currently active?
7105
7136
  */
7106
- __init8() {this._isEnabled = false;}
7107
7137
 
7108
7138
  /**
7109
7139
  * Paused is a state where:
7110
7140
  * - DOM Recording is not listening at all
7111
7141
  * - Nothing will be added to event buffer (e.g. core SDK events)
7112
7142
  */
7113
- __init9() {this._isPaused = false;}
7114
7143
 
7115
7144
  /**
7116
7145
  * Have we attached listeners to the core SDK?
7117
7146
  * Note we have to track this as there is no way to remove instrumentation handlers.
7118
7147
  */
7119
- __init10() {this._hasInitializedCoreListeners = false;}
7120
7148
 
7121
7149
  /**
7122
7150
  * Function to stop recording
7123
7151
  */
7124
- __init11() {this._stopRecording = null;}
7125
-
7126
- __init12() {this._context = {
7127
- errorIds: new Set(),
7128
- traceIds: new Set(),
7129
- urls: [],
7130
- initialTimestamp: Date.now(),
7131
- initialUrl: '',
7132
- };}
7133
7152
 
7134
7153
  constructor({
7135
7154
  options,
7136
7155
  recordingOptions,
7137
7156
  }
7138
7157
 
7139
- ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);ReplayContainer.prototype.__init7.call(this);ReplayContainer.prototype.__init8.call(this);ReplayContainer.prototype.__init9.call(this);ReplayContainer.prototype.__init10.call(this);ReplayContainer.prototype.__init11.call(this);ReplayContainer.prototype.__init12.call(this);ReplayContainer.prototype.__init13.call(this);ReplayContainer.prototype.__init14.call(this);ReplayContainer.prototype.__init15.call(this);ReplayContainer.prototype.__init16.call(this);ReplayContainer.prototype.__init17.call(this);ReplayContainer.prototype.__init18.call(this);
7158
+ ) {ReplayContainer.prototype.__init.call(this);ReplayContainer.prototype.__init2.call(this);ReplayContainer.prototype.__init3.call(this);ReplayContainer.prototype.__init4.call(this);ReplayContainer.prototype.__init5.call(this);ReplayContainer.prototype.__init6.call(this);
7159
+ this.eventBuffer = null;
7160
+ this.performanceEvents = [];
7161
+ this.recordingMode = 'session';
7162
+ this.timeouts = {
7163
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7164
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7165
+ maxSessionLife: MAX_SESSION_LIFE,
7166
+ } ;
7167
+ this._lastActivity = Date.now();
7168
+ this._isEnabled = false;
7169
+ this._isPaused = false;
7170
+ this._hasInitializedCoreListeners = false;
7171
+ this._context = {
7172
+ errorIds: new Set(),
7173
+ traceIds: new Set(),
7174
+ urls: [],
7175
+ initialTimestamp: Date.now(),
7176
+ initialUrl: '',
7177
+ };
7178
+
7140
7179
  this._recordingOptions = recordingOptions;
7141
7180
  this._options = options;
7142
7181
 
@@ -7225,6 +7264,8 @@ class ReplayContainer {
7225
7264
  this.recordingMode = 'buffer';
7226
7265
  }
7227
7266
 
7267
+ logInfo(`[Replay] Starting replay in ${this.recordingMode} mode`, this._options._experiments.traceInternals);
7268
+
7228
7269
  this._initializeRecording();
7229
7270
  }
7230
7271
 
@@ -7244,6 +7285,8 @@ class ReplayContainer {
7244
7285
  throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
7245
7286
  }
7246
7287
 
7288
+ logInfo('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
7289
+
7247
7290
  const previousSessionId = this.session && this.session.id;
7248
7291
 
7249
7292
  const { session } = getSession({
@@ -7253,6 +7296,7 @@ class ReplayContainer {
7253
7296
  // This is intentional: create a new session-based replay when calling `start()`
7254
7297
  sessionSampleRate: 1,
7255
7298
  allowBuffering: false,
7299
+ traceInternals: this._options._experiments.traceInternals,
7256
7300
  });
7257
7301
 
7258
7302
  session.previousSessionId = previousSessionId;
@@ -7270,6 +7314,8 @@ class ReplayContainer {
7270
7314
  throw new Error('Replay recording is already in progress');
7271
7315
  }
7272
7316
 
7317
+ logInfo('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
7318
+
7273
7319
  const previousSessionId = this.session && this.session.id;
7274
7320
 
7275
7321
  const { session } = getSession({
@@ -7278,6 +7324,7 @@ class ReplayContainer {
7278
7324
  currentSession: this.session,
7279
7325
  sessionSampleRate: 0,
7280
7326
  allowBuffering: true,
7327
+ traceInternals: this._options._experiments.traceInternals,
7281
7328
  });
7282
7329
 
7283
7330
  session.previousSessionId = previousSessionId;
@@ -7338,15 +7385,10 @@ class ReplayContainer {
7338
7385
  }
7339
7386
 
7340
7387
  try {
7341
- if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
7342
- const msg = `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`;
7343
-
7344
- // When `traceInternals` is enabled, we want to log this to the console
7345
- // Else, use the regular debug output
7346
- // eslint-disable-next-line
7347
- const log = this.getOptions()._experiments.traceInternals ? console.warn : logger.log;
7348
- log(msg);
7349
- }
7388
+ logInfo(
7389
+ `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`,
7390
+ this._options._experiments.traceInternals,
7391
+ );
7350
7392
 
7351
7393
  // We can't move `_isEnabled` after awaiting a flush, otherwise we can
7352
7394
  // enter into an infinite loop when `stop()` is called while flushing.
@@ -7379,8 +7421,14 @@ class ReplayContainer {
7379
7421
  * not as thorough of a shutdown as `stop()`.
7380
7422
  */
7381
7423
  pause() {
7424
+ if (this._isPaused) {
7425
+ return;
7426
+ }
7427
+
7382
7428
  this._isPaused = true;
7383
7429
  this.stopRecording();
7430
+
7431
+ logInfo('[Replay] Pausing replay', this._options._experiments.traceInternals);
7384
7432
  }
7385
7433
 
7386
7434
  /**
@@ -7390,12 +7438,14 @@ class ReplayContainer {
7390
7438
  * new DOM checkout.`
7391
7439
  */
7392
7440
  resume() {
7393
- if (!this._loadAndCheckSession()) {
7441
+ if (!this._isPaused || !this._loadAndCheckSession()) {
7394
7442
  return;
7395
7443
  }
7396
7444
 
7397
7445
  this._isPaused = false;
7398
7446
  this.startRecording();
7447
+
7448
+ logInfo('[Replay] Resuming replay', this._options._experiments.traceInternals);
7399
7449
  }
7400
7450
 
7401
7451
  /**
@@ -7412,9 +7462,7 @@ class ReplayContainer {
7412
7462
 
7413
7463
  const activityTime = Date.now();
7414
7464
 
7415
- // eslint-disable-next-line no-console
7416
- const log = this.getOptions()._experiments.traceInternals ? console.info : logger.info;
7417
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && log(`[Replay] Converting buffer to session, starting at ${activityTime}`);
7465
+ logInfo('[Replay] Converting buffer to session', this._options._experiments.traceInternals);
7418
7466
 
7419
7467
  // Allow flush to complete before resuming as a session recording, otherwise
7420
7468
  // the checkout from `startRecording` may be included in the payload.
@@ -7428,10 +7476,12 @@ class ReplayContainer {
7428
7476
  return;
7429
7477
  }
7430
7478
 
7431
- // Re-start recording, but in "session" recording mode
7479
+ // To avoid race conditions where this is called multiple times, we check here again that we are still buffering
7480
+ if ((this.recordingMode ) === 'session') {
7481
+ return;
7482
+ }
7432
7483
 
7433
- // Reset all "capture on error" configuration before
7434
- // starting a new recording
7484
+ // Re-start recording in session-mode
7435
7485
  this.recordingMode = 'session';
7436
7486
 
7437
7487
  // Once this session ends, we do not want to refresh it
@@ -7448,7 +7498,6 @@ class ReplayContainer {
7448
7498
  // (length of buffer), which we are ok with.
7449
7499
  this._updateUserActivity(activityTime);
7450
7500
  this._updateSessionActivity(activityTime);
7451
- this.session.started = activityTime;
7452
7501
  this._maybeSaveSession();
7453
7502
  }
7454
7503
 
@@ -7722,6 +7771,7 @@ class ReplayContainer {
7722
7771
  currentSession: this.session,
7723
7772
  sessionSampleRate: this._options.sessionSampleRate,
7724
7773
  allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
7774
+ traceInternals: this._options._experiments.traceInternals,
7725
7775
  });
7726
7776
 
7727
7777
  // If session was newly created (i.e. was not loaded from storage), then
@@ -7738,7 +7788,7 @@ class ReplayContainer {
7738
7788
  this.session = session;
7739
7789
 
7740
7790
  if (!this.session.sampled) {
7741
- void this.stop('session unsampled');
7791
+ void this.stop('session not refreshed');
7742
7792
  return false;
7743
7793
  }
7744
7794
 
@@ -7794,7 +7844,7 @@ class ReplayContainer {
7794
7844
 
7795
7845
  if (this._performanceObserver) {
7796
7846
  this._performanceObserver.disconnect();
7797
- this._performanceObserver = null;
7847
+ this._performanceObserver = undefined;
7798
7848
  }
7799
7849
  } catch (err) {
7800
7850
  this._handleException(err);
@@ -7807,7 +7857,7 @@ class ReplayContainer {
7807
7857
  * be hidden. Likewise, moving a different window to cover the contents of the
7808
7858
  * page will also trigger a change to a hidden state.
7809
7859
  */
7810
- __init13() {this._handleVisibilityChange = () => {
7860
+ __init() {this._handleVisibilityChange = () => {
7811
7861
  if (WINDOW.document.visibilityState === 'visible') {
7812
7862
  this._doChangeToForegroundTasks();
7813
7863
  } else {
@@ -7818,7 +7868,7 @@ class ReplayContainer {
7818
7868
  /**
7819
7869
  * Handle when page is blurred
7820
7870
  */
7821
- __init14() {this._handleWindowBlur = () => {
7871
+ __init2() {this._handleWindowBlur = () => {
7822
7872
  const breadcrumb = createBreadcrumb({
7823
7873
  category: 'ui.blur',
7824
7874
  });
@@ -7831,7 +7881,7 @@ class ReplayContainer {
7831
7881
  /**
7832
7882
  * Handle when page is focused
7833
7883
  */
7834
- __init15() {this._handleWindowFocus = () => {
7884
+ __init3() {this._handleWindowFocus = () => {
7835
7885
  const breadcrumb = createBreadcrumb({
7836
7886
  category: 'ui.focus',
7837
7887
  });
@@ -7842,7 +7892,7 @@ class ReplayContainer {
7842
7892
  };}
7843
7893
 
7844
7894
  /** Ensure page remains active when a key is pressed. */
7845
- __init16() {this._handleKeyboardEvent = (event) => {
7895
+ __init4() {this._handleKeyboardEvent = (event) => {
7846
7896
  handleKeyboardEvent(this, event);
7847
7897
  };}
7848
7898
 
@@ -7880,7 +7930,7 @@ class ReplayContainer {
7880
7930
  // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
7881
7931
  // ms, we will re-use the existing session, otherwise create a new
7882
7932
  // session
7883
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
7933
+ logInfo('[Replay] Document has become active, but session has expired');
7884
7934
  return;
7885
7935
  }
7886
7936
 
@@ -7895,7 +7945,7 @@ class ReplayContainer {
7895
7945
  */
7896
7946
  _triggerFullSnapshot(checkout = true) {
7897
7947
  try {
7898
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Taking full rrweb snapshot');
7948
+ logInfo('[Replay] Taking full rrweb snapshot');
7899
7949
  record.takeFullSnapshot(checkout);
7900
7950
  } catch (err) {
7901
7951
  this._handleException(err);
@@ -8065,7 +8115,7 @@ class ReplayContainer {
8065
8115
  * Flush recording data to Sentry. Creates a lock so that only a single flush
8066
8116
  * can be active at a time. Do not call this directly.
8067
8117
  */
8068
- __init17() {this._flush = async ({
8118
+ __init5() {this._flush = async ({
8069
8119
  force = false,
8070
8120
  }
8071
8121
 
@@ -8091,14 +8141,18 @@ class ReplayContainer {
8091
8141
 
8092
8142
  // If session is too short, or too long (allow some wiggle room over maxSessionLife), do not send it
8093
8143
  // This _should_ not happen, but it may happen if flush is triggered due to a page activity change or similar
8094
- if (duration < this._options.minReplayDuration || duration > this.timeouts.maxSessionLife + 5000) {
8095
- // eslint-disable-next-line no-console
8096
- const log = this.getOptions()._experiments.traceInternals ? console.warn : logger.warn;
8097
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
8098
- log(
8099
- `[Replay] Session duration (${Math.floor(duration / 1000)}s) is too short or too long, not sending replay.`,
8100
- );
8144
+ const tooShort = duration < this._options.minReplayDuration;
8145
+ const tooLong = duration > this.timeouts.maxSessionLife + 5000;
8146
+ if (tooShort || tooLong) {
8147
+ logInfo(
8148
+ `[Replay] Session duration (${Math.floor(duration / 1000)}s) is too ${
8149
+ tooShort ? 'short' : 'long'
8150
+ }, not sending replay.`,
8151
+ );
8101
8152
 
8153
+ if (tooShort) {
8154
+ this._debouncedFlush();
8155
+ }
8102
8156
  return;
8103
8157
  }
8104
8158
 
@@ -8110,7 +8164,7 @@ class ReplayContainer {
8110
8164
  if (!this._flushLock) {
8111
8165
  this._flushLock = this._runFlush();
8112
8166
  await this._flushLock;
8113
- this._flushLock = null;
8167
+ this._flushLock = undefined;
8114
8168
  return;
8115
8169
  }
8116
8170
 
@@ -8137,7 +8191,7 @@ class ReplayContainer {
8137
8191
  }
8138
8192
 
8139
8193
  /** Handler for rrweb.record.onMutation */
8140
- __init18() {this._onMutationHandler = (mutations) => {
8194
+ __init6() {this._onMutationHandler = (mutations) => {
8141
8195
  const count = mutations.length;
8142
8196
 
8143
8197
  const mutationLimit = this._options.mutationLimit;
@@ -8287,7 +8341,6 @@ class Replay {
8287
8341
  /**
8288
8342
  * @inheritDoc
8289
8343
  */
8290
- __init() {this.name = Replay.id;}
8291
8344
 
8292
8345
  /**
8293
8346
  * Options to pass to `rrweb.record()`
@@ -8347,7 +8400,9 @@ class Replay {
8347
8400
  maskTextSelector,
8348
8401
  // eslint-disable-next-line deprecation/deprecation
8349
8402
  ignoreClass,
8350
- } = {}) {Replay.prototype.__init.call(this);
8403
+ } = {}) {
8404
+ this.name = Replay.id;
8405
+
8351
8406
  this._recordingOptions = {
8352
8407
  maskAllInputs,
8353
8408
  maskAllText,