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