在页面上发起 XHR 请求
目前為
// ==UserScript==
// @name http-on-pages
// @namespace https://github.com/pansong291/
// @version 0.1.4
// @description 在页面上发起 XHR 请求
// @author paso
// @license Apache-2.0
// @match *://*/*
// @grant none
// @require https://update.greasyfork.org/scripts/473443/1294140/popup-inject.js
// ==/UserScript==
;(function () {
'use strict';
const namespace = 'paso-http-on-pages'
window.paso.injectPopup({
namespace,
actionName: 'Http Request',
collapse: '70%',
content: `<div class="tip-box info monospace">const data = { headers: {}, params: {}, body: void 0, withCredentials: true }</div>
<div class="flex gap-4" style="flex-direction: row;align-items: flex-start;">
<select id="${namespace}-http-method" class="input"></select>
<input type="text" id="${namespace}-ipt-url" class="monospace input" autocomplete="off">
<button type="button" id="${namespace}-btn-submit" class="button">Submit</button>
</div>
<div id="${namespace}-error-tip-box" class="monospace"></div>
<textarea id="${namespace}-ipt-data" class="monospace input" spellcheck="false"></textarea>`,
style: `<style>
.${namespace} .popup {
gap: 4px;
}
.${namespace} .gap-4 {
gap: 4px;
}
.${namespace} .tip-box.info {
background: #d3dff7;
border-left: 6px solid #3d7fff;
border-radius: 4px;
padding: 16px;
font-size: 14px;
}
#${namespace}-http-method {
width: 90px;
height: 32px;
}
#${namespace}-ipt-url {
flex: 1 0 300px;
height: 32px;
font-size: 14px;
}
#${namespace}-btn-submit {
width: 100px;
height: 32px;
}
#${namespace}-ipt-data {
height: 400px;
font-size: 14px;
}
#${namespace}-error-tip-box {
background: #fdd;
border-left: 6px solid #f66;
border-radius: 4px;
padding: 16px;
font-size: 14px;
}
#${namespace}-error-tip-box:empty {
display: none;
}
</style>`
}).then(() => {
const sel_http_method = document.getElementById(`${namespace}-http-method`)
const ipt_url = document.getElementById(`${namespace}-ipt-url`)
const ipt_data = document.getElementById(`${namespace}-ipt-data`)
const btn_submit = document.getElementById(`${namespace}-btn-submit`)
const error_tip = document.getElementById(`${namespace}-error-tip-box`)
const method_options = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']
sel_http_method.innerHTML = method_options.map(op => `<option value="${op}">${op}</option>`).join('')
const cache = getCache();
if (cache) {
if (cache.method) sel_http_method.value = cache.method
if (cache.url) ipt_url.value = cache.url
if (cache.data) ipt_data.value = cache.data
}
btn_submit.onclick = tryTo(() => {
const method = sel_http_method.value
const url = ipt_url.value
const dataStr = ipt_data.value
if (!url) throw 'Url is required'
const isGet = method === 'GET'
const data = {
headers: {'Content-Type': isGet ? 'application/x-www-form-urlencoded' : 'application/json'},
params: {},
body: void 0,
withCredentials: true
}
const handleData = new Function('data', dataStr)
handleData.call(data, data)
const request = new XMLHttpRequest()
request.open(method, url + serializeQueryParam(data.params))
request.withCredentials = !!data.withCredentials
Object.entries(data.headers).forEach(([n, v]) => {
request.setRequestHeader(n, v)
})
request.send(isGet ? void 0 : data.body)
saveCache({method, url, data: dataStr})
error_tip.innerText = ''
}, e => {
error_tip.innerText = String(e)
})
})
function tryTo(fn, errorCallback) {
return function (...args) {
try {
fn.apply(this, args)
} catch (e) {
console.error(e)
errorCallback?.(e)
}
}
}
function serializeQueryParam(param, prefix = '?') {
if (!param) return ''
if (typeof param === 'string') return prefix + param
const str = Object.entries(param).map(([k, v]) => k + '=' + encodeURIComponent(String(v))).join('&')
if (str) return prefix + str
return str
}
function saveCache(obj) {
localStorage.setItem(namespace, JSON.stringify(obj))
}
function getCache() {
const str = localStorage.getItem(namespace)
try {
if (str) return JSON.parse(str)
} catch (e) {
console.error(e)
}
}
})();