mirror of
https://github.com/liriliri/eruda.git
synced 2026-02-02 09:49:00 +08:00
feat(network): copy as curl #220
This commit is contained in:
@@ -13,28 +13,8 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.control {
|
||||
@include absolute(100%, 40px);
|
||||
cursor: default;
|
||||
font-size: 0;
|
||||
@include control();
|
||||
padding: 10px 10px 10px 35px;
|
||||
background: var(--darker-background);
|
||||
color: var(--primary);
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
.icon-clear,
|
||||
.icon-filter,
|
||||
.icon-copy {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
font-size: $font-size-l;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
transition: color $anim-duration;
|
||||
&:active {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
.icon-clear {
|
||||
padding-right: 0px;
|
||||
left: 0;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import Emitter from 'licia/Emitter'
|
||||
import trim from 'licia/trim'
|
||||
import isEmpty from 'licia/isEmpty'
|
||||
import map from 'licia/map'
|
||||
import escape from 'licia/escape'
|
||||
import copy from 'licia/copy'
|
||||
import extend from 'licia/extend'
|
||||
import { classPrefix as c } from '../lib/util'
|
||||
import { curlStr } from './util'
|
||||
|
||||
export default class Detail extends Emitter {
|
||||
constructor($container) {
|
||||
super()
|
||||
export default class Detail {
|
||||
constructor($container, devtools) {
|
||||
this._$container = $container
|
||||
this._devtools = devtools
|
||||
|
||||
this._detailData = {}
|
||||
this._bindEvent()
|
||||
@@ -54,9 +56,10 @@ export default class Detail extends Emitter {
|
||||
resTxt = `<pre class="${c('response')}">${escape(data.resTxt)}</pre>`
|
||||
}
|
||||
|
||||
const html = `<div class="${c('breadcrumb')}">
|
||||
const html = `<div class="${c('control')}">
|
||||
<span class="${c('icon-arrow-left back')}"></span>
|
||||
${escape(data.url)}
|
||||
<span class="${c('url')}">${escape(data.url)}</span>
|
||||
<span class="${c('icon-copy copy-curl')}"></span>
|
||||
</div>
|
||||
<div class="${c('http')}">
|
||||
${postData}
|
||||
@@ -85,9 +88,43 @@ export default class Detail extends Emitter {
|
||||
hide() {
|
||||
this._$container.hide()
|
||||
}
|
||||
_copyCurl = () => {
|
||||
const detailData = this._detailData
|
||||
|
||||
copy(
|
||||
curlStr({
|
||||
requestMethod: detailData.method,
|
||||
url() {
|
||||
return detailData.url
|
||||
},
|
||||
requestFormData() {
|
||||
return detailData.data
|
||||
},
|
||||
requestHeaders() {
|
||||
const reqHeaders = detailData.reqHeaders || {}
|
||||
extend(reqHeaders, {
|
||||
'User-Agent': navigator.userAgent,
|
||||
Referer: location.href,
|
||||
})
|
||||
|
||||
return map(reqHeaders, (value, name) => {
|
||||
return {
|
||||
name,
|
||||
value,
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
this._devtools.notify('Copied')
|
||||
}
|
||||
_bindEvent() {
|
||||
const devtools = this._devtools
|
||||
|
||||
this._$container
|
||||
.on('click', c('.back'), () => this.hide())
|
||||
.on('click', c('.copy-curl'), this._copyCurl)
|
||||
.on('click', c('.http .response'), () => {
|
||||
const data = this._detailData
|
||||
const resTxt = data.resTxt
|
||||
@@ -109,7 +146,14 @@ export default class Detail extends Emitter {
|
||||
})
|
||||
|
||||
const showSources = (type, data) => {
|
||||
this.emit('showSources', type, data)
|
||||
const sources = devtools.get('sources')
|
||||
if (!sources) {
|
||||
return
|
||||
}
|
||||
|
||||
sources.set(type, data)
|
||||
|
||||
devtools.showTool('sources')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import Tool from '../DevTools/Tool'
|
||||
import $ from 'licia/$'
|
||||
import ms from 'licia/ms'
|
||||
import each from 'licia/each'
|
||||
import last from 'licia/last'
|
||||
import Detail from './Detail'
|
||||
import throttle from 'licia/throttle'
|
||||
import { getFileName, classPrefix as c } from '../lib/util'
|
||||
@@ -10,6 +9,7 @@ import evalCss from '../lib/evalCss'
|
||||
import chobitsu from '../lib/chobitsu'
|
||||
import LunaDataGrid from 'luna-data-grid'
|
||||
import ResizeSensor from 'licia/ResizeSensor'
|
||||
import { getType } from './util'
|
||||
|
||||
export default class Network extends Tool {
|
||||
constructor() {
|
||||
@@ -25,7 +25,7 @@ export default class Network extends Tool {
|
||||
|
||||
this._container = container
|
||||
this._initTpl()
|
||||
this._detail = new Detail(this._$detail)
|
||||
this._detail = new Detail(this._$detail, container)
|
||||
this._requestDataGrid = new LunaDataGrid(this._$requests.get(0), {
|
||||
columns: [
|
||||
{
|
||||
@@ -183,7 +183,6 @@ export default class Network extends Tool {
|
||||
}
|
||||
_bindEvent() {
|
||||
const $el = this._$el
|
||||
const container = this._container
|
||||
|
||||
const self = this
|
||||
|
||||
@@ -198,15 +197,6 @@ export default class Network extends Tool {
|
||||
self._detail.show(request)
|
||||
})
|
||||
|
||||
this._detail.on('showSources', function (type, data) {
|
||||
const sources = container.get('sources')
|
||||
if (!sources) return
|
||||
|
||||
sources.set(type, data)
|
||||
|
||||
container.showTool('sources')
|
||||
})
|
||||
|
||||
this._resizeSensor.addListener(
|
||||
throttle(() => this._updateDataGridHeight(), 15)
|
||||
)
|
||||
@@ -247,14 +237,3 @@ export default class Network extends Tool {
|
||||
this._$requests = $el.find(c('.requests'))
|
||||
}
|
||||
}
|
||||
|
||||
function getType(contentType) {
|
||||
if (!contentType) return 'unknown'
|
||||
|
||||
const type = contentType.split(';')[0].split('/')
|
||||
|
||||
return {
|
||||
type: type[0],
|
||||
subType: last(type),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,29 +29,23 @@
|
||||
display: none;
|
||||
padding-top: 40px;
|
||||
background: var(--background);
|
||||
.breadcrumb {
|
||||
@include breadcrumb();
|
||||
@include absolute(100%, 40px);
|
||||
font-size: $font-size-s;
|
||||
.control {
|
||||
@include control();
|
||||
padding: 10px 35px;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.icon-arrow-left {
|
||||
.url {
|
||||
font-size: $font-size-s;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
font-size: $font-size-l;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
transition: color $anim-duration;
|
||||
&:active {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
.icon-arrow-left {
|
||||
left: 0;
|
||||
}
|
||||
.icon-copy {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.http {
|
||||
@include overflow-auto(y);
|
||||
@@ -90,7 +84,7 @@
|
||||
@include overflow-auto(x);
|
||||
padding: $padding;
|
||||
font-size: $font-size-s;
|
||||
margin-bottom: 10px;
|
||||
margin: 10px 0;
|
||||
white-space: pre-wrap;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--foreground);
|
||||
|
||||
106
src/Network/util.js
Normal file
106
src/Network/util.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import last from 'licia/last'
|
||||
import detectOs from 'licia/detectOs'
|
||||
|
||||
export function getType(contentType) {
|
||||
if (!contentType) return 'unknown'
|
||||
|
||||
const type = contentType.split(';')[0].split('/')
|
||||
|
||||
return {
|
||||
type: type[0],
|
||||
subType: last(type),
|
||||
}
|
||||
}
|
||||
|
||||
export function curlStr(request) {
|
||||
let platform = detectOs()
|
||||
if (platform === 'windows') {
|
||||
platform = 'win'
|
||||
}
|
||||
/* eslint-disable */
|
||||
let command = []
|
||||
const ignoredHeaders = new Set([
|
||||
'accept-encoding',
|
||||
'host',
|
||||
'method',
|
||||
'path',
|
||||
'scheme',
|
||||
'version',
|
||||
])
|
||||
|
||||
function escapeStringWin(str) {
|
||||
const encapsChars = /[\r\n]/.test(str) ? '^"' : '"'
|
||||
return (
|
||||
encapsChars +
|
||||
str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/[^a-zA-Z0-9\s_\-:=+~'\/.',?;()*`&]/g, '^$&')
|
||||
.replace(/%(?=[a-zA-Z0-9_])/g, '%^')
|
||||
.replace(/\r?\n/g, '^\n\n') +
|
||||
encapsChars
|
||||
)
|
||||
}
|
||||
|
||||
function escapeStringPosix(str) {
|
||||
function escapeCharacter(x) {
|
||||
const code = x.charCodeAt(0)
|
||||
let hexString = code.toString(16)
|
||||
while (hexString.length < 4) {
|
||||
hexString = '0' + hexString
|
||||
}
|
||||
|
||||
return '\\u' + hexString
|
||||
}
|
||||
|
||||
if (/[\0-\x1F\x7F-\x9F!]|\'/.test(str)) {
|
||||
return (
|
||||
"$'" +
|
||||
str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/\'/g, "\\'")
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/[\0-\x1F\x7F-\x9F!]/g, escapeCharacter) +
|
||||
"'"
|
||||
)
|
||||
}
|
||||
return "'" + str + "'"
|
||||
}
|
||||
|
||||
const escapeString = platform === 'win' ? escapeStringWin : escapeStringPosix
|
||||
|
||||
command.push(escapeString(request.url()).replace(/[[{}\]]/g, '\\$&'))
|
||||
|
||||
let inferredMethod = 'GET'
|
||||
const data = []
|
||||
const formData = request.requestFormData()
|
||||
if (formData) {
|
||||
data.push('--data-raw ' + escapeString(formData))
|
||||
ignoredHeaders.add('content-length')
|
||||
inferredMethod = 'POST'
|
||||
}
|
||||
|
||||
if (request.requestMethod !== inferredMethod) {
|
||||
command.push('-X ' + escapeString(request.requestMethod))
|
||||
}
|
||||
|
||||
const requestHeaders = request.requestHeaders()
|
||||
for (let i = 0; i < requestHeaders.length; i++) {
|
||||
const header = requestHeaders[i]
|
||||
const name = header.name.replace(/^:/, '')
|
||||
if (ignoredHeaders.has(name.toLowerCase())) {
|
||||
continue
|
||||
}
|
||||
command.push('-H ' + escapeString(name + ': ' + header.value))
|
||||
}
|
||||
command = command.concat(data)
|
||||
command.push('--compressed')
|
||||
|
||||
return (
|
||||
'curl ' +
|
||||
command.join(
|
||||
command.length >= 3 ? (platform === 'win' ? ' ^\n ' : ' \\\n ') : ' '
|
||||
)
|
||||
)
|
||||
}
|
||||
26
src/eruda.js
26
src/eruda.js
@@ -148,22 +148,23 @@ export default {
|
||||
if (!this._isInit) logger.error('Please call "eruda.init()" first')
|
||||
return this._isInit
|
||||
},
|
||||
_initContainer(el, useShadowDom) {
|
||||
if (!el) {
|
||||
el = document.createElement('div')
|
||||
document.documentElement.appendChild(el)
|
||||
_initContainer(container, useShadowDom) {
|
||||
if (!container) {
|
||||
container = document.createElement('div')
|
||||
document.documentElement.appendChild(container)
|
||||
}
|
||||
|
||||
el.id = 'eruda'
|
||||
el.style.all = 'initial'
|
||||
this._container = el
|
||||
container.id = 'eruda'
|
||||
container.style.all = 'initial'
|
||||
this._container = container
|
||||
|
||||
let shadowRoot
|
||||
let el
|
||||
if (useShadowDom) {
|
||||
if (el.attachShadow) {
|
||||
shadowRoot = el.attachShadow({ mode: 'open' })
|
||||
} else if (el.createShadowRoot) {
|
||||
shadowRoot = el.createShadowRoot()
|
||||
if (container.attachShadow) {
|
||||
shadowRoot = container.attachShadow({ mode: 'open' })
|
||||
} else if (container.createShadowRoot) {
|
||||
shadowRoot = container.createShadowRoot()
|
||||
}
|
||||
if (shadowRoot) {
|
||||
// font-face doesn't work inside shadow dom.
|
||||
@@ -178,9 +179,8 @@ export default {
|
||||
}
|
||||
|
||||
if (!this._shadowRoot) {
|
||||
const oldEl = el
|
||||
el = document.createElement('div')
|
||||
oldEl.appendChild(el)
|
||||
container.appendChild(el)
|
||||
}
|
||||
|
||||
extend(el, {
|
||||
|
||||
@@ -29,6 +29,29 @@
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
@mixin control {
|
||||
@include absolute(100%, 40px);
|
||||
cursor: default;
|
||||
font-size: 0;
|
||||
background: var(--darker-background);
|
||||
color: var(--primary);
|
||||
line-height: 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
[class^='icon-'],
|
||||
[class*=' icon-'] {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
font-size: $font-size-l;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
transition: color $anim-duration;
|
||||
&:active {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin clear-float {
|
||||
&:after {
|
||||
content: '';
|
||||
|
||||
Reference in New Issue
Block a user