UNPKG

@whitesev/utils

Version:

一个常用的工具库

1,028 lines (1,010 loc) 397 kB
define((function () { 'use strict'; const createCache = (lastNumberWeakMap) => { return (collection, nextNumber) => { lastNumberWeakMap.set(collection, nextNumber); return nextNumber; }; }; /* * The value of the constant Number.MAX_SAFE_INTEGER equals (2 ** 53 - 1) but it * is fairly new. */ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER === undefined ? 9007199254740991 : Number.MAX_SAFE_INTEGER; const TWO_TO_THE_POWER_OF_TWENTY_NINE = 536870912; const TWO_TO_THE_POWER_OF_THIRTY = TWO_TO_THE_POWER_OF_TWENTY_NINE * 2; const createGenerateUniqueNumber = (cache, lastNumberWeakMap) => { return (collection) => { const lastNumber = lastNumberWeakMap.get(collection); /* * Let's try the cheapest algorithm first. It might fail to produce a new * number, but it is so cheap that it is okay to take the risk. Just * increase the last number by one or reset it to 0 if we reached the upper * bound of SMIs (which stands for small integers). When the last number is * unknown it is assumed that the collection contains zero based consecutive * numbers. */ let nextNumber = lastNumber === undefined ? collection.size : lastNumber < TWO_TO_THE_POWER_OF_THIRTY ? lastNumber + 1 : 0; if (!collection.has(nextNumber)) { return cache(collection, nextNumber); } /* * If there are less than half of 2 ** 30 numbers stored in the collection, * the chance to generate a new random number in the range from 0 to 2 ** 30 * is at least 50%. It's benifitial to use only SMIs because they perform * much better in any environment based on V8. */ if (collection.size < TWO_TO_THE_POWER_OF_TWENTY_NINE) { while (collection.has(nextNumber)) { nextNumber = Math.floor(Math.random() * TWO_TO_THE_POWER_OF_THIRTY); } return cache(collection, nextNumber); } // Quickly check if there is a theoretical chance to generate a new number. if (collection.size > MAX_SAFE_INTEGER) { throw new Error('Congratulations, you created a collection of unique numbers which uses all available integers!'); } // Otherwise use the full scale of safely usable integers. while (collection.has(nextNumber)) { nextNumber = Math.floor(Math.random() * MAX_SAFE_INTEGER); } return cache(collection, nextNumber); }; }; const LAST_NUMBER_WEAK_MAP = new WeakMap(); const cache = createCache(LAST_NUMBER_WEAK_MAP); const generateUniqueNumber = createGenerateUniqueNumber(cache, LAST_NUMBER_WEAK_MAP); const isMessagePort = (sender) => { return typeof sender.start === 'function'; }; const PORT_MAP = new WeakMap(); const extendBrokerImplementation = (partialBrokerImplementation) => ({ ...partialBrokerImplementation, connect: ({ call }) => { return async () => { const { port1, port2 } = new MessageChannel(); const portId = await call('connect', { port: port1 }, [port1]); PORT_MAP.set(port2, portId); return port2; }; }, disconnect: ({ call }) => { return async (port) => { const portId = PORT_MAP.get(port); if (portId === undefined) { throw new Error('The given port is not connected.'); } await call('disconnect', { portId }); }; }, isSupported: ({ call }) => { return () => call('isSupported'); } }); const ONGOING_REQUESTS = new WeakMap(); const createOrGetOngoingRequests = (sender) => { if (ONGOING_REQUESTS.has(sender)) { // @todo TypeScript needs to be convinced that has() works as expected. return ONGOING_REQUESTS.get(sender); } const ongoingRequests = new Map(); ONGOING_REQUESTS.set(sender, ongoingRequests); return ongoingRequests; }; const createBroker = (brokerImplementation) => { const fullBrokerImplementation = extendBrokerImplementation(brokerImplementation); return (sender) => { const ongoingRequests = createOrGetOngoingRequests(sender); sender.addEventListener('message', (({ data: message }) => { const { id } = message; if (id !== null && ongoingRequests.has(id)) { const { reject, resolve } = ongoingRequests.get(id); ongoingRequests.delete(id); if (message.error === undefined) { resolve(message.result); } else { reject(new Error(message.error.message)); } } })); if (isMessagePort(sender)) { sender.start(); } const call = (method, params = null, transferables = []) => { return new Promise((resolve, reject) => { const id = generateUniqueNumber(ongoingRequests); ongoingRequests.set(id, { reject, resolve }); if (params === null) { sender.postMessage({ id, method }, transferables); } else { sender.postMessage({ id, method, params }, transferables); } }); }; const notify = (method, params, transferables = []) => { sender.postMessage({ id: null, method, params }, transferables); }; let functions = {}; for (const [key, handler] of Object.entries(fullBrokerImplementation)) { functions = { ...functions, [key]: handler({ call, notify }) }; } return { ...functions }; }; }; const createClearIntervalFactory = (scheduledIntervalsState) => (clear) => (timerId) => { if (typeof scheduledIntervalsState.get(timerId) === 'symbol') { scheduledIntervalsState.set(timerId, null); clear(timerId).then(() => { scheduledIntervalsState.delete(timerId); }); } }; const createClearTimeoutFactory = (scheduledTimeoutsState) => (clear) => (timerId) => { if (typeof scheduledTimeoutsState.get(timerId) === 'symbol') { scheduledTimeoutsState.set(timerId, null); clear(timerId).then(() => { scheduledTimeoutsState.delete(timerId); }); } }; const createSetIntervalFactory = (generateUniqueNumber, scheduledIntervalsState) => (set) => (func, delay = 0, ...args) => { const symbol = Symbol(); const timerId = generateUniqueNumber(scheduledIntervalsState); scheduledIntervalsState.set(timerId, symbol); const schedule = () => set(delay, timerId).then(() => { const state = scheduledIntervalsState.get(timerId); if (state === undefined) { throw new Error('The timer is in an undefined state.'); } if (state === symbol) { func(...args); // Doublecheck if the interval should still be rescheduled because it could have been cleared inside of func(). if (scheduledIntervalsState.get(timerId) === symbol) { schedule(); } } }); schedule(); return timerId; }; const createSetTimeoutFactory = (generateUniqueNumber, scheduledTimeoutsState) => (set) => (func, delay = 0, ...args) => { const symbol = Symbol(); const timerId = generateUniqueNumber(scheduledTimeoutsState); scheduledTimeoutsState.set(timerId, symbol); set(delay, timerId).then(() => { const state = scheduledTimeoutsState.get(timerId); if (state === undefined) { throw new Error('The timer is in an undefined state.'); } if (state === symbol) { // A timeout can be savely deleted because it is only called once. scheduledTimeoutsState.delete(timerId); func(...args); } }); return timerId; }; // Prefilling the Maps with a function indexed by zero is necessary to be compliant with the specification. const scheduledIntervalsState = new Map([[0, null]]); // tslint:disable-line no-empty const scheduledTimeoutsState = new Map([[0, null]]); // tslint:disable-line no-empty const createClearInterval = createClearIntervalFactory(scheduledIntervalsState); const createClearTimeout = createClearTimeoutFactory(scheduledTimeoutsState); const createSetInterval = createSetIntervalFactory(generateUniqueNumber, scheduledIntervalsState); const createSetTimeout = createSetTimeoutFactory(generateUniqueNumber, scheduledTimeoutsState); const wrap = createBroker({ clearInterval: ({ call }) => createClearInterval((timerId) => call('clear', { timerId, timerType: 'interval' })), clearTimeout: ({ call }) => createClearTimeout((timerId) => call('clear', { timerId, timerType: 'timeout' })), setInterval: ({ call }) => createSetInterval((delay, timerId) => call('set', { delay, now: performance.timeOrigin + performance.now(), timerId, timerType: 'interval' })), setTimeout: ({ call }) => createSetTimeout((delay, timerId) => call('set', { delay, now: performance.timeOrigin + performance.now(), timerId, timerType: 'timeout' })) }); const load = (url) => { const worker = new Worker(url); return wrap(worker); }; const createLoadOrReturnBroker = (loadBroker, worker) => { let broker = null; return () => { if (broker !== null) { return broker; } const blob = new Blob([worker], { type: 'application/javascript; charset=utf-8' }); const url = URL.createObjectURL(blob); broker = loadBroker(url); // Bug #1: Edge up until v18 didn't like the URL to be revoked directly. setTimeout(() => URL.revokeObjectURL(url)); return broker; }; }; // This is the minified and stringified code of the worker-timers-worker package. const worker = `(()=>{var e={455:function(e,t){!function(e){"use strict";var t=function(e){return function(t){var r=e(t);return t.add(r),r}},r=function(e){return function(t,r){return e.set(t,r),r}},n=void 0===Number.MAX_SAFE_INTEGER?9007199254740991:Number.MAX_SAFE_INTEGER,o=536870912,s=2*o,a=function(e,t){return function(r){var a=t.get(r),i=void 0===a?r.size:a<s?a+1:0;if(!r.has(i))return e(r,i);if(r.size<o){for(;r.has(i);)i=Math.floor(Math.random()*s);return e(r,i)}if(r.size>n)throw new Error("Congratulations, you created a collection of unique numbers which uses all available integers!");for(;r.has(i);)i=Math.floor(Math.random()*n);return e(r,i)}},i=new WeakMap,u=r(i),c=a(u,i),l=t(c);e.addUniqueNumber=l,e.generateUniqueNumber=c}(t)}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var s=t[n]={exports:{}};return e[n].call(s.exports,s,s.exports,r),s.exports}(()=>{"use strict";const e=-32603,t=-32602,n=-32601,o=(e,t)=>Object.assign(new Error(e),{status:t}),s=t=>o('The handler of the method called "'.concat(t,'" returned an unexpected result.'),e),a=(t,r)=>async({data:{id:a,method:i,params:u}})=>{const c=r[i];try{if(void 0===c)throw(e=>o('The requested method called "'.concat(e,'" is not supported.'),n))(i);const r=void 0===u?c():c(u);if(void 0===r)throw(t=>o('The handler of the method called "'.concat(t,'" returned no required result.'),e))(i);const l=r instanceof Promise?await r:r;if(null===a){if(void 0!==l.result)throw s(i)}else{if(void 0===l.result)throw s(i);const{result:e,transferables:r=[]}=l;t.postMessage({id:a,result:e},r)}}catch(e){const{message:r,status:n=-32603}=e;t.postMessage({error:{code:n,message:r},id:a})}};var i=r(455);const u=new Map,c=(e,r,n)=>({...r,connect:({port:t})=>{t.start();const n=e(t,r),o=(0,i.generateUniqueNumber)(u);return u.set(o,()=>{n(),t.close(),u.delete(o)}),{result:o}},disconnect:({portId:e})=>{const r=u.get(e);if(void 0===r)throw(e=>o('The specified parameter called "portId" with the given value "'.concat(e,'" does not identify a port connected to this worker.'),t))(e);return r(),{result:null}},isSupported:async()=>{if(await new Promise(e=>{const t=new ArrayBuffer(0),{port1:r,port2:n}=new MessageChannel;r.onmessage=({data:t})=>e(null!==t),n.postMessage(t,[t])})){const e=n();return{result:e instanceof Promise?await e:e}}return{result:!1}}}),l=(e,t,r=()=>!0)=>{const n=c(l,t,r),o=a(e,n);return e.addEventListener("message",o),()=>e.removeEventListener("message",o)},d=(e,t)=>r=>{const n=t.get(r);if(void 0===n)return Promise.resolve(!1);const[o,s]=n;return e(o),t.delete(r),s(!1),Promise.resolve(!0)},f=(e,t,r,n)=>(o,s,a)=>{const i=o+s-t.timeOrigin,u=i-t.now();return new Promise(t=>{e.set(a,[r(n,u,i,e,t,a),t])})},m=new Map,h=d(globalThis.clearTimeout,m),p=new Map,v=d(globalThis.clearTimeout,p),w=((e,t)=>{const r=(n,o,s,a)=>{const i=n-e.now();i>0?o.set(a,[t(r,i,n,o,s,a),s]):(o.delete(a),s(!0))};return r})(performance,globalThis.setTimeout),g=f(m,performance,globalThis.setTimeout,w),T=f(p,performance,globalThis.setTimeout,w);l(self,{clear:async({timerId:e,timerType:t})=>({result:await("interval"===t?h(e):v(e))}),set:async({delay:e,now:t,timerId:r,timerType:n})=>({result:await("interval"===n?g:T)(e,t,r)})})})()})();`; // tslint:disable-line:max-line-length const loadOrReturnBroker = createLoadOrReturnBroker(load, worker); const clearInterval = (timerId) => loadOrReturnBroker().clearInterval(timerId); const clearTimeout$1 = (timerId) => loadOrReturnBroker().clearTimeout(timerId); const setInterval = (...args) => loadOrReturnBroker().setInterval(...args); const setTimeout$1 = (...args) => loadOrReturnBroker().setTimeout(...args); const version = "2.9.10"; /* eslint-disable */ // ==UserScript== // @name ajaxHooker // @author cxxjackie // @version 1.4.8 // @supportURL https://bbs.tampermonkey.net.cn/thread-3284-1-1.html // @license GNU LGPL-3.0 // ==/UserScript== const ajaxHooker = function () { const version = "1.4.8"; const hookInst = { hookFns: [], filters: [], }; const win = window.unsafeWindow || document.defaultView || window; let winAh = win.__ajaxHooker; const resProto = win.Response.prototype; const xhrResponses = ["response", "responseText", "responseXML"]; const fetchResponses = ["arrayBuffer", "blob", "formData", "json", "text"]; const xhrExtraProps = ["responseType", "timeout", "withCredentials"]; const fetchExtraProps = [ "cache", "credentials", "integrity", "keepalive", "mode", "priority", "redirect", "referrer", "referrerPolicy", "signal", ]; const xhrAsyncEvents = ["readystatechange", "load", "loadend"]; const getType = {}.toString.call.bind({}.toString); const getDescriptor = Object.getOwnPropertyDescriptor.bind(Object); const emptyFn = () => { }; const errorFn = (e) => console.error(e); function isThenable(obj) { return obj && ["object", "function"].includes(typeof obj) && typeof obj.then === "function"; } function catchError(fn, ...args) { try { const result = fn(...args); if (isThenable(result)) return result.then(null, errorFn); return result; } catch (err) { console.error(err); } } function defineProp(obj, prop, getter, setter) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, get: getter, set: setter, }); } function readonly(obj, prop, value = obj[prop]) { defineProp(obj, prop, () => value, emptyFn); } function writable(obj, prop, value = obj[prop]) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, writable: true, value: value, }); } function parseHeaders(obj) { const headers = {}; switch (getType(obj)) { case "[object String]": for (const line of obj.trim().split(/[\r\n]+/)) { const [header, value] = line.split(/(?<=^[^:]+)\s*:\s*/); if (!value) continue; const lheader = header.toLowerCase(); headers[lheader] = lheader in headers ? `${headers[lheader]}, ${value}` : value; } break; case "[object Headers]": for (const [key, val] of obj) { headers[key] = val; } break; case "[object Object]": return { ...obj }; } return headers; } function stopImmediatePropagation() { this.ajaxHooker_isStopped = true; } class SyncThenable { then(fn) { fn && fn(); return new SyncThenable(); } } class AHRequest { constructor(request) { this.request = request; this.requestClone = { ...this.request }; } _recoverRequestKey(key) { if (key in this.requestClone) this.request[key] = this.requestClone[key]; else delete this.request[key]; } shouldFilter(filters) { const { type, url, method, async } = this.request; return (filters.length && !filters.find((obj) => { switch (true) { case obj.type && obj.type !== type: case getType(obj.url) === "[object String]" && !url.includes(obj.url): case getType(obj.url) === "[object RegExp]" && !obj.url.test(url): case obj.method && obj.method.toUpperCase() !== method.toUpperCase(): case "async" in obj && obj.async !== async: return false; } return true; })); } waitForRequestKeys() { if (!this.request.async) { win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => { if (this.shouldFilter(filters)) return; hookFns.forEach((fn) => { if (getType(fn) === "[object Function]") catchError(fn, this.request); }); for (const key in this.request) { if (isThenable(this.request[key])) this._recoverRequestKey(key); } }); return new SyncThenable(); } const promises = []; const ignoreKeys = new Set(["type", "async", "response"]); win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => { if (this.shouldFilter(filters)) return; promises.push(Promise.all(hookFns.map((fn) => catchError(fn, this.request))).then(() => { const requestKeys = []; for (const key in this.request) !ignoreKeys.has(key) && requestKeys.push(key); return Promise.all(requestKeys.map((key) => Promise.resolve(this.request[key]).then((val) => (this.request[key] = val), () => this._recoverRequestKey(key)))); })); }); return Promise.all(promises); } waitForResponseKeys(response) { const responseKeys = this.request.type === "xhr" ? xhrResponses : fetchResponses; if (!this.request.async) { if (getType(this.request.response) === "[object Function]") { catchError(this.request.response, response); responseKeys.forEach((key) => { if ("get" in getDescriptor(response, key) || isThenable(response[key])) { delete response[key]; } }); } return new SyncThenable(); } return Promise.resolve(catchError(this.request.response, response)).then(() => Promise.all(responseKeys.map((key) => { const descriptor = getDescriptor(response, key); if (descriptor && "value" in descriptor) { return Promise.resolve(descriptor.value).then((val) => (response[key] = val), () => delete response[key]); } else { delete response[key]; } }))); } } const proxyHandler = { get(target, prop) { const descriptor = getDescriptor(target, prop); if (descriptor && !descriptor.configurable && !descriptor.writable && !descriptor.get) return target[prop]; const ah = target.__ajaxHooker; if (ah && ah.proxyProps) { if (prop in ah.proxyProps) { const pDescriptor = ah.proxyProps[prop]; if ("get" in pDescriptor) return pDescriptor.get(); if (typeof pDescriptor.value === "function") return pDescriptor.value.bind(ah); return pDescriptor.value; } if (typeof target[prop] === "function") return target[prop].bind(target); } return target[prop]; }, set(target, prop, value) { const descriptor = getDescriptor(target, prop); if (descriptor && !descriptor.configurable && !descriptor.writable && !descriptor.set) return true; const ah = target.__ajaxHooker; if (ah && ah.proxyProps && prop in ah.proxyProps) { const pDescriptor = ah.proxyProps[prop]; pDescriptor.set ? pDescriptor.set(value) : (pDescriptor.value = value); } else { target[prop] = value; } return true; }, }; class XhrHooker { constructor(xhr) { const ah = this; Object.assign(ah, { originalXhr: xhr, proxyXhr: new Proxy(xhr, proxyHandler), resThenable: new SyncThenable(), proxyProps: {}, proxyEvents: {}, }); xhr.addEventListener("readystatechange", (e) => { if (ah.proxyXhr.readyState === 4 && ah.request && typeof ah.request.response === "function") { const response = { finalUrl: ah.proxyXhr.responseURL, status: ah.proxyXhr.status, responseHeaders: parseHeaders(ah.proxyXhr.getAllResponseHeaders()), }; const tempValues = {}; for (const key of xhrResponses) { try { tempValues[key] = ah.originalXhr[key]; } catch (err) { } defineProp(response, key, () => { return (response[key] = tempValues[key]); }, (val) => { delete response[key]; response[key] = val; }); } ah.resThenable = new AHRequest(ah.request).waitForResponseKeys(response).then(() => { for (const key of xhrResponses) { ah.proxyProps[key] = { get: () => { if (!(key in response)) response[key] = tempValues[key]; return response[key]; }, }; } }); } ah.dispatchEvent(e); }); xhr.addEventListener("load", (e) => ah.dispatchEvent(e)); xhr.addEventListener("loadend", (e) => ah.dispatchEvent(e)); for (const evt of xhrAsyncEvents) { const onEvt = "on" + evt; ah.proxyProps[onEvt] = { get: () => ah.proxyEvents[onEvt] || null, set: (val) => ah.addEvent(onEvt, val), }; } for (const method of ["setRequestHeader", "addEventListener", "removeEventListener", "open", "send"]) { ah.proxyProps[method] = { value: ah[method] }; } } toJSON() { } // Converting circular structure to JSON addEvent(type, event) { if (type.startsWith("on")) { this.proxyEvents[type] = typeof event === "function" ? event : null; } else { if (typeof event === "object" && event !== null) event = event.handleEvent; if (typeof event !== "function") return; this.proxyEvents[type] = this.proxyEvents[type] || new Set(); this.proxyEvents[type].add(event); } } removeEvent(type, event) { if (type.startsWith("on")) { this.proxyEvents[type] = null; } else { if (typeof event === "object" && event !== null) event = event.handleEvent; this.proxyEvents[type] && this.proxyEvents[type].delete(event); } } dispatchEvent(e) { e.stopImmediatePropagation = stopImmediatePropagation; defineProp(e, "target", () => this.proxyXhr); defineProp(e, "currentTarget", () => this.proxyXhr); defineProp(e, "srcElement", () => this.proxyXhr); this.proxyEvents[e.type] && this.proxyEvents[e.type].forEach((fn) => { this.resThenable.then(() => !e.ajaxHooker_isStopped && fn.call(this.proxyXhr, e)); }); if (e.ajaxHooker_isStopped) return; const onEvent = this.proxyEvents["on" + e.type]; onEvent && this.resThenable.then(onEvent.bind(this.proxyXhr, e)); } setRequestHeader(header, value) { this.originalXhr.setRequestHeader(header, value); if (!this.request) return; const headers = this.request.headers; headers[header] = header in headers ? `${headers[header]}, ${value}` : value; } addEventListener(...args) { if (xhrAsyncEvents.includes(args[0])) { this.addEvent(args[0], args[1]); } else { this.originalXhr.addEventListener(...args); } } removeEventListener(...args) { if (xhrAsyncEvents.includes(args[0])) { this.removeEvent(args[0], args[1]); } else { this.originalXhr.removeEventListener(...args); } } open(method, url, async = true, ...args) { this.request = { type: "xhr", url: url.toString(), method: method.toUpperCase(), abort: false, headers: {}, data: null, response: null, async: !!async, }; this.openArgs = args; this.resThenable = new SyncThenable(); ["responseURL", "readyState", "status", "statusText", ...xhrResponses].forEach((key) => { delete this.proxyProps[key]; }); return this.originalXhr.open(method, url, async, ...args); } send(data) { const ah = this; const xhr = ah.originalXhr; const request = ah.request; if (!request) return xhr.send(data); request.data = data; new AHRequest(request).waitForRequestKeys().then(() => { if (request.abort) { if (typeof request.response === "function") { Object.assign(ah.proxyProps, { responseURL: { value: request.url }, readyState: { value: 4 }, status: { value: 200 }, statusText: { value: "OK" }, }); xhrAsyncEvents.forEach((evt) => xhr.dispatchEvent(new Event(evt))); } } else { xhr.open(request.method, request.url, request.async, ...ah.openArgs); for (const header in request.headers) { xhr.setRequestHeader(header, request.headers[header]); } for (const prop of xhrExtraProps) { if (prop in request) xhr[prop] = request[prop]; } xhr.send(request.data); } }); } } function fakeXHR() { const xhr = new winAh.realXHR(); if ("__ajaxHooker" in xhr) console.warn("检测到不同版本的ajaxHooker,可能发生冲突!"); xhr.__ajaxHooker = new XhrHooker(xhr); return xhr.__ajaxHooker.proxyXhr; } fakeXHR.prototype = win.XMLHttpRequest.prototype; Object.keys(win.XMLHttpRequest).forEach((key) => (fakeXHR[key] = win.XMLHttpRequest[key])); function fakeFetch(url, options = {}) { if (!url) return winAh.realFetch.call(win, url, options); return new Promise(async (resolve, reject) => { const init = {}; if (getType(url) === "[object Request]") { init.method = url.method; init.headers = url.headers; if (url.body) init.body = await url.arrayBuffer(); for (const prop of fetchExtraProps) init[prop] = url[prop]; url = url.url; } url = url.toString(); Object.assign(init, options); init.method = init.method || "GET"; init.headers = init.headers || {}; const request = { type: "fetch", url: url, method: init.method.toUpperCase(), abort: false, headers: parseHeaders(init.headers), data: init.body, response: null, async: true, }; const req = new AHRequest(request); await req.waitForRequestKeys(); if (request.abort) { if (typeof request.response === "function") { const response = { finalUrl: request.url, status: 200, responseHeaders: {}, }; await req.waitForResponseKeys(response); const key = fetchResponses.find((k) => k in response); let val = response[key]; if (key === "json" && typeof val === "object") { val = catchError(JSON.stringify.bind(JSON), val); } const res = new Response(val, { status: 200, statusText: "OK", }); defineProp(res, "type", () => "basic"); defineProp(res, "url", () => request.url); resolve(res); } else { reject(new DOMException("aborted", "AbortError")); } return; } init.method = request.method; init.headers = request.headers; init.body = request.data; for (const prop of fetchExtraProps) { if (prop in request) init[prop] = request[prop]; } winAh.realFetch.call(win, request.url, init).then((res) => { if (typeof request.response === "function") { const response = { finalUrl: res.url, status: res.status, responseHeaders: parseHeaders(res.headers), }; if (res.ok) { fetchResponses.forEach((key) => (res[key] = function () { if (key in response) return Promise.resolve(response[key]); return resProto[key].call(this).then((val) => { response[key] = val; return req .waitForResponseKeys(response) .then(() => (key in response ? response[key] : val)); }); })); } else { catchError(request.response, response); } } resolve(res); }, reject); }); } function fakeFetchClone() { const descriptors = Object.getOwnPropertyDescriptors(this); const res = winAh.realFetchClone.call(this); Object.defineProperties(res, descriptors); return res; } winAh = win.__ajaxHooker = winAh || { version, fakeXHR, fakeFetch, fakeFetchClone, realXHR: win.XMLHttpRequest, realFetch: win.fetch, realFetchClone: resProto.clone, hookInsts: new Set(), }; if (winAh.version !== version) console.warn("检测到不同版本的ajaxHooker,可能发生冲突!"); win.XMLHttpRequest = winAh.fakeXHR; win.fetch = winAh.fakeFetch; resProto.clone = winAh.fakeFetchClone; winAh.hookInsts.add(hookInst); // 针对头条、抖音 secsdk.umd.js 的兼容性处理 class AHFunction extends Function { call(thisArg, ...args) { if (thisArg && thisArg.__ajaxHooker && thisArg.__ajaxHooker.proxyXhr === thisArg) { thisArg = thisArg.__ajaxHooker.originalXhr; } return Reflect.apply(this, thisArg, args); } apply(thisArg, args) { if (thisArg && thisArg.__ajaxHooker && thisArg.__ajaxHooker.proxyXhr === thisArg) { thisArg = thisArg.__ajaxHooker.originalXhr; } return Reflect.apply(this, thisArg, args || []); } } function hookSecsdk(csrf) { Object.setPrototypeOf(csrf.nativeXMLHttpRequestSetRequestHeader, AHFunction.prototype); Object.setPrototypeOf(csrf.nativeXMLHttpRequestOpen, AHFunction.prototype); Object.setPrototypeOf(csrf.nativeXMLHttpRequestSend, AHFunction.prototype); } if (win.secsdk) { if (win.secsdk.csrf && win.secsdk.csrf.nativeXMLHttpRequestOpen) hookSecsdk(win.secsdk.csrf); } else { defineProp(win, "secsdk", emptyFn, (secsdk) => { delete win.secsdk; win.secsdk = secsdk; defineProp(secsdk, "csrf", emptyFn, (csrf) => { delete secsdk.csrf; secsdk.csrf = csrf; if (csrf.nativeXMLHttpRequestOpen) hookSecsdk(csrf); }); }); } return { hook: (fn) => hookInst.hookFns.push(fn), filter: (arr) => { if (Array.isArray(arr)) hookInst.filters = arr; }, protect: () => { readonly(win, "XMLHttpRequest", winAh.fakeXHR); readonly(win, "fetch", winAh.fakeFetch); readonly(resProto, "clone", winAh.fakeFetchClone); }, unhook: () => { winAh.hookInsts.delete(hookInst); if (!winAh.hookInsts.size) { writable(win, "XMLHttpRequest", winAh.realXHR); writable(win, "fetch", winAh.realFetch); writable(resProto, "clone", winAh.realFetchClone); delete win.__ajaxHooker; } }, }; }; // ==UserScript== // @name ajaxHooker // @author cxxjackie // @version 1.2.4 // @supportURL https://bbs.tampermonkey.net.cn/thread-3284-1-1.html // ==/UserScript== const AjaxHooker1_2_4 = function () { return (function () { const win = window.unsafeWindow || document.defaultView || window; const hookFns = []; const realXhr = win.XMLHttpRequest; const resProto = win.Response.prototype; const toString = Object.prototype.toString; const realFetch = win.fetch; const xhrResponses = ["response", "responseText", "responseXML"]; const fetchResponses = ["arrayBuffer", "blob", "formData", "json", "text"]; const xhrAsyncEvents = ["readystatechange", "load", "loadend"]; let filter; function emptyFn() { } function errorFn(err) { console.error(err); } function defineProp(obj, prop, getter, setter) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, get: getter, set: setter, }); } function readonly(obj, prop, value = obj[prop]) { defineProp(obj, prop, () => value, emptyFn); } function writable(obj, prop, value = obj[prop]) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, writable: true, value: value, }); } function toFilterObj(obj) { return { type: obj.type, url: obj.url, method: obj.method && obj.method.toUpperCase(), }; } function shouldFilter(type, url, method) { return (filter && !filter.find((obj) => (!obj.type || obj.type === type) && (!obj.url || (toString.call(obj.url) === "[object String]" ? url.includes(obj.url) : obj.url.test(url))) && (!obj.method || obj.method === method.toUpperCase()))); } function lookupGetter(obj, prop) { let getter; let proto = obj; while (proto) { const descriptor = Object.getOwnPropertyDescriptor(proto, prop); getter = descriptor && descriptor.get; if (getter) break; proto = Object.getPrototypeOf(proto); } return getter ? getter.bind(obj) : emptyFn; } function waitForHookFns(request) { return Promise.all(hookFns.map((fn) => Promise.resolve(fn(request)).then(emptyFn, errorFn))); } function waitForRequestKeys(request, requestClone) { return Promise.all(["url", "method", "abort", "headers", "data"].map((key) => { return Promise.resolve(request[key]).then((val) => (request[key] = val), () => (request[key] = requestClone[key])); })); } function fakeEventSIP() { this.ajaxHooker_stopped = true; } function xhrDelegateEvent(e) { const xhr = e.target; e.stopImmediatePropagation = fakeEventSIP; xhr.__ajaxHooker.hookedEvents[e.type].forEach((fn) => !e.ajaxHooker_stopped && fn.call(xhr, e)); const onEvent = xhr.__ajaxHooker.hookedEvents["on" + e.type]; typeof onEvent === "function" && onEvent.call(xhr, e); } function xhrReadyStateChange(e) { if (e.target.readyState === 4) { e.target.dispatchEvent(new CustomEvent("ajaxHooker_responseReady", { detail: e })); } else { e.target.__ajaxHooker.delegateEvent(e); } } function xhrLoadAndLoadend(e) { e.target.__ajaxHooker.delegateEvent(e); } function fakeXhrOpen(method, url, ...args) { const ah = this.__ajaxHooker; ah.url = url.toString(); ah.method = method.toUpperCase(); ah.openArgs = args; ah.headers = {}; return ah.originalMethods.open(method, url, ...args); } function fakeXhr() { const xhr = new realXhr(); let ah = xhr.__ajaxHooker; if (!ah) { ah = xhr.__ajaxHooker = { headers: {}, hookedEvents: { readystatechange: new Set(), load: new Set(), loadend: new Set(), }, delegateEvent: xhrDelegateEvent, originalGetters: {}, originalMethods: {}, }; xhr.addEventListener("readystatechange", xhrReadyStateChange); xhr.addEventListener("load", xhrLoadAndLoadend); xhr.addEventListener("loadend", xhrLoadAndLoadend); for (const key of xhrResponses) { ah.originalGetters[key] = lookupGetter(xhr, key); } for (const method of [ "open", "setRequestHeader", "addEventListener", "removeEventListener", ]) { ah.originalMethods[method] = xhr[method].bind(xhr); } xhr.open = fakeXhrOpen; xhr.setRequestHeader = (header, value) => { ah.originalMethods.setRequestHeader(header, value); if (xhr.readyState === 1) { if (ah.headers[header]) { ah.headers[header] += ", " + value; } else { ah.headers[header] = value; } } }; xhr.addEventListener = function (...args) { if (xhrAsyncEvents.includes(args[0])) { ah.hookedEvents[args[0]].add(args[1]); } else { ah.originalMethods.addEventListener(...args); } }; xhr.removeEventListener = function (...args) { if (xhrAsyncEvents.includes(args[0])) { ah.hookedEvents[args[0]].delete(args[1]); } else { ah.originalMethods.removeEventListener(...args); } }; xhrAsyncEvents.forEach((evt) => { const onEvt = "on" + evt; defineProp(xhr, onEvt, () => { return ah.hookedEvents[onEvt] || null; }, (val) => { ah.hookedEvents[onEvt] = typeof val === "function" ? val : null; }); }); } const realSend = xhr.send.bind(xhr); xhr.send = function (data) { if (xhr.readyState !== 1) return realSend(data); ah.delegateEvent = xhrDelegateEvent; xhrResponses.forEach((prop) => { delete xhr[prop]; // delete descriptor }); if (shouldFilter("xhr", ah.url, ah.method)) { xhr.addEventListener("ajaxHooker_responseReady", (e) => { ah.delegateEvent(e.detail); }); return realSend(data); } try { const request = { type: "xhr", url: ah.url, method: ah.method, abort: false, headers: ah.headers, data: data, response: null, }; const requestClone = { ...request }; waitForHookFns(request).then(() => { waitForRequestKeys(request, requestClone).then(() => { if (request.abort) return; ah.originalMethods.open(request.method, request.url, ...ah.openArgs); for (const header in request.headers) { ah.originalMethods.setRequestHeader(header, request.headers[header]); } data = request.data; xhr.addEventListener("ajaxHooker_responseReady", (e) => { try { if (typeof request.response === "function") { const arg = { finalUrl: xhr.responseURL, status: xhr.status, responseHeaders: {}, }; for (const line of xhr .getAllResponseHeaders() .trim() .split(/[\r\n]+/)) { const parts = line.split(/:\s*/); if (parts.length === 2