You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
300 lines
9.6 KiB
TypeScript
300 lines
9.6 KiB
TypeScript
4 years ago
|
import React from 'react'
|
||
|
import Table from 'react-bootstrap/Table'
|
||
4 years ago
|
import Form from 'react-bootstrap/Form'
|
||
|
import Button from 'react-bootstrap/Button'
|
||
4 years ago
|
import './App.css'
|
||
|
|
||
4 years ago
|
import BreakPoint from './components/BreakPoint'
|
||
4 years ago
|
import EditFlow from './components/EditFlow'
|
||
4 years ago
|
|
||
4 years ago
|
import { FlowManager } from './flow'
|
||
4 years ago
|
import { isTextBody, getSize } from './utils'
|
||
4 years ago
|
import { parseMessage, SendMessageType, buildMessageMeta, IFlow, MessageType, IRequest, IResponse } from './message'
|
||
4 years ago
|
|
||
4 years ago
|
interface IState {
|
||
|
flows: IFlow[]
|
||
|
flow: IFlow | null
|
||
|
flowTab: 'Headers' | 'Preview' | 'Response'
|
||
|
}
|
||
|
|
||
|
class App extends React.Component<any, IState> {
|
||
|
private flowMgr: FlowManager
|
||
|
private ws: WebSocket | null
|
||
4 years ago
|
|
||
4 years ago
|
constructor(props: any) {
|
||
4 years ago
|
super(props)
|
||
|
|
||
4 years ago
|
this.flowMgr = new FlowManager()
|
||
|
|
||
4 years ago
|
this.state = {
|
||
4 years ago
|
flows: this.flowMgr.showList(),
|
||
4 years ago
|
flow: null,
|
||
|
|
||
|
flowTab: 'Headers', // Headers, Preview, Response
|
||
4 years ago
|
}
|
||
4 years ago
|
|
||
4 years ago
|
this.ws = null
|
||
|
}
|
||
|
|
||
|
componentDidMount() {
|
||
|
this.initWs()
|
||
|
}
|
||
|
|
||
|
componentWillUnmount() {
|
||
|
if (this.ws) {
|
||
|
this.ws.close()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
initWs() {
|
||
|
if (this.ws) return
|
||
|
|
||
4 years ago
|
let host
|
||
4 years ago
|
if (process.env.NODE_ENV === 'development') {
|
||
|
host = 'localhost:9081'
|
||
|
} else {
|
||
|
host = new URL(document.URL).host
|
||
|
}
|
||
|
this.ws = new WebSocket(`ws://${host}/echo`)
|
||
4 years ago
|
this.ws.binaryType = 'arraybuffer'
|
||
4 years ago
|
this.ws.onopen = () => { console.log('OPEN') }
|
||
|
this.ws.onclose = () => { console.log('CLOSE') }
|
||
|
this.ws.onmessage = evt => {
|
||
4 years ago
|
const msg = parseMessage(evt.data)
|
||
|
if (!msg) {
|
||
|
console.error('parse error:', evt.data)
|
||
|
return
|
||
|
}
|
||
4 years ago
|
// console.log('msg:', msg)
|
||
4 years ago
|
|
||
4 years ago
|
if (msg.type === MessageType.REQUEST) {
|
||
|
const flow = { id: msg.id, request: msg.content as IRequest, waitIntercept: msg.waitIntercept }
|
||
4 years ago
|
this.flowMgr.add(flow)
|
||
|
this.setState({ flows: this.flowMgr.showList() })
|
||
4 years ago
|
}
|
||
4 years ago
|
else if (msg.type === MessageType.REQUEST_BODY) {
|
||
4 years ago
|
const flow = this.flowMgr.get(msg.id)
|
||
|
if (!flow) return
|
||
4 years ago
|
flow.waitIntercept = msg.waitIntercept
|
||
4 years ago
|
flow.request.body = msg.content as ArrayBuffer
|
||
4 years ago
|
this.setState({ flows: this.state.flows })
|
||
|
}
|
||
4 years ago
|
else if (msg.type === MessageType.RESPONSE) {
|
||
4 years ago
|
const flow = this.flowMgr.get(msg.id)
|
||
4 years ago
|
if (!flow) return
|
||
4 years ago
|
flow.waitIntercept = msg.waitIntercept
|
||
4 years ago
|
flow.response = msg.content as IResponse
|
||
4 years ago
|
this.setState({ flows: this.state.flows })
|
||
|
}
|
||
4 years ago
|
else if (msg.type === MessageType.RESPONSE_BODY) {
|
||
4 years ago
|
const flow = this.flowMgr.get(msg.id)
|
||
4 years ago
|
if (!flow || !flow.response) return
|
||
4 years ago
|
flow.waitIntercept = msg.waitIntercept
|
||
4 years ago
|
flow.response.body = msg.content as ArrayBuffer
|
||
4 years ago
|
this.setState({ flows: this.state.flows })
|
||
4 years ago
|
}
|
||
|
}
|
||
|
this.ws.onerror = evt => {
|
||
4 years ago
|
console.log('ERROR:', evt)
|
||
4 years ago
|
}
|
||
|
}
|
||
4 years ago
|
|
||
|
renderFlow() {
|
||
|
const { flow, flowTab } = this.state
|
||
|
if (!flow) return null
|
||
|
|
||
|
const request = flow.request
|
||
4 years ago
|
const response: IResponse = (flow.response || {}) as any
|
||
4 years ago
|
|
||
|
return (
|
||
|
<div className="flow-detail">
|
||
|
<div className="header-tabs">
|
||
|
<span onClick={() => { this.setState({ flow: null }) }}>x</span>
|
||
4 years ago
|
<span className={flowTab === 'Headers' ? 'selected' : undefined} onClick={() => { this.setState({ flowTab: 'Headers' }) }}>Headers</span>
|
||
|
<span className={flowTab === 'Preview' ? 'selected' : undefined} onClick={() => { this.setState({ flowTab: 'Preview' }) }}>Preview</span>
|
||
|
<span className={flowTab === 'Response' ? 'selected' : undefined} onClick={() => { this.setState({ flowTab: 'Response' }) }}>Response</span>
|
||
4 years ago
|
|
||
|
<EditFlow
|
||
|
flow={flow}
|
||
|
onChangeRequest={request => {
|
||
|
flow.request.method = request.method
|
||
|
flow.request.url = request.url
|
||
|
flow.request.header = request.header
|
||
|
if (isTextBody(flow.request)) flow.request.body = request.body
|
||
|
this.setState({ flows: this.state.flows })
|
||
|
}}
|
||
|
onChangeResponse={response => {
|
||
4 years ago
|
if (!flow.response) flow.response = {} as IResponse
|
||
|
|
||
4 years ago
|
flow.response.statusCode = response.statusCode
|
||
|
flow.response.header = response.header
|
||
|
if (isTextBody(flow.response)) flow.response.body = response.body
|
||
|
this.setState({ flows: this.state.flows })
|
||
|
}}
|
||
|
onMessage={msg => {
|
||
4 years ago
|
if (this.ws) this.ws.send(msg)
|
||
4 years ago
|
flow.waitIntercept = false
|
||
|
this.setState({ flows: this.state.flows })
|
||
|
}}
|
||
|
/>
|
||
|
|
||
4 years ago
|
</div>
|
||
|
|
||
|
<div style={{ padding: '20px' }}>
|
||
|
{
|
||
|
!(flowTab === 'Headers') ? null :
|
||
4 years ago
|
<div>
|
||
4 years ago
|
<div className="header-block">
|
||
4 years ago
|
<p>General</p>
|
||
4 years ago
|
<div className="header-block-content">
|
||
4 years ago
|
<p>Request URL: {request.url}</p>
|
||
|
<p>Request Method: {request.method}</p>
|
||
|
<p>Status Code: {`${response.statusCode || '(pending)'}`}</p>
|
||
4 years ago
|
</div>
|
||
4 years ago
|
</div>
|
||
4 years ago
|
|
||
4 years ago
|
{
|
||
|
!(response.header) ? null :
|
||
|
<div className="header-block">
|
||
|
<p>Response Headers</p>
|
||
|
<div className="header-block-content">
|
||
|
{
|
||
|
Object.keys(response.header).map(key => {
|
||
|
return (
|
||
|
<p key={key}>{key}: {response.header[key].join(' ')}</p>
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
</div>
|
||
|
</div>
|
||
|
}
|
||
|
|
||
4 years ago
|
<div className="header-block">
|
||
4 years ago
|
<p>Request Headers</p>
|
||
4 years ago
|
<div className="header-block-content">
|
||
4 years ago
|
{
|
||
|
!(request.header) ? null :
|
||
|
Object.keys(request.header).map(key => {
|
||
|
return (
|
||
|
<p key={key}>{key}: {request.header[key].join(' ')}</p>
|
||
|
)
|
||
|
})
|
||
|
}
|
||
4 years ago
|
</div>
|
||
|
</div>
|
||
|
|
||
4 years ago
|
{
|
||
|
!(request.body && request.body.byteLength) ? null :
|
||
|
<div className="header-block">
|
||
|
<p>Request Body</p>
|
||
|
<div className="header-block-content">
|
||
|
<p>
|
||
|
{
|
||
|
!(isTextBody(request)) ? 'Not text' :
|
||
|
new TextDecoder().decode(request.body)
|
||
|
}
|
||
|
</p>
|
||
|
</div>
|
||
|
</div>
|
||
|
}
|
||
|
|
||
|
</div>
|
||
4 years ago
|
}
|
||
|
|
||
|
{
|
||
|
!(flowTab === 'Response') ? null :
|
||
4 years ago
|
!(response.body && response.body.byteLength) ? <div>No response</div> :
|
||
|
!(isTextBody(response)) ? <div>Not text response</div> :
|
||
|
<div>
|
||
|
{new TextDecoder().decode(response.body)}
|
||
|
</div>
|
||
4 years ago
|
}
|
||
|
</div>
|
||
|
|
||
|
</div>
|
||
|
)
|
||
|
}
|
||
4 years ago
|
|
||
4 years ago
|
render() {
|
||
4 years ago
|
const { flows } = this.state
|
||
4 years ago
|
return (
|
||
|
<div className="main-table-wrap">
|
||
4 years ago
|
<div className="top-control">
|
||
|
<div><Button size="sm" onClick={() => {
|
||
|
this.flowMgr.clear()
|
||
|
this.setState({ flows: this.flowMgr.showList(), flow: null })
|
||
|
}}>Clear</Button></div>
|
||
|
<div>
|
||
|
<Form.Control
|
||
|
size="sm" placeholder="Filter"
|
||
|
onChange={(e) => {
|
||
|
const value = e.target.value
|
||
|
this.flowMgr.changeFilterLazy(value, () => {
|
||
|
this.setState({ flows: this.flowMgr.showList() })
|
||
|
})
|
||
|
}}
|
||
|
>
|
||
|
</Form.Control>
|
||
|
</div>
|
||
4 years ago
|
|
||
4 years ago
|
<BreakPoint onSave={rules => {
|
||
4 years ago
|
const msg = buildMessageMeta(SendMessageType.CHANGE_BREAK_POINT_RULES, rules)
|
||
|
if (this.ws) this.ws.send(msg)
|
||
4 years ago
|
}} />
|
||
4 years ago
|
</div>
|
||
|
|
||
4 years ago
|
<Table striped bordered size="sm">
|
||
|
<thead>
|
||
|
<tr>
|
||
4 years ago
|
<th>No</th>
|
||
4 years ago
|
<th>Host</th>
|
||
|
<th>Path</th>
|
||
4 years ago
|
<th>Method</th>
|
||
|
<th>Status</th>
|
||
4 years ago
|
<th>Size</th>
|
||
4 years ago
|
</tr>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
{
|
||
|
flows.map(f => {
|
||
|
const url = f.request.url
|
||
|
const u = new URL(url)
|
||
4 years ago
|
let host = u.host
|
||
|
if (host.length > 35) host = host.slice(0, 35) + '...'
|
||
4 years ago
|
let path = u.pathname + u.search
|
||
4 years ago
|
if (path.length > 65) path = path.slice(0, 65) + '...'
|
||
4 years ago
|
|
||
|
const request = f.request
|
||
4 years ago
|
const response: IResponse = (f.response || {}) as any
|
||
4 years ago
|
|
||
|
const classNames = []
|
||
|
if (this.state.flow && this.state.flow.id === f.id) classNames.push('tr-selected')
|
||
|
if (f.waitIntercept) classNames.push('tr-wait-intercept')
|
||
|
|
||
4 years ago
|
return (
|
||
4 years ago
|
<tr className={classNames.length ? classNames.join(' ') : undefined} key={f.id}
|
||
4 years ago
|
onClick={() => {
|
||
|
this.setState({ flow: f })
|
||
|
}}
|
||
|
>
|
||
4 years ago
|
<td>{f.no}</td>
|
||
|
<td>{host}</td>
|
||
4 years ago
|
<td>{path}</td>
|
||
4 years ago
|
<td>{request.method}</td>
|
||
|
<td>{response.statusCode || '(pending)'}</td>
|
||
4 years ago
|
<td>{getSize(response)}</td>
|
||
4 years ago
|
</tr>
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
</tbody>
|
||
|
</Table>
|
||
4 years ago
|
|
||
|
{this.renderFlow()}
|
||
4 years ago
|
</div>
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default App
|