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