@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/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,56 @@ 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
+ addBreadcrumb(message);
4031
+ }
4032
+ }
4033
+
4034
+ /**
4035
+ * Log a message, and add a breadcrumb in the next tick.
4036
+ * This is necessary when the breadcrumb may be added before the replay is initialized.
4037
+ */
4038
+ function logInfoNextTick(message, shouldAddBreadcrumb) {
4039
+ if (!(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
4040
+ return;
4041
+ }
4042
+
4043
+ utils.logger.info(message);
4044
+
4045
+ if (shouldAddBreadcrumb) {
4046
+ // Wait a tick here to avoid race conditions for some initial logs
4047
+ // which may be added before replay is initialized
4048
+ setTimeout(() => {
4049
+ addBreadcrumb(message);
4050
+ }, 0);
4051
+ }
4052
+ }
4053
+
4054
+ function addBreadcrumb(message) {
4055
+ const hub = core.getCurrentHub();
4056
+ hub.addBreadcrumb(
4057
+ {
4058
+ category: 'console',
4059
+ data: {
4060
+ logger: 'replay',
4061
+ },
4062
+ level: 'info',
4063
+ message,
4064
+ },
4065
+ { level: 'info' },
4066
+ );
4067
+ }
4068
+
4019
4069
  /** This error indicates that the event buffer size exceeded the limit.. */
4020
4070
  class EventBufferSizeExceededError extends Error {
4021
4071
  constructor() {
@@ -4030,10 +4080,12 @@ class EventBufferSizeExceededError extends Error {
4030
4080
  class EventBufferArray {
4031
4081
  /** All the events that are buffered to be sent. */
4032
4082
 
4033
- __init() {this._totalSize = 0;}
4083
+ /** @inheritdoc */
4034
4084
 
4035
- constructor() {EventBufferArray.prototype.__init.call(this);
4085
+ constructor() {
4036
4086
  this.events = [];
4087
+ this._totalSize = 0;
4088
+ this.hasCheckout = false;
4037
4089
  }
4038
4090
 
4039
4091
  /** @inheritdoc */
@@ -4078,6 +4130,7 @@ class EventBufferArray {
4078
4130
  clear() {
4079
4131
  this.events = [];
4080
4132
  this._totalSize = 0;
4133
+ this.hasCheckout = false;
4081
4134
  }
4082
4135
 
4083
4136
  /** @inheritdoc */
@@ -4142,7 +4195,7 @@ class WorkerHandler {
4142
4195
  * Destroy the worker.
4143
4196
  */
4144
4197
  destroy() {
4145
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Destroying compression worker');
4198
+ logInfo('[Replay] Destroying compression worker');
4146
4199
  this._worker.terminate();
4147
4200
  }
4148
4201
 
@@ -4197,12 +4250,13 @@ class WorkerHandler {
4197
4250
  * Exported only for testing.
4198
4251
  */
4199
4252
  class EventBufferCompressionWorker {
4253
+ /** @inheritdoc */
4200
4254
 
4201
- __init() {this._totalSize = 0;}
4202
-
4203
- constructor(worker) {EventBufferCompressionWorker.prototype.__init.call(this);
4255
+ constructor(worker) {
4204
4256
  this._worker = new WorkerHandler(worker);
4205
4257
  this._earliestTimestamp = null;
4258
+ this._totalSize = 0;
4259
+ this.hasCheckout = false;
4206
4260
  }
4207
4261
 
4208
4262
  /** @inheritdoc */
@@ -4262,6 +4316,8 @@ class EventBufferCompressionWorker {
4262
4316
  clear() {
4263
4317
  this._earliestTimestamp = null;
4264
4318
  this._totalSize = 0;
4319
+ this.hasCheckout = false;
4320
+
4265
4321
  // We do not wait on this, as we assume the order of messages is consistent for the worker
4266
4322
  void this._worker.postMessage('clear');
4267
4323
  }
@@ -4316,6 +4372,15 @@ class EventBufferProxy {
4316
4372
  return this._used.hasEvents;
4317
4373
  }
4318
4374
 
4375
+ /** @inheritdoc */
4376
+ get hasCheckout() {
4377
+ return this._used.hasCheckout;
4378
+ }
4379
+ /** @inheritdoc */
4380
+ set hasCheckout(value) {
4381
+ this._used.hasCheckout = value;
4382
+ }
4383
+
4319
4384
  /** @inheritDoc */
4320
4385
  destroy() {
4321
4386
  this._fallback.destroy();
@@ -4361,7 +4426,7 @@ class EventBufferProxy {
4361
4426
  } catch (error) {
4362
4427
  // If the worker fails to load, we fall back to the simple buffer.
4363
4428
  // 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');
4429
+ logInfo('[Replay] Failed to load the compression worker, falling back to simple buffer');
4365
4430
  return;
4366
4431
  }
4367
4432
 
@@ -4400,16 +4465,16 @@ function createEventBuffer({ useCompression }) {
4400
4465
  try {
4401
4466
  const workerUrl = e();
4402
4467
 
4403
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Using compression worker');
4468
+ logInfo('[Replay] Using compression worker');
4404
4469
  const worker = new Worker(workerUrl);
4405
4470
  return new EventBufferProxy(worker);
4406
4471
  } catch (error) {
4407
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Failed to create compression worker');
4472
+ logInfo('[Replay] Failed to create compression worker');
4408
4473
  // Fall back to use simple event buffer array
4409
4474
  }
4410
4475
  }
4411
4476
 
4412
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Using simple buffer');
4477
+ logInfo('[Replay] Using simple buffer');
4413
4478
  return new EventBufferArray();
4414
4479
  }
4415
4480
 
@@ -4522,6 +4587,7 @@ function makeSession(session) {
4522
4587
  const lastActivity = session.lastActivity || now;
4523
4588
  const segmentId = session.segmentId || 0;
4524
4589
  const sampled = session.sampled;
4590
+ const shouldRefresh = typeof session.shouldRefresh === 'boolean' ? session.shouldRefresh : true;
4525
4591
 
4526
4592
  return {
4527
4593
  id,
@@ -4529,7 +4595,7 @@ function makeSession(session) {
4529
4595
  lastActivity,
4530
4596
  segmentId,
4531
4597
  sampled,
4532
- shouldRefresh: true,
4598
+ shouldRefresh,
4533
4599
  };
4534
4600
  }
4535
4601
 
@@ -4551,8 +4617,6 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4551
4617
  sampled,
4552
4618
  });
4553
4619
 
4554
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Replay] Creating new session: ${session.id}`);
4555
-
4556
4620
  if (stickySession) {
4557
4621
  saveSession(session);
4558
4622
  }
@@ -4563,7 +4627,7 @@ function createSession({ sessionSampleRate, allowBuffering, stickySession = fals
4563
4627
  /**
4564
4628
  * Fetches a session from storage
4565
4629
  */
4566
- function fetchSession() {
4630
+ function fetchSession(traceInternals) {
4567
4631
  if (!hasSessionStorage()) {
4568
4632
  return null;
4569
4633
  }
@@ -4578,6 +4642,8 @@ function fetchSession() {
4578
4642
 
4579
4643
  const sessionObj = JSON.parse(sessionStringFromStorage) ;
4580
4644
 
4645
+ logInfoNextTick('[Replay] Loading existing session', traceInternals);
4646
+
4581
4647
  return makeSession(sessionObj);
4582
4648
  } catch (e) {
4583
4649
  return null;
@@ -4593,9 +4659,10 @@ function getSession({
4593
4659
  stickySession,
4594
4660
  sessionSampleRate,
4595
4661
  allowBuffering,
4662
+ traceInternals,
4596
4663
  }) {
4597
4664
  // If session exists and is passed, use it instead of always hitting session storage
4598
- const session = currentSession || (stickySession && fetchSession());
4665
+ const session = currentSession || (stickySession && fetchSession(traceInternals));
4599
4666
 
4600
4667
  if (session) {
4601
4668
  // If there is a session, check if it is valid (e.g. "last activity" time
@@ -4611,9 +4678,10 @@ function getSession({
4611
4678
  // and when this session is expired, it will not be renewed until user
4612
4679
  // reloads.
4613
4680
  const discardedSession = makeSession({ sampled: false });
4681
+ logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
4614
4682
  return { type: 'new', session: discardedSession };
4615
4683
  } else {
4616
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Session has expired');
4684
+ logInfoNextTick('[Replay] Session has expired', traceInternals);
4617
4685
  }
4618
4686
  // Otherwise continue to create a new session
4619
4687
  }
@@ -4623,6 +4691,7 @@ function getSession({
4623
4691
  sessionSampleRate,
4624
4692
  allowBuffering,
4625
4693
  });
4694
+ logInfoNextTick('[Replay] Created new session', traceInternals);
4626
4695
 
4627
4696
  return { type: 'new', session: newSession };
4628
4697
  }
@@ -4661,8 +4730,9 @@ async function addEvent(
4661
4730
  }
4662
4731
 
4663
4732
  try {
4664
- if (isCheckout) {
4733
+ if (isCheckout && replay.recordingMode === 'buffer') {
4665
4734
  replay.eventBuffer.clear();
4735
+ replay.eventBuffer.hasCheckout = true;
4666
4736
  }
4667
4737
 
4668
4738
  const replayOptions = replay.getOptions();
@@ -4729,7 +4799,7 @@ function handleAfterSendEvent(replay) {
4729
4799
  const enforceStatusCode = isBaseTransportSend();
4730
4800
 
4731
4801
  return (event, sendResponse) => {
4732
- if (!isErrorEvent(event) && !isTransactionEvent(event)) {
4802
+ if (!replay.isEnabled() || (!isErrorEvent(event) && !isTransactionEvent(event))) {
4733
4803
  return;
4734
4804
  }
4735
4805
 
@@ -4742,36 +4812,47 @@ function handleAfterSendEvent(replay) {
4742
4812
  return;
4743
4813
  }
4744
4814
 
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 );
4815
+ if (isTransactionEvent(event)) {
4816
+ handleTransactionEvent(replay, event);
4749
4817
  return;
4750
4818
  }
4751
4819
 
4752
- // Everything below is just for error events
4753
- if (!isErrorEvent(event)) {
4754
- return;
4755
- }
4820
+ handleErrorEvent(replay, event);
4821
+ };
4822
+ }
4756
4823
 
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
- }
4824
+ function handleTransactionEvent(replay, event) {
4825
+ const replayContext = replay.getContext();
4765
4826
 
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
- };
4827
+ // Collect traceIds in _context regardless of `recordingMode`
4828
+ // In error mode, _context gets cleared on every checkout
4829
+ // We limit to max. 100 transactions linked
4830
+ if (event.contexts && event.contexts.trace && event.contexts.trace.trace_id && replayContext.traceIds.size < 100) {
4831
+ replayContext.traceIds.add(event.contexts.trace.trace_id );
4832
+ }
4833
+ }
4834
+
4835
+ function handleErrorEvent(replay, event) {
4836
+ const replayContext = replay.getContext();
4837
+
4838
+ // Add error to list of errorIds of replay. This is ok to do even if not
4839
+ // sampled because context will get reset at next checkout.
4840
+ // XXX: There is also a race condition where it's possible to capture an
4841
+ // error to Sentry before Replay SDK has loaded, but response returns after
4842
+ // it was loaded, and this gets called.
4843
+ // We limit to max. 100 errors linked
4844
+ if (event.event_id && replayContext.errorIds.size < 100) {
4845
+ replayContext.errorIds.add(event.event_id);
4846
+ }
4847
+
4848
+ // If error event is tagged with replay id it means it was sampled (when in buffer mode)
4849
+ // Need to be very careful that this does not cause an infinite loop
4850
+ if (replay.recordingMode === 'buffer' && event.tags && event.tags.replayId) {
4851
+ setTimeout(() => {
4852
+ // Capture current event buffer as new replay
4853
+ void replay.sendBufferedReplayOrFlush();
4854
+ });
4855
+ }
4775
4856
  }
4776
4857
 
4777
4858
  function isBaseTransportSend() {
@@ -4847,6 +4928,11 @@ function handleGlobalEventListener(
4847
4928
  const afterSendHandler = includeAfterSendEventHandling ? handleAfterSendEvent(replay) : undefined;
4848
4929
 
4849
4930
  return (event, hint) => {
4931
+ // Do nothing if replay has been disabled
4932
+ if (!replay.isEnabled()) {
4933
+ return event;
4934
+ }
4935
+
4850
4936
  if (isReplayEvent(event)) {
4851
4937
  // Replays have separate set of breadcrumbs, do not include breadcrumbs
4852
4938
  // from core SDK
@@ -6628,10 +6714,10 @@ function getHandleRecordingEmit(replay) {
6628
6714
  if (replay.recordingMode === 'buffer' && replay.session && replay.eventBuffer) {
6629
6715
  const earliestEvent = replay.eventBuffer.getEarliestTimestamp();
6630
6716
  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}`);
6717
+ logInfo(
6718
+ `[Replay] Updating session start time to earliest event in buffer to ${new Date(earliestEvent)}`,
6719
+ replay.getOptions()._experiments.traceInternals,
6720
+ );
6635
6721
 
6636
6722
  replay.session.started = earliestEvent;
6637
6723
 
@@ -6845,7 +6931,7 @@ async function sendReplayRequest({
6845
6931
  if (!replayEvent) {
6846
6932
  // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
6847
6933
  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.');
6934
+ logInfo('An event processor returned `null`, will not send event.');
6849
6935
  return;
6850
6936
  }
6851
6937
 
@@ -7058,12 +7144,10 @@ function throttle(
7058
7144
  * The main replay container class, which holds all the state and methods for recording and sending replays.
7059
7145
  */
7060
7146
  class ReplayContainer {
7061
- __init() {this.eventBuffer = null;}
7062
7147
 
7063
7148
  /**
7064
7149
  * List of PerformanceEntry from PerformanceObserver
7065
7150
  */
7066
- __init2() {this.performanceEvents = [];}
7067
7151
 
7068
7152
  /**
7069
7153
  * Recording can happen in one of three modes:
@@ -7072,7 +7156,6 @@ class ReplayContainer {
7072
7156
  * - having replaysOnErrorSampleRate > 0 to capture replay when an error occurs
7073
7157
  * - or calling `flush()` to send the replay
7074
7158
  */
7075
- __init3() {this.recordingMode = 'session';}
7076
7159
 
7077
7160
  /**
7078
7161
  * The current or last active transcation.
@@ -7083,62 +7166,60 @@ class ReplayContainer {
7083
7166
  * These are here so we can overwrite them in tests etc.
7084
7167
  * @hidden
7085
7168
  */
7086
- __init4() {this.timeouts = {
7087
- sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7088
- sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7089
- maxSessionLife: MAX_SESSION_LIFE,
7090
- }; }
7091
7169
 
7092
7170
  /**
7093
7171
  * Options to pass to `rrweb.record()`
7094
7172
  */
7095
7173
 
7096
- __init5() {this._performanceObserver = null;}
7097
-
7098
- __init6() {this._flushLock = null;}
7099
-
7100
7174
  /**
7101
7175
  * Timestamp of the last user activity. This lives across sessions.
7102
7176
  */
7103
- __init7() {this._lastActivity = Date.now();}
7104
7177
 
7105
7178
  /**
7106
7179
  * Is the integration currently active?
7107
7180
  */
7108
- __init8() {this._isEnabled = false;}
7109
7181
 
7110
7182
  /**
7111
7183
  * Paused is a state where:
7112
7184
  * - DOM Recording is not listening at all
7113
7185
  * - Nothing will be added to event buffer (e.g. core SDK events)
7114
7186
  */
7115
- __init9() {this._isPaused = false;}
7116
7187
 
7117
7188
  /**
7118
7189
  * Have we attached listeners to the core SDK?
7119
7190
  * Note we have to track this as there is no way to remove instrumentation handlers.
7120
7191
  */
7121
- __init10() {this._hasInitializedCoreListeners = false;}
7122
7192
 
7123
7193
  /**
7124
7194
  * Function to stop recording
7125
7195
  */
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
7196
 
7136
7197
  constructor({
7137
7198
  options,
7138
7199
  recordingOptions,
7139
7200
  }
7140
7201
 
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);
7202
+ ) {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);
7203
+ this.eventBuffer = null;
7204
+ this.performanceEvents = [];
7205
+ this.recordingMode = 'session';
7206
+ this.timeouts = {
7207
+ sessionIdlePause: SESSION_IDLE_PAUSE_DURATION,
7208
+ sessionIdleExpire: SESSION_IDLE_EXPIRE_DURATION,
7209
+ maxSessionLife: MAX_SESSION_LIFE,
7210
+ } ;
7211
+ this._lastActivity = Date.now();
7212
+ this._isEnabled = false;
7213
+ this._isPaused = false;
7214
+ this._hasInitializedCoreListeners = false;
7215
+ this._context = {
7216
+ errorIds: new Set(),
7217
+ traceIds: new Set(),
7218
+ urls: [],
7219
+ initialTimestamp: Date.now(),
7220
+ initialUrl: '',
7221
+ };
7222
+
7142
7223
  this._recordingOptions = recordingOptions;
7143
7224
  this._options = options;
7144
7225
 
@@ -7227,6 +7308,11 @@ class ReplayContainer {
7227
7308
  this.recordingMode = 'buffer';
7228
7309
  }
7229
7310
 
7311
+ logInfoNextTick(
7312
+ `[Replay] Starting replay in ${this.recordingMode} mode`,
7313
+ this._options._experiments.traceInternals,
7314
+ );
7315
+
7230
7316
  this._initializeRecording();
7231
7317
  }
7232
7318
 
@@ -7246,6 +7332,8 @@ class ReplayContainer {
7246
7332
  throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
7247
7333
  }
7248
7334
 
7335
+ logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
7336
+
7249
7337
  const previousSessionId = this.session && this.session.id;
7250
7338
 
7251
7339
  const { session } = getSession({
@@ -7255,6 +7343,7 @@ class ReplayContainer {
7255
7343
  // This is intentional: create a new session-based replay when calling `start()`
7256
7344
  sessionSampleRate: 1,
7257
7345
  allowBuffering: false,
7346
+ traceInternals: this._options._experiments.traceInternals,
7258
7347
  });
7259
7348
 
7260
7349
  session.previousSessionId = previousSessionId;
@@ -7272,6 +7361,8 @@ class ReplayContainer {
7272
7361
  throw new Error('Replay recording is already in progress');
7273
7362
  }
7274
7363
 
7364
+ logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
7365
+
7275
7366
  const previousSessionId = this.session && this.session.id;
7276
7367
 
7277
7368
  const { session } = getSession({
@@ -7280,6 +7371,7 @@ class ReplayContainer {
7280
7371
  currentSession: this.session,
7281
7372
  sessionSampleRate: 0,
7282
7373
  allowBuffering: true,
7374
+ traceInternals: this._options._experiments.traceInternals,
7283
7375
  });
7284
7376
 
7285
7377
  session.previousSessionId = previousSessionId;
@@ -7340,15 +7432,10 @@ class ReplayContainer {
7340
7432
  }
7341
7433
 
7342
7434
  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
- }
7435
+ logInfo(
7436
+ `[Replay] Stopping Replay${reason ? ` triggered by ${reason}` : ''}`,
7437
+ this._options._experiments.traceInternals,
7438
+ );
7352
7439
 
7353
7440
  // We can't move `_isEnabled` after awaiting a flush, otherwise we can
7354
7441
  // enter into an infinite loop when `stop()` is called while flushing.
@@ -7381,8 +7468,14 @@ class ReplayContainer {
7381
7468
  * not as thorough of a shutdown as `stop()`.
7382
7469
  */
7383
7470
  pause() {
7471
+ if (this._isPaused) {
7472
+ return;
7473
+ }
7474
+
7384
7475
  this._isPaused = true;
7385
7476
  this.stopRecording();
7477
+
7478
+ logInfo('[Replay] Pausing replay', this._options._experiments.traceInternals);
7386
7479
  }
7387
7480
 
7388
7481
  /**
@@ -7392,12 +7485,14 @@ class ReplayContainer {
7392
7485
  * new DOM checkout.`
7393
7486
  */
7394
7487
  resume() {
7395
- if (!this._loadAndCheckSession()) {
7488
+ if (!this._isPaused || !this._loadAndCheckSession()) {
7396
7489
  return;
7397
7490
  }
7398
7491
 
7399
7492
  this._isPaused = false;
7400
7493
  this.startRecording();
7494
+
7495
+ logInfo('[Replay] Resuming replay', this._options._experiments.traceInternals);
7401
7496
  }
7402
7497
 
7403
7498
  /**
@@ -7414,9 +7509,7 @@ class ReplayContainer {
7414
7509
 
7415
7510
  const activityTime = Date.now();
7416
7511
 
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}`);
7512
+ logInfo('[Replay] Converting buffer to session', this._options._experiments.traceInternals);
7420
7513
 
7421
7514
  // Allow flush to complete before resuming as a session recording, otherwise
7422
7515
  // the checkout from `startRecording` may be included in the payload.
@@ -7430,10 +7523,12 @@ class ReplayContainer {
7430
7523
  return;
7431
7524
  }
7432
7525
 
7433
- // Re-start recording, but in "session" recording mode
7526
+ // To avoid race conditions where this is called multiple times, we check here again that we are still buffering
7527
+ if ((this.recordingMode ) === 'session') {
7528
+ return;
7529
+ }
7434
7530
 
7435
- // Reset all "capture on error" configuration before
7436
- // starting a new recording
7531
+ // Re-start recording in session-mode
7437
7532
  this.recordingMode = 'session';
7438
7533
 
7439
7534
  // Once this session ends, we do not want to refresh it
@@ -7450,7 +7545,6 @@ class ReplayContainer {
7450
7545
  // (length of buffer), which we are ok with.
7451
7546
  this._updateUserActivity(activityTime);
7452
7547
  this._updateSessionActivity(activityTime);
7453
- this.session.started = activityTime;
7454
7548
  this._maybeSaveSession();
7455
7549
  }
7456
7550
 
@@ -7724,6 +7818,7 @@ class ReplayContainer {
7724
7818
  currentSession: this.session,
7725
7819
  sessionSampleRate: this._options.sessionSampleRate,
7726
7820
  allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer',
7821
+ traceInternals: this._options._experiments.traceInternals,
7727
7822
  });
7728
7823
 
7729
7824
  // If session was newly created (i.e. was not loaded from storage), then
@@ -7740,7 +7835,7 @@ class ReplayContainer {
7740
7835
  this.session = session;
7741
7836
 
7742
7837
  if (!this.session.sampled) {
7743
- void this.stop('session unsampled');
7838
+ void this.stop('session not refreshed');
7744
7839
  return false;
7745
7840
  }
7746
7841
 
@@ -7796,7 +7891,7 @@ class ReplayContainer {
7796
7891
 
7797
7892
  if (this._performanceObserver) {
7798
7893
  this._performanceObserver.disconnect();
7799
- this._performanceObserver = null;
7894
+ this._performanceObserver = undefined;
7800
7895
  }
7801
7896
  } catch (err) {
7802
7897
  this._handleException(err);
@@ -7809,7 +7904,7 @@ class ReplayContainer {
7809
7904
  * be hidden. Likewise, moving a different window to cover the contents of the
7810
7905
  * page will also trigger a change to a hidden state.
7811
7906
  */
7812
- __init13() {this._handleVisibilityChange = () => {
7907
+ __init() {this._handleVisibilityChange = () => {
7813
7908
  if (WINDOW.document.visibilityState === 'visible') {
7814
7909
  this._doChangeToForegroundTasks();
7815
7910
  } else {
@@ -7820,7 +7915,7 @@ class ReplayContainer {
7820
7915
  /**
7821
7916
  * Handle when page is blurred
7822
7917
  */
7823
- __init14() {this._handleWindowBlur = () => {
7918
+ __init2() {this._handleWindowBlur = () => {
7824
7919
  const breadcrumb = createBreadcrumb({
7825
7920
  category: 'ui.blur',
7826
7921
  });
@@ -7833,7 +7928,7 @@ class ReplayContainer {
7833
7928
  /**
7834
7929
  * Handle when page is focused
7835
7930
  */
7836
- __init15() {this._handleWindowFocus = () => {
7931
+ __init3() {this._handleWindowFocus = () => {
7837
7932
  const breadcrumb = createBreadcrumb({
7838
7933
  category: 'ui.focus',
7839
7934
  });
@@ -7844,7 +7939,7 @@ class ReplayContainer {
7844
7939
  };}
7845
7940
 
7846
7941
  /** Ensure page remains active when a key is pressed. */
7847
- __init16() {this._handleKeyboardEvent = (event) => {
7942
+ __init4() {this._handleKeyboardEvent = (event) => {
7848
7943
  handleKeyboardEvent(this, event);
7849
7944
  };}
7850
7945
 
@@ -7882,7 +7977,7 @@ class ReplayContainer {
7882
7977
  // If the user has come back to the page within SESSION_IDLE_PAUSE_DURATION
7883
7978
  // ms, we will re-use the existing session, otherwise create a new
7884
7979
  // session
7885
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Document has become active, but session has expired');
7980
+ logInfo('[Replay] Document has become active, but session has expired');
7886
7981
  return;
7887
7982
  }
7888
7983
 
@@ -7897,7 +7992,7 @@ class ReplayContainer {
7897
7992
  */
7898
7993
  _triggerFullSnapshot(checkout = true) {
7899
7994
  try {
7900
- (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log('[Replay] Taking full rrweb snapshot');
7995
+ logInfo('[Replay] Taking full rrweb snapshot');
7901
7996
  record.takeFullSnapshot(checkout);
7902
7997
  } catch (err) {
7903
7998
  this._handleException(err);
@@ -8067,7 +8162,7 @@ class ReplayContainer {
8067
8162
  * Flush recording data to Sentry. Creates a lock so that only a single flush
8068
8163
  * can be active at a time. Do not call this directly.
8069
8164
  */
8070
- __init17() {this._flush = async ({
8165
+ __init5() {this._flush = async ({
8071
8166
  force = false,
8072
8167
  }
8073
8168
 
@@ -8091,28 +8186,39 @@ class ReplayContainer {
8091
8186
  const now = Date.now();
8092
8187
  const duration = now - start;
8093
8188
 
8189
+ // A flush is about to happen, cancel any queued flushes
8190
+ this._debouncedFlush.cancel();
8191
+
8094
8192
  // If session is too short, or too long (allow some wiggle room over maxSessionLife), do not send it
8095
8193
  // 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
- );
8194
+ const tooShort = duration < this._options.minReplayDuration;
8195
+ const tooLong = duration > this.timeouts.maxSessionLife + 5000;
8196
+ if (tooShort || tooLong) {
8197
+ logInfo(
8198
+ `[Replay] Session duration (${Math.floor(duration / 1000)}s) is too ${
8199
+ tooShort ? 'short' : 'long'
8200
+ }, not sending replay.`,
8201
+ this._options._experiments.traceInternals,
8202
+ );
8103
8203
 
8204
+ if (tooShort) {
8205
+ this._debouncedFlush();
8206
+ }
8104
8207
  return;
8105
8208
  }
8106
8209
 
8107
- // A flush is about to happen, cancel any queued flushes
8108
- this._debouncedFlush.cancel();
8210
+ const eventBuffer = this.eventBuffer;
8211
+ if (eventBuffer && this.session.segmentId === 0 && !eventBuffer.hasCheckout) {
8212
+ logInfo('[Replay] Flushing initial segment without checkout.', this._options._experiments.traceInternals);
8213
+ // TODO FN: Evaluate if we want to stop here, or remove this again?
8214
+ }
8109
8215
 
8110
8216
  // this._flushLock acts as a lock so that future calls to `_flush()`
8111
8217
  // will be blocked until this promise resolves
8112
8218
  if (!this._flushLock) {
8113
8219
  this._flushLock = this._runFlush();
8114
8220
  await this._flushLock;
8115
- this._flushLock = null;
8221
+ this._flushLock = undefined;
8116
8222
  return;
8117
8223
  }
8118
8224
 
@@ -8139,7 +8245,7 @@ class ReplayContainer {
8139
8245
  }
8140
8246
 
8141
8247
  /** Handler for rrweb.record.onMutation */
8142
- __init18() {this._onMutationHandler = (mutations) => {
8248
+ __init6() {this._onMutationHandler = (mutations) => {
8143
8249
  const count = mutations.length;
8144
8250
 
8145
8251
  const mutationLimit = this._options.mutationLimit;
@@ -8289,7 +8395,6 @@ class Replay {
8289
8395
  /**
8290
8396
  * @inheritDoc
8291
8397
  */
8292
- __init() {this.name = Replay.id;}
8293
8398
 
8294
8399
  /**
8295
8400
  * Options to pass to `rrweb.record()`
@@ -8349,7 +8454,9 @@ class Replay {
8349
8454
  maskTextSelector,
8350
8455
  // eslint-disable-next-line deprecation/deprecation
8351
8456
  ignoreClass,
8352
- } = {}) {Replay.prototype.__init.call(this);
8457
+ } = {}) {
8458
+ this.name = Replay.id;
8459
+
8353
8460
  this._recordingOptions = {
8354
8461
  maskAllInputs,
8355
8462
  maskAllText,