Skip to content

Commit 5e312dc

Browse files
committed
fix(replay): Do not renew session in error mode
1 parent b86ac10 commit 5e312dc

File tree

3 files changed

+76
-11
lines changed

3 files changed

+76
-11
lines changed

packages/replay/src/replay.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ export class ReplayContainer implements ReplayContainerInterface {
264264
* new DOM checkout.`
265265
*/
266266
public resume(): void {
267+
if (!this.session || !this.session.sampled) {
268+
this.stop();
269+
return;
270+
}
271+
267272
this._isPaused = false;
268273
this.startRecording();
269274
}
@@ -312,6 +317,13 @@ export class ReplayContainer implements ReplayContainerInterface {
312317
// will get rejected due to an expired session.
313318
this._loadSession({ expiry: SESSION_IDLE_DURATION });
314319

320+
// We know this is set, because it is always set in _loadSession
321+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
322+
if (!this.session!.sampled) {
323+
this.stop();
324+
return;
325+
}
326+
315327
// Note: This will cause a new DOM checkout
316328
this.resume();
317329
return;
@@ -365,6 +377,13 @@ export class ReplayContainer implements ReplayContainerInterface {
365377
// This will create a new session if expired, based on expiry length
366378
this._loadSession({ expiry });
367379

380+
// We know this is set, because it is always set in _loadSession
381+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
382+
if (!this.session!.sampled) {
383+
this.stop();
384+
return;
385+
}
386+
368387
// Session was expired if session ids do not match
369388
const expired = oldSessionId !== this.getSessionId();
370389

packages/replay/src/session/getSession.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Session, SessionOptions } from '../types';
44
import { isSessionExpired } from '../util/isSessionExpired';
55
import { createSession } from './createSession';
66
import { fetchSession } from './fetchSession';
7+
import { makeSession } from './Session';
78

89
interface GetSessionParams extends SessionOptions {
910
/**
@@ -38,6 +39,10 @@ export function getSession({
3839

3940
if (!isExpired) {
4041
return { type: 'saved', session };
42+
} else if (session.sampled === 'error') {
43+
// Error samples should not be re-created when expired, but instead we stop when the replay is done
44+
const discardedSession = makeSession({ sampled: false });
45+
return { type: 'new', session: discardedSession };
4146
} else {
4247
__DEBUG_BUILD__ && logger.log('[Replay] Session has expired');
4348
}

packages/replay/test/integration/errorSampleRate.test.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { captureException } from '@sentry/core';
1+
import { captureException, getCurrentHub } from '@sentry/core';
22

33
import {
44
DEFAULT_FLUSH_MIN_DELAY,
55
ERROR_CHECKOUT_TIME,
6+
MAX_SESSION_LIFE,
67
REPLAY_SESSION_KEY,
78
VISIBILITY_CHANGE_TIMEOUT,
89
WINDOW,
@@ -264,12 +265,10 @@ describe('Integration | errorSampleRate', () => {
264265
expect(replay).not.toHaveLastSentReplay();
265266
});
266267

267-
it('does not upload if user has been idle for more than 15 minutes and comes back to move their mouse', async () => {
268+
it('stops replay if user has been idle for more than 15 minutes and comes back to move their mouse', async () => {
268269
// Idle for 15 minutes
269270
jest.advanceTimersByTime(15 * 60000);
270271

271-
// TBD: We are currently deciding that this event will get dropped, but
272-
// this could/should change in the future.
273272
const TEST_EVENT = {
274273
data: { name: 'lost event' },
275274
timestamp: BASE_TIMESTAMP,
@@ -281,15 +280,11 @@ describe('Integration | errorSampleRate', () => {
281280
jest.runAllTimers();
282281
await new Promise(process.nextTick);
283282

284-
// Instead of recording the above event, a full snapshot will occur.
285-
//
286-
// TODO: We could potentially figure out a way to save the last session,
287-
// and produce a checkout based on a previous checkout + updates, and then
288-
// replay the event on top. Or maybe replay the event on top of a refresh
289-
// snapshot.
283+
// We stop recording after 15 minutes of inactivity in error mode
290284

291285
expect(replay).not.toHaveLastSentReplay();
292-
expect(mockRecord.takeFullSnapshot).toHaveBeenCalledWith(true);
286+
expect(replay.isEnabled()).toBe(false);
287+
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
293288
});
294289

295290
it('has the correct timestamps with deferred root event and last replay update', async () => {
@@ -375,6 +370,52 @@ describe('Integration | errorSampleRate', () => {
375370
]),
376371
});
377372
});
373+
374+
it('stops replay when session expires', async () => {
375+
jest.setSystemTime(BASE_TIMESTAMP);
376+
377+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
378+
mockRecord._emitter(TEST_EVENT);
379+
380+
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
381+
expect(replay).not.toHaveLastSentReplay();
382+
383+
jest.runAllTimers();
384+
await new Promise(process.nextTick);
385+
386+
captureException(new Error('testing'));
387+
388+
jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY);
389+
await new Promise(process.nextTick);
390+
391+
expect(replay).toHaveLastSentReplay();
392+
393+
// Wait a bit, shortly before session expires
394+
jest.advanceTimersByTime(MAX_SESSION_LIFE - 1000);
395+
await new Promise(process.nextTick);
396+
397+
mockRecord._emitter(TEST_EVENT);
398+
replay.triggerUserActivity();
399+
400+
expect(replay).toHaveLastSentReplay();
401+
402+
// Now wait after session expires - should stop recording
403+
mockRecord.takeFullSnapshot.mockClear();
404+
(getCurrentHub().getClient()!.getTransport()!.send as unknown as jest.SpyInstance<any>).mockClear();
405+
406+
jest.advanceTimersByTime(10_000);
407+
await new Promise(process.nextTick);
408+
409+
mockRecord._emitter(TEST_EVENT);
410+
replay.triggerUserActivity();
411+
412+
jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY);
413+
await new Promise(process.nextTick);
414+
415+
expect(replay).not.toHaveLastSentReplay();
416+
expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0);
417+
expect(replay.isEnabled()).toBe(false);
418+
});
378419
});
379420

380421
/**

0 commit comments

Comments
 (0)