@whitesev/utils 2.6.9 → 2.7.1

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