Skip to content

Commit 60a60ad

Browse files
authored
feat(replay): Do not capture replays < 5 seconds (#7949)
Do not immediately flush on snapshot checkouts, instead delay by minimum flush delay (5 seconds). This means that we will not collect replays < 5 seconds. e.g. User opens site and immediately closes the tab. Ref getsentry/team-replay#63
1 parent 70df2c6 commit 60a60ad

File tree

4 files changed

+805
-13
lines changed

4 files changed

+805
-13
lines changed

packages/replay/src/replay.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,17 @@ export class ReplayContainer implements ReplayContainerInterface {
463463
}
464464

465465
/**
466-
*
466+
* Only flush if `this.recordingMode === 'session'`
467+
*/
468+
public conditionalFlush(): Promise<void> {
469+
if (this.recordingMode === 'buffer') {
470+
return Promise.resolve();
471+
}
472+
473+
return this.flushImmediate();
474+
}
475+
476+
/**
467477
* Always flush via `_debouncedFlush` so that we do not have flushes triggered
468478
* from calling both `flush` and `_debouncedFlush`. Otherwise, there could be
469479
* cases of mulitple flushes happening closely together.
@@ -474,6 +484,13 @@ export class ReplayContainer implements ReplayContainerInterface {
474484
return this._debouncedFlush.flush() as Promise<void>;
475485
}
476486

487+
/**
488+
* Cancels queued up flushes.
489+
*/
490+
public cancelFlush(): void {
491+
this._debouncedFlush.cancel();
492+
}
493+
477494
/** Get the current sesion (=replay) ID */
478495
public getSessionId(): string | undefined {
479496
return this.session && this.session.id;
@@ -723,7 +740,7 @@ export class ReplayContainer implements ReplayContainerInterface {
723740
// Send replay when the page/tab becomes hidden. There is no reason to send
724741
// replay if it becomes visible, since no actions we care about were done
725742
// while it was hidden
726-
this._conditionalFlush();
743+
void this.conditionalFlush();
727744
}
728745

729746
/**
@@ -807,17 +824,6 @@ export class ReplayContainer implements ReplayContainerInterface {
807824
return Promise.all(createPerformanceSpans(this, createPerformanceEntries(entries)));
808825
}
809826

810-
/**
811-
* Only flush if `this.recordingMode === 'session'`
812-
*/
813-
private _conditionalFlush(): void {
814-
if (this.recordingMode === 'buffer') {
815-
return;
816-
}
817-
818-
void this.flushImmediate();
819-
}
820-
821827
/**
822828
* Clear _context
823829
*/

packages/replay/src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
285285
scrollTimeout: number;
286286
ignoreSelectors: string[];
287287
};
288+
delayFlushOnCheckout: number;
288289
}>;
289290
}
290291

@@ -515,7 +516,9 @@ export interface ReplayContainer {
515516
startRecording(): void;
516517
stopRecording(): boolean;
517518
sendBufferedReplayOrFlush(options?: SendBufferedReplayOptions): Promise<void>;
519+
conditionalFlush(): Promise<void>;
518520
flushImmediate(): Promise<void>;
521+
cancelFlush(): void;
519522
triggerUserActivity(): void;
520523
addUpdate(cb: AddUpdateCallback): void;
521524
getOptions(): ReplayPluginOptions;

packages/replay/src/util/handleRecordingEmit.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,30 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
8080
}
8181
}
8282

83+
const options = replay.getOptions();
84+
85+
// TODO: We want this as an experiment so that we can test
86+
// internally and create metrics before making this the default
87+
if (options._experiments.delayFlushOnCheckout) {
88+
// If the full snapshot is due to an initial load, we will not have
89+
// a previous session ID. In this case, we want to buffer events
90+
// for a set amount of time before flushing. This can help avoid
91+
// capturing replays of users that immediately close the window.
92+
setTimeout(() => replay.conditionalFlush(), options._experiments.delayFlushOnCheckout);
93+
94+
// Cancel any previously debounced flushes to ensure there are no [near]
95+
// simultaneous flushes happening. The latter request should be
96+
// insignificant in this case, so wait for additional user interaction to
97+
// trigger a new flush.
98+
//
99+
// This can happen because there's no guarantee that a recording event
100+
// happens first. e.g. a mouse click can happen and trigger a debounced
101+
// flush before the checkout.
102+
replay.cancelFlush();
103+
104+
return true;
105+
}
106+
83107
// Flush immediately so that we do not miss the first segment, otherwise
84108
// it can prevent loading on the UI. This will cause an increase in short
85109
// replays (e.g. opening and closing a tab quickly), but these can be

0 commit comments

Comments
 (0)