@sentry/replay 7.61.1 → 7.63.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 +114 -31
- package/cjs/index.js.map +1 -1
- package/esm/index.js +114 -31
- package/esm/index.js.map +1 -1
- package/package.json +5 -5
- package/types/coreHandlers/handleClick.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/replay.d.ts +4 -1
- package/types/replay.d.ts.map +1 -1
- package/types/types/replay.d.ts +8 -1
- package/types/types/replay.d.ts.map +1 -1
- package/types/util/addEvent.d.ts.map +1 -1
- package/types/util/log.d.ts +5 -0
- package/types/util/log.d.ts.map +1 -1
package/esm/index.js
CHANGED
|
@@ -3496,6 +3496,14 @@ class ClickDetector {
|
|
|
3496
3496
|
clickCount: 0,
|
|
3497
3497
|
node,
|
|
3498
3498
|
};
|
|
3499
|
+
|
|
3500
|
+
// If there was a click in the last 1s on the same element, ignore it - only keep a single reference per second
|
|
3501
|
+
if (
|
|
3502
|
+
this._clicks.some(click => click.node === newClick.node && Math.abs(click.timestamp - newClick.timestamp) < 1)
|
|
3503
|
+
) {
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3499
3507
|
this._clicks.push(newClick);
|
|
3500
3508
|
|
|
3501
3509
|
// If this is the first new click, set a timeout to check for multi clicks
|
|
@@ -4025,21 +4033,45 @@ function logInfo(message, shouldAddBreadcrumb) {
|
|
|
4025
4033
|
logger.info(message);
|
|
4026
4034
|
|
|
4027
4035
|
if (shouldAddBreadcrumb) {
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4036
|
+
addBreadcrumb(message);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
/**
|
|
4041
|
+
* Log a message, and add a breadcrumb in the next tick.
|
|
4042
|
+
* This is necessary when the breadcrumb may be added before the replay is initialized.
|
|
4043
|
+
*/
|
|
4044
|
+
function logInfoNextTick(message, shouldAddBreadcrumb) {
|
|
4045
|
+
if (!(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
logger.info(message);
|
|
4050
|
+
|
|
4051
|
+
if (shouldAddBreadcrumb) {
|
|
4052
|
+
// Wait a tick here to avoid race conditions for some initial logs
|
|
4053
|
+
// which may be added before replay is initialized
|
|
4054
|
+
setTimeout(() => {
|
|
4055
|
+
addBreadcrumb(message);
|
|
4056
|
+
}, 0);
|
|
4040
4057
|
}
|
|
4041
4058
|
}
|
|
4042
4059
|
|
|
4060
|
+
function addBreadcrumb(message) {
|
|
4061
|
+
const hub = getCurrentHub();
|
|
4062
|
+
hub.addBreadcrumb(
|
|
4063
|
+
{
|
|
4064
|
+
category: 'console',
|
|
4065
|
+
data: {
|
|
4066
|
+
logger: 'replay',
|
|
4067
|
+
},
|
|
4068
|
+
level: 'info',
|
|
4069
|
+
message,
|
|
4070
|
+
},
|
|
4071
|
+
{ level: 'info' },
|
|
4072
|
+
);
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4043
4075
|
/** This error indicates that the event buffer size exceeded the limit.. */
|
|
4044
4076
|
class EventBufferSizeExceededError extends Error {
|
|
4045
4077
|
constructor() {
|
|
@@ -4054,9 +4086,12 @@ class EventBufferSizeExceededError extends Error {
|
|
|
4054
4086
|
class EventBufferArray {
|
|
4055
4087
|
/** All the events that are buffered to be sent. */
|
|
4056
4088
|
|
|
4089
|
+
/** @inheritdoc */
|
|
4090
|
+
|
|
4057
4091
|
constructor() {
|
|
4058
4092
|
this.events = [];
|
|
4059
4093
|
this._totalSize = 0;
|
|
4094
|
+
this.hasCheckout = false;
|
|
4060
4095
|
}
|
|
4061
4096
|
|
|
4062
4097
|
/** @inheritdoc */
|
|
@@ -4101,6 +4136,7 @@ class EventBufferArray {
|
|
|
4101
4136
|
clear() {
|
|
4102
4137
|
this.events = [];
|
|
4103
4138
|
this._totalSize = 0;
|
|
4139
|
+
this.hasCheckout = false;
|
|
4104
4140
|
}
|
|
4105
4141
|
|
|
4106
4142
|
/** @inheritdoc */
|
|
@@ -4220,11 +4256,13 @@ class WorkerHandler {
|
|
|
4220
4256
|
* Exported only for testing.
|
|
4221
4257
|
*/
|
|
4222
4258
|
class EventBufferCompressionWorker {
|
|
4259
|
+
/** @inheritdoc */
|
|
4223
4260
|
|
|
4224
4261
|
constructor(worker) {
|
|
4225
4262
|
this._worker = new WorkerHandler(worker);
|
|
4226
4263
|
this._earliestTimestamp = null;
|
|
4227
4264
|
this._totalSize = 0;
|
|
4265
|
+
this.hasCheckout = false;
|
|
4228
4266
|
}
|
|
4229
4267
|
|
|
4230
4268
|
/** @inheritdoc */
|
|
@@ -4284,6 +4322,8 @@ class EventBufferCompressionWorker {
|
|
|
4284
4322
|
clear() {
|
|
4285
4323
|
this._earliestTimestamp = null;
|
|
4286
4324
|
this._totalSize = 0;
|
|
4325
|
+
this.hasCheckout = false;
|
|
4326
|
+
|
|
4287
4327
|
// We do not wait on this, as we assume the order of messages is consistent for the worker
|
|
4288
4328
|
void this._worker.postMessage('clear');
|
|
4289
4329
|
}
|
|
@@ -4338,6 +4378,15 @@ class EventBufferProxy {
|
|
|
4338
4378
|
return this._used.hasEvents;
|
|
4339
4379
|
}
|
|
4340
4380
|
|
|
4381
|
+
/** @inheritdoc */
|
|
4382
|
+
get hasCheckout() {
|
|
4383
|
+
return this._used.hasCheckout;
|
|
4384
|
+
}
|
|
4385
|
+
/** @inheritdoc */
|
|
4386
|
+
set hasCheckout(value) {
|
|
4387
|
+
this._used.hasCheckout = value;
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4341
4390
|
/** @inheritDoc */
|
|
4342
4391
|
destroy() {
|
|
4343
4392
|
this._fallback.destroy();
|
|
@@ -4393,13 +4442,15 @@ class EventBufferProxy {
|
|
|
4393
4442
|
|
|
4394
4443
|
/** Switch the used buffer to the compression worker. */
|
|
4395
4444
|
async _switchToCompressionWorker() {
|
|
4396
|
-
const { events } = this._fallback;
|
|
4445
|
+
const { events, hasCheckout } = this._fallback;
|
|
4397
4446
|
|
|
4398
4447
|
const addEventPromises = [];
|
|
4399
4448
|
for (const event of events) {
|
|
4400
4449
|
addEventPromises.push(this._compression.addEvent(event));
|
|
4401
4450
|
}
|
|
4402
4451
|
|
|
4452
|
+
this._compression.hasCheckout = hasCheckout;
|
|
4453
|
+
|
|
4403
4454
|
// We switch over to the new buffer immediately - any further events will be added
|
|
4404
4455
|
// after the previously buffered ones
|
|
4405
4456
|
this._used = this._compression;
|
|
@@ -4599,7 +4650,7 @@ function fetchSession(traceInternals) {
|
|
|
4599
4650
|
|
|
4600
4651
|
const sessionObj = JSON.parse(sessionStringFromStorage) ;
|
|
4601
4652
|
|
|
4602
|
-
|
|
4653
|
+
logInfoNextTick('[Replay] Loading existing session', traceInternals);
|
|
4603
4654
|
|
|
4604
4655
|
return makeSession(sessionObj);
|
|
4605
4656
|
} catch (e) {
|
|
@@ -4635,10 +4686,10 @@ function getSession({
|
|
|
4635
4686
|
// and when this session is expired, it will not be renewed until user
|
|
4636
4687
|
// reloads.
|
|
4637
4688
|
const discardedSession = makeSession({ sampled: false });
|
|
4638
|
-
|
|
4689
|
+
logInfoNextTick('[Replay] Session should not be refreshed', traceInternals);
|
|
4639
4690
|
return { type: 'new', session: discardedSession };
|
|
4640
4691
|
} else {
|
|
4641
|
-
|
|
4692
|
+
logInfoNextTick('[Replay] Session has expired', traceInternals);
|
|
4642
4693
|
}
|
|
4643
4694
|
// Otherwise continue to create a new session
|
|
4644
4695
|
}
|
|
@@ -4648,7 +4699,7 @@ function getSession({
|
|
|
4648
4699
|
sessionSampleRate,
|
|
4649
4700
|
allowBuffering,
|
|
4650
4701
|
});
|
|
4651
|
-
|
|
4702
|
+
logInfoNextTick('[Replay] Created new session', traceInternals);
|
|
4652
4703
|
|
|
4653
4704
|
return { type: 'new', session: newSession };
|
|
4654
4705
|
}
|
|
@@ -4686,11 +4737,24 @@ async function addEvent(
|
|
|
4686
4737
|
return null;
|
|
4687
4738
|
}
|
|
4688
4739
|
|
|
4740
|
+
// Throw out events that are +60min from the initial timestamp
|
|
4741
|
+
if (timestampInMs > replay.getContext().initialTimestamp + replay.timeouts.maxSessionLife) {
|
|
4742
|
+
logInfo(
|
|
4743
|
+
`[Replay] Skipping event with timestamp ${timestampInMs} because it is after maxSessionLife`,
|
|
4744
|
+
replay.getOptions()._experiments.traceInternals,
|
|
4745
|
+
);
|
|
4746
|
+
return null;
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4689
4749
|
try {
|
|
4690
|
-
if (isCheckout) {
|
|
4750
|
+
if (isCheckout && replay.recordingMode === 'buffer') {
|
|
4691
4751
|
replay.eventBuffer.clear();
|
|
4692
4752
|
}
|
|
4693
4753
|
|
|
4754
|
+
if (isCheckout) {
|
|
4755
|
+
replay.eventBuffer.hasCheckout = true;
|
|
4756
|
+
}
|
|
4757
|
+
|
|
4694
4758
|
const replayOptions = replay.getOptions();
|
|
4695
4759
|
|
|
4696
4760
|
const eventAfterPossibleCallback = maybeApplyCallback(event, replayOptions.beforeAddRecordingEvent);
|
|
@@ -4704,7 +4768,7 @@ async function addEvent(
|
|
|
4704
4768
|
const reason = error && error instanceof EventBufferSizeExceededError ? 'addEventSizeExceeded' : 'addEvent';
|
|
4705
4769
|
|
|
4706
4770
|
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error(error);
|
|
4707
|
-
await replay.stop(reason);
|
|
4771
|
+
await replay.stop({ reason });
|
|
4708
4772
|
|
|
4709
4773
|
const client = getCurrentHub().getClient();
|
|
4710
4774
|
|
|
@@ -7264,7 +7328,10 @@ class ReplayContainer {
|
|
|
7264
7328
|
this.recordingMode = 'buffer';
|
|
7265
7329
|
}
|
|
7266
7330
|
|
|
7267
|
-
|
|
7331
|
+
logInfoNextTick(
|
|
7332
|
+
`[Replay] Starting replay in ${this.recordingMode} mode`,
|
|
7333
|
+
this._options._experiments.traceInternals,
|
|
7334
|
+
);
|
|
7268
7335
|
|
|
7269
7336
|
this._initializeRecording();
|
|
7270
7337
|
}
|
|
@@ -7285,7 +7352,7 @@ class ReplayContainer {
|
|
|
7285
7352
|
throw new Error('Replay buffering is in progress, call `flush()` to save the replay');
|
|
7286
7353
|
}
|
|
7287
7354
|
|
|
7288
|
-
|
|
7355
|
+
logInfoNextTick('[Replay] Starting replay in session mode', this._options._experiments.traceInternals);
|
|
7289
7356
|
|
|
7290
7357
|
const previousSessionId = this.session && this.session.id;
|
|
7291
7358
|
|
|
@@ -7314,7 +7381,7 @@ class ReplayContainer {
|
|
|
7314
7381
|
throw new Error('Replay recording is already in progress');
|
|
7315
7382
|
}
|
|
7316
7383
|
|
|
7317
|
-
|
|
7384
|
+
logInfoNextTick('[Replay] Starting replay in buffer mode', this._options._experiments.traceInternals);
|
|
7318
7385
|
|
|
7319
7386
|
const previousSessionId = this.session && this.session.id;
|
|
7320
7387
|
|
|
@@ -7379,7 +7446,7 @@ class ReplayContainer {
|
|
|
7379
7446
|
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
|
|
7380
7447
|
* does not support a teardown
|
|
7381
7448
|
*/
|
|
7382
|
-
async stop(reason) {
|
|
7449
|
+
async stop({ forceFlush = false, reason } = {}) {
|
|
7383
7450
|
if (!this._isEnabled) {
|
|
7384
7451
|
return;
|
|
7385
7452
|
}
|
|
@@ -7399,7 +7466,7 @@ class ReplayContainer {
|
|
|
7399
7466
|
this._debouncedFlush.cancel();
|
|
7400
7467
|
// See comment above re: `_isEnabled`, we "force" a flush, ignoring the
|
|
7401
7468
|
// `_isEnabled` state of the plugin since it was disabled above.
|
|
7402
|
-
if (
|
|
7469
|
+
if (forceFlush) {
|
|
7403
7470
|
await this._flush({ force: true });
|
|
7404
7471
|
}
|
|
7405
7472
|
|
|
@@ -7788,7 +7855,7 @@ class ReplayContainer {
|
|
|
7788
7855
|
this.session = session;
|
|
7789
7856
|
|
|
7790
7857
|
if (!this.session.sampled) {
|
|
7791
|
-
void this.stop('session not refreshed');
|
|
7858
|
+
void this.stop({ reason: 'session not refreshed' });
|
|
7792
7859
|
return false;
|
|
7793
7860
|
}
|
|
7794
7861
|
|
|
@@ -8078,6 +8145,15 @@ class ReplayContainer {
|
|
|
8078
8145
|
// Note this empties the event buffer regardless of outcome of sending replay
|
|
8079
8146
|
const recordingData = await this.eventBuffer.finish();
|
|
8080
8147
|
|
|
8148
|
+
const timestamp = Date.now();
|
|
8149
|
+
|
|
8150
|
+
// Check total duration again, to avoid sending outdated stuff
|
|
8151
|
+
// We leave 30s wiggle room to accomodate late flushing etc.
|
|
8152
|
+
// This _could_ happen when the browser is suspended during flushing, in which case we just want to stop
|
|
8153
|
+
if (timestamp - this._context.initialTimestamp > this.timeouts.maxSessionLife + 30000) {
|
|
8154
|
+
throw new Error('Session is too long, not sending replay');
|
|
8155
|
+
}
|
|
8156
|
+
|
|
8081
8157
|
// NOTE: Copy values from instance members, as it's possible they could
|
|
8082
8158
|
// change before the flush finishes.
|
|
8083
8159
|
const replayId = this.session.id;
|
|
@@ -8093,7 +8169,7 @@ class ReplayContainer {
|
|
|
8093
8169
|
eventContext,
|
|
8094
8170
|
session: this.session,
|
|
8095
8171
|
options: this.getOptions(),
|
|
8096
|
-
timestamp
|
|
8172
|
+
timestamp,
|
|
8097
8173
|
});
|
|
8098
8174
|
} catch (err) {
|
|
8099
8175
|
this._handleException(err);
|
|
@@ -8101,7 +8177,7 @@ class ReplayContainer {
|
|
|
8101
8177
|
// This means we retried 3 times and all of them failed,
|
|
8102
8178
|
// or we ran into a problem we don't want to retry, like rate limiting.
|
|
8103
8179
|
// In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
|
|
8104
|
-
void this.stop('sendReplay');
|
|
8180
|
+
void this.stop({ reason: 'sendReplay' });
|
|
8105
8181
|
|
|
8106
8182
|
const client = getCurrentHub().getClient();
|
|
8107
8183
|
|
|
@@ -8139,6 +8215,9 @@ class ReplayContainer {
|
|
|
8139
8215
|
const now = Date.now();
|
|
8140
8216
|
const duration = now - start;
|
|
8141
8217
|
|
|
8218
|
+
// A flush is about to happen, cancel any queued flushes
|
|
8219
|
+
this._debouncedFlush.cancel();
|
|
8220
|
+
|
|
8142
8221
|
// If session is too short, or too long (allow some wiggle room over maxSessionLife), do not send it
|
|
8143
8222
|
// This _should_ not happen, but it may happen if flush is triggered due to a page activity change or similar
|
|
8144
8223
|
const tooShort = duration < this._options.minReplayDuration;
|
|
@@ -8148,6 +8227,7 @@ class ReplayContainer {
|
|
|
8148
8227
|
`[Replay] Session duration (${Math.floor(duration / 1000)}s) is too ${
|
|
8149
8228
|
tooShort ? 'short' : 'long'
|
|
8150
8229
|
}, not sending replay.`,
|
|
8230
|
+
this._options._experiments.traceInternals,
|
|
8151
8231
|
);
|
|
8152
8232
|
|
|
8153
8233
|
if (tooShort) {
|
|
@@ -8156,8 +8236,11 @@ class ReplayContainer {
|
|
|
8156
8236
|
return;
|
|
8157
8237
|
}
|
|
8158
8238
|
|
|
8159
|
-
|
|
8160
|
-
this.
|
|
8239
|
+
const eventBuffer = this.eventBuffer;
|
|
8240
|
+
if (eventBuffer && this.session.segmentId === 0 && !eventBuffer.hasCheckout) {
|
|
8241
|
+
logInfo('[Replay] Flushing initial segment without checkout.', this._options._experiments.traceInternals);
|
|
8242
|
+
// TODO FN: Evaluate if we want to stop here, or remove this again?
|
|
8243
|
+
}
|
|
8161
8244
|
|
|
8162
8245
|
// this._flushLock acts as a lock so that future calls to `_flush()`
|
|
8163
8246
|
// will be blocked until this promise resolves
|
|
@@ -8213,7 +8296,7 @@ class ReplayContainer {
|
|
|
8213
8296
|
|
|
8214
8297
|
// Stop replay if over the mutation limit
|
|
8215
8298
|
if (overMutationLimit) {
|
|
8216
|
-
void this.stop('mutationLimit');
|
|
8299
|
+
void this.stop({ reason: 'mutationLimit', forceFlush: this.recordingMode === 'session' });
|
|
8217
8300
|
return false;
|
|
8218
8301
|
}
|
|
8219
8302
|
|
|
@@ -8564,7 +8647,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
|
|
|
8564
8647
|
return Promise.resolve();
|
|
8565
8648
|
}
|
|
8566
8649
|
|
|
8567
|
-
return this._replay.stop();
|
|
8650
|
+
return this._replay.stop({ forceFlush: this._replay.recordingMode === 'session' });
|
|
8568
8651
|
}
|
|
8569
8652
|
|
|
8570
8653
|
/**
|