@@ -24,6 +24,7 @@ import type {
24
24
EventBuffer ,
25
25
InternalEventContext ,
26
26
PopEventContext ,
27
+ RecordingEvent ,
27
28
RecordingOptions ,
28
29
ReplayContainer as ReplayContainerInterface ,
29
30
ReplayPluginOptions ,
@@ -42,6 +43,8 @@ import { getHandleRecordingEmit } from './util/handleRecordingEmit';
42
43
import { isExpired } from './util/isExpired' ;
43
44
import { isSessionExpired } from './util/isSessionExpired' ;
44
45
import { sendReplay } from './util/sendReplay' ;
46
+ import type { SKIPPED } from './util/throttle' ;
47
+ import { throttle , THROTTLED } from './util/throttle' ;
45
48
46
49
/**
47
50
* The main replay container class, which holds all the state and methods for recording and sending replays.
@@ -75,6 +78,11 @@ export class ReplayContainer implements ReplayContainerInterface {
75
78
maxSessionLife : MAX_SESSION_LIFE ,
76
79
} as const ;
77
80
81
+ private _throttledAddEvent : (
82
+ event : RecordingEvent ,
83
+ isCheckout ?: boolean ,
84
+ ) => typeof THROTTLED | typeof SKIPPED | Promise < AddEventResult | null > ;
85
+
78
86
/**
79
87
* Options to pass to `rrweb.record()`
80
88
*/
@@ -136,6 +144,14 @@ export class ReplayContainer implements ReplayContainerInterface {
136
144
this . _debouncedFlush = debounce ( ( ) => this . _flush ( ) , this . _options . flushMinDelay , {
137
145
maxWait : this . _options . flushMaxDelay ,
138
146
} ) ;
147
+
148
+ this . _throttledAddEvent = throttle (
149
+ ( event : RecordingEvent , isCheckout ?: boolean ) => addEvent ( this , event , isCheckout ) ,
150
+ // Max 300 events...
151
+ 300 ,
152
+ // ... per 5s
153
+ 5 ,
154
+ ) ;
139
155
}
140
156
141
157
/** Get the event context. */
@@ -565,6 +581,39 @@ export class ReplayContainer implements ReplayContainerInterface {
565
581
this . _context . urls . push ( url ) ;
566
582
}
567
583
584
+ /**
585
+ * Add a breadcrumb event, that may be throttled.
586
+ * If it was throttled, we add a custom breadcrumb to indicate that.
587
+ */
588
+ public throttledAddEvent (
589
+ event : RecordingEvent ,
590
+ isCheckout ?: boolean ,
591
+ ) : typeof THROTTLED | typeof SKIPPED | Promise < AddEventResult | null > {
592
+ const res = this . _throttledAddEvent ( event , isCheckout ) ;
593
+
594
+ // If this is THROTTLED, it means we have throttled the event for the first time
595
+ // In this case, we want to add a breadcrumb indicating that something was skipped
596
+ if ( res === THROTTLED ) {
597
+ const breadcrumb = createBreadcrumb ( {
598
+ category : 'replay.throttled' ,
599
+ } ) ;
600
+
601
+ this . addUpdate ( ( ) => {
602
+ void addEvent ( this , {
603
+ type : EventType . Custom ,
604
+ timestamp : breadcrumb . timestamp || 0 ,
605
+ data : {
606
+ tag : 'breadcrumb' ,
607
+ payload : breadcrumb ,
608
+ metric : true ,
609
+ } ,
610
+ } ) ;
611
+ } ) ;
612
+ }
613
+
614
+ return res ;
615
+ }
616
+
568
617
/**
569
618
* Initialize and start all listeners to varying events (DOM,
570
619
* Performance Observer, Recording, Sentry SDK, etc)
@@ -803,7 +852,7 @@ export class ReplayContainer implements ReplayContainerInterface {
803
852
*/
804
853
private _createCustomBreadcrumb ( breadcrumb : Breadcrumb ) : void {
805
854
this . addUpdate ( ( ) => {
806
- void addEvent ( this , {
855
+ void this . throttledAddEvent ( {
807
856
type : EventType . Custom ,
808
857
timestamp : breadcrumb . timestamp || 0 ,
809
858
data : {
0 commit comments