Ajax-hook-userscript

Ajax hook is a lightweight library for intercepting XMLHttpRequest objects.

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/450907/1090926/Ajax-hook-userscript.js

  1. // ==UserScript==
  2. // @name Ajax-hook-userscript
  3. // @namespace https://github.com/wendux/Ajax-hook
  4. // @version 2.0.3
  5. // @author wendux
  6. // @description Ajax hook is a lightweight library for intercepting XMLHttpRequest objects.
  7. // @license MIT
  8. // @grant unsafeWindow
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. const ah = (W => {
  13. /*
  14. * author: wendux
  15. * email: 824783146@qq.com
  16. * source code: https://github.com/wendux/Ajax-hook
  17. * modify by https://github.com/lzghzr
  18. */
  19.  
  20. // Save original XMLHttpRequest as _rxhr
  21. var realXhr = "_rxhr"
  22.  
  23. function configEvent(event, xhrProxy) {
  24. var e = {};
  25. for (var attr in event) e[attr] = event[attr];
  26. // xhrProxy instead
  27. e.target = e.currentTarget = xhrProxy
  28. return e;
  29. }
  30.  
  31. function hook(proxy) {
  32. // Avoid double hookAjax
  33. W[realXhr] = W[realXhr] || W.XMLHttpRequest
  34.  
  35. W.XMLHttpRequest = function () {
  36. var xhr = new W[realXhr];
  37. // We shouldn't hookAjax XMLHttpRequest.prototype because we can't
  38. // guarantee that all attributes are on the prototype。
  39. // Instead, hooking XMLHttpRequest instance can avoid this problem.
  40. for (var attr in xhr) {
  41. var type = "";
  42. try {
  43. type = typeof xhr[attr] // May cause exception on some browser
  44. } catch (e) {
  45. }
  46. if (type === "function") {
  47. // hookAjax methods of xhr, such as `open`、`send` ...
  48. this[attr] = hookFunction(attr);
  49. } else {
  50. Object.defineProperty(this, attr, {
  51. get: getterFactory(attr),
  52. set: setterFactory(attr),
  53. enumerable: true
  54. })
  55. }
  56. }
  57. var that = this;
  58. xhr.getProxy = function () {
  59. return that
  60. }
  61. this.xhr = xhr;
  62. }
  63.  
  64. // Generate getter for attributes of xhr
  65. function getterFactory(attr) {
  66. return function () {
  67. var v = this.hasOwnProperty(attr + "_") ? this[attr + "_"] : this.xhr[attr];
  68. var attrGetterHook = (proxy[attr] || {})["getter"]
  69. return attrGetterHook && attrGetterHook(v, this) || v
  70. }
  71. }
  72.  
  73. // Generate setter for attributes of xhr; by this we have an opportunity
  74. // to hookAjax event callbacks (eg: `onload`) of xhr;
  75. function setterFactory(attr) {
  76. return function (v) {
  77. var xhr = this.xhr;
  78. var that = this;
  79. var hook = proxy[attr];
  80. // hookAjax event callbacks such as `onload`、`onreadystatechange`...
  81. if (attr.substring(0, 2) === 'on') {
  82. that[attr + "_"] = v;
  83. xhr[attr] = function (e) {
  84. e = configEvent(e, that)
  85. var ret = proxy[attr] && proxy[attr].call(that, xhr, e)
  86. ret || v.call(that, e);
  87. }
  88. } else {
  89. //If the attribute isn't writable, generate proxy attribute
  90. var attrSetterHook = (hook || {})["setter"];
  91. v = attrSetterHook && attrSetterHook(v, that) || v
  92. this[attr + "_"] = v;
  93. try {
  94. // Not all attributes of xhr are writable(setter may undefined).
  95. xhr[attr] = v;
  96. } catch (e) {
  97. }
  98. }
  99. }
  100. }
  101.  
  102. // Hook methods of xhr.
  103. function hookFunction(fun) {
  104. return function () {
  105. var args = [].slice.call(arguments)
  106. if (proxy[fun]) {
  107. var ret = proxy[fun].call(this, args, this.xhr)
  108. // If the proxy return value exists, return it directly,
  109. // otherwise call the function of xhr.
  110. if (ret) return ret;
  111. }
  112. return this.xhr[fun].apply(this.xhr, args);
  113. }
  114. }
  115.  
  116. // Return the real XMLHttpRequest
  117. return W[realXhr];
  118. }
  119.  
  120. function unHook() {
  121. if (W[realXhr]) W.XMLHttpRequest = W[realXhr];
  122. W[realXhr] = undefined;
  123. }
  124.  
  125. /*
  126. * author: wendux
  127. * email: 824783146@qq.com
  128. * source code: https://github.com/wendux/Ajax-hook
  129. */
  130.  
  131.  
  132. var events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort'];
  133. var eventLoad = events[0],
  134. eventLoadEnd = events[1],
  135. eventTimeout = events[2],
  136. eventError = events[3],
  137. eventReadyStateChange = events[4],
  138. eventAbort = events[5];
  139.  
  140.  
  141. var singleton,
  142. prototype = 'prototype';
  143.  
  144.  
  145. function proxy(proxy) {
  146. if (singleton) throw "Proxy already exists";
  147. return singleton = new Proxy(proxy);
  148. }
  149.  
  150. function unProxy() {
  151. singleton = null
  152. unHook()
  153. }
  154.  
  155. function trim(str) {
  156. return str.replace(/^\s+|\s+$/g, '');
  157. }
  158.  
  159. function getEventTarget(xhr) {
  160. return xhr.watcher || (xhr.watcher = document.createElement('a'));
  161. }
  162.  
  163. function triggerListener(xhr, name) {
  164. var xhrProxy = xhr.getProxy();
  165. var callback = 'on' + name + '_';
  166. var event = configEvent({ type: name }, xhrProxy);
  167. xhrProxy[callback] && xhrProxy[callback](event);
  168. var evt;
  169. if (typeof (Event) === 'function') {
  170. evt = new Event(name, { bubbles: false });
  171. } else {
  172. // https://stackoverflow.com/questions/27176983/dispatchevent-not-working-in-ie11
  173. evt = document.createEvent('Event');
  174. evt.initEvent(name, false, true);
  175. }
  176. getEventTarget(xhr).dispatchEvent(evt);
  177. }
  178.  
  179.  
  180. function Handler(xhr) {
  181. this.xhr = xhr;
  182. this.xhrProxy = xhr.getProxy();
  183. }
  184.  
  185. Handler[prototype] = Object.create({
  186. resolve: function resolve(response) {
  187. var xhrProxy = this.xhrProxy;
  188. var xhr = this.xhr;
  189. xhrProxy.readyState = 4;
  190. xhr.resHeader = response.headers;
  191. xhrProxy.response = xhrProxy.responseText = response.response;
  192. xhrProxy.statusText = response.statusText;
  193. xhrProxy.status = response.status;
  194. triggerListener(xhr, eventReadyStateChange);
  195. triggerListener(xhr, eventLoad);
  196. triggerListener(xhr, eventLoadEnd);
  197. },
  198. reject: function reject(error) {
  199. this.xhrProxy.status = 0;
  200. triggerListener(this.xhr, error.type);
  201. triggerListener(this.xhr, eventLoadEnd);
  202. }
  203. });
  204.  
  205. function makeHandler(next) {
  206. function sub(xhr) {
  207. Handler.call(this, xhr);
  208. }
  209.  
  210. sub[prototype] = Object.create(Handler[prototype]);
  211. sub[prototype].next = next;
  212. return sub;
  213. }
  214.  
  215. var RequestHandler = makeHandler(function (rq) {
  216. var xhr = this.xhr;
  217. rq = rq || xhr.config;
  218. xhr.withCredentials = rq.withCredentials;
  219. xhr.open(rq.method, rq.url, rq.async !== false, rq.user, rq.password);
  220. for (var key in rq.headers) {
  221. xhr.setRequestHeader(key, rq.headers[key]);
  222. }
  223. xhr.send(rq.body);
  224. });
  225.  
  226. var ResponseHandler = makeHandler(function (response) {
  227. this.resolve(response);
  228. });
  229.  
  230. var ErrorHandler = makeHandler(function (error) {
  231. this.reject(error);
  232. });
  233.  
  234. function Proxy(proxy) {
  235. var onRequest = proxy.onRequest,
  236. onResponse = proxy.onResponse,
  237. onError = proxy.onError;
  238.  
  239. function handleResponse(xhr, xhrProxy) {
  240. var handler = new ResponseHandler(xhr);
  241. if (!onResponse) return handler.resolve();
  242. var ret = {
  243. response: xhrProxy.response,
  244. status: xhrProxy.status,
  245. statusText: xhrProxy.statusText,
  246. config: xhr.config,
  247. headers: xhr.resHeader || xhr.getAllResponseHeaders().split('\r\n').reduce(function (ob, str) {
  248. if (str === "") return ob;
  249. var m = str.split(":");
  250. ob[m.shift()] = trim(m.join(':'));
  251. return ob;
  252. }, {})
  253. };
  254. onResponse(ret, handler);
  255. }
  256.  
  257. function onerror(xhr, xhrProxy, e) {
  258. var handler = new ErrorHandler(xhr);
  259. var error = { config: xhr.config, error: e };
  260. if (onError) {
  261. onError(error, handler);
  262. } else {
  263. handler.next(error);
  264. }
  265. }
  266.  
  267. function preventXhrProxyCallback() {
  268. return true;
  269. }
  270.  
  271. function errorCallback(xhr, e) {
  272. onerror(xhr, this, e);
  273. return true;
  274. }
  275.  
  276. function stateChangeCallback(xhr, xhrProxy) {
  277. if (xhr.readyState === 4 && xhr.status !== 0) {
  278. handleResponse(xhr, xhrProxy);
  279. } else if (xhr.readyState !== 4) {
  280. triggerListener(xhr, eventReadyStateChange);
  281. }
  282. return true;
  283. }
  284.  
  285. return hook({
  286. onload: preventXhrProxyCallback,
  287. onloadend: preventXhrProxyCallback,
  288. onerror: errorCallback,
  289. ontimeout: errorCallback,
  290. onabort: errorCallback,
  291. onreadystatechange: function (xhr) {
  292. return stateChangeCallback(xhr, this);
  293. },
  294. open: function open(args, xhr) {
  295. var _this = this;
  296. var config = xhr.config = { headers: {} };
  297. config.method = args[0];
  298. config.url = args[1];
  299. config.async = args[2];
  300. config.user = args[3];
  301. config.password = args[4];
  302. config.xhr = xhr;
  303. var evName = 'on' + eventReadyStateChange;
  304. if (!xhr[evName]) {
  305. xhr[evName] = function () {
  306. return stateChangeCallback(xhr, _this);
  307. };
  308. }
  309.  
  310. var defaultErrorHandler = function defaultErrorHandler(e) {
  311. onerror(xhr, _this, configEvent(e, _this));
  312. };
  313. [eventError, eventTimeout, eventAbort].forEach(function (e) {
  314. var event = 'on' + e;
  315. if (!xhr[event]) xhr[event] = defaultErrorHandler;
  316. });
  317.  
  318. // 如果有请求拦截器,则在调用onRequest后再打开链接。因为onRequest最佳调用时机是在send前,
  319. // 所以我们在send拦截函数中再手动调用open,因此返回true阻止xhr.open调用。
  320. //
  321. // 如果没有请求拦截器,则不用阻断xhr.open调用
  322. if (onRequest) return true;
  323. },
  324. send: function (args, xhr) {
  325. var config = xhr.config
  326. config.withCredentials = xhr.withCredentials
  327. config.body = args[0];
  328. if (onRequest) {
  329. // In 'onRequest', we may call XHR's event handler, such as `xhr.onload`.
  330. // However, XHR's event handler may not be set until xhr.send is called in
  331. // the user's code, so we use `setTimeout` to avoid this situation
  332. var req = function () {
  333. onRequest(config, new RequestHandler(xhr));
  334. }
  335. config.async === false ? req() : setTimeout(req)
  336. return true;
  337. }
  338. },
  339. setRequestHeader: function (args, xhr) {
  340. // Collect request headers
  341. xhr.config.headers[args[0].toLowerCase()] = args[1];
  342. return true;
  343. },
  344. addEventListener: function (args, xhr) {
  345. var _this = this;
  346. if (events.indexOf(args[0]) !== -1) {
  347. var handler = args[1];
  348. getEventTarget(xhr).addEventListener(args[0], function (e) {
  349. var event = configEvent(e, _this);
  350. event.type = args[0];
  351. event.isTrusted = true;
  352. handler.call(_this, event);
  353. });
  354. return true;
  355. }
  356. },
  357. getAllResponseHeaders: function (_, xhr) {
  358. var headers = xhr.resHeader
  359. if (headers) {
  360. var header = "";
  361. for (var key in headers) {
  362. header += key + ': ' + headers[key] + '\r\n';
  363. }
  364. return header;
  365. }
  366. },
  367. getResponseHeader: function (args, xhr) {
  368. var headers = xhr.resHeader
  369. if (headers) {
  370. return headers[(args[0] || '').toLowerCase()];
  371. }
  372. }
  373. });
  374. }
  375.  
  376. return {
  377. proxy,
  378. unProxy,
  379. hook,
  380. unHook,
  381. }
  382. })(typeof unsafeWindow === 'undefined' ? window : unsafeWindow);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址