http-on-pages

在页面上发起 XHR 请求

Από την 19/01/2024. Δείτε την τελευταία έκδοση.

// ==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 = &#123; headers: &#123;}, params: &#123;}, 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)
        }
    }
})();