@@ -11,19 +11,15 @@ import { stylesheetEvaluator } from './stylesheet-evaluator'
1111import { handle } from '../../../common/event-emitter/handle'
1212import { SUPPORTABILITY_METRIC_CHANNEL } from '../../metrics/constants'
1313import { FEATURE_NAMES } from '../../../loaders/features/features'
14- import { buildNRMetaNode , customMasker } from './utils'
14+ import { customMasker } from './utils'
1515import { IDEAL_PAYLOAD_SIZE } from '../../../common/constants/agent-constants'
16- import { AggregateBase } from '../../utils/aggregate-base'
1716import { warn } from '../../../common/util/console'
1817import { single } from '../../../common/util/invoke'
18+ import { registerHandler } from '../../../common/event-emitter/register-handler'
19+
20+ const RRWEB_DATA_CHANNEL = 'rrweb-data'
1921
2022export class Recorder {
21- /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
22- #events
23- /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
24- #backloggedEvents
25- /** array of recorder events -- Will be filled only if forced harvest was triggered and harvester does not exist */
26- #preloaded
2723 /** flag that if true, blocks events from being "stored". Only set to true when a full snapshot has incomplete nodes (only stylesheets ATM) */
2824 #fixing = false
2925
@@ -34,47 +30,38 @@ export class Recorder {
3430 this . parent = parent
3531 /** A flag that can be set to false by failing conversions to stop the fetching process */
3632 this . shouldFix = this . parent . agentRef . init . session_replay . fix_stylesheets
37- /** Event Buffers */
38- this . #events = new RecorderEvents ( this . shouldFix )
39- this . #backloggedEvents = new RecorderEvents ( this . shouldFix )
40- this . #preloaded = [ new RecorderEvents ( this . shouldFix ) ]
41- /** The pointer to the current bucket holding rrweb events */
42- this . currentBufferTarget = this . #events
43- /** Only set to true once a snapshot node has been processed. Used to block preload harvests from sending before we know we have a snapshot */
33+
34+ /** Each page mutation or event will be stored (raw) in this array. This array will be cleared on each harvest */
35+ this . events = new RecorderEvents ( this . shouldFix )
36+ /** Backlog used for a 2-part sliding window to guarantee a 15-30s buffer window */
37+ this . backloggedEvents = new RecorderEvents ( this . shouldFix )
38+ /** Only set to true once a snapshot node has been processed. Used to block harvests from sending before we know we have a snapshot */
4439 this . hasSeenSnapshot = false
4540 /** Hold on to the last meta node, so that it can be re-inserted if the meta and snapshot nodes are broken up due to harvesting */
4641 this . lastMeta = false
4742 /** The method to stop recording. This defaults to a noop, but is overwritten once the recording library is imported and initialized */
4843 this . stopRecording = ( ) => { this . parent . agentRef . runtime . isRecording = false }
44+
45+ registerHandler ( RRWEB_DATA_CHANNEL , ( event , isCheckout ) => { this . audit ( event , isCheckout ) } , this . parent . featureName , this . parent . ee )
4946 }
5047
5148 getEvents ( ) {
52- if ( this . #preloaded[ 0 ] ?. events . length ) {
53- return {
54- ...this . #preloaded[ 0 ] ,
55- events : this . #preloaded[ 0 ] . events ,
56- payloadBytesEstimation : this . #preloaded[ 0 ] . payloadBytesEstimation ,
57- type : 'preloaded'
58- }
59- }
6049 return {
61- events : [ ...this . # backloggedEvents. events , ...this . # events. events ] . filter ( x => x ) ,
50+ events : [ ...this . backloggedEvents . events , ...this . events . events ] . filter ( x => x ) ,
6251 type : 'standard' ,
63- cycleTimestamp : Math . min ( this . # backloggedEvents. cycleTimestamp , this . # events. cycleTimestamp ) ,
64- payloadBytesEstimation : this . # backloggedEvents. payloadBytesEstimation + this . # events. payloadBytesEstimation ,
65- hasError : this . # backloggedEvents. hasError || this . # events. hasError ,
66- hasMeta : this . # backloggedEvents. hasMeta || this . # events. hasMeta ,
67- hasSnapshot : this . # backloggedEvents. hasSnapshot || this . # events. hasSnapshot ,
68- inlinedAllStylesheets : ( ! ! this . # backloggedEvents. events . length && this . # backloggedEvents. inlinedAllStylesheets ) || this . # events. inlinedAllStylesheets
52+ cycleTimestamp : Math . min ( this . backloggedEvents . cycleTimestamp , this . events . cycleTimestamp ) ,
53+ payloadBytesEstimation : this . backloggedEvents . payloadBytesEstimation + this . events . payloadBytesEstimation ,
54+ hasError : this . backloggedEvents . hasError || this . events . hasError ,
55+ hasMeta : this . backloggedEvents . hasMeta || this . events . hasMeta ,
56+ hasSnapshot : this . backloggedEvents . hasSnapshot || this . events . hasSnapshot ,
57+ inlinedAllStylesheets : ( ! ! this . backloggedEvents . events . length && this . backloggedEvents . inlinedAllStylesheets ) || this . events . inlinedAllStylesheets
6958 }
7059 }
7160
72- /** Clears the buffer (this.# events), and resets all payload metadata properties */
61+ /** Clears the buffer (this.events), and resets all payload metadata properties */
7362 clearBuffer ( ) {
74- if ( this . #preloaded[ 0 ] ?. events . length ) this . #preloaded. shift ( )
75- else if ( this . parent . mode === MODE . ERROR ) this . #backloggedEvents = this . #events
76- else this . #backloggedEvents = new RecorderEvents ( this . shouldFix )
77- this . #events = new RecorderEvents ( this . shouldFix )
63+ this . backloggedEvents = ( this . parent . mode === MODE . ERROR ) ? this . events : new RecorderEvents ( this . shouldFix )
64+ this . events = new RecorderEvents ( this . shouldFix )
7865 }
7966
8067 /** Begin recording using configured recording lib */
@@ -87,7 +74,7 @@ export class Recorder {
8774 let stop
8875 try {
8976 stop = recorder ( {
90- emit : this . audit . bind ( this ) ,
77+ emit : ( event , isCheckout ) => { handle ( RRWEB_DATA_CHANNEL , [ event , isCheckout ] , undefined , this . parent . featureName , this . parent . ee ) } ,
9178 blockClass : block_class ,
9279 ignoreClass : ignore_class ,
9380 maskTextClass : mask_text_class ,
@@ -126,7 +113,7 @@ export class Recorder {
126113 /** only run the full fixing behavior (more costly) if fix_stylesheets is configured as on (default behavior) */
127114 if ( ! this . shouldFix ) {
128115 if ( incompletes > 0 ) {
129- this . currentBufferTarget . inlinedAllStylesheets = false
116+ this . events . inlinedAllStylesheets = false
130117 this . #warnCSSOnce( )
131118 handle ( SUPPORTABILITY_METRIC_CHANNEL , [ missingInlineSMTag + 'Skipped' , incompletes ] , undefined , FEATURE_NAMES . metrics , this . parent . ee )
132119 }
@@ -138,7 +125,7 @@ export class Recorder {
138125 /** wait for the evaluator to download/replace the incompletes' src code and then take a new snap */
139126 stylesheetEvaluator . fix ( ) . then ( ( failedToFix ) => {
140127 if ( failedToFix > 0 ) {
141- this . currentBufferTarget . inlinedAllStylesheets = false
128+ this . events . inlinedAllStylesheets = false
142129 this . shouldFix = false
143130 }
144131 handle ( SUPPORTABILITY_METRIC_CHANNEL , [ missingInlineSMTag + 'Failed' , failedToFix ] , undefined , FEATURE_NAMES . metrics , this . parent . ee )
@@ -152,19 +139,12 @@ export class Recorder {
152139 if ( ! this . #fixing) this . store ( event , isCheckout )
153140 }
154141
155- /** Store a payload in the buffer (this.# events). This should be the callback to the recording lib noticing a mutation */
142+ /** Store a payload in the buffer (this.events). This should be the callback to the recording lib noticing a mutation */
156143 store ( event , isCheckout ) {
157- if ( ! event ) return
158-
159- if ( ! ( this . parent instanceof AggregateBase ) && this . #preloaded. length ) this . currentBufferTarget = this . #preloaded[ this . #preloaded. length - 1 ]
160- else this . currentBufferTarget = this . #events
144+ if ( ! event || this . parent . blocked ) return
161145
162- if ( this . parent . blocked ) return
163-
164- if ( this . parent . timeKeeper ?. ready && ! event . __newrelic ) {
165- event . __newrelic = buildNRMetaNode ( event . timestamp , this . parent . timeKeeper )
166- event . timestamp = this . parent . timeKeeper . correctAbsoluteTimestamp ( event . timestamp )
167- }
146+ /** because we've waited until draining to process the buffered rrweb events, we can guarantee the timekeeper exists */
147+ event . timestamp = this . parent . timeKeeper . correctAbsoluteTimestamp ( event . timestamp )
168148 event . __serialized = stringify ( event )
169149 const eventBytes = event . __serialized . length
170150 /** The estimated size of the payload after compression */
@@ -179,26 +159,18 @@ export class Recorder {
179159 }
180160
181161 // meta event
182- if ( event . type === RRWEB_EVENT_TYPES . Meta ) {
183- this . currentBufferTarget . hasMeta = true
184- }
162+ this . events . hasMeta ||= event . type === RRWEB_EVENT_TYPES . Meta
185163 // snapshot event
186- if ( event . type === RRWEB_EVENT_TYPES . FullSnapshot ) {
187- this . currentBufferTarget . hasSnapshot = true
188- this . hasSeenSnapshot = true
189- }
190- this . currentBufferTarget . add ( event )
164+ this . events . hasSnapshot ||= this . hasSeenSnapshot ||= event . type === RRWEB_EVENT_TYPES . FullSnapshot
165+
166+ //* dont let the EventBuffer class double evaluate the event data size, it's a performance burden and we have special reasons to do it outside the event buffer */
167+ this . events . add ( event , eventBytes )
191168
192169 // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
193170 // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
194- if ( ( ( event . type === RRWEB_EVENT_TYPES . FullSnapshot && this . currentBufferTarget . hasMeta ) || payloadSize > IDEAL_PAYLOAD_SIZE ) && this . parent . mode === MODE . FULL ) {
195- // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
196- if ( this . parent instanceof AggregateBase ) {
197- this . parent . agentRef . runtime . harvester . triggerHarvestFor ( this . parent )
198- } else {
199- // we are still in "preload" and it triggered a "stop point". Make a new set, which will get pointed at on next cycle
200- this . #preloaded. push ( new RecorderEvents ( this . shouldFix ) )
201- }
171+ if ( ( ( this . events . hasSnapshot && this . events . hasMeta ) || payloadSize > IDEAL_PAYLOAD_SIZE ) && this . parent . mode === MODE . FULL ) {
172+ // if we've made it to the ideal size of ~16kb before the interval timer, we should send early.
173+ this . parent . agentRef . runtime . harvester . triggerHarvestFor ( this . parent )
202174 }
203175 }
204176
@@ -213,13 +185,13 @@ export class Recorder {
213185 }
214186
215187 clearTimestamps ( ) {
216- this . currentBufferTarget . cycleTimestamp = undefined
188+ this . events . cycleTimestamp = undefined
217189 }
218190
219191 /** Estimate the payload size */
220192 getPayloadSize ( newBytes = 0 ) {
221193 // the query param padding constant gives us some padding for the other metadata to be safely injected
222- return this . estimateCompression ( this . currentBufferTarget . payloadBytesEstimation + newBytes ) + QUERY_PARAM_PADDING
194+ return this . estimateCompression ( this . events . payloadBytesEstimation + newBytes ) + QUERY_PARAM_PADDING
223195 }
224196
225197 /** Extensive research has yielded about an 88% compression factor on these payloads.
0 commit comments