@@ -16,6 +16,13 @@ import {
1616 Utf8Encoders
1717} from "rsocket-core" ;
1818import { Encoders } from "rsocket-core/RSocketEncoding" ;
19+ import Protocol from "devtools-protocol" ;
20+ import { action , makeObservable , observable } from "mobx" ;
21+ import { observer } from "mobx-react" ;
22+ import WebSocketFrameReceivedEvent = Protocol . Network . WebSocketFrameReceivedEvent ;
23+ import WebSocketFrameSentEvent = Protocol . Network . WebSocketFrameSentEvent ;
24+ import WebSocketFrame = Protocol . Network . WebSocketFrame ;
25+ import WebSocketCreatedEvent = Protocol . Network . WebSocketCreatedEvent ;
1926
2027function getRSocketType ( type : number ) : string {
2128 for ( const [ name , code ] of Object . entries ( FRAME_TYPES ) ) {
@@ -77,7 +84,8 @@ function base64ToArrayBuffer(base64: string) {
7784 return bytes ;
7885}
7986
80- const FrameEntry = ( { frame, selected, onClick} : { frame : WsFrame , selected : boolean , onClick : MouseEventHandler } ) => {
87+ const FrameEntry = ( { frame, selected, onClick} :
88+ { frame : WsFrameState , selected : boolean , onClick : MouseEventHandler } ) => {
8189 const rsocketFrame = tryDeserializeFrame ( frame . payload )
8290 const frameName = rsocketFrame
8391 ? < span className = "name" > { shortFrame ( rsocketFrame ) } </ span >
@@ -170,32 +178,6 @@ class RSocketFrame extends React.Component<RSocketFrameProps, any> {
170178 }
171179}
172180
173- class FrameList extends React . Component < any , any > {
174- render ( ) {
175- const { frames, activeId, onSelect, onClear, onStart, onStop, ...props } = this . props ;
176- return (
177- < Panel { ...props } className = "LeftPanel" >
178- < div className = "list-controls" >
179- < FontAwesome className = "list-button" name = "ban" onClick = { onClear } title = "Clear" />
180- < FontAwesome className = "list-button" name = "play" onClick = { onStart } title = "Start" />
181- < FontAwesome className = "list-button" name = "stop" onClick = { onStop } title = "Stop" />
182- </ div >
183- < ul className = "frame-list" onClick = { ( ) => onSelect ( null ) } >
184- { frames . map ( ( frame : WsFrame ) =>
185- < FrameEntry key = { frame . id }
186- frame = { frame }
187- selected = { frame . id === activeId }
188- onClick = { e => {
189- onSelect ( frame . id ) ;
190- e . stopPropagation ( ) ;
191- } }
192- /> ) }
193- </ ul >
194- </ Panel >
195- ) ;
196- }
197- }
198-
199181const TextViewer = ( { data} : { data : string | Uint8Array } ) => (
200182 < div className = "TextViewer tab-pane" >
201183 { data }
@@ -261,8 +243,8 @@ const RSocketViewer = ({frame, data}: { frame: Frame, data: string }) => {
261243} ;
262244
263245
264- class FrameView extends React . Component < { wsFrame : WsFrame } , { panel ?: string } > {
265- constructor ( props : { wsFrame : WsFrame } ) {
246+ class FrameView extends React . Component < { wsFrame : WsFrameState } , { panel ?: string } > {
247+ constructor ( props : { wsFrame : WsFrameState } ) {
266248 super ( props ) ;
267249 this . state = { panel : undefined } ;
268250 }
@@ -283,7 +265,7 @@ class FrameView extends React.Component<{ wsFrame: WsFrame }, { panel?: string }
283265 }
284266}
285267
286- interface WsFrame {
268+ interface WsFrameState {
287269 id : number ,
288270 type : 'incoming' | 'outgoing' ,
289271 time : Date ,
@@ -293,98 +275,92 @@ interface WsFrame {
293275 payload : string ,
294276}
295277
296- /**
297- * WebSocket message data. This represents an entire WebSocket message, not just a fragmented frame as the name suggests.
298- */
299- interface WebSocketFrame {
300- /** WebSocket message opcode. */
301- opcode : number ,
302- /** WebSocket message mask. */
303- mask : boolean ,
304- /**
305- * WebSocket message payload data. If the opcode is 1, this is a text message and payloadData is a UTF-8 string. If
306- * the opcode isn't 1, then payloadData is a base64 encoded string representing binary data.
307- */
308- payloadData : string ,
309- }
310-
311- interface AppState {
312- frames : WsFrame [ ] ;
313- capturing : boolean ;
314- activeId ?: number
278+ interface WsConnectionState {
279+ id : string ,
280+ url ?: string ,
281+ frames : WsFrameState [ ] ;
282+ activeFrame ?: number
315283}
316284
317285export type ChromeHandlers = { [ name : string ] : any } ;
318286
319- export default class App extends React . Component < { handlers : ChromeHandlers , } , AppState > {
287+ export class AppStateStore {
320288 _uniqueId = 0 ;
321289 issueTime ?: number = undefined ;
322290 issueWallTime ?: number = undefined ;
323-
324- state : AppState = {
325- frames : [ ] ,
326- activeId : undefined ,
327- capturing : true ,
291+ connections = new Map < string , WsConnectionState > ( ) ;
292+ activeConnection ?: string = undefined ;
293+
294+ constructor ( handlers : ChromeHandlers ) {
295+ makeObservable ( this , {
296+ connections : observable ,
297+ activeConnection : observable ,
298+ selectConnection : action . bound ,
299+ frameSent : action . bound ,
300+ frameReceived : action . bound ,
301+ webSocketCreated : action . bound ,
302+ selectFrame : action . bound ,
303+ clearFrames : action . bound ,
304+ } ) ;
305+
306+ handlers [ "Network.webSocketCreated" ] = this . webSocketCreated . bind ( this ) ;
307+ handlers [ "Network.webSocketFrameReceived" ] = this . frameReceived . bind ( this ) ;
308+ handlers [ "Network.webSocketFrameSent" ] = this . frameSent . bind ( this ) ;
328309 }
329310
330- getTime ( timestamp : number ) : Date {
331- if ( this . issueTime === undefined || this . issueWallTime === undefined ) {
332- this . issueTime = timestamp ;
333- this . issueWallTime = new Date ( ) . getTime ( ) ;
311+ clearFrames ( ) {
312+ if ( ! this . activeConnection ) {
313+ return
334314 }
335- return new Date ( ( timestamp - this . issueTime ) * 1000 + this . issueWallTime ) ;
315+ const connection = this . connections . get ( this . activeConnection ) ;
316+ if ( ! connection ) {
317+ return ;
318+ }
319+ connection . frames = [ ]
336320 }
337321
338- constructor ( props : { handlers : ChromeHandlers , } ) {
339- super ( props ) ;
340-
341- props . handlers [ "Network.webSocketFrameReceived" ] = this . frameReceived . bind ( this ) ;
342- props . handlers [ "Network.webSocketFrameSent" ] = this . frameSent . bind ( this ) ;
322+ selectFrame ( id ?: number ) {
323+ if ( ! this . activeConnection ) {
324+ return
325+ }
326+ const connection = this . connections . get ( this . activeConnection ) ;
327+ if ( ! connection ) {
328+ return ;
329+ }
330+ connection . activeFrame = id ;
343331 }
344332
345- render ( ) {
346- const { frames, activeId} = this . state ;
347- const active = frames . find ( f => f . id === activeId ) ;
348- return (
349- < Panel cols className = "App" >
350- < FrameList
351- size = { 300 }
352- minSize = { 180 }
353- resizable
354- frames = { frames }
355- activeId = { activeId }
356- onClear = { this . clearFrames }
357- onSelect = { this . selectFrame }
358- onStart = { this . startCapture }
359- onStop = { this . stopCapture }
360- />
361- < Panel minSize = { 100 } className = "PanelView" >
362- { active != null ? < FrameView wsFrame = { active } /> :
363- < span className = "message" > Select a frame to view its contents</ span > }
364- </ Panel >
365- </ Panel >
366- ) ;
333+ selectConnection ( value : string ) {
334+ this . activeConnection = value ;
367335 }
368336
369- selectFrame = ( id : number ) => {
370- this . setState ( { activeId : id } ) ;
371- } ;
372-
373- clearFrames = ( ) => {
374- this . setState ( { frames : [ ] } ) ;
375- } ;
337+ webSocketCreated ( event : WebSocketCreatedEvent ) {
338+ const { requestId, url} = event ;
339+ if ( this . connections . get ( requestId ) ) {
340+ // unexpected
341+ return ;
342+ }
343+ this . connections . set ( requestId , {
344+ id : requestId ,
345+ url : url ,
346+ frames : [ ] ,
347+ activeFrame : undefined ,
348+ } ) ;
349+ }
376350
377- startCapture = ( ) => {
378- this . setState ( { capturing : true } ) ;
351+ frameReceived ( event : WebSocketFrameReceivedEvent ) {
352+ const { requestId, timestamp, response} = event ;
353+ this . addFrame ( 'incoming' , requestId , timestamp , response ) ;
379354 }
380355
381- stopCapture = ( ) => {
382- this . setState ( { capturing : false } ) ;
356+ frameSent ( event : WebSocketFrameSentEvent ) {
357+ const { requestId, timestamp, response} = event ;
358+ this . addFrame ( 'outgoing' , requestId , timestamp , response ) ;
383359 }
384360
385- addFrame ( type : 'incoming' | 'outgoing' , timestamp : number , response : WebSocketFrame ) {
361+ addFrame ( type : 'incoming' | 'outgoing' , requestId : string , timestamp : number , response : WebSocketFrame ) {
386362 if ( response . opcode === 1 || response . opcode === 2 ) {
387- const frame : WsFrame = {
363+ const frame : WsFrameState = {
388364 type,
389365 id : ++ this . _uniqueId ,
390366 time : this . getTime ( timestamp ) ,
@@ -396,19 +372,80 @@ export default class App extends React.Component<{ handlers: ChromeHandlers, },
396372 } else {
397373 frame . binary = stringToBuffer ( response . payloadData ) ;
398374 }
399- this . setState ( ( { frames} ) => ( { frames : [ ...frames , frame ] } ) ) ;
375+ const connection = this . ensureConnection ( requestId ) ;
376+ connection . frames . push ( frame )
377+ this . activeConnection = requestId ;
400378 }
401379 }
402380
403- frameReceived ( { timestamp, response} : { timestamp : number , response : WebSocketFrame } ) {
404- if ( this . state . capturing ) {
405- this . addFrame ( 'incoming' , timestamp , response ) ;
381+ private ensureConnection ( requestId : string ) : WsConnectionState {
382+ const connection = this . connections . get ( requestId ) ;
383+ if ( connection ) {
384+ return connection ;
406385 }
386+ const newConnection = {
387+ id : requestId ,
388+ frames : [ ] ,
389+ activeFrame : undefined ,
390+ }
391+ this . connections . set ( requestId , newConnection ) ;
392+ return newConnection ;
407393 }
408394
409- frameSent ( { timestamp, response} : { timestamp : number , response : WebSocketFrame } ) {
410- if ( this . state . capturing ) {
411- this . addFrame ( 'outgoing' , timestamp , response ) ;
395+ private getTime ( timestamp : number ) : Date {
396+ if ( this . issueTime === undefined || this . issueWallTime === undefined ) {
397+ this . issueTime = timestamp ;
398+ this . issueWallTime = new Date ( ) . getTime ( ) ;
412399 }
400+ return new Date ( ( timestamp - this . issueTime ) * 1000 + this . issueWallTime ) ;
413401 }
414402}
403+
404+ export const App = observer ( ( { store} : { store : AppStateStore } ) => {
405+ const { connections, activeConnection} = store ;
406+ if ( ! activeConnection ) {
407+ return < div > No active WebSocket connections</ div >
408+ }
409+ const connection = connections . get ( activeConnection ) ;
410+ if ( ! connection ) {
411+ throw Error ( `the active connection: "${ activeConnection } " is missing` ) ;
412+ }
413+ const { frames, activeFrame} = connection ;
414+ const active = frames . find ( f => f . id === activeFrame ) ;
415+ return (
416+ < Panel cols className = "App" >
417+ < Panel size = { 300 } minSize = { 180 } resizable className = "LeftPanel" >
418+ < div className = "list-controls" >
419+ < FontAwesome className = "list-button" name = "ban" onClick = { ( ) => store . clearFrames ( ) } title = "Clear" />
420+ < select
421+ style = { { width : "100%" } }
422+ value = { activeConnection }
423+ onChange = { e => store . selectConnection ( e . target . value ) }
424+ >
425+ { [ ...connections . entries ( ) ]
426+ . map ( ( [ id , connection ] ) =>
427+ < option value = { id } key = { id } >
428+ { `${ id } : ${ connection . url ?? '' } ` }
429+ </ option > )
430+ }
431+ </ select >
432+ </ div >
433+ < ul className = "frame-list" onClick = { ( ) => store . selectFrame ( undefined ) } >
434+ { frames . map ( ( frame : WsFrameState ) =>
435+ < FrameEntry key = { frame . id }
436+ frame = { frame }
437+ selected = { frame . id === activeFrame }
438+ onClick = { e => {
439+ store . selectFrame ( frame . id ) ;
440+ e . stopPropagation ( ) ;
441+ } }
442+ /> ) }
443+ </ ul >
444+ </ Panel >
445+ < Panel minSize = { 100 } className = "PanelView" >
446+ { active != null ? < FrameView wsFrame = { active } /> :
447+ < span className = "message" > Select a frame to view its contents</ span > }
448+ </ Panel >
449+ </ Panel >
450+ ) ;
451+ } ) ;
0 commit comments