Skip to content

Commit 4e79ed3

Browse files
committed
(#2): support multiple connections
1 parent 39b3575 commit 4e79ed3

File tree

6 files changed

+186
-123
lines changed

6 files changed

+186
-123
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rsocket-chrome-devtools",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "RSocket Chrome Devtools",
55
"scripts": {
66
"clean": "rm -rf dist && rm -f dist.zip",
@@ -23,6 +23,7 @@
2323
"clean-webpack-plugin": "^3.0.0",
2424
"copy-webpack-plugin": "^6.2.1",
2525
"css-loader": "^5.0.1",
26+
"devtools-protocol": "^0.0.852555",
2627
"file-loader": "^6.2.0",
2728
"html-loader": "^1.3.2",
2829
"html-webpack-plugin": "4.5.1",
@@ -37,6 +38,8 @@
3738
},
3839
"dependencies": {
3940
"classnames": "^2.2.6",
41+
"mobx": "^6.1.6",
42+
"mobx-react": "^7.1.0",
4043
"react": "^16.14.0",
4144
"react-dom": "^16.14.0",
4245
"react-flex-panel": "^1.0.0",

src/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// to allow for importing icons in react
2+
declare module '*.png';

src/inspector.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import ReactDOM from 'react-dom';
33
import './reset.css';
4-
import App, {ChromeHandlers} from './viewer/App';
4+
import {App, AppStateStore, ChromeHandlers} from './viewer/App';
55

66
const handlers: ChromeHandlers = {};
77

@@ -79,4 +79,6 @@ window.addEventListener("load", function () {
7979
startDebugging();
8080
});
8181

82-
ReactDOM.render(<App handlers={handlers}/>, document.getElementById('root'));
82+
ReactDOM.render(
83+
<App store={new AppStateStore(handlers)}/>,
84+
document.getElementById('root'));

src/viewer/App.tsx

Lines changed: 144 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import {
1616
Utf8Encoders
1717
} from "rsocket-core";
1818
import {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

2027
function 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-
199181
const 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

317285
export 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

Comments
 (0)