@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.
- package/cjs/index.js +225 -118
- package/cjs/index.js.map +1 -1
- package/esm/index.js +225 -118
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/types/coreHandlers/handleAfterSendEvent.d.ts.map +1 -1
- package/types/coreHandlers/handleClick.d.ts.map +1 -1
- package/types/coreHandlers/handleGlobalEvent.d.ts.map +1 -1
- package/types/eventBuffer/EventBufferArray.d.ts +2 -0
- package/types/eventBuffer/EventBufferArray.d.ts.map +1 -1
- package/types/eventBuffer/EventBufferCompressionWorker.d.ts +2 -0
- package/types/eventBuffer/EventBufferCompressionWorker.d.ts.map +1 -1
- package/types/eventBuffer/EventBufferProxy.d.ts +4 -0
- package/types/eventBuffer/EventBufferProxy.d.ts.map +1 -1
- package/types/eventBuffer/WorkerHandler.d.ts.map +1 -1
- package/types/eventBuffer/index.d.ts.map +1 -1
- package/types/integration.d.ts.map +1 -1
- package/types/replay.d.ts.map +1 -1
- package/types/session/Session.d.ts.map +1 -1
- package/types/session/createSession.d.ts.map +1 -1
- package/types/session/fetchSession.d.ts +1 -1
- package/types/session/fetchSession.d.ts.map +1 -1
- package/types/session/getSession.d.ts +2 -1
- package/types/session/getSession.d.ts.map +1 -1
- package/types/types/replay.d.ts +4 -0
- package/types/types/replay.d.ts.map +1 -1
- package/types/types/replayFrame.d.ts +4 -3
- package/types/types/replayFrame.d.ts.map +1 -1
- package/types/util/addEvent.d.ts.map +1 -1
- package/types/util/handleRecordingEmit.d.ts.map +1 -1
- package/types/util/log.d.ts +10 -0
- package/types/util/log.d.ts.map +1 -0
- 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
|
-
) {
|
|
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
|
-
|
|
4083
|
+
/** @inheritdoc */
|
|
4034
4084
|
|
|
4035
|
-
constructor() {
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
4468
|
+
logInfo('[Replay] Using compression worker');
|
|
4404
4469
|
const worker = new Worker(workerUrl);
|
|
4405
4470
|
return new EventBufferProxy(worker);
|
|
4406
4471
|
} catch (error) {
|
|
4407
|
-
(
|
|
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
|
-
(
|
|
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
|
|
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
|
-
(
|
|
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
|
-
|
|
4746
|
-
|
|
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
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
}
|
|
4820
|
+
handleErrorEvent(replay, event);
|
|
4821
|
+
};
|
|
4822
|
+
}
|
|
4756
4823
|
|
|
4757
|
-
|
|
4758
|
-
|
|
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
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
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
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
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
|
-
(
|
|
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);
|
|
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
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
(
|
|
8100
|
-
|
|
8101
|
-
|
|
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
|
-
|
|
8108
|
-
this.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
} = {}) {
|
|
8457
|
+
} = {}) {
|
|
8458
|
+
this.name = Replay.id;
|
|
8459
|
+
|
|
8353
8460
|
this._recordingOptions = {
|
|
8354
8461
|
maskAllInputs,
|
|
8355
8462
|
maskAllText,
|