@@ -12,8 +12,9 @@ import {
1212 HttpExchange ,
1313 CollectedEvent ,
1414 TimingEvents ,
15- InputTlsFailure ,
16- FailedTlsConnection
15+ FailedTlsConnection ,
16+ InputWebSocketMessage ,
17+ InputTlsFailure
1718} from '../../types' ;
1819
1920import { stringToBuffer } from '../../util' ;
@@ -62,9 +63,23 @@ export interface ExtendedHarRequest extends HarFormat.Request {
6263}
6364
6465export interface HarEntry extends HarFormat . Entry {
66+ _resourceType ?: 'websocket' ;
67+ _webSocketMessages ?: HarWebSocketMessage [ ] ;
68+ _webSocketClose ?: {
69+ code ?: number ;
70+ reason ?: string ;
71+ timestamp ?: number ;
72+ } | 'aborted'
6573 _pinned ?: true ;
6674}
6775
76+ export interface HarWebSocketMessage {
77+ type : 'send' | 'receive' ;
78+ opcode : 1 | 2 ;
79+ data : string ;
80+ time : number ; // Epoch timestamp, as a float in seconds
81+ }
82+
6883export type HarTlsErrorEntry = {
6984 startedDateTime : string ;
7085 time : number ; // Floating-point high-resolution duration, in ms
@@ -354,7 +369,8 @@ async function generateHarHttpEntry(
354369 ? timingEvents . responseSentTimestamp ! - timingEvents . headersSentTimestamp !
355370 : 0 ;
356371
357- const endTimestamp = timingEvents . responseSentTimestamp ??
372+ const endTimestamp = timingEvents . wsClosedTimestamp ??
373+ timingEvents . responseSentTimestamp ??
358374 timingEvents . abortedTimestamp ;
359375
360376 const totalDuration = endTimestamp
@@ -387,7 +403,16 @@ async function generateHarHttpEntry(
387403 _resourceType : 'websocket' ,
388404 _webSocketMessages : exchange . messages . map ( ( message ) =>
389405 generateHarWebSocketMessage ( message , timingEvents )
390- )
406+ ) ,
407+ _webSocketClose : exchange . closeState && exchange . closeState !== 'aborted'
408+ ? {
409+ code : exchange . closeState . closeCode ,
410+ reason : exchange . closeState . closeReason ,
411+ timestamp : timingEvents . wsClosedTimestamp
412+ ? timingEvents . wsClosedTimestamp / 1000 // Match _webSocketMessage format
413+ : undefined
414+ }
415+ : exchange . closeState
391416 } : { } )
392417 } ;
393418}
@@ -461,8 +486,9 @@ export async function parseHar(harContents: unknown): Promise<ParsedHar> {
461486
462487 har . log . entries . forEach ( ( entry , i ) => {
463488 const id = baseId + i ;
489+ const isWebSocket = entry . _resourceType === 'websocket' ;
464490
465- const timingEvents : TimingEvents = Object . assign ( {
491+ const timingEvents : TimingEvents = {
466492 startTime : dateFns . parse ( entry . startedDateTime ) . getTime ( ) ,
467493 startTimestamp : 0 ,
468494 bodyReceivedTimestamp : sumTimings ( entry . timings ,
@@ -478,44 +504,92 @@ export async function parseHar(harContents: unknown): Promise<ParsedHar> {
478504 'send' ,
479505 'wait'
480506 )
481- } , entry . response . status !== 0
482- ? { responseSentTimestamp : entry . time }
483- : { abortedTimestamp : entry . time }
507+ } ;
508+
509+ Object . assign ( timingEvents ,
510+ entry . response . status !== 0
511+ ? { responseSentTimestamp : entry . time }
512+ : { abortedTimestamp : entry . time } ,
513+
514+ isWebSocket
515+ ? {
516+ wsAcceptedTimestamp : timingEvents . headersSentTimestamp ,
517+ wsClosedTimestamp : entry . time
518+ }
519+ : { }
484520 ) ;
485521
522+
486523 const request = parseHarRequest ( id , entry . request , timingEvents ) ;
487- events . push ( { type : 'request' , event : request } ) ;
524+
525+ events . push ( {
526+ type : isWebSocket ? 'websocket-request' : 'request' ,
527+ event : request
528+ } ) ;
488529
489530 if ( entry . response . status !== 0 ) {
490531 events . push ( {
491- type : 'response' ,
532+ type : isWebSocket && entry . response . status === 101
533+ ? 'websocket-accepted'
534+ : 'response' ,
492535 event : parseHarResponse ( id , entry . response , timingEvents )
493536 } ) ;
494537 } else {
495538 events . push ( { type : 'abort' , event : request } ) ;
496539 }
497540
541+ if ( isWebSocket ) {
542+ events . push ( ...entry . _webSocketMessages ?. map ( message => ( {
543+ type : `websocket-message-${ message . type === 'send' ? 'received' : 'sent' } ` as const ,
544+ event : {
545+ streamId : request . id ,
546+ direction : message . type === 'send' ? 'received' : 'sent' ,
547+ isBinary : message . opcode === 2 ,
548+ content : Buffer . from ( message . data , message . opcode === 2 ? 'base64' : 'utf8' ) ,
549+ eventTimestamp : ( message . time * 1000 ) - timingEvents . startTime ,
550+ timingEvents : timingEvents ,
551+ tags : [ ]
552+ } satisfies InputWebSocketMessage
553+ } ) ) ?? [ ] ) ;
554+
555+ const closeEvent = entry . _webSocketClose ;
556+
557+ if ( closeEvent && closeEvent !== 'aborted' ) {
558+ events . push ( {
559+ type : 'websocket-close' ,
560+ event : {
561+ streamId : request . id ,
562+ closeCode : closeEvent . code ,
563+ closeReason : closeEvent . reason ?? "" ,
564+ timingEvents : timingEvents ,
565+ tags : [ ]
566+ }
567+ } ) ;
568+ } else {
569+ // N.b. WebSockets can abort _after_ the response event!
570+ events . push ( { type : 'abort' , event : request } ) ;
571+ }
572+ }
573+
498574 if ( entry . _pinned ) pinnedIds . push ( id ) ;
499575 } ) ;
500576
501577 if ( har . log . _tlsErrors ) {
502- har . log . _tlsErrors . forEach ( ( entry ) => {
503- events . push ( {
504- type : 'tls-client-error' ,
505- event : {
506- failureCause : entry . cause ,
507- hostname : entry . hostname ,
508- remoteIpAddress : entry . clientIPAddress ,
509- remotePort : entry . clientPort ,
510- tags : [ ] ,
511- timingEvents : {
512- startTime : dateFns . parse ( entry . startedDateTime ) . getTime ( ) ,
513- connectTimestamp : 0 ,
514- failureTimestamp : entry . time
515- }
578+ events . push ( ...har . log . _tlsErrors . map ( ( entry ) => ( {
579+ type : 'tls-client-error' as const ,
580+ event : {
581+ failureCause : entry . cause ,
582+ hostname : entry . hostname ,
583+ remoteIpAddress : entry . clientIPAddress ,
584+ remotePort : entry . clientPort ,
585+ tags : [ ] ,
586+ timingEvents : {
587+ startTime : dateFns . parse ( entry . startedDateTime ) . getTime ( ) ,
588+ connectTimestamp : 0 ,
589+ failureTimestamp : entry . time
516590 }
517- } ) ;
518- } ) ;
591+ }
592+ } ) ) ) ;
519593 }
520594
521595 return { events, pinnedIds } ;
0 commit comments