From 206567fc90708fd96fca6e7eacacc959a089c9ad Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 10 Aug 2023 11:53:49 +0200 Subject: [PATCH] fix(replay): Ensure we do not flush if flush took too long --- packages/replay/src/replay.ts | 11 ++++- .../replay/test/integration/flush.test.ts | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index dd82e26f3d57..4a0a7e91d97d 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -1067,6 +1067,15 @@ export class ReplayContainer implements ReplayContainerInterface { // Note this empties the event buffer regardless of outcome of sending replay const recordingData = await this.eventBuffer.finish(); + const timestamp = Date.now(); + + // Check total duration again, to avoid sending outdated stuff + // We leave 30s wiggle room to accomodate late flushing etc. + // This _could_ happen when the browser is suspended during flushing, in which case we just want to stop + if (timestamp - this._context.initialTimestamp > this.timeouts.maxSessionLife + 30_000) { + throw new Error('Session is too long, not sending replay'); + } + // NOTE: Copy values from instance members, as it's possible they could // change before the flush finishes. const replayId = this.session.id; @@ -1082,7 +1091,7 @@ export class ReplayContainer implements ReplayContainerInterface { eventContext, session: this.session, options: this.getOptions(), - timestamp: Date.now(), + timestamp, }); } catch (err) { this._handleException(err); diff --git a/packages/replay/test/integration/flush.test.ts b/packages/replay/test/integration/flush.test.ts index 6e6e02d86620..1e0b3b1366d6 100644 --- a/packages/replay/test/integration/flush.test.ts +++ b/packages/replay/test/integration/flush.test.ts @@ -442,4 +442,46 @@ describe('Integration | flush', () => { replay.getOptions()._experiments.traceInternals = false; }); + + /** + * This tests the case where a flush happens in time, + * but something takes too long (e.g. because we are idle, ...) + * so by the time we actually send the replay it's too late. + * In this case, we want to stop the replay. + */ + it('stops if flushing after maxSessionLife', async () => { + replay.timeouts.maxSessionLife = 100_000; + + sessionStorage.clear(); + clearSession(replay); + replay['_loadAndCheckSession'](); + await new Promise(process.nextTick); + jest.setSystemTime(BASE_TIMESTAMP); + + replay.eventBuffer!.clear(); + + // We do not care about this warning here + replay.eventBuffer!.hasCheckout = true; + + // We want to simulate that flushing happens _way_ late + replay['_addPerformanceEntries'] = () => { + return new Promise(resolve => setTimeout(resolve, 140_000)); + }; + + // Add event inside of session life timespan + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP + 100, type: 2 }; + mockRecord._emitter(TEST_EVENT); + + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); + await advanceTimers(160_000); + + expect(mockFlush).toHaveBeenCalledTimes(2); + expect(mockSendReplay).toHaveBeenCalledTimes(0); + expect(replay.isEnabled()).toBe(false); + + replay.timeouts.maxSessionLife = MAX_SESSION_LIFE; + + // Start again for following tests + await replay.start(); + }); });