@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/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
|
-
) {
|
|
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
|
-
|
|
4081
|
+
/** @inheritdoc */
|
|
4032
4082
|
|
|
4033
|
-
constructor() {
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
4466
|
+
logInfo('[Replay] Using compression worker');
|
|
4402
4467
|
const worker = new Worker(workerUrl);
|
|
4403
4468
|
return new EventBufferProxy(worker);
|
|
4404
4469
|
} catch (error) {
|
|
4405
|
-
(
|
|
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
|
-
(
|
|
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
|
|
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
|
-
(
|
|
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
|
-
|
|
4744
|
-
|
|
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
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
}
|
|
4818
|
+
handleErrorEvent(replay, event);
|
|
4819
|
+
};
|
|
4820
|
+
}
|
|
4754
4821
|
|
|
4755
|
-
|
|
4756
|
-
|
|
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
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
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
|
-
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
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
|
-
(
|
|
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);
|
|
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
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
(
|
|
8098
|
-
|
|
8099
|
-
|
|
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
|
-
|
|
8106
|
-
this.
|
|
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 =
|
|
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
|
-
|
|
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
|
-
} = {}) {
|
|
8455
|
+
} = {}) {
|
|
8456
|
+
this.name = Replay.id;
|
|
8457
|
+
|
|
8351
8458
|
this._recordingOptions = {
|
|
8352
8459
|
maskAllInputs,
|
|
8353
8460
|
maskAllText,
|