| 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')) |
| } |
| } |
|
|