@whitesev/utils 2.6.9 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,187 +1,187 @@
1
+ // ==UserScript==
1
2
  // @name ajaxHooker
2
3
  // @author cxxjackie
3
- // @version 1.4.4
4
- // @updateLog 修复头条、抖音部分站点下this引用错误的问题。
4
+ // @version 1.4.6
5
5
  // @supportURL https://bbs.tampermonkey.net.cn/thread-3284-1-1.html
6
+ // @license GNU LGPL-3.0
7
+ // ==/UserScript==
6
8
 
7
- export const AjaxHooker = function () {
8
- return (function () {
9
- "use strict";
10
- const version = "1.4.4";
11
- const hookInst = {
12
- hookFns: [],
13
- filters: [],
14
- };
15
- const win = window.unsafeWindow || document.defaultView || window;
16
- let winAh = win.__ajaxHooker;
17
- const resProto = win.Response.prototype;
18
- const xhrResponses = ["response", "responseText", "responseXML"];
19
- const fetchResponses = ["arrayBuffer", "blob", "formData", "json", "text"];
20
- const fetchInitProps = [
21
- "method",
22
- "headers",
23
- "body",
24
- "mode",
25
- "credentials",
26
- "cache",
27
- "redirect",
28
- "referrer",
29
- "referrerPolicy",
30
- "integrity",
31
- "keepalive",
32
- "signal",
33
- "priority",
34
- ];
35
- const xhrAsyncEvents = ["readystatechange", "load", "loadend"];
36
- const getType = {}.toString.call.bind({}.toString);
37
- const getDescriptor = Object.getOwnPropertyDescriptor.bind(Object);
38
- const emptyFn = () => {};
39
- const errorFn = (e) => console.error(e);
40
- function isThenable(obj) {
41
- return (
42
- obj &&
43
- ["object", "function"].includes(typeof obj) &&
44
- typeof obj.then === "function"
45
- );
46
- }
47
- function catchError(fn, ...args) {
48
- try {
49
- const result = fn(...args);
50
- if (isThenable(result)) return result.then(null, errorFn);
51
- return result;
52
- } catch (err) {
53
- console.error(err);
54
- }
9
+ export const ajaxHooker = function () {
10
+ "use strict";
11
+ const version = "1.4.6";
12
+ const hookInst = {
13
+ hookFns: [],
14
+ filters: [],
15
+ };
16
+ const win = window.unsafeWindow || document.defaultView || window;
17
+ let winAh = win.__ajaxHooker;
18
+ const resProto = win.Response.prototype;
19
+ const xhrResponses = ["response", "responseText", "responseXML"];
20
+ const fetchResponses = ["arrayBuffer", "blob", "formData", "json", "text"];
21
+ const fetchInitProps = [
22
+ "method",
23
+ "headers",
24
+ "body",
25
+ "mode",
26
+ "credentials",
27
+ "cache",
28
+ "redirect",
29
+ "referrer",
30
+ "referrerPolicy",
31
+ "integrity",
32
+ "keepalive",
33
+ "signal",
34
+ "priority",
35
+ ];
36
+ const xhrAsyncEvents = ["readystatechange", "load", "loadend"];
37
+ const getType = {}.toString.call.bind({}.toString);
38
+ const getDescriptor = Object.getOwnPropertyDescriptor.bind(Object);
39
+ const emptyFn = () => {};
40
+ const errorFn = (e) => console.error(e);
41
+ function isThenable(obj) {
42
+ return (
43
+ obj &&
44
+ ["object", "function"].includes(typeof obj) &&
45
+ typeof obj.then === "function"
46
+ );
47
+ }
48
+ function catchError(fn, ...args) {
49
+ try {
50
+ const result = fn(...args);
51
+ if (isThenable(result)) return result.then(null, errorFn);
52
+ return result;
53
+ } catch (err) {
54
+ console.error(err);
55
55
  }
56
- function defineProp(obj, prop, getter, setter) {
57
- Object.defineProperty(obj, prop, {
58
- configurable: true,
59
- enumerable: true,
60
- get: getter,
61
- set: setter,
62
- });
56
+ }
57
+ function defineProp(obj, prop, getter, setter) {
58
+ Object.defineProperty(obj, prop, {
59
+ configurable: true,
60
+ enumerable: true,
61
+ get: getter,
62
+ set: setter,
63
+ });
64
+ }
65
+ function readonly(obj, prop, value = obj[prop]) {
66
+ defineProp(obj, prop, () => value, emptyFn);
67
+ }
68
+ function writable(obj, prop, value = obj[prop]) {
69
+ Object.defineProperty(obj, prop, {
70
+ configurable: true,
71
+ enumerable: true,
72
+ writable: true,
73
+ value: value,
74
+ });
75
+ }
76
+ function parseHeaders(obj) {
77
+ const headers = {};
78
+ switch (getType(obj)) {
79
+ case "[object String]":
80
+ for (const line of obj.trim().split(/[\r\n]+/)) {
81
+ const [header, value] = line.split(/(?<=^[^:]+)\s*:\s*/);
82
+ if (!value) continue;
83
+ const lheader = header.toLowerCase();
84
+ headers[lheader] =
85
+ lheader in headers ? `${headers[lheader]}, ${value}` : value;
86
+ }
87
+ break;
88
+ case "[object Headers]":
89
+ for (const [key, val] of obj) {
90
+ headers[key] = val;
91
+ }
92
+ break;
93
+ case "[object Object]":
94
+ return { ...obj };
63
95
  }
64
- function readonly(obj, prop, value = obj[prop]) {
65
- defineProp(obj, prop, () => value, emptyFn);
96
+ return headers;
97
+ }
98
+ function stopImmediatePropagation() {
99
+ this.ajaxHooker_isStopped = true;
100
+ }
101
+ class SyncThenable {
102
+ then(fn) {
103
+ fn && fn();
104
+ return new SyncThenable();
66
105
  }
67
- function writable(obj, prop, value = obj[prop]) {
68
- Object.defineProperty(obj, prop, {
69
- configurable: true,
70
- enumerable: true,
71
- writable: true,
72
- value: value,
73
- });
106
+ }
107
+ class AHRequest {
108
+ constructor(request) {
109
+ this.request = request;
110
+ this.requestClone = { ...this.request };
74
111
  }
75
- function parseHeaders(obj) {
76
- const headers = {};
77
- switch (getType(obj)) {
78
- case "[object String]":
79
- for (const line of obj.trim().split(/[\r\n]+/)) {
80
- const [header, value] = line.split(/\s*:\s*/);
81
- if (!header) break;
82
- const lheader = header.toLowerCase();
83
- headers[lheader] =
84
- lheader in headers ? `${headers[lheader]}, ${value}` : value;
85
- }
86
- break;
87
- case "[object Headers]":
88
- for (const [key, val] of obj) {
89
- headers[key] = val;
112
+ shouldFilter(filters) {
113
+ const { type, url, method, async } = this.request;
114
+ return (
115
+ filters.length &&
116
+ !filters.find((obj) => {
117
+ switch (true) {
118
+ case obj.type && obj.type !== type:
119
+ case getType(obj.url) === "[object String]" &&
120
+ !url.includes(obj.url):
121
+ case getType(obj.url) === "[object RegExp]" && !obj.url.test(url):
122
+ case obj.method &&
123
+ obj.method.toUpperCase() !== method.toUpperCase():
124
+ case "async" in obj && obj.async !== async:
125
+ return false;
90
126
  }
91
- break;
92
- case "[object Object]":
93
- return { ...obj };
94
- }
95
- return headers;
96
- }
97
- function stopImmediatePropagation() {
98
- this.ajaxHooker_isStopped = true;
127
+ return true;
128
+ })
129
+ );
99
130
  }
100
- class SyncThenable {
101
- then(fn) {
102
- fn && fn();
131
+ waitForRequestKeys() {
132
+ const requestKeys = ["url", "method", "abort", "headers", "data"];
133
+ if (!this.request.async) {
134
+ win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => {
135
+ if (this.shouldFilter(filters)) return;
136
+ hookFns.forEach((fn) => {
137
+ if (getType(fn) === "[object Function]")
138
+ catchError(fn, this.request);
139
+ });
140
+ requestKeys.forEach((key) => {
141
+ if (isThenable(this.request[key]))
142
+ this.request[key] = this.requestClone[key];
143
+ });
144
+ });
103
145
  return new SyncThenable();
104
146
  }
147
+ const promises = [];
148
+ win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => {
149
+ if (this.shouldFilter(filters)) return;
150
+ promises.push(
151
+ Promise.all(hookFns.map((fn) => catchError(fn, this.request))).then(
152
+ () =>
153
+ Promise.all(
154
+ requestKeys.map((key) =>
155
+ Promise.resolve(this.request[key]).then(
156
+ (val) => (this.request[key] = val),
157
+ () => (this.request[key] = this.requestClone[key])
158
+ )
159
+ )
160
+ )
161
+ )
162
+ );
163
+ });
164
+ return Promise.all(promises);
105
165
  }
106
- class AHRequest {
107
- constructor(request) {
108
- this.request = request;
109
- this.requestClone = { ...this.request };
110
- }
111
- shouldFilter(filters) {
112
- const { type, url, method, async } = this.request;
113
- return (
114
- filters.length &&
115
- !filters.find((obj) => {
116
- switch (true) {
117
- case obj.type && obj.type !== type:
118
- case getType(obj.url) === "[object String]" &&
119
- !url.includes(obj.url):
120
- case getType(obj.url) === "[object RegExp]" && !obj.url.test(url):
121
- case obj.method &&
122
- obj.method.toUpperCase() !== method.toUpperCase():
123
- case "async" in obj && obj.async !== async:
124
- return false;
166
+ waitForResponseKeys(response) {
167
+ const responseKeys =
168
+ this.request.type === "xhr" ? xhrResponses : fetchResponses;
169
+ if (!this.request.async) {
170
+ if (getType(this.request.response) === "[object Function]") {
171
+ catchError(this.request.response, response);
172
+ responseKeys.forEach((key) => {
173
+ if (
174
+ "get" in getDescriptor(response, key) ||
175
+ isThenable(response[key])
176
+ ) {
177
+ delete response[key];
125
178
  }
126
- return true;
127
- })
128
- );
129
- }
130
- waitForRequestKeys() {
131
- const requestKeys = ["url", "method", "abort", "headers", "data"];
132
- if (!this.request.async) {
133
- win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => {
134
- if (this.shouldFilter(filters)) return;
135
- hookFns.forEach((fn) => {
136
- if (getType(fn) === "[object Function]")
137
- catchError(fn, this.request);
138
- });
139
- requestKeys.forEach((key) => {
140
- if (isThenable(this.request[key]))
141
- this.request[key] = this.requestClone[key];
142
- });
143
179
  });
144
- return new SyncThenable();
145
180
  }
146
- const promises = [];
147
- win.__ajaxHooker.hookInsts.forEach(({ hookFns, filters }) => {
148
- if (this.shouldFilter(filters)) return;
149
- promises.push(
150
- Promise.all(hookFns.map((fn) => catchError(fn, this.request))).then(
151
- () =>
152
- Promise.all(
153
- requestKeys.map((key) =>
154
- Promise.resolve(this.request[key]).then(
155
- (val) => (this.request[key] = val),
156
- () => (this.request[key] = this.requestClone[key])
157
- )
158
- )
159
- )
160
- )
161
- );
162
- });
163
- return Promise.all(promises);
181
+ return new SyncThenable();
164
182
  }
165
- waitForResponseKeys(response) {
166
- const responseKeys =
167
- this.request.type === "xhr" ? xhrResponses : fetchResponses;
168
- if (!this.request.async) {
169
- if (getType(this.request.response) === "[object Function]") {
170
- catchError(this.request.response, response);
171
- responseKeys.forEach((key) => {
172
- if (
173
- "get" in getDescriptor(response, key) ||
174
- isThenable(response[key])
175
- ) {
176
- delete response[key];
177
- }
178
- });
179
- }
180
- return new SyncThenable();
181
- }
182
- return Promise.resolve(
183
- catchError(this.request.response, response)
184
- ).then(() =>
183
+ return Promise.resolve(catchError(this.request.response, response)).then(
184
+ () =>
185
185
  Promise.all(
186
186
  responseKeys.map((key) => {
187
187
  const descriptor = getDescriptor(response, key);
@@ -195,422 +195,404 @@ export const AjaxHooker = function () {
195
195
  }
196
196
  })
197
197
  )
198
- );
199
- }
198
+ );
200
199
  }
201
- const proxyHandler = {
202
- get(target, prop) {
203
- const descriptor = getDescriptor(target, prop);
204
- if (
205
- descriptor &&
206
- !descriptor.configurable &&
207
- !descriptor.writable &&
208
- !descriptor.get
209
- )
210
- return target[prop];
211
- const ah = target.__ajaxHooker;
212
- if (ah && ah.proxyProps) {
213
- if (prop in ah.proxyProps) {
214
- const pDescriptor = ah.proxyProps[prop];
215
- if ("get" in pDescriptor) return pDescriptor.get();
216
- if (typeof pDescriptor.value === "function")
217
- return pDescriptor.value.bind(ah);
218
- return pDescriptor.value;
219
- }
220
- if (typeof target[prop] === "function")
221
- return target[prop].bind(target);
222
- }
200
+ }
201
+ const proxyHandler = {
202
+ get(target, prop) {
203
+ const descriptor = getDescriptor(target, prop);
204
+ if (
205
+ descriptor &&
206
+ !descriptor.configurable &&
207
+ !descriptor.writable &&
208
+ !descriptor.get
209
+ )
223
210
  return target[prop];
224
- },
225
- set(target, prop, value) {
226
- const descriptor = getDescriptor(target, prop);
227
- if (
228
- descriptor &&
229
- !descriptor.configurable &&
230
- !descriptor.writable &&
231
- !descriptor.set
232
- )
233
- return true;
234
- const ah = target.__ajaxHooker;
235
- if (ah && ah.proxyProps && prop in ah.proxyProps) {
211
+ const ah = target.__ajaxHooker;
212
+ if (ah && ah.proxyProps) {
213
+ if (prop in ah.proxyProps) {
236
214
  const pDescriptor = ah.proxyProps[prop];
237
- pDescriptor.set
238
- ? pDescriptor.set(value)
239
- : (pDescriptor.value = value);
240
- } else {
241
- target[prop] = value;
242
- }
243
- return true;
244
- },
245
- };
246
- class XhrHooker {
247
- constructor(xhr) {
248
- const ah = this;
249
- Object.assign(ah, {
250
- originalXhr: xhr,
251
- proxyXhr: new Proxy(xhr, proxyHandler),
252
- resThenable: new SyncThenable(),
253
- proxyProps: {},
254
- proxyEvents: {},
255
- });
256
- xhr.addEventListener("readystatechange", (e) => {
257
- if (
258
- ah.proxyXhr.readyState === 4 &&
259
- ah.request &&
260
- typeof ah.request.response === "function"
261
- ) {
262
- const response = {
263
- finalUrl: ah.proxyXhr.responseURL,
264
- status: ah.proxyXhr.status,
265
- responseHeaders: parseHeaders(
266
- ah.proxyXhr.getAllResponseHeaders()
267
- ),
268
- };
269
- const tempValues = {};
270
- for (const key of xhrResponses) {
271
- try {
272
- tempValues[key] = ah.originalXhr[key];
273
- } catch (err) {}
274
- defineProp(
275
- response,
276
- key,
277
- () => {
278
- return (response[key] = tempValues[key]);
279
- },
280
- (val) => {
281
- delete response[key];
282
- response[key] = val;
283
- }
284
- );
285
- }
286
- ah.resThenable = new AHRequest(ah.request)
287
- .waitForResponseKeys(response)
288
- .then(() => {
289
- for (const key of xhrResponses) {
290
- ah.proxyProps[key] = {
291
- get: () => {
292
- if (!(key in response)) response[key] = tempValues[key];
293
- return response[key];
294
- },
295
- };
296
- }
297
- });
298
- }
299
- ah.dispatchEvent(e);
300
- });
301
- xhr.addEventListener("load", (e) => ah.dispatchEvent(e));
302
- xhr.addEventListener("loadend", (e) => ah.dispatchEvent(e));
303
- for (const evt of xhrAsyncEvents) {
304
- const onEvt = "on" + evt;
305
- ah.proxyProps[onEvt] = {
306
- get: () => ah.proxyEvents[onEvt] || null,
307
- set: (val) => ah.addEvent(onEvt, val),
308
- };
309
- }
310
- for (const method of [
311
- "setRequestHeader",
312
- "addEventListener",
313
- "removeEventListener",
314
- "open",
315
- "send",
316
- ]) {
317
- ah.proxyProps[method] = { value: ah[method] };
318
- }
319
- }
320
- toJSON() {} // Converting circular structure to JSON
321
- addEvent(type, event) {
322
- if (type.startsWith("on")) {
323
- this.proxyEvents[type] = typeof event === "function" ? event : null;
324
- } else {
325
- if (typeof event === "object" && event !== null)
326
- event = event.handleEvent;
327
- if (typeof event !== "function") return;
328
- this.proxyEvents[type] = this.proxyEvents[type] || new Set();
329
- this.proxyEvents[type].add(event);
215
+ if ("get" in pDescriptor) return pDescriptor.get();
216
+ if (typeof pDescriptor.value === "function")
217
+ return pDescriptor.value.bind(ah);
218
+ return pDescriptor.value;
330
219
  }
220
+ if (typeof target[prop] === "function")
221
+ return target[prop].bind(target);
331
222
  }
332
- removeEvent(type, event) {
333
- if (type.startsWith("on")) {
334
- this.proxyEvents[type] = null;
335
- } else {
336
- if (typeof event === "object" && event !== null)
337
- event = event.handleEvent;
338
- this.proxyEvents[type] && this.proxyEvents[type].delete(event);
339
- }
223
+ return target[prop];
224
+ },
225
+ set(target, prop, value) {
226
+ const descriptor = getDescriptor(target, prop);
227
+ if (
228
+ descriptor &&
229
+ !descriptor.configurable &&
230
+ !descriptor.writable &&
231
+ !descriptor.set
232
+ )
233
+ return true;
234
+ const ah = target.__ajaxHooker;
235
+ if (ah && ah.proxyProps && prop in ah.proxyProps) {
236
+ const pDescriptor = ah.proxyProps[prop];
237
+ pDescriptor.set ? pDescriptor.set(value) : (pDescriptor.value = value);
238
+ } else {
239
+ target[prop] = value;
340
240
  }
341
- dispatchEvent(e) {
342
- e.stopImmediatePropagation = stopImmediatePropagation;
343
- defineProp(e, "target", () => this.proxyXhr);
344
- defineProp(e, "currentTarget", () => this.proxyXhr);
345
- this.proxyEvents[e.type] &&
346
- this.proxyEvents[e.type].forEach((fn) => {
347
- this.resThenable.then(
348
- () => !e.ajaxHooker_isStopped && fn.call(this.proxyXhr, e)
241
+ return true;
242
+ },
243
+ };
244
+ class XhrHooker {
245
+ constructor(xhr) {
246
+ const ah = this;
247
+ Object.assign(ah, {
248
+ originalXhr: xhr,
249
+ proxyXhr: new Proxy(xhr, proxyHandler),
250
+ resThenable: new SyncThenable(),
251
+ proxyProps: {},
252
+ proxyEvents: {},
253
+ });
254
+ xhr.addEventListener("readystatechange", (e) => {
255
+ if (
256
+ ah.proxyXhr.readyState === 4 &&
257
+ ah.request &&
258
+ typeof ah.request.response === "function"
259
+ ) {
260
+ const response = {
261
+ finalUrl: ah.proxyXhr.responseURL,
262
+ status: ah.proxyXhr.status,
263
+ responseHeaders: parseHeaders(ah.proxyXhr.getAllResponseHeaders()),
264
+ };
265
+ const tempValues = {};
266
+ for (const key of xhrResponses) {
267
+ try {
268
+ tempValues[key] = ah.originalXhr[key];
269
+ } catch (err) {}
270
+ defineProp(
271
+ response,
272
+ key,
273
+ () => {
274
+ return (response[key] = tempValues[key]);
275
+ },
276
+ (val) => {
277
+ delete response[key];
278
+ response[key] = val;
279
+ }
349
280
  );
350
- });
351
- if (e.ajaxHooker_isStopped) return;
352
- const onEvent = this.proxyEvents["on" + e.type];
353
- onEvent && this.resThenable.then(onEvent.bind(this.proxyXhr, e));
281
+ }
282
+ ah.resThenable = new AHRequest(ah.request)
283
+ .waitForResponseKeys(response)
284
+ .then(() => {
285
+ for (const key of xhrResponses) {
286
+ ah.proxyProps[key] = {
287
+ get: () => {
288
+ if (!(key in response)) response[key] = tempValues[key];
289
+ return response[key];
290
+ },
291
+ };
292
+ }
293
+ });
294
+ }
295
+ ah.dispatchEvent(e);
296
+ });
297
+ xhr.addEventListener("load", (e) => ah.dispatchEvent(e));
298
+ xhr.addEventListener("loadend", (e) => ah.dispatchEvent(e));
299
+ for (const evt of xhrAsyncEvents) {
300
+ const onEvt = "on" + evt;
301
+ ah.proxyProps[onEvt] = {
302
+ get: () => ah.proxyEvents[onEvt] || null,
303
+ set: (val) => ah.addEvent(onEvt, val),
304
+ };
354
305
  }
355
- setRequestHeader(header, value) {
356
- this.originalXhr.setRequestHeader(header, value);
357
- if (!this.request) return;
358
- const headers = this.request.headers;
359
- headers[header] =
360
- header in headers ? `${headers[header]}, ${value}` : value;
306
+ for (const method of [
307
+ "setRequestHeader",
308
+ "addEventListener",
309
+ "removeEventListener",
310
+ "open",
311
+ "send",
312
+ ]) {
313
+ ah.proxyProps[method] = { value: ah[method] };
361
314
  }
362
- addEventListener(...args) {
363
- if (xhrAsyncEvents.includes(args[0])) {
364
- this.addEvent(args[0], args[1]);
365
- } else {
366
- this.originalXhr.addEventListener(...args);
367
- }
315
+ }
316
+ toJSON() {} // Converting circular structure to JSON
317
+ addEvent(type, event) {
318
+ if (type.startsWith("on")) {
319
+ this.proxyEvents[type] = typeof event === "function" ? event : null;
320
+ } else {
321
+ if (typeof event === "object" && event !== null)
322
+ event = event.handleEvent;
323
+ if (typeof event !== "function") return;
324
+ this.proxyEvents[type] = this.proxyEvents[type] || new Set();
325
+ this.proxyEvents[type].add(event);
368
326
  }
369
- removeEventListener(...args) {
370
- if (xhrAsyncEvents.includes(args[0])) {
371
- this.removeEvent(args[0], args[1]);
372
- } else {
373
- this.originalXhr.removeEventListener(...args);
374
- }
327
+ }
328
+ removeEvent(type, event) {
329
+ if (type.startsWith("on")) {
330
+ this.proxyEvents[type] = null;
331
+ } else {
332
+ if (typeof event === "object" && event !== null)
333
+ event = event.handleEvent;
334
+ this.proxyEvents[type] && this.proxyEvents[type].delete(event);
375
335
  }
376
- open(method, url, async = true, ...args) {
377
- this.request = {
378
- type: "xhr",
379
- url: url.toString(),
380
- method: method.toUpperCase(),
381
- abort: false,
382
- headers: {},
383
- data: null,
384
- response: null,
385
- async: !!async,
386
- };
387
- this.openArgs = args;
388
- this.resThenable = new SyncThenable();
389
- [
390
- "responseURL",
391
- "readyState",
392
- "status",
393
- "statusText",
394
- ...xhrResponses,
395
- ].forEach((key) => {
396
- delete this.proxyProps[key];
336
+ }
337
+ dispatchEvent(e) {
338
+ e.stopImmediatePropagation = stopImmediatePropagation;
339
+ defineProp(e, "target", () => this.proxyXhr);
340
+ defineProp(e, "currentTarget", () => this.proxyXhr);
341
+ this.proxyEvents[e.type] &&
342
+ this.proxyEvents[e.type].forEach((fn) => {
343
+ this.resThenable.then(
344
+ () => !e.ajaxHooker_isStopped && fn.call(this.proxyXhr, e)
345
+ );
397
346
  });
398
- return this.originalXhr.open(method, url, async, ...args);
347
+ if (e.ajaxHooker_isStopped) return;
348
+ const onEvent = this.proxyEvents["on" + e.type];
349
+ onEvent && this.resThenable.then(onEvent.bind(this.proxyXhr, e));
350
+ }
351
+ setRequestHeader(header, value) {
352
+ this.originalXhr.setRequestHeader(header, value);
353
+ if (!this.request) return;
354
+ const headers = this.request.headers;
355
+ headers[header] =
356
+ header in headers ? `${headers[header]}, ${value}` : value;
357
+ }
358
+ addEventListener(...args) {
359
+ if (xhrAsyncEvents.includes(args[0])) {
360
+ this.addEvent(args[0], args[1]);
361
+ } else {
362
+ this.originalXhr.addEventListener(...args);
399
363
  }
400
- send(data) {
401
- const ah = this;
402
- const xhr = ah.originalXhr;
403
- const request = ah.request;
404
- if (!request) return xhr.send(data);
405
- request.data = data;
406
- new AHRequest(request).waitForRequestKeys().then(() => {
407
- if (request.abort) {
408
- if (typeof request.response === "function") {
409
- Object.assign(ah.proxyProps, {
410
- responseURL: { value: request.url },
411
- readyState: { value: 4 },
412
- status: { value: 200 },
413
- statusText: { value: "OK" },
414
- });
415
- xhrAsyncEvents.forEach((evt) =>
416
- xhr.dispatchEvent(new Event(evt))
417
- );
418
- }
419
- } else {
420
- xhr.open(
421
- request.method,
422
- request.url,
423
- request.async,
424
- ...ah.openArgs
425
- );
426
- for (const header in request.headers) {
427
- xhr.setRequestHeader(header, request.headers[header]);
428
- }
429
- xhr.send(request.data);
430
- }
431
- });
364
+ }
365
+ removeEventListener(...args) {
366
+ if (xhrAsyncEvents.includes(args[0])) {
367
+ this.removeEvent(args[0], args[1]);
368
+ } else {
369
+ this.originalXhr.removeEventListener(...args);
432
370
  }
433
371
  }
434
- function fakeXHR() {
435
- const xhr = new winAh.realXHR();
436
- if ("__ajaxHooker" in xhr)
437
- console.warn("检测到不同版本的ajaxHooker,可能发生冲突!");
438
- xhr.__ajaxHooker = new XhrHooker(xhr);
439
- return xhr.__ajaxHooker.proxyXhr;
372
+ open(method, url, async = true, ...args) {
373
+ this.request = {
374
+ type: "xhr",
375
+ url: url.toString(),
376
+ method: method.toUpperCase(),
377
+ abort: false,
378
+ headers: {},
379
+ data: null,
380
+ response: null,
381
+ async: !!async,
382
+ };
383
+ this.openArgs = args;
384
+ this.resThenable = new SyncThenable();
385
+ [
386
+ "responseURL",
387
+ "readyState",
388
+ "status",
389
+ "statusText",
390
+ ...xhrResponses,
391
+ ].forEach((key) => {
392
+ delete this.proxyProps[key];
393
+ });
394
+ return this.originalXhr.open(method, url, async, ...args);
440
395
  }
441
- fakeXHR.prototype = win.XMLHttpRequest.prototype;
442
- Object.keys(win.XMLHttpRequest).forEach(
443
- (key) => (fakeXHR[key] = win.XMLHttpRequest[key])
444
- );
445
- function fakeFetch(url, options = {}) {
446
- if (!url) return winAh.realFetch.call(win, url, options);
447
- return new Promise(async (resolve, reject) => {
448
- const init = {};
449
- if (getType(url) === "[object Request]") {
450
- for (const prop of fetchInitProps) init[prop] = url[prop];
451
- if (url.body) init.body = await url.arrayBuffer();
452
- url = url.url;
453
- }
454
- url = url.toString();
455
- Object.assign(init, options);
456
- init.method = init.method || "GET";
457
- init.headers = init.headers || {};
458
- const request = {
459
- type: "fetch",
460
- url: url,
461
- method: init.method.toUpperCase(),
462
- abort: false,
463
- headers: parseHeaders(init.headers),
464
- data: init.body,
465
- response: null,
466
- async: true,
467
- };
468
- const req = new AHRequest(request);
469
- await req.waitForRequestKeys();
396
+ send(data) {
397
+ const ah = this;
398
+ const xhr = ah.originalXhr;
399
+ const request = ah.request;
400
+ if (!request) return xhr.send(data);
401
+ request.data = data;
402
+ new AHRequest(request).waitForRequestKeys().then(() => {
470
403
  if (request.abort) {
471
404
  if (typeof request.response === "function") {
472
- const response = {
473
- finalUrl: request.url,
474
- status: 200,
475
- responseHeaders: {},
476
- };
477
- await req.waitForResponseKeys(response);
478
- const key = fetchResponses.find((k) => k in response);
479
- let val = response[key];
480
- if (key === "json" && typeof val === "object") {
481
- val = catchError(JSON.stringify.bind(JSON), val);
482
- }
483
- const res = new Response(val, {
484
- status: 200,
485
- statusText: "OK",
405
+ Object.assign(ah.proxyProps, {
406
+ responseURL: { value: request.url },
407
+ readyState: { value: 4 },
408
+ status: { value: 200 },
409
+ statusText: { value: "OK" },
486
410
  });
487
- defineProp(res, "type", () => "basic");
488
- defineProp(res, "url", () => request.url);
489
- resolve(res);
490
- } else {
491
- reject(new DOMException("aborted", "AbortError"));
411
+ xhrAsyncEvents.forEach((evt) => xhr.dispatchEvent(new Event(evt)));
492
412
  }
493
- return;
494
- }
495
- init.method = request.method;
496
- init.headers = request.headers;
497
- init.body = request.data;
498
- winAh.realFetch.call(win, request.url, init).then((res) => {
499
- if (typeof request.response === "function") {
500
- const response = {
501
- finalUrl: res.url,
502
- status: res.status,
503
- responseHeaders: parseHeaders(res.headers),
504
- };
505
- fetchResponses.forEach(
506
- (key) =>
507
- (res[key] = function () {
508
- if (key in response) return Promise.resolve(response[key]);
509
- return resProto[key].call(this).then((val) => {
510
- response[key] = val;
511
- return req
512
- .waitForResponseKeys(response)
513
- .then(() => (key in response ? response[key] : val));
514
- });
515
- })
516
- );
413
+ } else {
414
+ xhr.open(request.method, request.url, request.async, ...ah.openArgs);
415
+ for (const header in request.headers) {
416
+ xhr.setRequestHeader(header, request.headers[header]);
517
417
  }
518
- resolve(res);
519
- }, reject);
418
+ xhr.send(request.data);
419
+ }
520
420
  });
521
421
  }
522
- function fakeFetchClone() {
523
- const descriptors = Object.getOwnPropertyDescriptors(this);
524
- const res = winAh.realFetchClone.call(this);
525
- Object.defineProperties(res, descriptors);
526
- return res;
527
- }
528
- winAh = win.__ajaxHooker = winAh || {
529
- version,
530
- fakeXHR,
531
- fakeFetch,
532
- fakeFetchClone,
533
- realXHR: win.XMLHttpRequest,
534
- realFetch: win.fetch,
535
- realFetchClone: resProto.clone,
536
- hookInsts: new Set(),
537
- };
538
- if (winAh.version !== version)
422
+ }
423
+ function fakeXHR() {
424
+ const xhr = new winAh.realXHR();
425
+ if ("__ajaxHooker" in xhr)
539
426
  console.warn("检测到不同版本的ajaxHooker,可能发生冲突!");
540
- win.XMLHttpRequest = winAh.fakeXHR;
541
- win.fetch = winAh.fakeFetch;
542
- resProto.clone = winAh.fakeFetchClone;
543
- winAh.hookInsts.add(hookInst);
544
- // 针对头条、抖音 secsdk.umd.js 的兼容性处理
545
- class AHFunction {
546
- call(thisArg, ...args) {
547
- if (
548
- thisArg &&
549
- thisArg.__ajaxHooker &&
550
- thisArg.__ajaxHooker.proxyXhr === thisArg
551
- ) {
552
- thisArg = thisArg.__ajaxHooker.originalXhr;
427
+ xhr.__ajaxHooker = new XhrHooker(xhr);
428
+ return xhr.__ajaxHooker.proxyXhr;
429
+ }
430
+ fakeXHR.prototype = win.XMLHttpRequest.prototype;
431
+ Object.keys(win.XMLHttpRequest).forEach(
432
+ (key) => (fakeXHR[key] = win.XMLHttpRequest[key])
433
+ );
434
+ function fakeFetch(url, options = {}) {
435
+ if (!url) return winAh.realFetch.call(win, url, options);
436
+ return new Promise(async (resolve, reject) => {
437
+ const init = {};
438
+ if (getType(url) === "[object Request]") {
439
+ for (const prop of fetchInitProps) init[prop] = url[prop];
440
+ if (url.body) init.body = await url.arrayBuffer();
441
+ url = url.url;
442
+ }
443
+ url = url.toString();
444
+ Object.assign(init, options);
445
+ init.method = init.method || "GET";
446
+ init.headers = init.headers || {};
447
+ const request = {
448
+ type: "fetch",
449
+ url: url,
450
+ method: init.method.toUpperCase(),
451
+ abort: false,
452
+ headers: parseHeaders(init.headers),
453
+ data: init.body,
454
+ response: null,
455
+ async: true,
456
+ };
457
+ const req = new AHRequest(request);
458
+ await req.waitForRequestKeys();
459
+ if (request.abort) {
460
+ if (typeof request.response === "function") {
461
+ const response = {
462
+ finalUrl: request.url,
463
+ status: 200,
464
+ responseHeaders: {},
465
+ };
466
+ await req.waitForResponseKeys(response);
467
+ const key = fetchResponses.find((k) => k in response);
468
+ let val = response[key];
469
+ if (key === "json" && typeof val === "object") {
470
+ val = catchError(JSON.stringify.bind(JSON), val);
471
+ }
472
+ const res = new Response(val, {
473
+ status: 200,
474
+ statusText: "OK",
475
+ });
476
+ defineProp(res, "type", () => "basic");
477
+ defineProp(res, "url", () => request.url);
478
+ resolve(res);
479
+ } else {
480
+ reject(new DOMException("aborted", "AbortError"));
553
481
  }
554
- return Reflect.apply(this, thisArg, args);
482
+ return;
555
483
  }
556
- apply(thisArg, args) {
557
- if (
558
- thisArg &&
559
- thisArg.__ajaxHooker &&
560
- thisArg.__ajaxHooker.proxyXhr === thisArg
561
- ) {
562
- thisArg = thisArg.__ajaxHooker.originalXhr;
484
+ init.method = request.method;
485
+ init.headers = request.headers;
486
+ init.body = request.data;
487
+ winAh.realFetch.call(win, request.url, init).then((res) => {
488
+ if (typeof request.response === "function") {
489
+ const response = {
490
+ finalUrl: res.url,
491
+ status: res.status,
492
+ responseHeaders: parseHeaders(res.headers),
493
+ };
494
+ fetchResponses.forEach(
495
+ (key) =>
496
+ (res[key] = function () {
497
+ if (key in response) return Promise.resolve(response[key]);
498
+ return resProto[key].call(this).then((val) => {
499
+ response[key] = val;
500
+ return req
501
+ .waitForResponseKeys(response)
502
+ .then(() => (key in response ? response[key] : val));
503
+ });
504
+ })
505
+ );
563
506
  }
564
- return Reflect.apply(this, thisArg, args || []);
507
+ resolve(res);
508
+ }, reject);
509
+ });
510
+ }
511
+ function fakeFetchClone() {
512
+ const descriptors = Object.getOwnPropertyDescriptors(this);
513
+ const res = winAh.realFetchClone.call(this);
514
+ Object.defineProperties(res, descriptors);
515
+ return res;
516
+ }
517
+ winAh = win.__ajaxHooker = winAh || {
518
+ version,
519
+ fakeXHR,
520
+ fakeFetch,
521
+ fakeFetchClone,
522
+ realXHR: win.XMLHttpRequest,
523
+ realFetch: win.fetch,
524
+ realFetchClone: resProto.clone,
525
+ hookInsts: new Set(),
526
+ };
527
+ if (winAh.version !== version)
528
+ console.warn("检测到不同版本的ajaxHooker,可能发生冲突!");
529
+ win.XMLHttpRequest = winAh.fakeXHR;
530
+ win.fetch = winAh.fakeFetch;
531
+ resProto.clone = winAh.fakeFetchClone;
532
+ winAh.hookInsts.add(hookInst);
533
+ // 针对头条、抖音 secsdk.umd.js 的兼容性处理
534
+ class AHFunction extends Function {
535
+ call(thisArg, ...args) {
536
+ if (
537
+ thisArg &&
538
+ thisArg.__ajaxHooker &&
539
+ thisArg.__ajaxHooker.proxyXhr === thisArg
540
+ ) {
541
+ thisArg = thisArg.__ajaxHooker.originalXhr;
565
542
  }
543
+ return Reflect.apply(this, thisArg, args);
566
544
  }
567
- function hookSecsdk(csrf) {
568
- Object.setPrototypeOf(
569
- csrf.nativeXMLHttpRequestSetRequestHeader,
570
- AHFunction.prototype
571
- );
572
- Object.setPrototypeOf(
573
- csrf.nativeXMLHttpRequestOpen,
574
- AHFunction.prototype
575
- );
576
- Object.setPrototypeOf(
577
- csrf.nativeXMLHttpRequestSend,
578
- AHFunction.prototype
579
- );
545
+ apply(thisArg, args) {
546
+ if (
547
+ thisArg &&
548
+ thisArg.__ajaxHooker &&
549
+ thisArg.__ajaxHooker.proxyXhr === thisArg
550
+ ) {
551
+ thisArg = thisArg.__ajaxHooker.originalXhr;
552
+ }
553
+ return Reflect.apply(this, thisArg, args || []);
580
554
  }
581
- if (win.secsdk) {
582
- if (win.secsdk.csrf && win.secsdk.csrf.nativeXMLHttpRequestOpen)
583
- hookSecsdk(win.secsdk.csrf);
584
- } else {
585
- defineProp(win, "secsdk", emptyFn, (secsdk) => {
586
- delete win.secsdk;
587
- win.secsdk = secsdk;
588
- defineProp(secsdk, "csrf", emptyFn, (csrf) => {
589
- delete secsdk.csrf;
590
- secsdk.csrf = csrf;
591
- if (csrf.nativeXMLHttpRequestOpen) hookSecsdk(csrf);
592
- });
555
+ }
556
+ function hookSecsdk(csrf) {
557
+ Object.setPrototypeOf(
558
+ csrf.nativeXMLHttpRequestSetRequestHeader,
559
+ AHFunction.prototype
560
+ );
561
+ Object.setPrototypeOf(csrf.nativeXMLHttpRequestOpen, AHFunction.prototype);
562
+ Object.setPrototypeOf(csrf.nativeXMLHttpRequestSend, AHFunction.prototype);
563
+ }
564
+ if (win.secsdk) {
565
+ if (win.secsdk.csrf && win.secsdk.csrf.nativeXMLHttpRequestOpen)
566
+ hookSecsdk(win.secsdk.csrf);
567
+ } else {
568
+ defineProp(win, "secsdk", emptyFn, (secsdk) => {
569
+ delete win.secsdk;
570
+ win.secsdk = secsdk;
571
+ defineProp(secsdk, "csrf", emptyFn, (csrf) => {
572
+ delete secsdk.csrf;
573
+ secsdk.csrf = csrf;
574
+ if (csrf.nativeXMLHttpRequestOpen) hookSecsdk(csrf);
593
575
  });
594
- }
595
- return {
596
- hook: (fn) => hookInst.hookFns.push(fn),
597
- filter: (arr) => {
598
- if (Array.isArray(arr)) hookInst.filters = arr;
599
- },
600
- protect: () => {
601
- readonly(win, "XMLHttpRequest", winAh.fakeXHR);
602
- readonly(win, "fetch", winAh.fakeFetch);
603
- readonly(resProto, "clone", winAh.fakeFetchClone);
604
- },
605
- unhook: () => {
606
- winAh.hookInsts.delete(hookInst);
607
- if (!winAh.hookInsts.size) {
608
- writable(win, "XMLHttpRequest", winAh.realXHR);
609
- writable(win, "fetch", winAh.realFetch);
610
- writable(resProto, "clone", winAh.realFetchClone);
611
- delete win.__ajaxHooker;
612
- }
613
- },
614
- };
615
- })();
576
+ });
577
+ }
578
+ return {
579
+ hook: (fn) => hookInst.hookFns.push(fn),
580
+ filter: (arr) => {
581
+ if (Array.isArray(arr)) hookInst.filters = arr;
582
+ },
583
+ protect: () => {
584
+ readonly(win, "XMLHttpRequest", winAh.fakeXHR);
585
+ readonly(win, "fetch", winAh.fakeFetch);
586
+ readonly(resProto, "clone", winAh.fakeFetchClone);
587
+ },
588
+ unhook: () => {
589
+ winAh.hookInsts.delete(hookInst);
590
+ if (!winAh.hookInsts.size) {
591
+ writable(win, "XMLHttpRequest", winAh.realXHR);
592
+ writable(win, "fetch", winAh.realFetch);
593
+ writable(resProto, "clone", winAh.realFetchClone);
594
+ delete win.__ajaxHooker;
595
+ }
596
+ },
597
+ };
616
598
  };