@sentry/replay 7.61.0 → 7.62.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.
Files changed (33) hide show
  1. package/cjs/index.js +225 -118
  2. package/cjs/index.js.map +1 -1
  3. package/esm/index.js +225 -118
  4. package/esm/index.js.map +1 -1
  5. package/package.json +5 -5
  6. package/types/coreHandlers/handleAfterSendEvent.d.ts.map +1 -1
  7. package/types/coreHandlers/handleClick.d.ts.map +1 -1
  8. package/types/coreHandlers/handleGlobalEvent.d.ts.map +1 -1
  9. package/types/eventBuffer/EventBufferArray.d.ts +2 -0
  10. package/types/eventBuffer/EventBufferArray.d.ts.map +1 -1
  11. package/types/eventBuffer/EventBufferCompressionWorker.d.ts +2 -0
  12. package/types/eventBuffer/EventBufferCompressionWorker.d.ts.map +1 -1
  13. package/types/eventBuffer/EventBufferProxy.d.ts +4 -0
  14. package/types/eventBuffer/EventBufferProxy.d.ts.map +1 -1
  15. package/types/eventBuffer/WorkerHandler.d.ts.map +1 -1
  16. package/types/eventBuffer/index.d.ts.map +1 -1
  17. package/types/integration.d.ts.map +1 -1
  18. package/types/replay.d.ts.map +1 -1
  19. package/types/session/Session.d.ts.map +1 -1
  20. package/types/session/createSession.d.ts.map +1 -1
  21. package/types/session/fetchSession.d.ts +1 -1
  22. package/types/session/fetchSession.d.ts.map +1 -1
  23. package/types/session/getSession.d.ts +2 -1
  24. package/types/session/getSession.d.ts.map +1 -1
  25. package/types/types/replay.d.ts +4 -0
  26. package/types/types/replay.d.ts.map +1 -1
  27. package/types/types/replayFrame.d.ts +4 -3
  28. package/types/types/replayFrame.d.ts.map +1 -1
  29. package/types/util/addEvent.d.ts.map +1 -1
  30. package/types/util/handleRecordingEmit.d.ts.map +1 -1
  31. package/types/util/log.d.ts +10 -0
  32. package/types/util/log.d.ts.map +1 -0
  33. package/types/util/sendReplayRequest.d.ts.map +1 -1
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,56 @@ 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
+ addBreadcrumb(message);
4029
+ }
4030
+ }
4031
+
4032
+ /**
4033
+ * Log a message, and add a breadcrumb in the next tick.
4034
+ * This is necessary when the breadcrumb may be added before the replay is initialized.
4035
+ */
4036
+ function logInfoNextTick(message, shouldAddBreadcrumb) {
4037
+ if (!(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
4038
+ return;
4039
+ }
4040
+
4041
+ logger.info(message);
4042
+
4043
+ if (shouldAddBreadcrumb) {
4044
+ // Wait a tick here to avoid race conditions for some initial logs
4045
+ // which may be added before replay is initialized
4046
+ setTimeout(() => {
4047
+ addBreadcrumb(message);
4048
+ }, 0);
4049
+ }
4050
+ }
4051
+
4052
+ function addBreadcrumb(message) {
4053
+ const hub = getCurrentHub();
4054
+ hub.addBreadcrumb(
4055
+ {
4056
+ category: 'console',
4057
+ data: {
4058
+ logger: 'replay',
4059
+ },
4060
+ level: 'info',
4061
+ message,
4062
+ },
4063
+ { level: 'info' },
4064
+ );
4065
+ }
4066
+
4017
4067
  /** This error indicates that the event buffer size exceeded the limit.. */
4018
4068
  class EventBufferSizeExceededError extends Error {
4019
4069
  constructor() {
@@ -4028,10 +4078,12 @@ class EventBufferSizeExceededError extends Error {
4028
4078
  class EventBufferArray {
4029
4079
  /** All the events that are buffered to be sent. */
4030
4080
 
4031
- __init() {this._totalSize = 0;}
4081
+ /** @inheritdoc */
4032
4082
 
4033
- constructor() {EventBufferArray.prototype.__init.call(this);
4083
+ constructor() {
4034
4084
  this.events = [];
4085
+ this._totalSize = 0;
4086
+ this.hasCheckout = false;
4035
4087
  }
4036
4088
 
4037
4089
  /** @inheritdoc */
@@ -4076,6 +4128,7 @@ class EventBufferArray {
4076
4128
  clear() {
4077
4129
  this.events = [];
4078
4130
  this._totalSize = 0;
4131
+ this.hasCheckout = false;
4079
4132
  }
4080
4133
 
4081
4134
  /** @inheritdoc */
@@ -4140,7 +4193,7 @@ class WorkerHandler {
4140
4193
  * Destroy the worker.
4141
4194
  */
4142
4195
  destroy() {
4143
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Destroying compression worker');
4196
+ logInfo('[Replay] Destroying compression worker');
4144
4197
  this._worker.terminate();
4145
4198
  }
4146
4199
 
@@ -4195,12 +4248,13 @@ class WorkerHandler {
4195
4248
  * Exported only for testing.
4196
4249
  */
4197
4250
  class EventBufferCompressionWorker {
4251
+ /** @inheritdoc */
4198
4252
 
4199
- __init() {this._totalSize = 0;}
4200
-
4201
- constructor(worker) {EventBufferCompressionWorker.prototype.__init.call(this);
4253
+ constructor(worker) {
4202
4254
  this._worker = new WorkerHandler(worker);
4203
4255
  this._earliestTimestamp = null;
4256
+ this._totalSize = 0;
4257
+ this.hasCheckout = false;
4204
4258
  }
4205
4259
 
4206
4260
  /** @inheritdoc */
@@ -4260,6 +4314,8 @@ class EventBufferCompressionWorker {
4260
4314
  clear() {
4261
4315
  this._earliestTimestamp = null;
4262
4316
  this._totalSize = 0;
4317
+ this.hasCheckout = false;
4318
+
4263
4319
  // We do not wait on this, as we assume the order of messages is consistent for the worker
4264
4320
  void this._worker.postMessage('clear');
4265
4321
  }
@@ -4314,6 +4370,15 @@ class EventBufferProxy {
4314
4370
  return this._used.hasEvents;
4315
4371
  }
4316
4372
 
4373
+ /** @inheritdoc */
4374
+ get hasCheckout() {
4375
+ return this._used.hasCheckout;
4376
+ }
4377
+ /** @inheritdoc */
4378
+ set hasCheckout(value) {
4379
+ this._used.hasCheckout = value;
4380
+ }
4381
+
4317
4382
  /** @inheritDoc */
4318
4383
  destroy() {
4319
4384
  this._fallback.destroy();
@@ -4359,7 +4424,7 @@ class EventBufferProxy {
4359
4424
  } catch (error) {
4360
4425
  // If the worker fails to load, we fall back to the simple buffer.
4361
4426
  // 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');
4427
+ logInfo('[Replay] Failed to load the compression worker, falling back to simple buffer');
4363
4428
  return;
4364
4429
  }
4365
4430
 
@@ -4398,16 +4463,16 @@ function createEventBuffer({ useCompression }) {
4398
4463
  try {
4399
4464
  const workerUrl = e();
4400
4465
 
4401
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using compression worker');
4466
+ logInfo('[Replay] Using compression worker');
4402
4467
  const worker = new Worker(workerUrl);
4403
4468
  return new EventBufferProxy(worker);
4404
4469
  } catch (error) {
4405
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Failed to create compression worker');
4470
+ logInfo('[Replay] Failed to create compression worker');
4406
4471
  // Fall back to use simple event buffer array
4407
4472
  }
4408
4473
  }
4409
4474
 
4410
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Using simple buffer');
4475
+ logInfo('[Replay] Using simple buffer');
4411
4476
  return new EventBufferArray();
4412
4477
  }
4413
4478
 
@@ -4520,6 +4585,7 @@ function makeSession(session) {
4520
4585
  const lastActivity = session.lastActivity || now;
4521
4586
  const segmentId = session.segmentId || 0;
4522
4587
  const sampled = session.sampled;
4588
+ const shouldRefresh = typeof session.shouldRefresh === 'boolean' ? session.shouldRefresh : true;
4523
4589
 
4524
4590
  return {
4525
4591
  id,
@@ -4527,7 +4593,7 @@ function makeSession(session) {
4527
4593
  lastActivity,
4528
4594
  segmentId,
4529
4595
  sampled,
4530
- shouldRefresh: true,
4596
+ shouldRefresh,
4531
4597
  };
4532
4598
  }
4533
4599
 
@@ -4549,8 +4615,6 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4549
4615
  sampled,
4550
4616
  });
4551
4617
 
4552
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Replay] Creating new session: ${session.id}`);
4553
-
4554
4618
  if (stickySession) {
4555
4619
  saveSession(session);
4556
4620
  }
@@ -4561,7 +4625,7 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4561
4625
  /**
4562
4626
  * Fetches a session from storage
4563
4627
  */
4564
- function fetchSession() {
4628
+ function fetchSession(traceInternals) {
4565
4629
  if (!hasSessionStorage()) {
4566
4630
  return null;
4567
4631
  }
@@ -4576,6 +4640,8 @@ function fetchSession() {
4576
4640
 
4577
4641
  const sessionObj = JSON.parse(sessionStringFromStorage) ;
4578
4642
 
4643
+ logInfoNextTick('[Replay] Loading existing session', traceInternals);
4644
+
4579
4645
  return makeSession(sessionObj);
4580
4646
  } catch (e) {
4581
4647
  return null;
@@ -4591,9 +4657,10 @@ function getSession({
4591
4657
  stickySession,
4592
4658
  sessionSampleRate,
4593
4659
  allowBuffering,
4660
+ traceInternals,
4594
4661
  }) {
4595
4662
  // If session exists and is passed, use it instead of always hitting session storage
4596
- const session = currentSession || (stickySession && fetchSession());
4663
+ const session = currentSession || (stickySession && fetchSession(traceInternals));
4597
4664
 
4598
4665
  if (session) {
4599
4666
  // If there is a session, check if it is valid (e.g. "last activity" time
@@ -4609,9 +4676,10 @@ function getSession({
4609
4676
  // and when this session is expired, it will not be renewed until user
4610
4677
  // reloads.
4611
4678
  const discardedSession = makeSession({ sampled: false });
4679
+ logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
4612
4680
  return { type: 'new', session: discardedSession };
4613
4681
  } else {
4614
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Session has expired');
4682
+ logInfoNextTick('[Replay] Session has expired', traceInternals);
4615
4683
  }
4616
4684
  // Otherwise continue to create a new session
4617
4685
  }
@@ -4621,6 +4689,7 @@ function getSession({
4621
4689
  sessionSampleRate,
4622
4690
  allowBuffering,
4623
4691
  });
4692
+ logInfoNextTick('[Replay] Created new session', traceInternals);
4624
4693
 
4625
4694
  return { type: 'new', session: newSession };
4626
4695
  }
@@ -4659,8 +4728,9 @@ async function addEvent(
4659
4728
  }
4660
4729
 
4661
4730
  try {
4662
- if (isCheckout) {
4731
+ if (isCheckout && replay.recordingMode === 'buffer') {
4663
4732
  replay.eventBuffer.clear();
4733
+ replay.eventBuffer.hasCheckout = true;
4664
4734
  }
4665
4735
 
4666
4736
  const replayOptions = replay.getOptions();
@@ -4727,7 +4797,7 @@ function handleAfterSendEvent(replay) {
4727
4797
  const enforceStatusCode = isBaseTransportSend();
4728
4798
 
4729
4799
  return (event, sendResponse) => {
4730
- if (!isErrorEvent(event) && !isTransactionEvent(event)) {
4800
+ if (!replay.isEnabled() || (!isErrorEvent(event) && !isTransactionEvent(event))) {
4731
4801
  return;
4732
4802
  }
4733
4803
 
@@ -4740,36 +4810,47 @@ function handleAfterSendEvent(replay) {
4740
4810
  return;
4741
4811
  }
4742
4812
 
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 );
4813
+ if (isTransactionEvent(event)) {
4814
+ handleTransactionEvent(replay, event);
4747
4815
  return;
4748
4816
  }
4749
4817
 
4750
- // Everything below is just for error events
4751
- if (!isErrorEvent(event)) {
4752
- return;
4753
- }
4818
+ handleErrorEvent(replay, event);
4819
+ };
4820
+ }
4754
4821
 
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
- }
4822
+ function handleTransactionEvent(replay, event) {
4823
+ const replayContext = replay.getContext();
4763
4824
 
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
- };
4825
+ // Collect traceIds in _context regardless of `recordingMode`
4826
+ // In error mode, _context gets cleared on every checkout
4827
+ // We limit to max. 100 transactions linked
4828
+ if (event.contexts && event.contexts.trace && event.contexts.trace.trace_id && replayContext.traceIds.size < 100) {
4829
+ replayContext.traceIds.add(event.contexts.trace.trace_id );
4830
+ }
4831
+ }
4832
+
4833
+ function handleErrorEvent(replay, event) {
4834
+ const replayContext = replay.getContext();
4835
+
4836
+ // Add error to list of errorIds of replay. This is ok to do even if not
4837
+ // sampled because context will get reset at next checkout.
4838
+ // XXX: There is also a race condition where it's possible to capture an
4839
+ // error to Sentry before Replay SDK has loaded, but response returns after
4840
+ // it was loaded, and this gets called.
4841
+ // We limit to max. 100 errors linked
4842
+ if (event.event_id && replayContext.errorIds.size < 100) {
4843
+ replayContext.errorIds.add(event.event_id);
4844
+ }
4845
+
4846
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
4847
+ // Need to be very careful that this does not cause an infinite loop
4848
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
4849
+ setTimeout(() => {
4850
+ // Capture current event buffer as new replay
4851
+ void replay.sendBufferedReplayOrFlush();
4852
+ });
4853
+ }
4773
4854
  }
4774
4855
 
4775
4856
  function isBaseTransportSend() {
@@ -4845,6 +4926,11 @@ function handleGlobalEventListener(
4845
4926
  const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined;
4846
4927
 
4847
4928
  return (event, hint) => {
4929
+ // Do nothing if replay has been disabled
4930
+ if (!replay.isEnabled()) {
4931
+ return event;
4932
+ }
4933
+
4848
4934
  if (isReplayEvent(event)) {
4849
4935
  // Replays have separate set of breadcrumbs, do not include breadcrumbs
4850
4936
  // from core SDK
@@ -6626,10 +6712,10 @@ function getHandleRecordingEmit(replay) {
6626
6712
  if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
6627
6713
  const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
6628
6714
  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}`);
6715
+ logInfo(
6716
+ `[Replay] Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`,
6717
+ replay.getOptions()._experiments.traceInternals,
6718
+ );
6633
6719
 
6634
6720
  replay.session.started = earliestEvent;
6635
6721
 
@@ -6843,7 +6929,7 @@ async function sendReplayRequest({
6843
6929
  if (!replayEvent) {
6844
6930
  // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
6845
6931
  client.recordDroppedEvent('event_processor', 'replay', baseEvent);
6846
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('An event processor returned `null`, will not send event.');
6932
+ logInfo('An event processor returned `null`, will not send event.');
6847
6933
  return;
6848
6934
  }
6849
6935
 
@@ -7056,12 +7142,10 @@ function throttle(
7056
7142
  * The main replay container class, which holds all the state and methods for recording and sending replays.
7057
7143
  */
7058
7144
  class ReplayContainer {
7059
- __init() {this.eventBuffer = null;}
7060
7145
 
7061
7146
  /**
7062
7147
  * List of PerformanceEntry from PerformanceObserver
7063
7148
  */
7064
- __init2() {this.performanceEvents = [];}
7065
7149
 
7066
7150
  /**
7067
7151
  * Recording can happen in one of three modes:
@@ -7070,7 +7154,6 @@ class ReplayContainer {
7070
7154
  * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
7071
7155
  * - or calling `flush()` to send the replay
7072
7156
  */
7073
- __init3() {this.recordingMode = 'session';}
7074
7157
 
7075
7158
  /**
7076
7159
  * The current or last active transcation.
@@ -7081,62 +7164,60 @@ class ReplayContainer {
7081
7164
  * These are here so we can overwrite them in tests etc.
7082
7165
  * @hidden
7083
7166
  */
7084
- __init4() {this.timeouts = {
7085
- sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7086
- sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7087
- maxSessionLife: MAX_SESSION_LIFE,
7088
- }; }
7089
7167
 
7090
7168
  /**
7091
7169
  * Options to pass to `rrweb.record()`
7092
7170
  */
7093
7171
 
7094
- __init5() {this._performanceObserver = null;}
7095
-
7096
- __init6() {this._flushLock = null;}
7097
-
7098
7172
  /**
7099
7173
  * Timestamp of the last user activity. This lives across sessions.
7100
7174
  */
7101
- __init7() {this._lastActivity = Date.now();}
7102
7175
 
7103
7176
  /**
7104
7177
  * Is the integration currently active?
7105
7178
  */
7106
- __init8() {this._isEnabled = false;}
7107
7179
 
7108
7180
  /**
7109
7181
  * Paused is a state where:
7110
7182
  * - DOM Recording is not listening at all
7111
7183
  * - Nothing will be added to event buffer (e.g. core SDK events)
7112
7184
  */
7113
- __init9() {this._isPaused = false;}
7114
7185
 
7115
7186
  /**
7116
7187
  * Have we attached listeners to the core SDK?
7117
7188
  * Note we have to track this as there is no way to remove instrumentation handlers.
7118
7189
  */
7119
- __init10() {this._hasInitializedCoreListeners = false;}
7120
7190
 
7121
7191
  /**
7122
7192
  * Function to stop recording
7123
7193
  */
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
7194
 
7134
7195
  constructor({
7135
7196
  options,
7136
7197
  recordingOptions,
7137
7198
  }
7138
7199
 
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);
7200
+ ) {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);
7201
+ this.eventBuffer = null;
7202
+ this.performanceEvents = [];
7203
+ this.recordingMode = 'session';
7204
+ this.timeouts = {
7205
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7206
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7207
+ maxSessionLife: MAX_SESSION_LIFE,
7208
+ } ;
7209
+ this._lastActivity = Date.now();
7210
+ this._isEnabled = false;
7211
+ this._isPaused = false;
7212
+ this._hasInitializedCoreListeners = false;
7213
+ this._context = {
7214
+ errorIds: new Set(),
7215
+ traceIds: new Set(),
7216
+ urls: [],
7217
+ initialTimestamp: Date.now(),
7218
+ initialUrl: '',
7219
+ };
7220
+
7140
7221
  this._recordingOptions = recordingOptions;
7141
7222
  this._options = options;
7142
7223
 
@@ -7225,6 +7306,11 @@ class ReplayContainer {
7225
7306
  this.recordingMode = 'buffer';
7226
7307
  }
7227
7308
 
7309
+ logInfoNextTick(
7310
+ `[Replay] Starting replay in ${this.recordingMode} mode`,
7311
+ this._options._experiments.traceInternals,
7312
+ );
7313
+
7228
7314
  this._initializeRecording();
7229
7315
  }
7230
7316
 
@@ -7244,6 +7330,8 @@ class ReplayContainer {
7244
7330
  throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
7245
7331
  }
7246
7332
 
7333
+ logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
7334
+
7247
7335
  const previousSessionId = this.session && this.session.id;
7248
7336
 
7249
7337
  const { session } = getSession({
@@ -7253,6 +7341,7 @@ class ReplayContainer {
7253
7341
  // This is intentional: create a new session-based replay when calling `start()`
7254
7342
  sessionSampleRate: 1,
7255
7343
  allowBuffering: false,
7344
+ traceInternals: this._options._experiments.traceInternals,
7256
7345
  });
7257
7346
 
7258
7347
  session.previousSessionId = previousSessionId;
@@ -7270,6 +7359,8 @@ class ReplayContainer {
7270
7359
  throw new Error('Replay recording is already in progress');
7271
7360
  }
7272
7361
 
7362
+ logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
7363
+
7273
7364
  const previousSessionId = this.session && this.session.id;
7274
7365
 
7275
7366
  const { session } = getSession({
@@ -7278,6 +7369,7 @@ class ReplayContainer {
7278
7369
  currentSession: this.session,
7279
7370
  sessionSampleRate: 0,
7280
7371
  allowBuffering: true,
7372
+ traceInternals: this._options._experiments.traceInternals,
7281
7373
  });
7282
7374
 
7283
7375
  session.previousSessionId = previousSessionId;
@@ -7338,15 +7430,10 @@ class ReplayContainer {
7338
7430
  }
7339
7431
 
7340
7432
  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
- }
7433
+ logInfo(
7434
+ `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`,
7435
+ this._options._experiments.traceInternals,
7436
+ );
7350
7437
 
7351
7438
  // We can't move `_isEnabled` after awaiting a flush, otherwise we can
7352
7439
  // enter into an infinite loop when `stop()` is called while flushing.
@@ -7379,8 +7466,14 @@ class ReplayContainer {
7379
7466
  * not as thorough of a shutdown as `stop()`.
7380
7467
  */
7381
7468
  pause() {
7469
+ if (this._isPaused) {
7470
+ return;
7471
+ }
7472
+
7382
7473
  this._isPaused = true;
7383
7474
  this.stopRecording();
7475
+
7476
+ logInfo('[Replay] Pausing replay', this._options._experiments.traceInternals);
7384
7477
  }
7385
7478
 
7386
7479
  /**
@@ -7390,12 +7483,14 @@ class ReplayContainer {
7390
7483
  * new DOM checkout.`
7391
7484
  */
7392
7485
  resume() {
7393
- if (!this._loadAndCheckSession()) {
7486
+ if (!this._isPaused || !this._loadAndCheckSession()) {
7394
7487
  return;
7395
7488
  }
7396
7489
 
7397
7490
  this._isPaused = false;
7398
7491
  this.startRecording();
7492
+
7493
+ logInfo('[Replay] Resuming replay', this._options._experiments.traceInternals);
7399
7494
  }
7400
7495
 
7401
7496
  /**
@@ -7412,9 +7507,7 @@ class ReplayContainer {
7412
7507
 
7413
7508
  const activityTime = Date.now();
7414
7509
 
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}`);
7510
+ logInfo('[Replay] Converting buffer to session', this._options._experiments.traceInternals);
7418
7511
 
7419
7512
  // Allow flush to complete before resuming as a session recording, otherwise
7420
7513
  // the checkout from `startRecording` may be included in the payload.
@@ -7428,10 +7521,12 @@ class ReplayContainer {
7428
7521
  return;
7429
7522
  }
7430
7523
 
7431
- // Re-start recording, but in "session" recording mode
7524
+ // To avoid race conditions where this is called multiple times, we check here again that we are still buffering
7525
+ if ((this.recordingMode ) === 'session') {
7526
+ return;
7527
+ }
7432
7528
 
7433
- // Reset all "capture on error" configuration before
7434
- // starting a new recording
7529
+ // Re-start recording in session-mode
7435
7530
  this.recordingMode = 'session';
7436
7531
 
7437
7532
  // Once this session ends, we do not want to refresh it
@@ -7448,7 +7543,6 @@ class ReplayContainer {
7448
7543
  // (length of buffer), which we are ok with.
7449
7544
  this._updateUserActivity(activityTime);
7450
7545
  this._updateSessionActivity(activityTime);
7451
- this.session.started = activityTime;
7452
7546
  this._maybeSaveSession();
7453
7547
  }
7454
7548
 
@@ -7722,6 +7816,7 @@ class ReplayContainer {
7722
7816
  currentSession: this.session,
7723
7817
  sessionSampleRate: this._options.sessionSampleRate,
7724
7818
  allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
7819
+ traceInternals: this._options._experiments.traceInternals,
7725
7820
  });
7726
7821
 
7727
7822
  // If session was newly created (i.e. was not loaded from storage), then
@@ -7738,7 +7833,7 @@ class ReplayContainer {
7738
7833
  this.session = session;
7739
7834
 
7740
7835
  if (!this.session.sampled) {
7741
- void this.stop('session unsampled');
7836
+ void this.stop('session not refreshed');
7742
7837
  return false;
7743
7838
  }
7744
7839
 
@@ -7794,7 +7889,7 @@ class ReplayContainer {
7794
7889
 
7795
7890
  if (this._performanceObserver) {
7796
7891
  this._performanceObserver.disconnect();
7797
- this._performanceObserver = null;
7892
+ this._performanceObserver = undefined;
7798
7893
  }
7799
7894
  } catch (err) {
7800
7895
  this._handleException(err);
@@ -7807,7 +7902,7 @@ class ReplayContainer {
7807
7902
  * be hidden. Likewise, moving a different window to cover the contents of the
7808
7903
  * page will also trigger a change to a hidden state.
7809
7904
  */
7810
- __init13() {this._handleVisibilityChange = () => {
7905
+ __init() {this._handleVisibilityChange = () => {
7811
7906
  if (WINDOW.document.visibilityState === 'visible') {
7812
7907
  this._doChangeToForegroundTasks();
7813
7908
  } else {
@@ -7818,7 +7913,7 @@ class ReplayContainer {
7818
7913
  /**
7819
7914
  * Handle when page is blurred
7820
7915
  */
7821
- __init14() {this._handleWindowBlur = () => {
7916
+ __init2() {this._handleWindowBlur = () => {
7822
7917
  const breadcrumb = createBreadcrumb({
7823
7918
  category: 'ui.blur',
7824
7919
  });
@@ -7831,7 +7926,7 @@ class ReplayContainer {
7831
7926
  /**
7832
7927
  * Handle when page is focused
7833
7928
  */
7834
- __init15() {this._handleWindowFocus = () => {
7929
+ __init3() {this._handleWindowFocus = () => {
7835
7930
  const breadcrumb = createBreadcrumb({
7836
7931
  category: 'ui.focus',
7837
7932
  });
@@ -7842,7 +7937,7 @@ class ReplayContainer {
7842
7937
  };}
7843
7938
 
7844
7939
  /** Ensure page remains active when a key is pressed. */
7845
- __init16() {this._handleKeyboardEvent = (event) => {
7940
+ __init4() {this._handleKeyboardEvent = (event) => {
7846
7941
  handleKeyboardEvent(this, event);
7847
7942
  };}
7848
7943
 
@@ -7880,7 +7975,7 @@ class ReplayContainer {
7880
7975
  // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
7881
7976
  // ms, we will re-use the existing session, otherwise create a new
7882
7977
  // session
7883
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Document has become active, but session has expired');
7978
+ logInfo('[Replay] Document has become active, but session has expired');
7884
7979
  return;
7885
7980
  }
7886
7981
 
@@ -7895,7 +7990,7 @@ class ReplayContainer {
7895
7990
  */
7896
7991
  _triggerFullSnapshot(checkout = true) {
7897
7992
  try {
7898
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log('[Replay] Taking full rrweb snapshot');
7993
+ logInfo('[Replay] Taking full rrweb snapshot');
7899
7994
  record.takeFullSnapshot(checkout);
7900
7995
  } catch (err) {
7901
7996
  this._handleException(err);
@@ -8065,7 +8160,7 @@ class ReplayContainer {
8065
8160
  * Flush recording data to Sentry. Creates a lock so that only a single flush
8066
8161
  * can be active at a time. Do not call this directly.
8067
8162
  */
8068
- __init17() {this._flush = async ({
8163
+ __init5() {this._flush = async ({
8069
8164
  force = false,
8070
8165
  }
8071
8166
 
@@ -8089,28 +8184,39 @@ class ReplayContainer {
8089
8184
  const now = Date.now();
8090
8185
  const duration = now - start;
8091
8186
 
8187
+ // A flush is about to happen, cancel any queued flushes
8188
+ this._debouncedFlush.cancel();
8189
+
8092
8190
  // If session is too short, or too long (allow some wiggle room over maxSessionLife), do not send it
8093
8191
  // 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
- );
8192
+ const tooShort = duration < this._options.minReplayDuration;
8193
+ const tooLong = duration > this.timeouts.maxSessionLife + 5000;
8194
+ if (tooShort || tooLong) {
8195
+ logInfo(
8196
+ `[Replay] Session duration (${Math.floor(duration / 1000)}s) is too ${
8197
+ tooShort ? 'short' : 'long'
8198
+ }, not sending replay.`,
8199
+ this._options._experiments.traceInternals,
8200
+ );
8101
8201
 
8202
+ if (tooShort) {
8203
+ this._debouncedFlush();
8204
+ }
8102
8205
  return;
8103
8206
  }
8104
8207
 
8105
- // A flush is about to happen, cancel any queued flushes
8106
- this._debouncedFlush.cancel();
8208
+ const eventBuffer = this.eventBuffer;
8209
+ if (eventBuffer && this.session.segmentId === 0 && !eventBuffer.hasCheckout) {
8210
+ logInfo('[Replay] Flushing initial segment without checkout.', this._options._experiments.traceInternals);
8211
+ // TODO FN: Evaluate if we want to stop here, or remove this again?
8212
+ }
8107
8213
 
8108
8214
  // this._flushLock acts as a lock so that future calls to `_flush()`
8109
8215
  // will be blocked until this promise resolves
8110
8216
  if (!this._flushLock) {
8111
8217
  this._flushLock = this._runFlush();
8112
8218
  await this._flushLock;
8113
- this._flushLock = null;
8219
+ this._flushLock = undefined;
8114
8220
  return;
8115
8221
  }
8116
8222
 
@@ -8137,7 +8243,7 @@ class ReplayContainer {
8137
8243
  }
8138
8244
 
8139
8245
  /** Handler for rrweb.record.onMutation */
8140
- __init18() {this._onMutationHandler = (mutations) => {
8246
+ __init6() {this._onMutationHandler = (mutations) => {
8141
8247
  const count = mutations.length;
8142
8248
 
8143
8249
  const mutationLimit = this._options.mutationLimit;
@@ -8287,7 +8393,6 @@ class Replay {
8287
8393
  /**
8288
8394
  * @inheritDoc
8289
8395
  */
8290
- __init() {this.name = Replay.id;}
8291
8396
 
8292
8397
  /**
8293
8398
  * Options to pass to `rrweb.record()`
@@ -8347,7 +8452,9 @@ class Replay {
8347
8452
  maskTextSelector,
8348
8453
  // eslint-disable-next-line deprecation/deprecation
8349
8454
  ignoreClass,
8350
- } = {}) {Replay.prototype.__init.call(this);
8455
+ } = {}) {
8456
+ this.name = Replay.id;
8457
+
8351
8458
  this._recordingOptions = {
8352
8459
  maskAllInputs,
8353
8460
  maskAllText,