http-on-pages

在页面上发起 XHR 请求

当前为 2024-01-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name http-on-pages
  3. // @namespace https://github.com/pansong291/
  4. // @version 0.1.4
  5. // @description 在页面上发起 XHR 请求
  6. // @author paso
  7. // @license Apache-2.0
  8. // @match *://*/*
  9. // @grant none
  10. // @require https://update.greasyfork.org/scripts/473443/1294140/popup-inject.js
  11. // ==/UserScript==
  12.  
  13. ;(function () {
  14. 'use strict';
  15. const namespace = 'paso-http-on-pages'
  16. window.paso.injectPopup({
  17. namespace,
  18. actionName: 'Http Request',
  19. collapse: '70%',
  20. content: `<div class="tip-box info monospace">const data = &#123; headers: &#123;}, params: &#123;}, body: void 0, withCredentials: true }</div>
  21. <div class="flex gap-4" style="flex-direction: row;align-items: flex-start;">
  22. <select id="${namespace}-http-method" class="input"></select>
  23. <input type="text" id="${namespace}-ipt-url" class="monospace input" autocomplete="off">
  24. <button type="button" id="${namespace}-btn-submit" class="button">Submit</button>
  25. </div>
  26. <div id="${namespace}-error-tip-box" class="monospace"></div>
  27. <textarea id="${namespace}-ipt-data" class="monospace input" spellcheck="false"></textarea>`,
  28. style: `<style>
  29. .${namespace} .popup {
  30. gap: 4px;
  31. }
  32. .${namespace} .gap-4 {
  33. gap: 4px;
  34. }
  35. .${namespace} .tip-box.info {
  36. background: #d3dff7;
  37. border-left: 6px solid #3d7fff;
  38. border-radius: 4px;
  39. padding: 16px;
  40. font-size: 14px;
  41. }
  42. #${namespace}-http-method {
  43. width: 90px;
  44. height: 32px;
  45. }
  46. #${namespace}-ipt-url {
  47. flex: 1 0 300px;
  48. height: 32px;
  49. font-size: 14px;
  50. }
  51. #${namespace}-btn-submit {
  52. width: 100px;
  53. height: 32px;
  54. }
  55. #${namespace}-ipt-data {
  56. height: 400px;
  57. font-size: 14px;
  58. }
  59. #${namespace}-error-tip-box {
  60. background: #fdd;
  61. border-left: 6px solid #f66;
  62. border-radius: 4px;
  63. padding: 16px;
  64. font-size: 14px;
  65. }
  66. #${namespace}-error-tip-box:empty {
  67. display: none;
  68. }
  69. </style>`
  70. }).then(() => {
  71. const sel_http_method = document.getElementById(`${namespace}-http-method`)
  72. const ipt_url = document.getElementById(`${namespace}-ipt-url`)
  73. const ipt_data = document.getElementById(`${namespace}-ipt-data`)
  74. const btn_submit = document.getElementById(`${namespace}-btn-submit`)
  75. const error_tip = document.getElementById(`${namespace}-error-tip-box`)
  76. const method_options = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH']
  77. sel_http_method.innerHTML = method_options.map(op => `<option value="${op}">${op}</option>`).join('')
  78.  
  79. const cache = getCache();
  80. if (cache) {
  81. if (cache.method) sel_http_method.value = cache.method
  82. if (cache.url) ipt_url.value = cache.url
  83. if (cache.data) ipt_data.value = cache.data
  84. }
  85.  
  86. btn_submit.onclick = tryTo(() => {
  87. const method = sel_http_method.value
  88. const url = ipt_url.value
  89. const dataStr = ipt_data.value
  90. if (!url) throw 'Url is required'
  91. const isGet = method === 'GET'
  92. const data = {
  93. headers: {'Content-Type': isGet ? 'application/x-www-form-urlencoded' : 'application/json'},
  94. params: {},
  95. body: void 0,
  96. withCredentials: true
  97. }
  98. const handleData = new Function('data', dataStr)
  99. handleData.call(data, data)
  100. const request = new XMLHttpRequest()
  101. request.open(method, url + serializeQueryParam(data.params))
  102. request.withCredentials = !!data.withCredentials
  103. Object.entries(data.headers).forEach(([n, v]) => {
  104. request.setRequestHeader(n, v)
  105. })
  106. request.send(isGet ? void 0 : data.body)
  107. saveCache({method, url, data: dataStr})
  108. error_tip.innerText = ''
  109. }, e => {
  110. error_tip.innerText = String(e)
  111. })
  112. })
  113.  
  114. function tryTo(fn, errorCallback) {
  115. return function (...args) {
  116. try {
  117. fn.apply(this, args)
  118. } catch (e) {
  119. console.error(e)
  120. errorCallback?.(e)
  121. }
  122. }
  123. }
  124.  
  125. function serializeQueryParam(param, prefix = '?') {
  126. if (!param) return ''
  127. if (typeof param === 'string') return prefix + param
  128. const str = Object.entries(param).map(([k, v]) => k + '=' + encodeURIComponent(String(v))).join('&')
  129. if (str) return prefix + str
  130. return str
  131. }
  132.  
  133. function saveCache(obj) {
  134. localStorage.setItem(namespace, JSON.stringify(obj))
  135. }
  136.  
  137. function getCache() {
  138. const str = localStorage.getItem(namespace)
  139. try {
  140. if (str) return JSON.parse(str)
  141. } catch (e) {
  142. console.error(e)
  143. }
  144. }
  145. })();