Skip to content

Commit e586397

Browse files
mydeabillyvg
andauthored
fix(replay): Ensure buffer->session switch is reliable (#8712)
This does two things: 1. Ensure we switch the `recordingMode` only once, to avoid running this process twice if e.g. multiple errors happen 2. Do not update `session.started` property when switching buffer->session. We just keep started at whatever was the first event sent. --------- Co-authored-by: Billy Vong <[email protected]>
1 parent 2748691 commit e586397

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

packages/replay/src/replay.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,12 @@ export class ReplayContainer implements ReplayContainerInterface {
462462
return;
463463
}
464464

465-
// Re-start recording, but in "session" recording mode
465+
// To avoid race conditions where this is called multiple times, we check here again that we are still buffering
466+
if ((this.recordingMode as ReplayRecordingMode) === 'session') {
467+
return;
468+
}
466469

467-
// Reset all "capture on error" configuration before
468-
// starting a new recording
470+
// Re-start recording in session-mode
469471
this.recordingMode = 'session';
470472

471473
// Once this session ends, we do not want to refresh it
@@ -482,7 +484,6 @@ export class ReplayContainer implements ReplayContainerInterface {
482484
// (length of buffer), which we are ok with.
483485
this._updateUserActivity(activityTime);
484486
this._updateSessionActivity(activityTime);
485-
this.session.started = activityTime;
486487
this._maybeSaveSession();
487488
}
488489

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

+67-1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,71 @@ describe('Integration | errorSampleRate', () => {
226226
});
227227
});
228228

229+
it('handles multiple simultaneous flushes', async () => {
230+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 };
231+
mockRecord._emitter(TEST_EVENT);
232+
const optionsEvent = createOptionsEvent(replay);
233+
234+
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
235+
expect(replay).not.toHaveLastSentReplay();
236+
237+
// Does not capture on mouse click
238+
domHandler({
239+
name: 'click',
240+
});
241+
jest.runAllTimers();
242+
await new Promise(process.nextTick);
243+
expect(replay).not.toHaveLastSentReplay();
244+
245+
replay.sendBufferedReplayOrFlush({ continueRecording: true });
246+
replay.sendBufferedReplayOrFlush({ continueRecording: true });
247+
248+
await waitForBufferFlush();
249+
250+
expect(replay).toHaveSentReplay({
251+
recordingPayloadHeader: { segment_id: 0 },
252+
replayEventPayload: expect.objectContaining({
253+
replay_type: 'buffer',
254+
}),
255+
recordingData: JSON.stringify([
256+
{ data: { isCheckout: true }, timestamp: BASE_TIMESTAMP, type: 2 },
257+
optionsEvent,
258+
TEST_EVENT,
259+
{
260+
type: 5,
261+
timestamp: BASE_TIMESTAMP,
262+
data: {
263+
tag: 'breadcrumb',
264+
payload: {
265+
timestamp: BASE_TIMESTAMP / 1000,
266+
type: 'default',
267+
category: 'ui.click',
268+
message: '<unknown>',
269+
data: {},
270+
},
271+
},
272+
},
273+
]),
274+
});
275+
276+
jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY);
277+
// Check that click will not get captured
278+
domHandler({
279+
name: 'click',
280+
});
281+
282+
await waitForFlush();
283+
284+
// This is still the last replay sent since we passed `continueRecording:
285+
// false`.
286+
expect(replay).toHaveLastSentReplay({
287+
recordingPayloadHeader: { segment_id: 1 },
288+
replayEventPayload: expect.objectContaining({
289+
replay_type: 'buffer',
290+
}),
291+
});
292+
});
293+
229294
// This tests a regression where we were calling flush indiscriminantly in `stop()`
230295
it('does not upload a replay event if error is not sampled', async () => {
231296
// We are trying to replicate the case where error rate is 0 and session
@@ -620,7 +685,8 @@ describe('Integration | errorSampleRate', () => {
620685

621686
await waitForBufferFlush();
622687

623-
expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + TICK + TICK);
688+
// This is still the timestamp from the full snapshot we took earlier
689+
expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + TICK);
624690

625691
// Does not capture mouse click
626692
expect(replay).toHaveSentReplay({

0 commit comments

Comments
 (0)