From b8301620c63f2ede5abc409e4ea7ff227aca63a7 Mon Sep 17 00:00:00 2001 From: redhoodsu Date: Sun, 11 Dec 2022 21:49:17 +0800 Subject: [PATCH] feat(network): copy as curl #220 --- src/Console/Console.scss | 22 +------- src/Network/Detail.js | 58 ++++++++++++++++++--- src/Network/Network.js | 25 +-------- src/Network/Network.scss | 30 +++++------ src/Network/util.js | 106 +++++++++++++++++++++++++++++++++++++++ src/eruda.js | 26 +++++----- src/style/mixin.scss | 23 +++++++++ 7 files changed, 208 insertions(+), 82 deletions(-) create mode 100644 src/Network/util.js diff --git a/src/Console/Console.scss b/src/Console/Console.scss index 37499eb..9b8e90d 100644 --- a/src/Console/Console.scss +++ b/src/Console/Console.scss @@ -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; diff --git a/src/Network/Detail.js b/src/Network/Detail.js index fe2a348..164bf58 100644 --- a/src/Network/Detail.js +++ b/src/Network/Detail.js @@ -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 = `
${escape(data.resTxt)}
` } - const html = `
+ const html = `
- ${escape(data.url)} + ${escape(data.url)} +
${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') } } } diff --git a/src/Network/Network.js b/src/Network/Network.js index ff3cea1..6a81a6c 100644 --- a/src/Network/Network.js +++ b/src/Network/Network.js @@ -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), - } -} diff --git a/src/Network/Network.scss b/src/Network/Network.scss index 19a2a76..5b260d4 100644 --- a/src/Network/Network.scss +++ b/src/Network/Network.scss @@ -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); diff --git a/src/Network/util.js b/src/Network/util.js new file mode 100644 index 0000000..54e1266 --- /dev/null +++ b/src/Network/util.js @@ -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 ') : ' ' + ) + ) +} diff --git a/src/eruda.js b/src/eruda.js index 108d648..5e6e588 100644 --- a/src/eruda.js +++ b/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, { diff --git a/src/style/mixin.scss b/src/style/mixin.scss index c508e8d..0994f13 100644 --- a/src/style/mixin.scss +++ b/src/style/mixin.scss @@ -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: '';