eruda3 / src /Network /Network.js
soiz1's picture
Update src/Network/Network.js
2dbe09a verified
import Tool from '../DevTools/Tool'
import $ from 'licia/$'
import ms from 'licia/ms'
import each from 'licia/each'
import map from 'licia/map'
import Detail from './Detail'
import throttle from 'licia/throttle'
import { getFileName, classPrefix as c } from '../lib/util'
import evalCss from '../lib/evalCss'
import chobitsu from '../lib/chobitsu'
import emitter from '../lib/emitter'
import LunaDataGrid from 'luna-data-grid'
import ResizeSensor from 'licia/ResizeSensor'
import MediaQuery from 'licia/MediaQuery'
import { getType } from './util'
import copy from 'licia/copy'
import extend from 'licia/extend'
import trim from 'licia/trim'
import isNull from 'licia/isNull'
import LunaModal from 'luna-modal'
import { curlStr } from './util'
export default class Network extends Tool {
constructor() {
super()
this._style = evalCss(require('./Network.scss'))
this.name = 'network'
this.displayName = 'ネットワーク'
this._requests = {}
this._selectedRequest = null
this._isRecording = true
}
init($el, container) {
super.init($el)
this._container = container
this._initTpl()
this._detail = new Detail(this._$detail, container)
this._splitMediaQuery = new MediaQuery('screen and (min-width: 680px)')
this._splitMode = this._splitMediaQuery.isMatch()
this._requestDataGrid = new LunaDataGrid(this._$requests.get(0), {
columns: [
{
id: 'name',
title: '名前',
sortable: true,
weight: 30,
},
{
id: 'method',
title: 'メソッド',
sortable: true,
weight: 14,
},
{
id: 'status',
title: 'ステータス',
sortable: true,
weight: 14,
},
{
id: 'type',
title: '種類',
sortable: true,
weight: 14,
},
{
id: 'size',
title: '大きさ',
sortable: true,
weight: 14,
},
{
id: 'time',
title: '時間',
sortable: true,
weight: 14,
},
],
})
this._resizeSensor = new ResizeSensor($el.get(0))
this._bindEvent()
}
show() {
super.show()
this._updateDataGridHeight()
}
clear() {
this._requests = {}
this._requestDataGrid.clear()
}
requests() {
const ret = []
each(this._requests, (request) => {
ret.push(request)
})
return ret
}
_updateDataGridHeight() {
const height = this._$el.offset().height - this._$control.offset().height
this._requestDataGrid.setOption({
minHeight: height,
maxHeight: height,
})
}
_reqWillBeSent = (params) => {
if (!this._isRecording) {
return
}
const request = {
name: getFileName(params.request.url),
url: params.request.url,
status: 'pending',
type: 'unknown',
subType: 'unknown',
size: 0,
data: params.request.postData,
method: params.request.method,
startTime: params.timestamp * 1000,
time: 0,
resTxt: '',
done: false,
reqHeaders: params.request.headers || {},
resHeaders: {},
}
let node
request.render = () => {
const data = {
name: request.name,
method: request.method,
status: request.status,
type: request.subType,
size: request.size,
time: request.displayTime,
}
if (node) {
node.data = data
node.render()
} else {
node = this._requestDataGrid.append(data, { selectable: true })
$(node.container).data('id', params.requestId)
}
if (request.hasErr) {
$(node.container).addClass(c('request-error'))
}
}
request.render()
this._requests[params.requestId] = request
}
_resReceivedExtraInfo = (params) => {
const request = this._requests[params.requestId]
if (!this._isRecording || !request) {
return
}
request.resHeaders = params.headers
this._updateType(request)
request.render()
}
_updateType(request) {
const contentType = request.resHeaders['content-type'] || ''
const { type, subType } = getType(contentType)
request.type = type
request.subType = subType
}
_resReceived = (params) => {
const request = this._requests[params.requestId]
if (!this._isRecording || !request) {
return
}
const { response } = params
const { status, headers } = response
request.status = status
if (status < 200 || status >= 300) {
request.hasErr = true
}
if (headers) {
request.resHeaders = headers
this._updateType(request)
}
request.render()
}
_loadingFinished = (params) => {
const request = this._requests[params.requestId]
if (!this._isRecording || !request) {
return
}
const time = params.timestamp * 1000
request.time = time - request.startTime
request.displayTime = ms(request.time)
request.size = params.encodedDataLength
request.done = true
request.resTxt = chobitsu.domain('Network').getResponseBody({
requestId: params.requestId,
}).body
request.render()
}
_loadingFailed = (params) => {
const request = this._requests[params.requestId]
if (!this._isRecording || !request) {
return
}
const time = params.timestamp * 1000
request.time = time - request.startTime
request.displayTime = ms(request.time)
request.hasErr = true
request.status = 0
request.done = true
request.render()
}
_copyCurl = () => {
const request = this._selectedRequest
copy(
curlStr({
requestMethod: request.method,
url() {
return request.url
},
requestFormData() {
return request.data
},
requestHeaders() {
const reqHeaders = request.reqHeaders || {}
extend(reqHeaders, {
'User-Agent': navigator.userAgent,
Referer: location.href,
})
return map(reqHeaders, (value, name) => {
return {
name,
value,
}
})
},
})
)
this._container.notify('コピーしました', { icon: 'success' })
}
_updateButtons() {
const $control = this._$control
const $showDetail = $control.find(c('.show-detail'))
const $copyCurl = $control.find(c('.copy-curl'))
const iconDisabled = c('icon-disabled')
$showDetail.addClass(iconDisabled)
$copyCurl.addClass(iconDisabled)
if (this._selectedRequest) {
$showDetail.rmClass(iconDisabled)
$copyCurl.rmClass(iconDisabled)
}
}
_toggleRecording = () => {
this._$control.find(c('.record')).toggleClass(c('recording'))
this._isRecording = !this._isRecording
}
_showDetail = () => {
if (this._selectedRequest) {
if (this._splitMode) {
this._$network.css('width', '50%')
}
this._detail.show(this._selectedRequest)
}
}
_bindEvent() {
const $control = this._$control
const $filterText = this._$filterText
const requestDataGrid = this._requestDataGrid
const self = this
$control
.on('click', c('.clear-request'), () => this.clear())
.on('click', c('.show-detail'), this._showDetail)
.on('click', c('.copy-curl'), this._copyCurl)
.on('click', c('.record'), this._toggleRecording)
.on('click', c('.filter'), () => {
LunaModal.prompt('フィルター').then((filter) => {
if (isNull(filter)) return
$filterText.text(filter)
requestDataGrid.setOption('filter', trim(filter))
})
})
requestDataGrid.on('select', (node) => {
const id = $(node.container).data('id')
const request = self._requests[id]
this._selectedRequest = request
this._updateButtons()
if (this._splitMode) {
this._showDetail()
}
})
requestDataGrid.on('deselect', () => {
this._selectedRequest = null
this._updateButtons()
this._detail.hide()
})
this._resizeSensor.addListener(
throttle(() => this._updateDataGridHeight(), 15)
)
this._splitMediaQuery.on('match', () => {
this._detail.hide()
this._splitMode = true
})
this._splitMediaQuery.on('unmatch', () => {
this._detail.hide()
this._splitMode = false
})
this._detail.on('hide', () => {
if (this._splitMode) {
this._$network.css('width', '100%')
}
})
chobitsu.domain('Network').enable()
const network = chobitsu.domain('Network')
network.on('requestWillBeSent', this._reqWillBeSent)
network.on('responseReceivedExtraInfo', this._resReceivedExtraInfo)
network.on('responseReceived', this._resReceived)
network.on('loadingFinished', this._loadingFinished)
network.on('loadingFailed', this._loadingFailed)
emitter.on(emitter.SCALE, this._updateScale)
}
_updateScale = (scale) => {
this._splitMediaQuery.setQuery(`screen and (min-width: ${680 * scale}px)`)
}
destroy() {
super.destroy()
this._resizeSensor.destroy()
evalCss.remove(this._style)
this._splitMediaQuery.removeAllListeners()
const network = chobitsu.domain('Network')
network.off('requestWillBeSent', this._reqWillBeSent)
network.off('responseReceivedExtraInfo', this._resReceivedExtraInfo)
network.off('responseReceived', this._resReceived)
network.off('loadingFinished', this._loadingFinished)
emitter.off(emitter.SCALE, this._updateScale)
}
_initTpl() {
const $el = this._$el
$el.html(
c(`<div class="network">
<div class="control">
<span class="icon-record record recording"></span>
<span class="icon-clear clear-request"></span>
<span class="icon-eye icon-disabled show-detail"></span>
<span class="icon-copy icon-disabled copy-curl"></span>
<span class="filter-text"></span>
<span class="icon-filter filter"></span>
</div>
<div class="requests"></div>
</div>
<div class="detail"></div>`)
)
this._$network = $el.find(c('.network'))
this._$detail = $el.find(c('.detail'))
this._$requests = $el.find(c('.requests'))
this._$control = $el.find(c('.control'))
this._$filterText = $el.find(c('.filter-text'))
}
}