@@ -22,6 +22,7 @@ import { LifeCycle, LifeCycleEventType } from '../lifeCycle'
2222import type { RequestCompleteEvent } from '../requestCollection'
2323import { getDocumentTraceId } from '../tracing/getDocumentTraceId'
2424import { createSpanIdentifier , createTraceIdentifier } from '../tracing/identifier'
25+ import type { WebSocketCompleteEvent } from '../webSocketCollection'
2526import { REQUEST_MATCHING_DELAY , startResourceCollection } from './resourceCollection'
2627
2728function buildMatchHeadersForAllUrls ( headerNames : MatchOption [ ] ) : MatchHeader [ ] {
@@ -1278,6 +1279,129 @@ describe('resourceCollection', () => {
12781279 } )
12791280 } )
12801281
1282+ describe ( 'websocket' , ( ) => {
1283+ const wsUrl = 'wss://example.com/socket'
1284+ const ONE_MILLISECOND_IN_NANOSECONDS = 1e6
1285+
1286+ function toServerDurationFromMs ( durationInMilliseconds : number ) : ServerDuration {
1287+ return ( durationInMilliseconds * ONE_MILLISECOND_IN_NANOSECONDS ) as ServerDuration
1288+ }
1289+
1290+ function getRawWebsocketResourceEvent ( index = 0 ) : RawRumResourceEvent {
1291+ return rawRumEvents [ index ] . rawRumEvent as RawRumResourceEvent
1292+ }
1293+
1294+ function getWebsocketResource ( index = 0 ) {
1295+ return getRawWebsocketResourceEvent ( index ) . resource
1296+ }
1297+
1298+ function notifyWebSocket ( overrides : Partial < WebSocketCompleteEvent > = { } ) {
1299+ const defaultStartTime = 1_700_000_000_000 as TimeStamp
1300+ const defaultStartRelativeTime = 200 as RelativeTime
1301+ const defaultEndTime = 1_700_000_005_000 as TimeStamp
1302+ const defaultEndRelativeTime = 5_200 as RelativeTime
1303+ const defaultMessagesIn = { count : 3 , size : 300 }
1304+ const defaultMessagesOut = { count : 2 , size : 200 }
1305+ const defaultCloseCode = 1000
1306+
1307+ const event : WebSocketCompleteEvent = {
1308+ connectionId : 'connection-uuid' ,
1309+ url : wsUrl ,
1310+ startClocks : { relative : defaultStartRelativeTime , timeStamp : defaultStartTime } ,
1311+ endClocks : { relative : defaultEndRelativeTime , timeStamp : defaultEndTime } ,
1312+ messagesIn : defaultMessagesIn ,
1313+ messagesOut : defaultMessagesOut ,
1314+ longestSilence : 0 as Duration ,
1315+ bufferedAmountMax : 0 ,
1316+ handshakeSucceeded : false ,
1317+ trackingEndReason : 'close_event' ,
1318+ closeCode : defaultCloseCode ,
1319+ closeReason : 'bye' ,
1320+ wasClean : true ,
1321+ ...overrides ,
1322+ }
1323+ lifeCycle . notify ( LifeCycleEventType . WEBSOCKET_COMPLETED , event )
1324+ runTasks ( )
1325+ return event
1326+ }
1327+
1328+ it ( 'emits a resource event with type=websocket on close' , ( ) => {
1329+ setupResourceCollection ( )
1330+
1331+ const protocol = 'chat.v1'
1332+ const viewId = 'view-1'
1333+ const timeToFirstMessageIn = 10 as Duration
1334+ const timeToFirstMessageOut = 25 as Duration
1335+ const lastMessageAt = 1_700_000_004_000 as TimeStamp
1336+ const longestSilence = 200 as Duration
1337+ const bufferedAmountMax = 1024
1338+ const idleDurationBeforeClose = 1000 as Duration
1339+ const setupDuration = 42 as Duration
1340+
1341+ const event = notifyWebSocket ( {
1342+ protocol,
1343+ startViewId : viewId ,
1344+ endViewId : viewId ,
1345+ firstMessageInOffset : timeToFirstMessageIn ,
1346+ firstMessageOutOffset : timeToFirstMessageOut ,
1347+ lastMessageAt,
1348+ longestSilence,
1349+ bufferedAmountMax,
1350+ idleDurationBeforeClose,
1351+ setupDuration,
1352+ handshakeSucceeded : true ,
1353+ } )
1354+
1355+ const expectedEventCount = 1
1356+ const expectedResourceDuration = toServerDurationFromMs ( event . endClocks . relative - event . startClocks . relative )
1357+
1358+ expect ( rawRumEvents . length ) . toBe ( expectedEventCount )
1359+
1360+ const rawEvent = getRawWebsocketResourceEvent ( )
1361+ expect ( rawEvent . resource . type ) . toBe ( ResourceType . WEBSOCKET )
1362+ expect ( rawEvent . resource . status_code ) . toBeUndefined ( )
1363+ expect ( rawEvent . resource . url ) . toBe ( wsUrl )
1364+ expect ( rawEvent . resource . duration ) . toBe ( expectedResourceDuration )
1365+ expect ( rawEvent . date ) . toBe ( event . startClocks . timeStamp )
1366+ expect ( rawEvent . resource . websocket ) . toEqual ( {
1367+ connection_id : event . connectionId ,
1368+ handshake_succeeded : true ,
1369+ start_time : event . startClocks . timeStamp ,
1370+ end_time : event . endClocks . timeStamp ,
1371+ start_view_id : viewId ,
1372+ end_view_id : viewId ,
1373+ tracking_end_reason : 'close_event' ,
1374+ close_code : event . closeCode ,
1375+ close_reason : 'bye' ,
1376+ was_clean : true ,
1377+ messages_in : event . messagesIn ,
1378+ messages_out : event . messagesOut ,
1379+ time_to_first_message_in : timeToFirstMessageIn ,
1380+ time_to_first_message_out : timeToFirstMessageOut ,
1381+ last_message_at : lastMessageAt ,
1382+ longest_silence : longestSilence ,
1383+ idle_duration_before_close : idleDurationBeforeClose ,
1384+ buffered_amount_max : bufferedAmountMax ,
1385+ protocol,
1386+ setup_duration : setupDuration ,
1387+ } )
1388+ } )
1389+
1390+ it ( 'emits an event spanning two views' , ( ) => {
1391+ setupResourceCollection ( )
1392+ const startViewId = 'view-a'
1393+ const endViewId = 'view-b'
1394+ notifyWebSocket ( { startViewId, endViewId } )
1395+
1396+ const expectedEventCount = 1
1397+ expect ( rawRumEvents . length ) . toBe ( expectedEventCount )
1398+
1399+ const websocket = getWebsocketResource ( ) . websocket !
1400+ expect ( websocket . start_view_id ) . toBe ( startViewId )
1401+ expect ( websocket . end_view_id ) . toBe ( endViewId )
1402+ } )
1403+ } )
1404+
12811405 function runTasks ( ) {
12821406 // Request-type entries are queued through a `setTimeout(…, REQUEST_MATCHING_DELAY)` before
12831407 // they reach the task queue — advance past it so they get pushed.
0 commit comments