diff --git a/.size-limit.js b/.size-limit.js index 8a7670e6d638..a244ccb2a2a3 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -86,7 +86,7 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'), gzip: true, - limit: '91 KB', + limit: '95 KB', }, { name: '@sentry/browser (incl. Tracing, Replay, Feedback, metrics)', diff --git a/packages/replay-internal/src/util/handleRecordingEmit.ts b/packages/replay-internal/src/util/handleRecordingEmit.ts index 0467edefa9a2..4f4637276116 100644 --- a/packages/replay-internal/src/util/handleRecordingEmit.ts +++ b/packages/replay-internal/src/util/handleRecordingEmit.ts @@ -71,16 +71,6 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa // only be added for checkouts addSettingsEvent(replay, isCheckout); - // If there is a previousSessionId after a full snapshot occurs, then - // the replay session was started due to session expiration. The new session - // is started before triggering a new checkout and contains the id - // of the previous session. Do not immediately flush in this case - // to avoid capturing only the checkout and instead the replay will - // be captured if they perform any follow-up actions. - if (session && session.previousSessionId) { - return true; - } - // When in buffer mode, make sure we adjust the session started date to the current earliest event of the buffer // this should usually be the timestamp of the checkout event, but to be safe... if (replay.recordingMode === 'buffer' && session && replay.eventBuffer) { @@ -97,6 +87,16 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa } } + // If there is a previousSessionId after a full snapshot occurs, then + // the replay session was started due to session expiration. The new session + // is started before triggering a new checkout and contains the id + // of the previous session. Do not immediately flush in this case + // to avoid capturing only the checkout and instead the replay will + // be captured if they perform any follow-up actions. + if (session && session.previousSessionId) { + return true; + } + if (replay.recordingMode === 'session') { // If the full snapshot is due to an initial load, we will not have // a previous session ID. In this case, we want to buffer events diff --git a/packages/replay-internal/test/integration/errorSampleRate.test.ts b/packages/replay-internal/test/integration/errorSampleRate.test.ts index 3763ef83de44..e81d9df1b43d 100644 --- a/packages/replay-internal/test/integration/errorSampleRate.test.ts +++ b/packages/replay-internal/test/integration/errorSampleRate.test.ts @@ -148,6 +148,90 @@ describe('Integration | errorSampleRate', () => { }); }); + it('loads an old session with a previousSessionId set and can send buffered replay', async () => { + vi.mock('../../src/session/fetchSession', () => ({ + fetchSession: () => ({ + id: 'newreplayid', + started: BASE_TIMESTAMP, + lastActivity: BASE_TIMESTAMP, + segmentId: 0, + sampled: 'buffer', + previousSessionId: 'previoussessionid', + }), + })); + + const ADVANCED_TIME = 86400000; + const optionsEvent = createOptionsEvent(replay); + + expect(replay.session.started).toBe(BASE_TIMESTAMP); + + // advance time to make sure replay duration is invalid + vi.advanceTimersByTime(ADVANCED_TIME); + + // full snapshot should update session start time + mockRecord.takeFullSnapshot(true); + expect(replay.session.started).toBe(BASE_TIMESTAMP + ADVANCED_TIME); + expect(replay.recordingMode).toBe('buffer'); + + // advance so we can flush + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + + captureException(new Error('testing')); + await vi.advanceTimersToNextTimerAsync(); + + // Converts to session mode + expect(replay.recordingMode).toBe('session'); + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + replayEventPayload: expect.objectContaining({ + replay_type: 'buffer', + }), + recordingData: JSON.stringify([ + { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + ADVANCED_TIME, type: 2 }, + { ...optionsEvent, timestamp: BASE_TIMESTAMP + ADVANCED_TIME }, + ]), + }); + + // capture next event + domHandler({ + name: 'click', + event: new Event('click'), + }); + + vi.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await vi.advanceTimersToNextTimerAsync(); + + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 1 }, + replayEventPayload: expect.objectContaining({ + // We don't change replay_type as it starts in buffer mode and that's + // what we're interested in, even though recordingMode changes to + // 'session' + replay_type: 'buffer', + }), + recordingData: JSON.stringify([ + // There's a new checkout because we convert to session mode + { data: { isCheckout: true }, timestamp: BASE_TIMESTAMP + ADVANCED_TIME + DEFAULT_FLUSH_MIN_DELAY, type: 2 }, + { + type: 5, + timestamp: BASE_TIMESTAMP + ADVANCED_TIME + DEFAULT_FLUSH_MIN_DELAY, + data: { + tag: 'breadcrumb', + payload: { + timestamp: (BASE_TIMESTAMP + ADVANCED_TIME + DEFAULT_FLUSH_MIN_DELAY) / 1000, + type: 'default', + category: 'ui.click', + message: '', + data: {}, + }, + }, + }, + ]), + }); + vi.unmock('../../src/session/fetchSession'); + await waitForFlush(); + }); + it('manually flushes replay and does not continue to record', async () => { const TEST_EVENT = getTestEventIncremental({ timestamp: BASE_TIMESTAMP }); mockRecord._emitter(TEST_EVENT);