1
0
mirror of synced 2025-12-09 07:08:17 +08:00

refactor(network): use chobitsu

This commit is contained in:
redhoodsu
2020-09-06 07:46:11 +08:00
parent fb75788e20
commit 7d872efb68
5 changed files with 93 additions and 406 deletions

View File

@@ -99,12 +99,6 @@ elements.set(document.body);
Display requests.
### Config
|Name |Type |Desc |
|-------------|-------|--------------------|
|overrideFetch|boolean|Catch Fetch Requests|
### clear
Clear requests.

View File

@@ -1,85 +0,0 @@
import { getType, lenToUtf8Bytes } from './util'
import {
Emitter,
fullUrl,
uniqId,
isStr,
getFileName,
now,
toNum,
fileSize,
isEmpty
} from '../lib/util'
export default class FetchRequest extends Emitter {
constructor(url, options = {}) {
super()
if (url instanceof window.Request) url = url.url
this._url = fullUrl(url)
this._id = uniqId('request')
this._options = options
this._reqHeaders = options.headers || {}
this._method = options.method || 'GET'
}
send(fetchResult) {
const options = this._options
const data = isStr(options.body) ? options.body : ''
this._fetch = fetchResult
this.emit('send', this._id, {
name: getFileName(this._url),
url: this._url,
data,
method: this._method
})
fetchResult.then(res => {
res = res.clone()
const type = getType(res.headers.get('Content-Type'))
res.text().then(resTxt => {
const data = {
type: type.type,
subType: type.subType,
time: now(),
size: getSize(res, resTxt),
resTxt: resTxt,
resHeaders: getHeaders(res),
status: res.status,
done: true
}
if (!isEmpty(this._reqHeaders)) {
data.reqHeaders = this._reqHeaders
}
this.emit('update', this._id, data)
})
return res
})
}
}
function getSize(res, resTxt) {
let size = 0
const contentLen = res.headers.get('Content-length')
if (contentLen) {
size = toNum(contentLen)
} else {
size = lenToUtf8Bytes(resTxt)
}
return `${fileSize(size)}B`
}
function getHeaders(res) {
const ret = {}
res.headers.forEach((val, key) => (ret[key] = val))
return ret
}

View File

@@ -1,19 +1,9 @@
import Tool from '../DevTools/Tool'
import XhrRequest from './XhrRequest'
import FetchRequest from './FetchRequest'
import Settings from '../Settings/Settings'
import {
isNative,
defaults,
now,
extend,
isEmpty,
$,
ms,
trim,
each
} from '../lib/util'
import { getFileName, isEmpty, $, ms, trim, each, last } from '../lib/util'
import evalCss from '../lib/evalCss'
import chobitsu from 'chobitsu'
chobitsu.domain('Network').enable()
export default class Network extends Tool {
constructor() {
@@ -26,17 +16,13 @@ export default class Network extends Tool {
this._tpl = require('./Network.hbs')
this._detailTpl = require('./detail.hbs')
this._requestsTpl = require('./requests.hbs')
this._datailData = {}
this._isFetchSupported = false
if (window.fetch) this._isFetchSupported = isNative(window.fetch)
this._detailData = {}
}
init($el, container) {
super.init($el)
this._container = container
this._bindEvent()
this._initCfg()
this.overrideXhr()
this._appendTpl()
}
show() {
@@ -48,82 +34,6 @@ export default class Network extends Tool {
this._requests = {}
this._render()
}
overrideXhr() {
const winXhrProto = window.XMLHttpRequest.prototype
const origSend = (this._origSend = winXhrProto.send)
const origOpen = (this._origOpen = winXhrProto.open)
const origSetRequestHeader = (this._origSetRequestHeader =
winXhrProto.setRequestHeader)
const self = this
winXhrProto.open = function(method, url) {
const xhr = this
const req = (xhr.erudaRequest = new XhrRequest(xhr, method, url))
req.on('send', (id, data) => self._addReq(id, data))
req.on('update', (id, data) => self._updateReq(id, data))
xhr.addEventListener('readystatechange', function() {
switch (xhr.readyState) {
case 2:
return req.handleHeadersReceived()
case 4:
return req.handleDone()
}
})
origOpen.apply(this, arguments)
}
winXhrProto.send = function(data) {
const req = this.erudaRequest
if (req) req.handleSend(data)
origSend.apply(this, arguments)
}
winXhrProto.setRequestHeader = function(key, val) {
const req = this.erudaRequest
if (req) req.handleReqHeadersSet(key, val)
origSetRequestHeader.apply(this, arguments)
}
}
restoreXhr() {
const winXhrProto = window.XMLHttpRequest.prototype
if (this._origOpen) winXhrProto.open = this._origOpen
if (this._origSend) winXhrProto.send = this._origSend
if (this._origSetRequestHeader) {
winXhrProto.setRequestHeader = this._origSetRequestHeader
}
}
overrideFetch() {
if (!this._isFetchSupported) return
const origFetch = (this._origFetch = window.fetch)
const self = this
window.fetch = function(...args) {
const req = new FetchRequest(...args)
req.on('send', (id, data) => self._addReq(id, data))
req.on('update', (id, data) => self._updateReq(id, data))
const fetchResult = origFetch(...args)
req.send(fetchResult)
return fetchResult
}
}
restoreFetch() {
if (!this._isFetchSupported) return
if (this._origFetch) window.fetch = this._origFetch
}
requests() {
const ret = []
each(this._requests, request => {
@@ -131,38 +41,75 @@ export default class Network extends Tool {
})
return ret
}
_addReq(id, data) {
defaults(data, {
name: '',
url: '',
_reqWillBeSent = params => {
this._requests[params.requestId] = {
name: getFileName(params.request.url),
url: params.request.url,
status: 'pending',
type: 'unknown',
subType: 'unknown',
size: 0,
data: '',
method: 'GET',
startTime: now(),
data: params.request.postData,
method: params.request.method,
startTime: params.timestamp * 1000,
time: 0,
resTxt: '',
done: false
})
done: false,
reqHeaders: params.request.headers || {},
resHeaders: {}
}
}
_resReceivedExtraInfo = params => {
const target = this._requests[params.requestId]
if (!target) {
return
}
this._requests[id] = data
target.resHeaders = params.headers
this._updateType(target)
this._render()
}
_updateType(target) {
const contentType = target.resHeaders['content-type'] || ''
const { type, subType } = getType(contentType)
target.type = type
target.subType = subType
}
_resReceived = params => {
const target = this._requests[params.requestId]
if (!target) {
return
}
const { response } = params
const { status, headers } = response
target.status = status
if (status < 200 || status >= 300) {
target.hasErr = true
}
if (headers) {
target.resHeaders = headers
this._updateType(target)
}
this._render()
}
_updateReq(id, data) {
const target = this._requests[id]
_loadingFinished = params => {
const target = this._requests[params.requestId]
if (!target) {
return
}
if (!target) return
extend(target, data)
target.time = target.time - target.startTime
const time = params.timestamp * 1000
target.time = time - target.startTime
target.displayTime = ms(target.time)
if (target.done && (target.status < 200 || target >= 300))
target.hasErr = true
target.size = params.encodedDataLength
target.done = true
target.resTxt = chobitsu.domain('Network').getResponseBody({
requestId: params.requestId
}).body
this._render()
}
@@ -211,59 +158,46 @@ export default class Network extends Tool {
container.showTool('sources')
}
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)
}
destroy() {
super.destroy()
evalCss.remove(this._style)
this.restoreXhr()
this.restoreFetch()
this._rmCfg()
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)
}
_showDetail(data) {
if (data.resTxt && trim(data.resTxt) === '') delete data.resTxt
if (isEmpty(data.resHeaders)) delete data.resHeaders
if (data.resTxt && trim(data.resTxt) === '') {
delete data.resTxt
}
if (isEmpty(data.resHeaders)) {
delete data.resHeaders
}
if (isEmpty(data.reqHeaders)) {
delete data.reqHeaders
}
this._$detail.html(this._detailTpl(data)).show()
this._detailData = data
}
_hideDetail() {
this._$detail.hide()
}
_rmCfg() {
const cfg = this.config
const settings = this._container.get('settings')
if (!settings) return
settings.remove(cfg, 'overrideFetch').remove('Network')
}
_appendTpl() {
const $el = this._$el
$el.html(this._tpl())
this._$detail = $el.find('.eruda-detail')
this._$requests = $el.find('.eruda-requests')
}
_initCfg() {
const cfg = (this.config = Settings.createCfg('network', {
overrideFetch: true
}))
if (cfg.get('overrideFetch')) this.overrideFetch()
cfg.on('change', (key, val) => {
switch (key) {
case 'overrideFetch':
return val ? this.overrideFetch() : this.restoreFetch()
}
})
const settings = this._container.get('settings')
settings
.text('Network')
.switch(cfg, 'overrideFetch', 'Catch Fetch Requests')
.separator()
}
_render() {
if (!this.active) return
@@ -279,3 +213,14 @@ export default class Network extends Tool {
this._$requests.html(html)
}
}
function getType(contentType) {
if (!contentType) return 'unknown'
const type = contentType.split(';')[0].split('/')
return {
type: type[0],
subType: last(type)
}
}

View File

@@ -1,138 +0,0 @@
import { getType, lenToUtf8Bytes, readBlobAsText } from './util'
import {
Emitter,
fullUrl,
uniqId,
isStr,
getFileName,
now,
each,
trim,
isCrossOrig,
toNum,
fileSize,
isEmpty
} from '../lib/util'
export default class XhrRequest extends Emitter {
constructor(xhr, method, url) {
super()
this._xhr = xhr
this._reqHeaders = {}
this._method = method
this._url = fullUrl(url)
this._id = uniqId('request')
}
handleSend(data) {
if (!isStr(data)) data = ''
data = {
name: getFileName(this._url),
url: this._url,
data,
method: this._method
}
if (!isEmpty(this._reqHeaders)) {
data.reqHeaders = this._reqHeaders
}
this.emit('send', this._id, data)
}
handleReqHeadersSet(key, val) {
if (key && val) {
this._reqHeaders[key] = val
}
}
handleHeadersReceived() {
const xhr = this._xhr
const type = getType(xhr.getResponseHeader('Content-Type'))
this.emit('update', this._id, {
type: type.type,
subType: type.subType,
size: getSize(xhr, true, this._url),
time: now(),
resHeaders: getHeaders(xhr)
})
}
handleDone() {
const xhr = this._xhr
const resType = xhr.responseType
let resTxt = ''
const update = () => {
this.emit('update', this._id, {
status: xhr.status,
done: true,
size: getSize(xhr, false, this._url),
time: now(),
resTxt
})
}
const type = getType(xhr.getResponseHeader('Content-Type'))
if (
resType === 'blob' &&
(type.type === 'text' ||
type.subType === 'javascript' ||
type.subType === 'json')
) {
readBlobAsText(xhr.response, (err, result) => {
if (result) resTxt = result
update()
})
} else {
if (resType === '' || resType === 'text') resTxt = xhr.responseText
if (resType === 'json') resTxt = JSON.stringify(xhr.response)
update()
}
}
}
function getHeaders(xhr) {
const raw = xhr.getAllResponseHeaders()
const lines = raw.split('\n')
const ret = {}
each(lines, line => {
line = trim(line)
if (line === '') return
const [key, val] = line.split(':', 2)
ret[key] = trim(val)
})
return ret
}
function getSize(xhr, headersOnly, url) {
let size = 0
function getStrSize() {
if (!headersOnly) {
const resType = xhr.responseType
let resTxt = ''
if (resType === '' || resType === 'text') resTxt = xhr.responseText
if (resTxt) size = lenToUtf8Bytes(resTxt)
}
}
if (isCrossOrig(url)) {
getStrSize()
} else {
try {
size = toNum(xhr.getResponseHeader('Content-Length'))
} catch (e) {
getStrSize()
}
}
if (size === 0) getStrSize()
return `${fileSize(size)}B`
}

View File

@@ -1,29 +0,0 @@
import { last } from '../lib/util'
export function getType(contentType) {
if (!contentType) return 'unknown'
const type = contentType.split(';')[0].split('/')
return {
type: type[0],
subType: last(type)
}
}
export function lenToUtf8Bytes(str) {
const m = encodeURIComponent(str).match(/%[89ABab]/g)
return str.length + (m ? m.length : 0)
}
export function readBlobAsText(blob, callback) {
const reader = new FileReader()
reader.onload = () => {
callback(null, reader.result)
}
reader.onerror = err => {
callback(err)
}
reader.readAsText(blob)
}