@whitesev/utils 2.8.2 → 2.9.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.
Files changed (73) hide show
  1. package/README.md +176 -176
  2. package/dist/index.amd.js +283 -776
  3. package/dist/index.amd.js.map +1 -1
  4. package/dist/index.amd.min.js +2 -0
  5. package/dist/index.amd.min.js.map +1 -0
  6. package/dist/index.cjs.js +283 -776
  7. package/dist/index.cjs.js.map +1 -1
  8. package/dist/index.cjs.min.js +2 -0
  9. package/dist/index.cjs.min.js.map +1 -0
  10. package/dist/index.esm.js +283 -776
  11. package/dist/index.esm.js.map +1 -1
  12. package/dist/index.esm.min.js +2 -0
  13. package/dist/index.esm.min.js.map +1 -0
  14. package/dist/index.iife.js +283 -776
  15. package/dist/index.iife.js.map +1 -1
  16. package/dist/index.iife.min.js +2 -0
  17. package/dist/index.iife.min.js.map +1 -0
  18. package/dist/index.system.js +283 -776
  19. package/dist/index.system.js.map +1 -1
  20. package/dist/index.system.min.js +2 -0
  21. package/dist/index.system.min.js.map +1 -0
  22. package/dist/index.umd.js +283 -776
  23. package/dist/index.umd.js.map +1 -1
  24. package/dist/index.umd.min.js +2 -0
  25. package/dist/index.umd.min.js.map +1 -0
  26. package/dist/types/src/Utils.d.ts +132 -448
  27. package/dist/types/src/UtilsGMCookie.d.ts +4 -0
  28. package/dist/types/src/UtilsGMMenu.d.ts +3 -6
  29. package/dist/types/src/types/Httpx.d.ts +1344 -1344
  30. package/dist/types/src/types/Log.d.ts +19 -19
  31. package/dist/types/src/types/Progress.d.ts +20 -20
  32. package/dist/types/src/types/React.d.ts +119 -119
  33. package/dist/types/src/types/TryCatch.d.ts +9 -9
  34. package/dist/types/src/types/UtilsGMCookie.d.ts +93 -93
  35. package/dist/types/src/types/UtilsGMMenu.d.ts +77 -77
  36. package/dist/types/src/types/Vue2.d.ts +166 -166
  37. package/dist/types/src/types/WindowApi.d.ts +14 -14
  38. package/dist/types/src/types/ajaxHooker.d.ts +153 -151
  39. package/dist/types/src/types/env.d.ts +7 -7
  40. package/dist/types/src/types/global.d.ts +31 -31
  41. package/package.json +11 -10
  42. package/src/ColorConversion.ts +105 -105
  43. package/src/CommonUtil.ts +280 -280
  44. package/src/DOMUtils.ts +251 -251
  45. package/src/Dictionary.ts +153 -153
  46. package/src/GBKEncoder.ts +108 -108
  47. package/src/Hooks.ts +73 -73
  48. package/src/Httpx.ts +1457 -1457
  49. package/src/LockFunction.ts +62 -62
  50. package/src/Log.ts +258 -258
  51. package/src/Progress.ts +108 -108
  52. package/src/TryCatch.ts +86 -86
  53. package/src/Utils.ts +3827 -4773
  54. package/src/UtilsCommon.ts +14 -14
  55. package/src/UtilsGMCookie.ts +272 -254
  56. package/src/UtilsGMMenu.ts +441 -445
  57. package/src/Vue.ts +233 -233
  58. package/src/WindowApi.ts +59 -59
  59. package/src/indexedDB.ts +497 -497
  60. package/src/types/Httpx.d.ts +1344 -1344
  61. package/src/types/Log.d.ts +19 -19
  62. package/src/types/Progress.d.ts +20 -20
  63. package/src/types/React.d.ts +119 -119
  64. package/src/types/TryCatch.d.ts +9 -9
  65. package/src/types/UtilsGMCookie.d.ts +93 -93
  66. package/src/types/UtilsGMMenu.d.ts +77 -77
  67. package/src/types/Vue2.d.ts +166 -166
  68. package/src/types/WindowApi.d.ts +14 -14
  69. package/src/types/ajaxHooker.d.ts +153 -151
  70. package/src/types/env.d.ts +7 -7
  71. package/src/types/global.d.ts +31 -31
  72. package/dist/types/src/types/Event.d.ts +0 -188
  73. package/src/types/Event.d.ts +0 -188
package/src/Httpx.ts CHANGED
@@ -1,1457 +1,1457 @@
1
- import { CommonUtil } from "./CommonUtil";
2
- import { TryCatch } from "./TryCatch";
3
- import type {
4
- HttpxAllowInterceptConfig,
5
- HttpxHookErrorData,
6
- HttpxMethod,
7
- HttpxRequestOption,
8
- HttpxResponse,
9
- HttpxResponseData,
10
- HttpxPromise,
11
- HttpxInitOption,
12
- } from "./types/Httpx";
13
- import { GenerateUUID } from "./UtilsCommon";
14
-
15
- export class Httpx {
16
- private GM_Api = {
17
- xmlHttpRequest: null as ((...args: any[]) => any) | null,
18
- };
19
- private HttpxRequestHook = {
20
- /**
21
- * @private
22
- */
23
- $config: {
24
- configList: <
25
- {
26
- id: string;
27
- fn: ((...args: any[]) => any) | Promise<(...args: any[]) => any>;
28
- }[]
29
- >[],
30
- },
31
- /**
32
- * 发送请求前的回调
33
- * 如果返回false则阻止本次返回
34
- * @param details 当前的请求配置
35
- * @private
36
- */
37
- async beforeRequestCallBack(details: HttpxRequestOption) {
38
- if (typeof details.allowInterceptConfig === "boolean") {
39
- if (!details.allowInterceptConfig) {
40
- // 不允许拦截
41
- return details;
42
- }
43
- } else {
44
- if (details.allowInterceptConfig != null) {
45
- // 配置存在
46
- // 细分处理是否拦截
47
- if (
48
- typeof details.allowInterceptConfig.beforeRequest === "boolean" &&
49
- !details.allowInterceptConfig.beforeRequest
50
- ) {
51
- // 设置了禁止拦截
52
- return details;
53
- }
54
- } else {
55
- // 配置不存在
56
- // 默认允许拦截
57
- }
58
- }
59
- for (let index = 0; index < this.$config.configList.length; index++) {
60
- const item = this.$config.configList[index];
61
- if (typeof item.fn === "function") {
62
- const result = await item.fn(details);
63
- if (result == null) {
64
- return;
65
- }
66
- }
67
- }
68
- return details;
69
- },
70
- /**
71
- * 添加请求前的回调处理配置
72
- */
73
- add(fn: (...args: any[]) => any) {
74
- if (typeof fn === "function") {
75
- const uuid = GenerateUUID();
76
- this.$config.configList.push({
77
- id: uuid,
78
- fn: fn,
79
- });
80
- return uuid;
81
- } else {
82
- console.warn("[Httpx-HttpxRequestHook.addBeforeRequestCallBack] fn is not a function");
83
- }
84
- },
85
- /**
86
- * 删除请求前的回调处理配置
87
- * @param id
88
- */
89
- delete(id: string) {
90
- if (typeof id === "string") {
91
- const findIndex = this.$config.configList.findIndex((item) => item.id === id);
92
- if (findIndex !== -1) {
93
- this.$config.configList.splice(findIndex, 1);
94
- return true;
95
- }
96
- }
97
- return false;
98
- },
99
- /**
100
- * 清空设置的请求前的回调处理配置
101
- */
102
- clearAll() {
103
- this.$config.configList = [];
104
- },
105
- };
106
- private HttpxResponseHook = {
107
- /**
108
- * @private
109
- */
110
- $config: {
111
- configList: <
112
- {
113
- id: string;
114
- successFn?: (...args: any[]) => any | Promise<(...args: any[]) => any>;
115
- errorFn?: (...args: any[]) => any | Promise<(...args: any[]) => any>;
116
- }[]
117
- >[],
118
- },
119
- /**
120
- * 成功的回调
121
- * @param response 响应
122
- * @param details 请求的配置
123
- */
124
- async successResponseCallBack(response: HttpxResponseData<HttpxRequestOption>, details: HttpxRequestOption) {
125
- if (typeof details.allowInterceptConfig === "boolean") {
126
- if (!details.allowInterceptConfig) {
127
- // 不允许拦截
128
- return details;
129
- }
130
- } else {
131
- if (details.allowInterceptConfig != null) {
132
- // 配置存在
133
- // 细分处理是否拦截
134
- if (
135
- typeof details.allowInterceptConfig.afterResponseSuccess === "boolean" &&
136
- !details.allowInterceptConfig.afterResponseSuccess
137
- ) {
138
- // 设置了禁止拦截
139
- return details;
140
- }
141
- } else {
142
- // 配置不存在
143
- // 默认允许拦截
144
- }
145
- }
146
- for (let index = 0; index < this.$config.configList.length; index++) {
147
- const item = this.$config.configList[index];
148
- if (typeof item.successFn === "function") {
149
- const result = await item.successFn(response, details);
150
- if (result == null) {
151
- return;
152
- }
153
- }
154
- }
155
- return response;
156
- },
157
- /**
158
- * 失败的回调
159
- * @param data 配置
160
- * @returns
161
- * 返回null|undefined就是拦截掉了
162
- */
163
- async errorResponseCallBack<T extends HttpxHookErrorData>(data: T) {
164
- if (typeof data.details.allowInterceptConfig === "boolean") {
165
- if (!data.details.allowInterceptConfig) {
166
- // 不允许拦截
167
- return data;
168
- }
169
- } else {
170
- if (data.details.allowInterceptConfig != null) {
171
- // 配置存在
172
- // 细分处理是否拦截
173
- if (
174
- typeof data.details.allowInterceptConfig.afterResponseError === "boolean" &&
175
- !data.details.allowInterceptConfig.afterResponseError
176
- ) {
177
- // 设置了禁止拦截
178
- return data;
179
- }
180
- } else {
181
- // 配置不存在
182
- // 默认允许拦截
183
- }
184
- }
185
- for (let index = 0; index < this.$config.configList.length; index++) {
186
- const item = this.$config.configList[index];
187
- if (typeof item.errorFn === "function") {
188
- const result = await item.errorFn(data);
189
- if (result == null) {
190
- return;
191
- }
192
- }
193
- }
194
- return data;
195
- },
196
- /**
197
- * 添加请求前的回调处理配置
198
- */
199
- add(successFn?: (...args: any[]) => any, errorFn?: (...args: any[]) => any) {
200
- const id = GenerateUUID();
201
- this.$config.configList.push({
202
- id: id,
203
- successFn: successFn,
204
- errorFn: errorFn,
205
- });
206
- return id;
207
- },
208
- /**
209
- * 删除请求前的回调处理配置
210
- * @param id
211
- */
212
- delete(id: string) {
213
- if (typeof id === "string") {
214
- const findIndex = this.$config.configList.findIndex((item) => item.id === id);
215
- if (findIndex !== -1) {
216
- this.$config.configList.splice(findIndex, 1);
217
- return true;
218
- }
219
- }
220
- return false;
221
- },
222
- /**
223
- * 清空设置的请求前的回调处理配置
224
- */
225
- clearAll() {
226
- this.$config.configList = [];
227
- },
228
- };
229
- private HttpxRequestOption = {
230
- context: this,
231
- /**
232
- * 对请求的参数进行合并处理
233
- */
234
- handleBeforeRequestOptionArgs(...args: (HttpxRequestOption | string)[]) {
235
- const option: HttpxRequestOption = {
236
- url: void 0 as any as string,
237
- };
238
- if (typeof args[0] === "string") {
239
- /* 传入的是url,转为配置 */
240
- const url = args[0];
241
- option.url = url;
242
- if (typeof args[1] === "object") {
243
- /* 处理第二个参数details */
244
- const optionArg = args[1];
245
- CommonUtil.assign(option, optionArg, true);
246
- option.url = url;
247
- }
248
- } else {
249
- /* 传入的是配置 */
250
- const optionArg = args[0];
251
- CommonUtil.assign(option, optionArg, true);
252
- }
253
- return option;
254
- },
255
- /**
256
- * 获取请求配置
257
- * @param method 当前请求方法,默认get
258
- * @param userRequestOption 用户的请求配置
259
- * @param resolve promise回调
260
- * @param reject promise抛出错误回调
261
- */
262
- getRequestOption(
263
- method: HttpxMethod,
264
- userRequestOption: HttpxRequestOption,
265
- resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
266
- reject: (...args: any[]) => void
267
- ) {
268
- const that = this;
269
- let url = userRequestOption.url || this.context.#defaultRequestOption.url;
270
- if (typeof url === "string") {
271
- // 去除左右空格
272
- url = url.trim();
273
- if (url.startsWith("http://") || url.startsWith("https://")) {
274
- // 标准的http请求
275
- } else {
276
- if (typeof this.context.#defaultInitOption.baseURL === "string") {
277
- // 设置了基础域
278
- url = this.context.#defaultInitOption.baseURL + url;
279
- }
280
- }
281
- }
282
- const requestOption = <Required<HttpxRequestOption>>{
283
- url: url,
284
- method: (method || "GET").toString().toUpperCase().trim(),
285
- timeout: userRequestOption.timeout || this.context.#defaultRequestOption.timeout,
286
- responseType: userRequestOption.responseType || this.context.#defaultRequestOption.responseType,
287
- /* 对象使用深拷贝 */
288
- headers: CommonUtil.deepClone(this.context.#defaultRequestOption.headers),
289
- data: userRequestOption.data || this.context.#defaultRequestOption.data,
290
- redirect: userRequestOption.redirect || this.context.#defaultRequestOption.redirect,
291
- cookie: userRequestOption.cookie || this.context.#defaultRequestOption.cookie,
292
- cookiePartition: userRequestOption.cookiePartition || this.context.#defaultRequestOption.cookiePartition,
293
- binary: userRequestOption.binary || this.context.#defaultRequestOption.binary,
294
- nocache: userRequestOption.nocache || this.context.#defaultRequestOption.nocache,
295
- revalidate: userRequestOption.revalidate || this.context.#defaultRequestOption.revalidate,
296
- /* 对象使用深拷贝 */
297
- context: CommonUtil.deepClone(userRequestOption.context || this.context.#defaultRequestOption.context),
298
- overrideMimeType: userRequestOption.overrideMimeType || this.context.#defaultRequestOption.overrideMimeType,
299
- anonymous: userRequestOption.anonymous || this.context.#defaultRequestOption.anonymous,
300
- fetch: userRequestOption.fetch || this.context.#defaultRequestOption.fetch,
301
- /* 对象使用深拷贝 */
302
- fetchInit: CommonUtil.deepClone(this.context.#defaultRequestOption.fetchInit),
303
- allowInterceptConfig: {
304
- beforeRequest: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
305
- .beforeRequest,
306
- afterResponseSuccess: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
307
- .afterResponseSuccess,
308
- afterResponseError: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
309
- .afterResponseError,
310
- },
311
- user: userRequestOption.user || this.context.#defaultRequestOption.user,
312
- password: userRequestOption.password || this.context.#defaultRequestOption.password,
313
- onabort(...args) {
314
- that.context.HttpxResponseCallBack.onAbort(
315
- userRequestOption as Required<HttpxRequestOption>,
316
- resolve,
317
- reject,
318
- args
319
- );
320
- },
321
- onerror(...args) {
322
- that.context.HttpxResponseCallBack.onError(
323
- userRequestOption as Required<HttpxRequestOption>,
324
- resolve,
325
- reject,
326
- args
327
- );
328
- },
329
- onloadstart(...args) {
330
- that.context.HttpxResponseCallBack.onLoadStart(userRequestOption as Required<HttpxRequestOption>, args);
331
- },
332
- onprogress(...args) {
333
- that.context.HttpxResponseCallBack.onProgress(userRequestOption as Required<HttpxRequestOption>, args);
334
- },
335
- onreadystatechange(...args) {
336
- that.context.HttpxResponseCallBack.onReadyStateChange(
337
- userRequestOption as Required<HttpxRequestOption>,
338
- args
339
- );
340
- },
341
- ontimeout(...args) {
342
- that.context.HttpxResponseCallBack.onTimeout(
343
- userRequestOption as Required<HttpxRequestOption>,
344
- resolve,
345
- reject,
346
- args
347
- );
348
- },
349
- onload(...args) {
350
- that.context.HttpxResponseCallBack.onLoad(
351
- userRequestOption as Required<HttpxRequestOption>,
352
- resolve,
353
- reject,
354
- args
355
- );
356
- },
357
- };
358
- // 补全allowInterceptConfig参数
359
- if (typeof userRequestOption.allowInterceptConfig === "boolean") {
360
- const allowInterceptConfigKeys = Object.keys(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig);
361
- allowInterceptConfigKeys.forEach((keyName) => {
362
- Reflect.set(
363
- requestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
364
- keyName,
365
- userRequestOption.allowInterceptConfig
366
- );
367
- });
368
- } else {
369
- if (
370
- typeof userRequestOption.allowInterceptConfig === "object" &&
371
- userRequestOption.allowInterceptConfig != null
372
- ) {
373
- const allowInterceptConfigKeys = Object.keys(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig);
374
- allowInterceptConfigKeys.forEach((keyName) => {
375
- const value = Reflect.get(
376
- userRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
377
- keyName
378
- ) as boolean;
379
- if (
380
- typeof value === "boolean" &&
381
- Reflect.has(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig, keyName)
382
- ) {
383
- Reflect.set(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig, keyName, value);
384
- }
385
- });
386
- }
387
- }
388
- if (typeof this.context.GM_Api.xmlHttpRequest !== "function") {
389
- // GM函数不存在,强制使用fetch
390
- requestOption.fetch = true;
391
- }
392
- if (typeof requestOption.headers === "object") {
393
- if (typeof userRequestOption.headers === "object") {
394
- const headerKeys = Object.keys(requestOption.headers);
395
- headerKeys.forEach((keyName) => {
396
- if (keyName in requestOption.headers && userRequestOption!.headers?.[keyName] == null) {
397
- /* 在默认的header中存在,且设置它新的值为空,那么就是默认的值 */
398
- Reflect.deleteProperty(requestOption.headers, keyName);
399
- } else {
400
- requestOption.headers[keyName] = userRequestOption?.headers?.[keyName];
401
- }
402
- });
403
- } else {
404
- /* details.headers为空 */
405
- /* 不做处理 */
406
- }
407
- } else {
408
- /* 默认的headers不是对象,那么就直接使用新的 */
409
- Reflect.set(requestOption, "headers", userRequestOption.headers);
410
- }
411
- if (typeof requestOption.fetchInit === "object") {
412
- /* 使用assign替换且添加 */
413
- if (typeof userRequestOption.fetchInit === "object") {
414
- const fetchInitKeys = Object.keys(requestOption.fetchInit);
415
- fetchInitKeys.forEach((keyName) => {
416
- if (keyName in requestOption.fetchInit && Reflect.get(userRequestOption.fetchInit ?? {}, keyName) == null) {
417
- /* 在默认的fetchInit中存在,且设置它新的值为空,那么就是默认的值 */
418
- Reflect.deleteProperty(requestOption.fetchInit, keyName);
419
- } else {
420
- Reflect.set(requestOption.fetchInit, keyName, Reflect.get(userRequestOption.fetchInit!, keyName));
421
- }
422
- });
423
- }
424
- } else {
425
- Reflect.set(requestOption, "fetchInit", userRequestOption.fetchInit);
426
- }
427
-
428
- // 处理新的cookiePartition
429
- if (typeof requestOption.cookiePartition === "object" && requestOption.cookiePartition != null) {
430
- if (
431
- Reflect.has(requestOption.cookiePartition, "topLevelSite") &&
432
- typeof requestOption.cookiePartition.topLevelSite !== "string"
433
- ) {
434
- // topLevelSite必须是字符串
435
- Reflect.deleteProperty(requestOption.cookiePartition, "topLevelSite");
436
- }
437
- }
438
-
439
- /* 完善请求的url */
440
- try {
441
- new URL(requestOption.url);
442
- } catch {
443
- if (requestOption.url.startsWith("//")) {
444
- // 补充https:
445
- requestOption.url = globalThis.location.protocol + requestOption.url;
446
- } else if (requestOption.url.startsWith("/")) {
447
- // 补充origin
448
- requestOption.url = globalThis.location.origin + requestOption.url;
449
- } else {
450
- // 补充origin+/
451
- requestOption.url = `${globalThis.location.origin}/${requestOption.url}`;
452
- }
453
- }
454
-
455
- if (requestOption.fetchInit && !requestOption.fetch) {
456
- // 清空fetchInit
457
- Reflect.deleteProperty(requestOption, "fetchInit");
458
- }
459
-
460
- // 转换data类型
461
- try {
462
- /** 是否对数据进行处理 */
463
- const processData = userRequestOption.processData ?? true;
464
- if (requestOption.data != null && processData) {
465
- const method = requestOption.method;
466
- if (method === "GET" || method === "HEAD") {
467
- // GET类型,data如果有,那么需要转为searchParams
468
- const urlObj = new URL(requestOption.url);
469
- let urlSearch = "";
470
- let isHandler = false;
471
- if (typeof requestOption.data === "string") {
472
- isHandler = true;
473
- urlSearch = requestOption.data;
474
- } else if (typeof requestOption.data === "object") {
475
- isHandler = true;
476
- // URLSearchParams参数可以转普通的string:string,包括FormData
477
- const searchParams = new URLSearchParams(requestOption.data as Record<string, string>);
478
- urlSearch = searchParams.toString();
479
- }
480
- if (isHandler) {
481
- // GET/HEAD请求不支持data参数
482
- // 对data进行处理了才可以删除
483
- Reflect.deleteProperty(requestOption, "data");
484
- }
485
- if (urlSearch != "") {
486
- if (urlObj.search === "") {
487
- // url没有search参数,直接覆盖
488
- urlObj.search = urlSearch;
489
- } else {
490
- // 有search参数
491
- if (urlObj.search.endsWith("&")) {
492
- // xxx=xxx&
493
- urlObj.search = urlObj.search + urlSearch;
494
- } else {
495
- // xxx=xxx&xxx=
496
- urlObj.search = `${urlObj.search}&${urlSearch}`;
497
- }
498
- }
499
- }
500
- requestOption.url = urlObj.toString();
501
- } else if (method === "POST" && requestOption.headers != null) {
502
- // POST类型,data如果是FormData,那么需要转为string
503
- const headersKeyList = Object.keys(requestOption.headers);
504
- const ContentTypeIndex = headersKeyList.findIndex((headerKey) => {
505
- return (
506
- headerKey.trim().toLowerCase() === "content-type" &&
507
- typeof requestOption.headers[headerKey] === "string"
508
- );
509
- });
510
- if (ContentTypeIndex !== -1) {
511
- const ContentTypeKey = headersKeyList[ContentTypeIndex];
512
- const ContentType = requestOption.headers[ContentTypeKey] as string;
513
- // 设置了Content-Type
514
- if (ContentType.includes("application/json")) {
515
- // application/json
516
- if (requestOption.data instanceof FormData) {
517
- const entries: { [key: string]: any } = {};
518
- requestOption.data.forEach((value, key) => {
519
- entries[key] = value;
520
- });
521
- requestOption.data = JSON.stringify(entries);
522
- } else if (typeof requestOption.data === "object") {
523
- requestOption.data = JSON.stringify(requestOption.data);
524
- }
525
- } else if (ContentType.includes("application/x-www-form-urlencoded")) {
526
- // application/x-www-form-urlencoded
527
- if (typeof requestOption.data === "object") {
528
- requestOption.data = new URLSearchParams(requestOption.data as Record<string, string>).toString();
529
- }
530
- } else if (ContentType.includes("multipart/form-data")) {
531
- // multipart/form-data
532
- if (requestOption.data instanceof FormData) {
533
- Reflect.deleteProperty(requestOption.headers, ContentTypeKey);
534
- }
535
- }
536
- }
537
- }
538
- }
539
- } catch (error) {
540
- console.warn("Httpx ==> 转换data参数错误", error);
541
- }
542
-
543
- return requestOption;
544
- },
545
- /**
546
- * 处理发送请求的配置,去除值为undefined、空function的值
547
- * @param option 请求配置
548
- */
549
- removeRequestNullOption(option: Required<HttpxRequestOption>): Required<HttpxRequestOption> {
550
- const optionKeys = Object.keys(option);
551
- optionKeys.forEach((keyName) => {
552
- const optionValue = option[keyName as keyof HttpxRequestOption];
553
- if (optionValue == null || (optionValue instanceof Function && CommonUtil.isNull(optionValue))) {
554
- Reflect.deleteProperty(option, keyName);
555
- return;
556
- }
557
- });
558
- if (CommonUtil.isNull(option.url)) {
559
- throw new TypeError(`Utils.Httpx 参数url不能为空:${option.url}`);
560
- }
561
- return option;
562
- },
563
- /**
564
- * 处理fetch的配置
565
- * @param option 请求配置
566
- */
567
- handleFetchOption(option: Required<HttpxRequestOption>) {
568
- /**
569
- * fetch的请求配置
570
- **/
571
- const fetchRequestOption = <RequestInit>{};
572
- if ((option.method === "GET" || option.method === "HEAD") && option.data != null) {
573
- /* GET 或 HEAD 方法的请求不能包含 body 信息 */
574
- Reflect.deleteProperty(option, "data");
575
- }
576
- /* 中止信号控制器 */
577
- const abortController = new AbortController();
578
- const signal = abortController.signal;
579
- signal.onabort = () => {
580
- option.onabort({
581
- isFetch: true,
582
- responseText: "",
583
- response: null,
584
- readyState: 4,
585
- responseHeaders: "",
586
- status: 0,
587
- statusText: "",
588
- error: "aborted",
589
- });
590
- };
591
- // 设置请求
592
- fetchRequestOption.method = option.method ?? "GET";
593
- // 设置请求头
594
- fetchRequestOption.headers = option.headers;
595
- // 设置请求体
596
- fetchRequestOption.body = option.data as string | FormData;
597
- // 设置跨域
598
- fetchRequestOption.mode = "cors";
599
- // 设置包含
600
- fetchRequestOption.credentials = "include";
601
- // 设置不缓存
602
- fetchRequestOption.cache = "no-cache";
603
- // 设置始终重定向
604
- fetchRequestOption.redirect = "follow";
605
- // 设置referer跨域
606
- fetchRequestOption.referrerPolicy = "origin-when-cross-origin";
607
- // 设置信号中断
608
- fetchRequestOption.signal = signal;
609
- Object.assign(fetchRequestOption, option.fetchInit || {});
610
- return {
611
- fetchOption: option,
612
- fetchRequestOption: fetchRequestOption,
613
- abortController: abortController,
614
- };
615
- },
616
- };
617
- private HttpxResponseCallBack = {
618
- context: this,
619
- /**
620
- * onabort请求被取消-触发
621
- * @param details 配置
622
- * @param resolve promise回调
623
- * @param _reject promise抛出错误回调
624
- * @param argsResult 返回的参数列表
625
- */
626
- async onAbort(
627
- details: Required<HttpxRequestOption>,
628
- resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
629
- _reject: (...args: any[]) => void,
630
- argsResult: any
631
- ) {
632
- // console.log(argsResult);
633
- if (typeof details?.onabort === "function") {
634
- details.onabort.apply(this, argsResult);
635
- } else if (typeof this.context.#defaultRequestOption?.onabort === "function") {
636
- this.context.#defaultRequestOption.onabort.apply(this, argsResult);
637
- }
638
- let response = argsResult;
639
- if (response.length) {
640
- response = response[0];
641
- }
642
- if (
643
- (await this.context.HttpxResponseHook.errorResponseCallBack({
644
- type: "onabort",
645
- error: new Error("request canceled"),
646
- response: null,
647
- details: details,
648
- })) == null
649
- ) {
650
- // reject(new Error("response is intercept with onabort"));
651
- return;
652
- }
653
- resolve({
654
- data: response,
655
- details: details,
656
- msg: "请求被取消",
657
- status: false,
658
- statusCode: -1,
659
- type: "onabort",
660
- });
661
- },
662
- /**
663
- * ontimeout请求超时-触发
664
- * @param details 配置
665
- * @param resolve 回调
666
- * @param reject 抛出错误
667
- * @param argsResult 返回的参数列表
668
- */
669
- async onTimeout(
670
- details: Required<HttpxRequestOption>,
671
- resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
672
- _reject: (...args: any[]) => void,
673
- argsResult: any
674
- ) {
675
- // console.log(argsResult);
676
- if (typeof details?.ontimeout === "function") {
677
- // 执行配置中的ontime回调
678
- details.ontimeout.apply(this, argsResult);
679
- } else if (typeof this.context.#defaultRequestOption?.ontimeout === "function") {
680
- // 执行默认配置的ontime回调
681
- this.context.#defaultRequestOption.ontimeout.apply(this, argsResult);
682
- }
683
- // 获取响应结果
684
- let response = argsResult;
685
- if (response.length) {
686
- response = response[0];
687
- }
688
- // 执行错误回调的钩子
689
- if (
690
- (await this.context.HttpxResponseHook.errorResponseCallBack({
691
- type: "ontimeout",
692
- error: new Error("request timeout"),
693
- response: response,
694
- details: details,
695
- })) == null
696
- ) {
697
- // reject(new Error("response is intercept with ontimeout"));
698
- return;
699
- }
700
- resolve({
701
- data: response,
702
- details: details,
703
- msg: "请求超时",
704
- status: false,
705
- statusCode: 0,
706
- type: "ontimeout",
707
- });
708
- },
709
- /**
710
- * onerror请求异常-触发
711
- * @param details 配置
712
- * @param resolve 回调
713
- * @param _reject 抛出错误
714
- * @param argsResult 返回的参数列表
715
- */
716
- async onError(
717
- details: Required<HttpxRequestOption>,
718
- resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
719
- _reject: (...args: any[]) => void,
720
- argsResult: any
721
- ) {
722
- // console.log(argsResult);
723
- if (typeof details?.onerror === "function") {
724
- details.onerror.apply(this, argsResult);
725
- } else if (typeof this.context.#defaultRequestOption?.onerror === "function") {
726
- this.context.#defaultRequestOption.onerror.apply(this, argsResult);
727
- }
728
- let response = argsResult;
729
- if (response.length) {
730
- response = response[0];
731
- }
732
- if (
733
- (await this.context.HttpxResponseHook.errorResponseCallBack({
734
- type: "onerror",
735
- error: new Error("request error"),
736
- response: response,
737
- details: details,
738
- })) == null
739
- ) {
740
- // reject(new Error("response is intercept with onerror"));
741
- return;
742
- }
743
- resolve({
744
- data: response,
745
- details: details,
746
- msg: "请求异常",
747
- status: false,
748
- statusCode: response["status"],
749
- type: "onerror",
750
- });
751
- },
752
- /**
753
- * onload加载完毕-触发
754
- * @param details 请求的配置
755
- * @param resolve 回调
756
- * @param _reject 抛出错误
757
- * @param argsResult 返回的参数列表
758
- */
759
- async onLoad(
760
- details: Required<HttpxRequestOption>,
761
- resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
762
- _reject: (...args: any[]) => void,
763
- argsResult: any[]
764
- ) {
765
- // console.log(argsResult);
766
- /* X浏览器会因为设置了responseType导致不返回responseText */
767
- const originResponse: HttpxResponseData<HttpxRequestOption> = argsResult[0];
768
- /* responseText为空,response不为空的情况 */
769
- if (CommonUtil.isNull(originResponse["responseText"]) && CommonUtil.isNotNull(originResponse["response"])) {
770
- if (typeof originResponse["response"] === "object") {
771
- TryCatch().run(() => {
772
- originResponse["responseText"] = JSON.stringify(originResponse["response"]);
773
- });
774
- } else {
775
- originResponse["responseText"] = originResponse["response"] as string;
776
- }
777
- }
778
-
779
- /* response为空,responseText不为空的情况 */
780
- if (
781
- originResponse["response"] == null &&
782
- typeof originResponse["responseText"] === "string" &&
783
- originResponse["responseText"].trim() !== ""
784
- ) {
785
- /** 原始的请求text */
786
- const httpxResponseText = originResponse.responseText;
787
- // 自定义个新的response
788
- let httpxResponse: any = httpxResponseText;
789
- if (details.responseType === "json") {
790
- httpxResponse = CommonUtil.toJSON(httpxResponseText);
791
- } else if (details.responseType === "document") {
792
- const parser = new DOMParser();
793
- httpxResponse = parser.parseFromString(httpxResponseText, "text/html");
794
- } else if (details.responseType === "arraybuffer") {
795
- const encoder = new TextEncoder();
796
- const arrayBuffer = encoder.encode(httpxResponseText);
797
- httpxResponse = arrayBuffer;
798
- } else if (details.responseType === "blob") {
799
- const encoder = new TextEncoder();
800
- const arrayBuffer = encoder.encode(httpxResponseText);
801
- httpxResponse = new Blob([arrayBuffer]);
802
- }
803
- // 尝试覆盖原response
804
- try {
805
- const setStatus = Reflect.set(originResponse, "response", httpxResponse);
806
- if (!setStatus) {
807
- console.warn("[Httpx-HttpxCallBack.oonLoad] 覆盖原始 response 失败,尝试添加新的httpxResponse");
808
- try {
809
- Reflect.set(originResponse, "httpxResponse", httpxResponse);
810
- } catch {
811
- console.warn("[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖");
812
- }
813
- }
814
- } catch {
815
- console.warn("[Httpx-HttpxCallBack.oonLoad] 原始 response 无法被覆盖,尝试添加新的httpxResponse");
816
- try {
817
- Reflect.set(originResponse, "httpxResponse", httpxResponse);
818
- } catch {
819
- console.warn("[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖");
820
- }
821
- }
822
- }
823
- /* Stay扩展中没有finalUrl,对应的是responseURL */
824
- const originResponseURL = Reflect.get(originResponse, "responseURL");
825
- if (originResponse["finalUrl"] == null && originResponseURL != null) {
826
- Reflect.set(originResponse, "finalUrl", originResponseURL);
827
- }
828
-
829
- /* 状态码2xx都是成功的 */
830
- if (Math.floor(originResponse.status / 100) === 2) {
831
- if ((await this.context.HttpxResponseHook.successResponseCallBack(originResponse, details)) == null) {
832
- // reject(new Error("response is intercept with onloada"));
833
- return;
834
- }
835
- resolve({
836
- data: originResponse,
837
- details: details,
838
- msg: "请求成功",
839
- status: true,
840
- statusCode: originResponse.status,
841
- type: "onload",
842
- });
843
- } else {
844
- this.context.HttpxResponseCallBack.onError(details, resolve, _reject, argsResult);
845
- }
846
- },
847
- /**
848
- * onloadstart请求开始-触发
849
- * @param details 配置
850
- * @param argsResult 返回的参数列表
851
- */
852
- onLoadStart(details: Required<HttpxRequestOption>, argsResult: any[]) {
853
- // console.log(argsResult);
854
- if (typeof details?.onloadstart === "function") {
855
- details.onloadstart.apply(this, argsResult);
856
- } else if (typeof this.context.#defaultRequestOption?.onloadstart === "function") {
857
- this.context.#defaultRequestOption.onloadstart.apply(this, argsResult);
858
- }
859
- },
860
- /**
861
- * onreadystatechange准备状态改变-触发
862
- * @param details 配置
863
- * @param argsResult 返回的参数列表
864
- */
865
- onReadyStateChange(details: Required<HttpxRequestOption>, argsResult: any[]) {
866
- // console.log(argsResult);
867
- if (typeof details?.onreadystatechange === "function") {
868
- details.onreadystatechange.apply(this, argsResult);
869
- } else if (typeof this.context.#defaultRequestOption?.onreadystatechange === "function") {
870
- this.context.#defaultRequestOption.onreadystatechange.apply(this, argsResult);
871
- }
872
- },
873
- /**
874
- * onprogress上传进度-触发
875
- * @param details 配置
876
- * @param argsResult 返回的参数列表
877
- */
878
- onProgress(details: Required<HttpxRequestOption>, argsResult: any[]) {
879
- // console.log(argsResult);
880
- if (typeof details?.onprogress === "function") {
881
- details.onprogress.apply(this, argsResult);
882
- } else if (typeof this.context.#defaultRequestOption?.onprogress === "function") {
883
- this.context.#defaultRequestOption.onprogress.apply(this, argsResult);
884
- }
885
- },
886
- };
887
- private HttpxRequest = {
888
- context: this,
889
- /**
890
- * 发送请求
891
- * @param details
892
- */
893
- async request(details: Required<HttpxRequestOption>) {
894
- if (this.context.#defaultInitOption.logDetails) {
895
- console.log("[Httpx-HttpxRequest.request] 请求前的配置👇", details);
896
- }
897
- if (typeof this.context.HttpxRequestHook.beforeRequestCallBack === "function") {
898
- const hookResult = await this.context.HttpxRequestHook.beforeRequestCallBack(details);
899
- if (hookResult == null) {
900
- return;
901
- }
902
- }
903
- if (details.fetch) {
904
- // 使用fetch请求
905
- const {
906
- fetchOption: fetchOption,
907
- fetchRequestOption: fetchRequestOption,
908
- abortController,
909
- } = this.context.HttpxRequestOption.handleFetchOption(details);
910
- return this.fetch(fetchOption, fetchRequestOption, abortController);
911
- } else {
912
- // 使用GM_xmlHttpRequest请求
913
- return this.xmlHttpRequest(details);
914
- }
915
- },
916
- /**
917
- * 使用油猴函数GM_xmlhttpRequest发送请求
918
- * @param details
919
- */
920
- xmlHttpRequest(details: Required<HttpxRequestOption>) {
921
- return this.context.GM_Api.xmlHttpRequest!(details) as {
922
- abort: () => void;
923
- };
924
- },
925
- /**
926
- * 使用fetch发送请求
927
- * @param option
928
- * @param fetchRequestOption
929
- * @param abortController
930
- */
931
- fetch(option: Required<HttpxRequestOption>, fetchRequestOption: RequestInit, abortController: AbortController) {
932
- fetch(option.url, fetchRequestOption)
933
- .then(async (fetchResponse) => {
934
- /** 自定义的response */
935
- const httpxResponse: HttpxResponseData<HttpxRequestOption> = {
936
- isFetch: true,
937
- finalUrl: fetchResponse.url,
938
- readyState: 4,
939
- status: fetchResponse.status,
940
- statusText: fetchResponse.statusText,
941
- response: "",
942
- responseFetchHeaders: fetchResponse.headers,
943
- responseHeaders: "",
944
- responseText: "",
945
- responseType: option.responseType,
946
- responseXML: void 0,
947
- };
948
- Object.assign(httpxResponse, option.context || {});
949
-
950
- // 把headers转为字符串
951
- fetchResponse.headers.forEach((value, key) => {
952
- httpxResponse.responseHeaders += `${key}: ${value}\n`;
953
- });
954
-
955
- /** 请求返回的类型 */
956
- const fetchResponseType = fetchResponse.headers.get("Content-Type");
957
-
958
- /* 如果需要stream,且获取到的是stream,那直接返回 */
959
- if (
960
- option.responseType === "stream" ||
961
- (fetchResponse.headers.has("Content-Type") &&
962
- fetchResponse.headers.get("Content-Type")!.includes("text/event-stream"))
963
- ) {
964
- Reflect.set(httpxResponse, "isStream", true);
965
- Reflect.set(httpxResponse, "response", fetchResponse.body);
966
- Reflect.deleteProperty(httpxResponse, "responseText");
967
- Reflect.deleteProperty(httpxResponse, "responseXML");
968
- option.onload(httpxResponse);
969
- return;
970
- }
971
-
972
- /** 响应 */
973
- let response: any = "";
974
- /** 响应字符串 */
975
- let responseText: string = "";
976
- /** 响应xml文档 */
977
- let responseXML: XMLDocument | string = "";
978
- /** 先获取二进制数据 */
979
- const arrayBuffer = await fetchResponse.arrayBuffer();
980
-
981
- /** 数据编码 */
982
- let encoding = "utf-8";
983
- if (fetchResponse.headers.has("Content-Type")) {
984
- const charsetMatched = fetchResponse.headers.get("Content-Type")?.match(/charset=(.+)/);
985
- if (charsetMatched) {
986
- encoding = charsetMatched[1];
987
- encoding = encoding.toLowerCase();
988
- }
989
- }
990
- // Failed to construct 'TextDecoder': The encoding label provided ('"UTF-8"') is invalid.
991
- // 去除引号
992
- encoding = encoding.replace(/('|")/gi, "");
993
- // 编码
994
- const textDecoder = new TextDecoder(encoding);
995
- responseText = textDecoder.decode(arrayBuffer);
996
- response = responseText;
997
-
998
- if (option.responseType === "arraybuffer") {
999
- // response返回格式是二进制流
1000
- response = arrayBuffer;
1001
- } else if (option.responseType === "blob") {
1002
- // response返回格式是blob
1003
- response = new Blob([arrayBuffer]);
1004
- } else if (
1005
- option.responseType === "json" ||
1006
- (typeof fetchResponseType === "string" && fetchResponseType.includes("application/json"))
1007
- ) {
1008
- // response返回格式是JSON格式
1009
- response = CommonUtil.toJSON(responseText);
1010
- } else if (option.responseType === "document" || option.responseType == null) {
1011
- // response返回格式是文档格式
1012
- const parser = new DOMParser();
1013
- response = parser.parseFromString(responseText, "text/html");
1014
- }
1015
- // 转为XML结构
1016
- const parser = new DOMParser();
1017
- responseXML = parser.parseFromString(responseText, "text/xml");
1018
-
1019
- httpxResponse.response = response;
1020
- httpxResponse.responseText = responseText;
1021
- httpxResponse.responseXML = responseXML;
1022
-
1023
- // 执行回调
1024
- option.onload(httpxResponse);
1025
- })
1026
- .catch((error: any) => {
1027
- if (error.name === "AbortError") {
1028
- return;
1029
- }
1030
- option.onerror({
1031
- isFetch: true,
1032
- finalUrl: option.url,
1033
- readyState: 4,
1034
- status: 0,
1035
- statusText: "",
1036
- responseHeaders: "",
1037
- responseText: "",
1038
- error: error,
1039
- });
1040
- });
1041
- option.onloadstart({
1042
- isFetch: true,
1043
- finalUrl: option.url,
1044
- readyState: 1,
1045
- responseHeaders: "",
1046
- responseText: "",
1047
- status: 0,
1048
- statusText: "",
1049
- });
1050
- return {
1051
- abort() {
1052
- abortController.abort();
1053
- },
1054
- };
1055
- },
1056
- };
1057
- /**
1058
- * 默认配置
1059
- */
1060
- #defaultRequestOption = <HttpxRequestOption>{
1061
- url: void 0 as undefined | string,
1062
- timeout: 5000,
1063
- async: false,
1064
- responseType: void 0,
1065
- headers: void 0,
1066
- data: void 0,
1067
- redirect: void 0,
1068
- cookie: void 0,
1069
- cookiePartition: void 0,
1070
- binary: void 0,
1071
- nocache: void 0,
1072
- revalidate: void 0,
1073
- context: void 0,
1074
- overrideMimeType: void 0,
1075
- anonymous: void 0,
1076
- fetch: void 0,
1077
- fetchInit: void 0,
1078
- allowInterceptConfig: {
1079
- beforeRequest: true,
1080
- afterResponseSuccess: true,
1081
- afterResponseError: true,
1082
- },
1083
- user: void 0,
1084
- password: void 0,
1085
- onabort() {},
1086
- onerror() {},
1087
- ontimeout() {},
1088
- onloadstart() {},
1089
- onreadystatechange() {},
1090
- onprogress() {},
1091
- };
1092
- /**
1093
- * 实例化的默认配置
1094
- */
1095
- #defaultInitOption = {
1096
- /**
1097
- * `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
1098
- */
1099
- baseURL: void 0 as undefined | string,
1100
- /**
1101
- * 当前使用请求时,输出请求的配置,一般用于DEBUG|DEV
1102
- */
1103
- logDetails: false,
1104
- };
1105
- /**
1106
- * 实例化
1107
- * @param option 初始化配置
1108
- */
1109
- constructor(option: Partial<HttpxInitOption> = {}) {
1110
- if (typeof option.xmlHttpRequest !== "function") {
1111
- console.warn(
1112
- "[Httpx-constructor] 未传入GM_xmlhttpRequest函数或传入的GM_xmlhttpRequest不是Function,将默认使用window.fetch"
1113
- );
1114
- }
1115
- CommonUtil.coverObjectFunctionThis(this);
1116
- this.interceptors.request.context = this;
1117
- this.interceptors.response.context = this;
1118
- this.config(option);
1119
- }
1120
- /**
1121
- * 覆盖当前配置
1122
- * @param option
1123
- */
1124
- config(option: Partial<HttpxInitOption> = {}) {
1125
- if (typeof option.xmlHttpRequest === "function") {
1126
- this.GM_Api.xmlHttpRequest = option.xmlHttpRequest;
1127
- }
1128
- this.#defaultRequestOption = CommonUtil.assign(this.#defaultRequestOption, option);
1129
- this.#defaultInitOption = CommonUtil.assign(this.#defaultInitOption, option);
1130
- }
1131
- /**
1132
- * 拦截器
1133
- */
1134
- interceptors = {
1135
- /**
1136
- * 请求拦截器
1137
- */
1138
- request: {
1139
- context: null as any as Httpx,
1140
- /**
1141
- * 添加拦截器
1142
- * @param fn 设置的请求前回调函数,如果返回配置,则使用返回的配置,如果返回null|undefined,则阻止请求
1143
- */
1144
- use(fn: <T extends Required<HttpxRequestOption>>(details: T) => void | T | Promise<void | T>) {
1145
- if (typeof fn !== "function") {
1146
- console.warn("[Httpx-interceptors-request] 请传入拦截器函数");
1147
- return;
1148
- }
1149
- return this.context.HttpxRequestHook.add(fn);
1150
- },
1151
- /**
1152
- * 移除拦截器
1153
- * @param id 通过use返回的id
1154
- */
1155
- eject(id: string) {
1156
- return this.context.HttpxRequestHook.delete(id);
1157
- },
1158
- /**
1159
- * 移除所有拦截器
1160
- */
1161
- ejectAll() {
1162
- this.context.HttpxRequestHook.clearAll();
1163
- },
1164
- },
1165
- /**
1166
- * 响应拦截器
1167
- */
1168
- response: {
1169
- context: null as any as Httpx,
1170
- /**
1171
- * 添加拦截器
1172
- * @param successFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
1173
- * + 2xx 范围内的状态码都会触发该函数
1174
- * @param errorFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
1175
- * + 超出 2xx 范围的状态码都会触发该函数
1176
- */
1177
- use(
1178
- successFn?: <T extends HttpxResponseData<HttpxRequestOption>>(
1179
- response: T,
1180
- details: HttpxRequestOption
1181
- ) => void | T,
1182
- errorFn?: <T extends HttpxHookErrorData>(data: T) => void | T | Promise<void | T>
1183
- ) {
1184
- if (typeof successFn !== "function" && typeof errorFn !== "function") {
1185
- console.warn("[Httpx-interceptors-response] 必须传入一个拦截器函数");
1186
- return;
1187
- }
1188
- return this.context.HttpxResponseHook.add(successFn!, errorFn!);
1189
- },
1190
- /**
1191
- * 移除拦截器
1192
- * @param id 通过use返回的id
1193
- */
1194
- eject(id: string) {
1195
- return this.context.HttpxResponseHook.delete(id);
1196
- },
1197
- /**
1198
- * 移除所有拦截器
1199
- */
1200
- ejectAll() {
1201
- this.context.HttpxResponseHook.clearAll();
1202
- },
1203
- },
1204
- };
1205
- /**
1206
- * 修改xmlHttpRequest
1207
- * @param httpRequest 网络请求函数
1208
- */
1209
- setXMLHttpRequest(httpRequest: (...args: any[]) => any) {
1210
- this.GM_Api.xmlHttpRequest = httpRequest;
1211
- }
1212
- /**
1213
- * GET 请求
1214
- * @param details 配置
1215
- */
1216
- get<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1217
- /**
1218
- * GET 请求
1219
- * @param url 请求的url
1220
- * @param details 配置
1221
- */
1222
- get<T extends Omit<HttpxRequestOption, "url">>(
1223
- url: string,
1224
- details?: T
1225
- ): HttpxPromise<
1226
- HttpxResponse<
1227
- T & {
1228
- /**
1229
- * 请求的url
1230
- */
1231
- url: string;
1232
- }
1233
- >
1234
- >;
1235
- /**
1236
- * GET 请求
1237
- * @param url 请求的url
1238
- * @param details 配置
1239
- */
1240
- get(...args: (string | HttpxRequestOption)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1241
- const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1242
- useRequestOption.method = "GET";
1243
- return this.request(useRequestOption, (option) => {
1244
- Reflect.deleteProperty(option, "onprogress");
1245
- });
1246
- }
1247
- /**
1248
- * POST 请求
1249
- * @param details 配置
1250
- */
1251
- post<T extends HttpxRequestOption>(details?: T): HttpxPromise<HttpxResponse<T>>;
1252
- /**
1253
- * POST 请求
1254
- * @param url 请求的url
1255
- * @param details 配置
1256
- */
1257
- post<T extends Omit<HttpxRequestOption, "url">>(
1258
- url: string,
1259
- details?: T
1260
- ): HttpxPromise<
1261
- HttpxResponse<
1262
- T & {
1263
- /**
1264
- * 请求的url
1265
- */
1266
- url: string;
1267
- }
1268
- >
1269
- >;
1270
- /**
1271
- * POST 请求
1272
- */
1273
- post(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1274
- const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1275
- useRequestOption.method = "POST";
1276
- return this.request(useRequestOption);
1277
- }
1278
- /**
1279
- * HEAD 请求
1280
- * @param details 配置
1281
- */
1282
- head<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1283
- /**
1284
- * HEAD 请求
1285
- * @param url 请求的url
1286
- * @param details 配置
1287
- */
1288
- head<T extends Omit<HttpxRequestOption, "url">>(
1289
- url: string,
1290
- details?: T
1291
- ): HttpxPromise<
1292
- HttpxResponse<
1293
- T & {
1294
- /**
1295
- * 请求的url
1296
- */
1297
- url: string;
1298
- }
1299
- >
1300
- >;
1301
- /**
1302
- * HEAD 请求
1303
- */
1304
- head(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1305
- const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1306
- useRequestOption.method = "HEAD";
1307
- return this.request(useRequestOption, (option) => {
1308
- Reflect.deleteProperty(option, "onprogress");
1309
- });
1310
- }
1311
- /**
1312
- * OPTIONS 请求
1313
- * @param details 配置
1314
- */
1315
- options<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1316
- /**
1317
- * OPTIONS 请求
1318
- * @param url 请求的url
1319
- * @param details 配置
1320
- */
1321
- options<T extends Omit<HttpxRequestOption, "url">>(
1322
- url: string,
1323
- details?: T
1324
- ): HttpxPromise<
1325
- HttpxResponse<
1326
- T & {
1327
- /**
1328
- * 请求的url
1329
- */
1330
- url: string;
1331
- }
1332
- >
1333
- >;
1334
- /**
1335
- * OPTIONS 请求
1336
- */
1337
- options(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1338
- const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1339
- useRequestOption.method = "OPTIONS";
1340
- return this.request(useRequestOption, (option) => {
1341
- Reflect.deleteProperty(option, "onprogress");
1342
- });
1343
- }
1344
- /**
1345
- * DELETE 请求
1346
- * @param details 配置
1347
- */
1348
- delete<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1349
- /**
1350
- * DELETE 请求
1351
- * @param url 请求的url
1352
- * @param details 配置
1353
- */
1354
- delete<T extends Omit<HttpxRequestOption, "url">>(
1355
- url: string,
1356
- details?: T
1357
- ): HttpxPromise<
1358
- HttpxResponse<
1359
- T & {
1360
- /**
1361
- * 请求的url
1362
- */
1363
- url: string;
1364
- }
1365
- >
1366
- >;
1367
- /**
1368
- * DELETE 请求
1369
- */
1370
- delete(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1371
- const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1372
- useRequestOption.method = "DELETE";
1373
- return this.request(useRequestOption, (option) => {
1374
- Reflect.deleteProperty(option, "onprogress");
1375
- });
1376
- }
1377
- /**
1378
- * PUT 请求
1379
- * @param details 配置
1380
- */
1381
- put<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1382
- /**
1383
- * PUT 请求
1384
- * @param url 请求的url
1385
- * @param details 配置
1386
- */
1387
- put<T extends Omit<HttpxRequestOption, "url">>(
1388
- url: string,
1389
- details?: T
1390
- ): HttpxPromise<
1391
- HttpxResponse<
1392
- T & {
1393
- /**
1394
- * 请求的url
1395
- */
1396
- url: string;
1397
- }
1398
- >
1399
- >;
1400
- /**
1401
- * PUT 请求
1402
- */
1403
- put(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1404
- const userRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1405
- userRequestOption.method = "PUT";
1406
- return this.request(userRequestOption);
1407
- }
1408
- /**
1409
- * 发送请求
1410
- * @param details 配置
1411
- * @param beforeRequestOption 处理请求前的配置
1412
- */
1413
- request<T extends HttpxRequestOption>(
1414
- details: T,
1415
- beforeRequestOption?: (option: Required<T | HttpxRequestOption>) => void
1416
- ): HttpxPromise<HttpxResponse<T>> {
1417
- // 对请求的参数进行合并处理
1418
- const userRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(details);
1419
-
1420
- /**
1421
- * 取消请求
1422
- */
1423
- let abortFn: ((...args: any[]) => any) | null = null;
1424
- const promise = new globalThis.Promise<HttpxResponse<HttpxRequestOption>>(async (resolve, reject) => {
1425
- // 请求配置
1426
- let requestOption = this.HttpxRequestOption.getRequestOption(
1427
- userRequestOption.method!,
1428
- userRequestOption,
1429
- (resultOption) => {
1430
- resolve(resultOption);
1431
- },
1432
- (...args: any[]) => {
1433
- reject(...args);
1434
- }
1435
- );
1436
- requestOption = this.HttpxRequestOption.removeRequestNullOption(requestOption);
1437
- if (typeof beforeRequestOption === "function") {
1438
- beforeRequestOption(requestOption as Required<T>);
1439
- }
1440
-
1441
- // 处理重试逻辑
1442
- const requestResult = await this.HttpxRequest.request(requestOption);
1443
- if (requestResult != null && typeof requestResult.abort === "function") {
1444
- abortFn = () => {
1445
- // 取消请求
1446
- requestResult.abort();
1447
- };
1448
- }
1449
- }) as HttpxPromise<HttpxResponse<T>>;
1450
- promise.abort = () => {
1451
- if (typeof abortFn === "function") {
1452
- abortFn();
1453
- }
1454
- };
1455
- return promise;
1456
- }
1457
- }
1
+ import { CommonUtil } from "./CommonUtil";
2
+ import { TryCatch } from "./TryCatch";
3
+ import type {
4
+ HttpxAllowInterceptConfig,
5
+ HttpxHookErrorData,
6
+ HttpxMethod,
7
+ HttpxRequestOption,
8
+ HttpxResponse,
9
+ HttpxResponseData,
10
+ HttpxPromise,
11
+ HttpxInitOption,
12
+ } from "./types/Httpx";
13
+ import { GenerateUUID } from "./UtilsCommon";
14
+
15
+ export class Httpx {
16
+ private GM_Api = {
17
+ xmlHttpRequest: null as ((...args: any[]) => any) | null,
18
+ };
19
+ private HttpxRequestHook = {
20
+ /**
21
+ * @private
22
+ */
23
+ $config: {
24
+ configList: <
25
+ {
26
+ id: string;
27
+ fn: ((...args: any[]) => any) | Promise<(...args: any[]) => any>;
28
+ }[]
29
+ >[],
30
+ },
31
+ /**
32
+ * 发送请求前的回调
33
+ * 如果返回false则阻止本次返回
34
+ * @param details 当前的请求配置
35
+ * @private
36
+ */
37
+ async beforeRequestCallBack(details: HttpxRequestOption) {
38
+ if (typeof details.allowInterceptConfig === "boolean") {
39
+ if (!details.allowInterceptConfig) {
40
+ // 不允许拦截
41
+ return details;
42
+ }
43
+ } else {
44
+ if (details.allowInterceptConfig != null) {
45
+ // 配置存在
46
+ // 细分处理是否拦截
47
+ if (
48
+ typeof details.allowInterceptConfig.beforeRequest === "boolean" &&
49
+ !details.allowInterceptConfig.beforeRequest
50
+ ) {
51
+ // 设置了禁止拦截
52
+ return details;
53
+ }
54
+ } else {
55
+ // 配置不存在
56
+ // 默认允许拦截
57
+ }
58
+ }
59
+ for (let index = 0; index < this.$config.configList.length; index++) {
60
+ const item = this.$config.configList[index];
61
+ if (typeof item.fn === "function") {
62
+ const result = await item.fn(details);
63
+ if (result == null) {
64
+ return;
65
+ }
66
+ }
67
+ }
68
+ return details;
69
+ },
70
+ /**
71
+ * 添加请求前的回调处理配置
72
+ */
73
+ add(fn: (...args: any[]) => any) {
74
+ if (typeof fn === "function") {
75
+ const uuid = GenerateUUID();
76
+ this.$config.configList.push({
77
+ id: uuid,
78
+ fn: fn,
79
+ });
80
+ return uuid;
81
+ } else {
82
+ console.warn("[Httpx-HttpxRequestHook.addBeforeRequestCallBack] fn is not a function");
83
+ }
84
+ },
85
+ /**
86
+ * 删除请求前的回调处理配置
87
+ * @param id
88
+ */
89
+ delete(id: string) {
90
+ if (typeof id === "string") {
91
+ const findIndex = this.$config.configList.findIndex((item) => item.id === id);
92
+ if (findIndex !== -1) {
93
+ this.$config.configList.splice(findIndex, 1);
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ },
99
+ /**
100
+ * 清空设置的请求前的回调处理配置
101
+ */
102
+ clearAll() {
103
+ this.$config.configList = [];
104
+ },
105
+ };
106
+ private HttpxResponseHook = {
107
+ /**
108
+ * @private
109
+ */
110
+ $config: {
111
+ configList: <
112
+ {
113
+ id: string;
114
+ successFn?: (...args: any[]) => any | Promise<(...args: any[]) => any>;
115
+ errorFn?: (...args: any[]) => any | Promise<(...args: any[]) => any>;
116
+ }[]
117
+ >[],
118
+ },
119
+ /**
120
+ * 成功的回调
121
+ * @param response 响应
122
+ * @param details 请求的配置
123
+ */
124
+ async successResponseCallBack(response: HttpxResponseData<HttpxRequestOption>, details: HttpxRequestOption) {
125
+ if (typeof details.allowInterceptConfig === "boolean") {
126
+ if (!details.allowInterceptConfig) {
127
+ // 不允许拦截
128
+ return details;
129
+ }
130
+ } else {
131
+ if (details.allowInterceptConfig != null) {
132
+ // 配置存在
133
+ // 细分处理是否拦截
134
+ if (
135
+ typeof details.allowInterceptConfig.afterResponseSuccess === "boolean" &&
136
+ !details.allowInterceptConfig.afterResponseSuccess
137
+ ) {
138
+ // 设置了禁止拦截
139
+ return details;
140
+ }
141
+ } else {
142
+ // 配置不存在
143
+ // 默认允许拦截
144
+ }
145
+ }
146
+ for (let index = 0; index < this.$config.configList.length; index++) {
147
+ const item = this.$config.configList[index];
148
+ if (typeof item.successFn === "function") {
149
+ const result = await item.successFn(response, details);
150
+ if (result == null) {
151
+ return;
152
+ }
153
+ }
154
+ }
155
+ return response;
156
+ },
157
+ /**
158
+ * 失败的回调
159
+ * @param data 配置
160
+ * @returns
161
+ * 返回null|undefined就是拦截掉了
162
+ */
163
+ async errorResponseCallBack<T extends HttpxHookErrorData>(data: T) {
164
+ if (typeof data.details.allowInterceptConfig === "boolean") {
165
+ if (!data.details.allowInterceptConfig) {
166
+ // 不允许拦截
167
+ return data;
168
+ }
169
+ } else {
170
+ if (data.details.allowInterceptConfig != null) {
171
+ // 配置存在
172
+ // 细分处理是否拦截
173
+ if (
174
+ typeof data.details.allowInterceptConfig.afterResponseError === "boolean" &&
175
+ !data.details.allowInterceptConfig.afterResponseError
176
+ ) {
177
+ // 设置了禁止拦截
178
+ return data;
179
+ }
180
+ } else {
181
+ // 配置不存在
182
+ // 默认允许拦截
183
+ }
184
+ }
185
+ for (let index = 0; index < this.$config.configList.length; index++) {
186
+ const item = this.$config.configList[index];
187
+ if (typeof item.errorFn === "function") {
188
+ const result = await item.errorFn(data);
189
+ if (result == null) {
190
+ return;
191
+ }
192
+ }
193
+ }
194
+ return data;
195
+ },
196
+ /**
197
+ * 添加请求前的回调处理配置
198
+ */
199
+ add(successFn?: (...args: any[]) => any, errorFn?: (...args: any[]) => any) {
200
+ const id = GenerateUUID();
201
+ this.$config.configList.push({
202
+ id: id,
203
+ successFn: successFn,
204
+ errorFn: errorFn,
205
+ });
206
+ return id;
207
+ },
208
+ /**
209
+ * 删除请求前的回调处理配置
210
+ * @param id
211
+ */
212
+ delete(id: string) {
213
+ if (typeof id === "string") {
214
+ const findIndex = this.$config.configList.findIndex((item) => item.id === id);
215
+ if (findIndex !== -1) {
216
+ this.$config.configList.splice(findIndex, 1);
217
+ return true;
218
+ }
219
+ }
220
+ return false;
221
+ },
222
+ /**
223
+ * 清空设置的请求前的回调处理配置
224
+ */
225
+ clearAll() {
226
+ this.$config.configList = [];
227
+ },
228
+ };
229
+ private HttpxRequestOption = {
230
+ context: this,
231
+ /**
232
+ * 对请求的参数进行合并处理
233
+ */
234
+ handleBeforeRequestOptionArgs(...args: (HttpxRequestOption | string)[]) {
235
+ const option: HttpxRequestOption = {
236
+ url: void 0 as any as string,
237
+ };
238
+ if (typeof args[0] === "string") {
239
+ /* 传入的是url,转为配置 */
240
+ const url = args[0];
241
+ option.url = url;
242
+ if (typeof args[1] === "object") {
243
+ /* 处理第二个参数details */
244
+ const optionArg = args[1];
245
+ CommonUtil.assign(option, optionArg, true);
246
+ option.url = url;
247
+ }
248
+ } else {
249
+ /* 传入的是配置 */
250
+ const optionArg = args[0];
251
+ CommonUtil.assign(option, optionArg, true);
252
+ }
253
+ return option;
254
+ },
255
+ /**
256
+ * 获取请求配置
257
+ * @param method 当前请求方法,默认get
258
+ * @param userRequestOption 用户的请求配置
259
+ * @param resolve promise回调
260
+ * @param reject promise抛出错误回调
261
+ */
262
+ getRequestOption(
263
+ method: HttpxMethod,
264
+ userRequestOption: HttpxRequestOption,
265
+ resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
266
+ reject: (...args: any[]) => void
267
+ ) {
268
+ const that = this;
269
+ let url = userRequestOption.url || this.context.#defaultRequestOption.url;
270
+ if (typeof url === "string") {
271
+ // 去除左右空格
272
+ url = url.trim();
273
+ if (url.startsWith("http://") || url.startsWith("https://")) {
274
+ // 标准的http请求
275
+ } else {
276
+ if (typeof this.context.#defaultInitOption.baseURL === "string") {
277
+ // 设置了基础域
278
+ url = this.context.#defaultInitOption.baseURL + url;
279
+ }
280
+ }
281
+ }
282
+ const requestOption = <Required<HttpxRequestOption>>{
283
+ url: url,
284
+ method: (method || "GET").toString().toUpperCase().trim(),
285
+ timeout: userRequestOption.timeout || this.context.#defaultRequestOption.timeout,
286
+ responseType: userRequestOption.responseType || this.context.#defaultRequestOption.responseType,
287
+ /* 对象使用深拷贝 */
288
+ headers: CommonUtil.deepClone(this.context.#defaultRequestOption.headers),
289
+ data: userRequestOption.data || this.context.#defaultRequestOption.data,
290
+ redirect: userRequestOption.redirect || this.context.#defaultRequestOption.redirect,
291
+ cookie: userRequestOption.cookie || this.context.#defaultRequestOption.cookie,
292
+ cookiePartition: userRequestOption.cookiePartition || this.context.#defaultRequestOption.cookiePartition,
293
+ binary: userRequestOption.binary || this.context.#defaultRequestOption.binary,
294
+ nocache: userRequestOption.nocache || this.context.#defaultRequestOption.nocache,
295
+ revalidate: userRequestOption.revalidate || this.context.#defaultRequestOption.revalidate,
296
+ /* 对象使用深拷贝 */
297
+ context: CommonUtil.deepClone(userRequestOption.context || this.context.#defaultRequestOption.context),
298
+ overrideMimeType: userRequestOption.overrideMimeType || this.context.#defaultRequestOption.overrideMimeType,
299
+ anonymous: userRequestOption.anonymous || this.context.#defaultRequestOption.anonymous,
300
+ fetch: userRequestOption.fetch || this.context.#defaultRequestOption.fetch,
301
+ /* 对象使用深拷贝 */
302
+ fetchInit: CommonUtil.deepClone(this.context.#defaultRequestOption.fetchInit),
303
+ allowInterceptConfig: {
304
+ beforeRequest: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
305
+ .beforeRequest,
306
+ afterResponseSuccess: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
307
+ .afterResponseSuccess,
308
+ afterResponseError: (this.context.#defaultRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig)
309
+ .afterResponseError,
310
+ },
311
+ user: userRequestOption.user || this.context.#defaultRequestOption.user,
312
+ password: userRequestOption.password || this.context.#defaultRequestOption.password,
313
+ onabort(...args) {
314
+ that.context.HttpxResponseCallBack.onAbort(
315
+ userRequestOption as Required<HttpxRequestOption>,
316
+ resolve,
317
+ reject,
318
+ args
319
+ );
320
+ },
321
+ onerror(...args) {
322
+ that.context.HttpxResponseCallBack.onError(
323
+ userRequestOption as Required<HttpxRequestOption>,
324
+ resolve,
325
+ reject,
326
+ args
327
+ );
328
+ },
329
+ onloadstart(...args) {
330
+ that.context.HttpxResponseCallBack.onLoadStart(userRequestOption as Required<HttpxRequestOption>, args);
331
+ },
332
+ onprogress(...args) {
333
+ that.context.HttpxResponseCallBack.onProgress(userRequestOption as Required<HttpxRequestOption>, args);
334
+ },
335
+ onreadystatechange(...args) {
336
+ that.context.HttpxResponseCallBack.onReadyStateChange(
337
+ userRequestOption as Required<HttpxRequestOption>,
338
+ args
339
+ );
340
+ },
341
+ ontimeout(...args) {
342
+ that.context.HttpxResponseCallBack.onTimeout(
343
+ userRequestOption as Required<HttpxRequestOption>,
344
+ resolve,
345
+ reject,
346
+ args
347
+ );
348
+ },
349
+ onload(...args) {
350
+ that.context.HttpxResponseCallBack.onLoad(
351
+ userRequestOption as Required<HttpxRequestOption>,
352
+ resolve,
353
+ reject,
354
+ args
355
+ );
356
+ },
357
+ };
358
+ // 补全allowInterceptConfig参数
359
+ if (typeof userRequestOption.allowInterceptConfig === "boolean") {
360
+ const allowInterceptConfigKeys = Object.keys(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig);
361
+ allowInterceptConfigKeys.forEach((keyName) => {
362
+ Reflect.set(
363
+ requestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
364
+ keyName,
365
+ userRequestOption.allowInterceptConfig
366
+ );
367
+ });
368
+ } else {
369
+ if (
370
+ typeof userRequestOption.allowInterceptConfig === "object" &&
371
+ userRequestOption.allowInterceptConfig != null
372
+ ) {
373
+ const allowInterceptConfigKeys = Object.keys(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig);
374
+ allowInterceptConfigKeys.forEach((keyName) => {
375
+ const value = Reflect.get(
376
+ userRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
377
+ keyName
378
+ ) as boolean;
379
+ if (
380
+ typeof value === "boolean" &&
381
+ Reflect.has(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig, keyName)
382
+ ) {
383
+ Reflect.set(requestOption.allowInterceptConfig as HttpxAllowInterceptConfig, keyName, value);
384
+ }
385
+ });
386
+ }
387
+ }
388
+ if (typeof this.context.GM_Api.xmlHttpRequest !== "function") {
389
+ // GM函数不存在,强制使用fetch
390
+ requestOption.fetch = true;
391
+ }
392
+ if (typeof requestOption.headers === "object") {
393
+ if (typeof userRequestOption.headers === "object") {
394
+ const headerKeys = Object.keys(requestOption.headers);
395
+ headerKeys.forEach((keyName) => {
396
+ if (keyName in requestOption.headers && userRequestOption!.headers?.[keyName] == null) {
397
+ /* 在默认的header中存在,且设置它新的值为空,那么就是默认的值 */
398
+ Reflect.deleteProperty(requestOption.headers, keyName);
399
+ } else {
400
+ requestOption.headers[keyName] = userRequestOption?.headers?.[keyName];
401
+ }
402
+ });
403
+ } else {
404
+ /* details.headers为空 */
405
+ /* 不做处理 */
406
+ }
407
+ } else {
408
+ /* 默认的headers不是对象,那么就直接使用新的 */
409
+ Reflect.set(requestOption, "headers", userRequestOption.headers);
410
+ }
411
+ if (typeof requestOption.fetchInit === "object") {
412
+ /* 使用assign替换且添加 */
413
+ if (typeof userRequestOption.fetchInit === "object") {
414
+ const fetchInitKeys = Object.keys(requestOption.fetchInit);
415
+ fetchInitKeys.forEach((keyName) => {
416
+ if (keyName in requestOption.fetchInit && Reflect.get(userRequestOption.fetchInit ?? {}, keyName) == null) {
417
+ /* 在默认的fetchInit中存在,且设置它新的值为空,那么就是默认的值 */
418
+ Reflect.deleteProperty(requestOption.fetchInit, keyName);
419
+ } else {
420
+ Reflect.set(requestOption.fetchInit, keyName, Reflect.get(userRequestOption.fetchInit!, keyName));
421
+ }
422
+ });
423
+ }
424
+ } else {
425
+ Reflect.set(requestOption, "fetchInit", userRequestOption.fetchInit);
426
+ }
427
+
428
+ // 处理新的cookiePartition
429
+ if (typeof requestOption.cookiePartition === "object" && requestOption.cookiePartition != null) {
430
+ if (
431
+ Reflect.has(requestOption.cookiePartition, "topLevelSite") &&
432
+ typeof requestOption.cookiePartition.topLevelSite !== "string"
433
+ ) {
434
+ // topLevelSite必须是字符串
435
+ Reflect.deleteProperty(requestOption.cookiePartition, "topLevelSite");
436
+ }
437
+ }
438
+
439
+ /* 完善请求的url */
440
+ try {
441
+ new URL(requestOption.url);
442
+ } catch {
443
+ if (requestOption.url.startsWith("//")) {
444
+ // 补充https:
445
+ requestOption.url = globalThis.location.protocol + requestOption.url;
446
+ } else if (requestOption.url.startsWith("/")) {
447
+ // 补充origin
448
+ requestOption.url = globalThis.location.origin + requestOption.url;
449
+ } else {
450
+ // 补充origin+/
451
+ requestOption.url = `${globalThis.location.origin}/${requestOption.url}`;
452
+ }
453
+ }
454
+
455
+ if (requestOption.fetchInit && !requestOption.fetch) {
456
+ // 清空fetchInit
457
+ Reflect.deleteProperty(requestOption, "fetchInit");
458
+ }
459
+
460
+ // 转换data类型
461
+ try {
462
+ /** 是否对数据进行处理 */
463
+ const processData = userRequestOption.processData ?? true;
464
+ if (requestOption.data != null && processData) {
465
+ const method = requestOption.method;
466
+ if (method === "GET" || method === "HEAD") {
467
+ // GET类型,data如果有,那么需要转为searchParams
468
+ const urlObj = new URL(requestOption.url);
469
+ let urlSearch = "";
470
+ let isHandler = false;
471
+ if (typeof requestOption.data === "string") {
472
+ isHandler = true;
473
+ urlSearch = requestOption.data;
474
+ } else if (typeof requestOption.data === "object") {
475
+ isHandler = true;
476
+ // URLSearchParams参数可以转普通的string:string,包括FormData
477
+ const searchParams = new URLSearchParams(requestOption.data as Record<string, string>);
478
+ urlSearch = searchParams.toString();
479
+ }
480
+ if (isHandler) {
481
+ // GET/HEAD请求不支持data参数
482
+ // 对data进行处理了才可以删除
483
+ Reflect.deleteProperty(requestOption, "data");
484
+ }
485
+ if (urlSearch != "") {
486
+ if (urlObj.search === "") {
487
+ // url没有search参数,直接覆盖
488
+ urlObj.search = urlSearch;
489
+ } else {
490
+ // 有search参数
491
+ if (urlObj.search.endsWith("&")) {
492
+ // xxx=xxx&
493
+ urlObj.search = urlObj.search + urlSearch;
494
+ } else {
495
+ // xxx=xxx&xxx=
496
+ urlObj.search = `${urlObj.search}&${urlSearch}`;
497
+ }
498
+ }
499
+ }
500
+ requestOption.url = urlObj.toString();
501
+ } else if (method === "POST" && requestOption.headers != null) {
502
+ // POST类型,data如果是FormData,那么需要转为string
503
+ const headersKeyList = Object.keys(requestOption.headers);
504
+ const ContentTypeIndex = headersKeyList.findIndex((headerKey) => {
505
+ return (
506
+ headerKey.trim().toLowerCase() === "content-type" &&
507
+ typeof requestOption.headers[headerKey] === "string"
508
+ );
509
+ });
510
+ if (ContentTypeIndex !== -1) {
511
+ const ContentTypeKey = headersKeyList[ContentTypeIndex];
512
+ const ContentType = requestOption.headers[ContentTypeKey] as string;
513
+ // 设置了Content-Type
514
+ if (ContentType.includes("application/json")) {
515
+ // application/json
516
+ if (requestOption.data instanceof FormData) {
517
+ const entries: { [key: string]: any } = {};
518
+ requestOption.data.forEach((value, key) => {
519
+ entries[key] = value;
520
+ });
521
+ requestOption.data = JSON.stringify(entries);
522
+ } else if (typeof requestOption.data === "object") {
523
+ requestOption.data = JSON.stringify(requestOption.data);
524
+ }
525
+ } else if (ContentType.includes("application/x-www-form-urlencoded")) {
526
+ // application/x-www-form-urlencoded
527
+ if (typeof requestOption.data === "object") {
528
+ requestOption.data = new URLSearchParams(requestOption.data as Record<string, string>).toString();
529
+ }
530
+ } else if (ContentType.includes("multipart/form-data")) {
531
+ // multipart/form-data
532
+ if (requestOption.data instanceof FormData) {
533
+ Reflect.deleteProperty(requestOption.headers, ContentTypeKey);
534
+ }
535
+ }
536
+ }
537
+ }
538
+ }
539
+ } catch (error) {
540
+ console.warn("Httpx ==> 转换data参数错误", error);
541
+ }
542
+
543
+ return requestOption;
544
+ },
545
+ /**
546
+ * 处理发送请求的配置,去除值为undefined、空function的值
547
+ * @param option 请求配置
548
+ */
549
+ removeRequestNullOption(option: Required<HttpxRequestOption>): Required<HttpxRequestOption> {
550
+ const optionKeys = Object.keys(option);
551
+ optionKeys.forEach((keyName) => {
552
+ const optionValue = option[keyName as keyof HttpxRequestOption];
553
+ if (optionValue == null || (optionValue instanceof Function && CommonUtil.isNull(optionValue))) {
554
+ Reflect.deleteProperty(option, keyName);
555
+ return;
556
+ }
557
+ });
558
+ if (CommonUtil.isNull(option.url)) {
559
+ throw new TypeError(`Utils.Httpx 参数url不能为空:${option.url}`);
560
+ }
561
+ return option;
562
+ },
563
+ /**
564
+ * 处理fetch的配置
565
+ * @param option 请求配置
566
+ */
567
+ handleFetchOption(option: Required<HttpxRequestOption>) {
568
+ /**
569
+ * fetch的请求配置
570
+ **/
571
+ const fetchRequestOption = <RequestInit>{};
572
+ if ((option.method === "GET" || option.method === "HEAD") && option.data != null) {
573
+ /* GET 或 HEAD 方法的请求不能包含 body 信息 */
574
+ Reflect.deleteProperty(option, "data");
575
+ }
576
+ /* 中止信号控制器 */
577
+ const abortController = new AbortController();
578
+ const signal = abortController.signal;
579
+ signal.onabort = () => {
580
+ option.onabort({
581
+ isFetch: true,
582
+ responseText: "",
583
+ response: null,
584
+ readyState: 4,
585
+ responseHeaders: "",
586
+ status: 0,
587
+ statusText: "",
588
+ error: "aborted",
589
+ });
590
+ };
591
+ // 设置请求
592
+ fetchRequestOption.method = option.method ?? "GET";
593
+ // 设置请求头
594
+ fetchRequestOption.headers = option.headers;
595
+ // 设置请求体
596
+ fetchRequestOption.body = option.data as string | FormData;
597
+ // 设置跨域
598
+ fetchRequestOption.mode = "cors";
599
+ // 设置包含
600
+ fetchRequestOption.credentials = "include";
601
+ // 设置不缓存
602
+ fetchRequestOption.cache = "no-cache";
603
+ // 设置始终重定向
604
+ fetchRequestOption.redirect = "follow";
605
+ // 设置referer跨域
606
+ fetchRequestOption.referrerPolicy = "origin-when-cross-origin";
607
+ // 设置信号中断
608
+ fetchRequestOption.signal = signal;
609
+ Object.assign(fetchRequestOption, option.fetchInit || {});
610
+ return {
611
+ fetchOption: option,
612
+ fetchRequestOption: fetchRequestOption,
613
+ abortController: abortController,
614
+ };
615
+ },
616
+ };
617
+ private HttpxResponseCallBack = {
618
+ context: this,
619
+ /**
620
+ * onabort请求被取消-触发
621
+ * @param details 配置
622
+ * @param resolve promise回调
623
+ * @param _reject promise抛出错误回调
624
+ * @param argsResult 返回的参数列表
625
+ */
626
+ async onAbort(
627
+ details: Required<HttpxRequestOption>,
628
+ resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
629
+ _reject: (...args: any[]) => void,
630
+ argsResult: any
631
+ ) {
632
+ // console.log(argsResult);
633
+ if (typeof details?.onabort === "function") {
634
+ details.onabort.apply(this, argsResult);
635
+ } else if (typeof this.context.#defaultRequestOption?.onabort === "function") {
636
+ this.context.#defaultRequestOption.onabort.apply(this, argsResult);
637
+ }
638
+ let response = argsResult;
639
+ if (response.length) {
640
+ response = response[0];
641
+ }
642
+ if (
643
+ (await this.context.HttpxResponseHook.errorResponseCallBack({
644
+ type: "onabort",
645
+ error: new Error("request canceled"),
646
+ response: null,
647
+ details: details,
648
+ })) == null
649
+ ) {
650
+ // reject(new Error("response is intercept with onabort"));
651
+ return;
652
+ }
653
+ resolve({
654
+ data: response,
655
+ details: details,
656
+ msg: "请求被取消",
657
+ status: false,
658
+ statusCode: -1,
659
+ type: "onabort",
660
+ });
661
+ },
662
+ /**
663
+ * ontimeout请求超时-触发
664
+ * @param details 配置
665
+ * @param resolve 回调
666
+ * @param reject 抛出错误
667
+ * @param argsResult 返回的参数列表
668
+ */
669
+ async onTimeout(
670
+ details: Required<HttpxRequestOption>,
671
+ resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
672
+ _reject: (...args: any[]) => void,
673
+ argsResult: any
674
+ ) {
675
+ // console.log(argsResult);
676
+ if (typeof details?.ontimeout === "function") {
677
+ // 执行配置中的ontime回调
678
+ details.ontimeout.apply(this, argsResult);
679
+ } else if (typeof this.context.#defaultRequestOption?.ontimeout === "function") {
680
+ // 执行默认配置的ontime回调
681
+ this.context.#defaultRequestOption.ontimeout.apply(this, argsResult);
682
+ }
683
+ // 获取响应结果
684
+ let response = argsResult;
685
+ if (response.length) {
686
+ response = response[0];
687
+ }
688
+ // 执行错误回调的钩子
689
+ if (
690
+ (await this.context.HttpxResponseHook.errorResponseCallBack({
691
+ type: "ontimeout",
692
+ error: new Error("request timeout"),
693
+ response: response,
694
+ details: details,
695
+ })) == null
696
+ ) {
697
+ // reject(new Error("response is intercept with ontimeout"));
698
+ return;
699
+ }
700
+ resolve({
701
+ data: response,
702
+ details: details,
703
+ msg: "请求超时",
704
+ status: false,
705
+ statusCode: 0,
706
+ type: "ontimeout",
707
+ });
708
+ },
709
+ /**
710
+ * onerror请求异常-触发
711
+ * @param details 配置
712
+ * @param resolve 回调
713
+ * @param _reject 抛出错误
714
+ * @param argsResult 返回的参数列表
715
+ */
716
+ async onError(
717
+ details: Required<HttpxRequestOption>,
718
+ resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
719
+ _reject: (...args: any[]) => void,
720
+ argsResult: any
721
+ ) {
722
+ // console.log(argsResult);
723
+ if (typeof details?.onerror === "function") {
724
+ details.onerror.apply(this, argsResult);
725
+ } else if (typeof this.context.#defaultRequestOption?.onerror === "function") {
726
+ this.context.#defaultRequestOption.onerror.apply(this, argsResult);
727
+ }
728
+ let response = argsResult;
729
+ if (response.length) {
730
+ response = response[0];
731
+ }
732
+ if (
733
+ (await this.context.HttpxResponseHook.errorResponseCallBack({
734
+ type: "onerror",
735
+ error: new Error("request error"),
736
+ response: response,
737
+ details: details,
738
+ })) == null
739
+ ) {
740
+ // reject(new Error("response is intercept with onerror"));
741
+ return;
742
+ }
743
+ resolve({
744
+ data: response,
745
+ details: details,
746
+ msg: "请求异常",
747
+ status: false,
748
+ statusCode: response["status"],
749
+ type: "onerror",
750
+ });
751
+ },
752
+ /**
753
+ * onload加载完毕-触发
754
+ * @param details 请求的配置
755
+ * @param resolve 回调
756
+ * @param _reject 抛出错误
757
+ * @param argsResult 返回的参数列表
758
+ */
759
+ async onLoad(
760
+ details: Required<HttpxRequestOption>,
761
+ resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
762
+ _reject: (...args: any[]) => void,
763
+ argsResult: any[]
764
+ ) {
765
+ // console.log(argsResult);
766
+ /* X浏览器会因为设置了responseType导致不返回responseText */
767
+ const originResponse: HttpxResponseData<HttpxRequestOption> = argsResult[0];
768
+ /* responseText为空,response不为空的情况 */
769
+ if (CommonUtil.isNull(originResponse["responseText"]) && CommonUtil.isNotNull(originResponse["response"])) {
770
+ if (typeof originResponse["response"] === "object") {
771
+ TryCatch().run(() => {
772
+ originResponse["responseText"] = JSON.stringify(originResponse["response"]);
773
+ });
774
+ } else {
775
+ originResponse["responseText"] = originResponse["response"] as string;
776
+ }
777
+ }
778
+
779
+ /* response为空,responseText不为空的情况 */
780
+ if (
781
+ originResponse["response"] == null &&
782
+ typeof originResponse["responseText"] === "string" &&
783
+ originResponse["responseText"].trim() !== ""
784
+ ) {
785
+ /** 原始的请求text */
786
+ const httpxResponseText = originResponse.responseText;
787
+ // 自定义个新的response
788
+ let httpxResponse: any = httpxResponseText;
789
+ if (details.responseType === "json") {
790
+ httpxResponse = CommonUtil.toJSON(httpxResponseText);
791
+ } else if (details.responseType === "document") {
792
+ const parser = new DOMParser();
793
+ httpxResponse = parser.parseFromString(httpxResponseText, "text/html");
794
+ } else if (details.responseType === "arraybuffer") {
795
+ const encoder = new TextEncoder();
796
+ const arrayBuffer = encoder.encode(httpxResponseText);
797
+ httpxResponse = arrayBuffer;
798
+ } else if (details.responseType === "blob") {
799
+ const encoder = new TextEncoder();
800
+ const arrayBuffer = encoder.encode(httpxResponseText);
801
+ httpxResponse = new Blob([arrayBuffer]);
802
+ }
803
+ // 尝试覆盖原response
804
+ try {
805
+ const setStatus = Reflect.set(originResponse, "response", httpxResponse);
806
+ if (!setStatus) {
807
+ console.warn("[Httpx-HttpxCallBack.oonLoad] 覆盖原始 response 失败,尝试添加新的httpxResponse");
808
+ try {
809
+ Reflect.set(originResponse, "httpxResponse", httpxResponse);
810
+ } catch {
811
+ console.warn("[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖");
812
+ }
813
+ }
814
+ } catch {
815
+ console.warn("[Httpx-HttpxCallBack.oonLoad] 原始 response 无法被覆盖,尝试添加新的httpxResponse");
816
+ try {
817
+ Reflect.set(originResponse, "httpxResponse", httpxResponse);
818
+ } catch {
819
+ console.warn("[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖");
820
+ }
821
+ }
822
+ }
823
+ /* Stay扩展中没有finalUrl,对应的是responseURL */
824
+ const originResponseURL = Reflect.get(originResponse, "responseURL");
825
+ if (originResponse["finalUrl"] == null && originResponseURL != null) {
826
+ Reflect.set(originResponse, "finalUrl", originResponseURL);
827
+ }
828
+
829
+ /* 状态码2xx都是成功的 */
830
+ if (Math.floor(originResponse.status / 100) === 2) {
831
+ if ((await this.context.HttpxResponseHook.successResponseCallBack(originResponse, details)) == null) {
832
+ // reject(new Error("response is intercept with onloada"));
833
+ return;
834
+ }
835
+ resolve({
836
+ data: originResponse,
837
+ details: details,
838
+ msg: "请求成功",
839
+ status: true,
840
+ statusCode: originResponse.status,
841
+ type: "onload",
842
+ });
843
+ } else {
844
+ this.context.HttpxResponseCallBack.onError(details, resolve, _reject, argsResult);
845
+ }
846
+ },
847
+ /**
848
+ * onloadstart请求开始-触发
849
+ * @param details 配置
850
+ * @param argsResult 返回的参数列表
851
+ */
852
+ onLoadStart(details: Required<HttpxRequestOption>, argsResult: any[]) {
853
+ // console.log(argsResult);
854
+ if (typeof details?.onloadstart === "function") {
855
+ details.onloadstart.apply(this, argsResult);
856
+ } else if (typeof this.context.#defaultRequestOption?.onloadstart === "function") {
857
+ this.context.#defaultRequestOption.onloadstart.apply(this, argsResult);
858
+ }
859
+ },
860
+ /**
861
+ * onreadystatechange准备状态改变-触发
862
+ * @param details 配置
863
+ * @param argsResult 返回的参数列表
864
+ */
865
+ onReadyStateChange(details: Required<HttpxRequestOption>, argsResult: any[]) {
866
+ // console.log(argsResult);
867
+ if (typeof details?.onreadystatechange === "function") {
868
+ details.onreadystatechange.apply(this, argsResult);
869
+ } else if (typeof this.context.#defaultRequestOption?.onreadystatechange === "function") {
870
+ this.context.#defaultRequestOption.onreadystatechange.apply(this, argsResult);
871
+ }
872
+ },
873
+ /**
874
+ * onprogress上传进度-触发
875
+ * @param details 配置
876
+ * @param argsResult 返回的参数列表
877
+ */
878
+ onProgress(details: Required<HttpxRequestOption>, argsResult: any[]) {
879
+ // console.log(argsResult);
880
+ if (typeof details?.onprogress === "function") {
881
+ details.onprogress.apply(this, argsResult);
882
+ } else if (typeof this.context.#defaultRequestOption?.onprogress === "function") {
883
+ this.context.#defaultRequestOption.onprogress.apply(this, argsResult);
884
+ }
885
+ },
886
+ };
887
+ private HttpxRequest = {
888
+ context: this,
889
+ /**
890
+ * 发送请求
891
+ * @param details
892
+ */
893
+ async request(details: Required<HttpxRequestOption>) {
894
+ if (this.context.#defaultInitOption.logDetails) {
895
+ console.log("[Httpx-HttpxRequest.request] 请求前的配置👇", details);
896
+ }
897
+ if (typeof this.context.HttpxRequestHook.beforeRequestCallBack === "function") {
898
+ const hookResult = await this.context.HttpxRequestHook.beforeRequestCallBack(details);
899
+ if (hookResult == null) {
900
+ return;
901
+ }
902
+ }
903
+ if (details.fetch) {
904
+ // 使用fetch请求
905
+ const {
906
+ fetchOption: fetchOption,
907
+ fetchRequestOption: fetchRequestOption,
908
+ abortController,
909
+ } = this.context.HttpxRequestOption.handleFetchOption(details);
910
+ return this.fetch(fetchOption, fetchRequestOption, abortController);
911
+ } else {
912
+ // 使用GM_xmlHttpRequest请求
913
+ return this.xmlHttpRequest(details);
914
+ }
915
+ },
916
+ /**
917
+ * 使用油猴函数GM_xmlhttpRequest发送请求
918
+ * @param details
919
+ */
920
+ xmlHttpRequest(details: Required<HttpxRequestOption>) {
921
+ return this.context.GM_Api.xmlHttpRequest!(details) as {
922
+ abort: () => void;
923
+ };
924
+ },
925
+ /**
926
+ * 使用fetch发送请求
927
+ * @param option
928
+ * @param fetchRequestOption
929
+ * @param abortController
930
+ */
931
+ fetch(option: Required<HttpxRequestOption>, fetchRequestOption: RequestInit, abortController: AbortController) {
932
+ fetch(option.url, fetchRequestOption)
933
+ .then(async (fetchResponse) => {
934
+ /** 自定义的response */
935
+ const httpxResponse: HttpxResponseData<HttpxRequestOption> = {
936
+ isFetch: true,
937
+ finalUrl: fetchResponse.url,
938
+ readyState: 4,
939
+ status: fetchResponse.status,
940
+ statusText: fetchResponse.statusText,
941
+ response: "",
942
+ responseFetchHeaders: fetchResponse.headers,
943
+ responseHeaders: "",
944
+ responseText: "",
945
+ responseType: option.responseType,
946
+ responseXML: void 0,
947
+ };
948
+ Object.assign(httpxResponse, option.context || {});
949
+
950
+ // 把headers转为字符串
951
+ fetchResponse.headers.forEach((value, key) => {
952
+ httpxResponse.responseHeaders += `${key}: ${value}\n`;
953
+ });
954
+
955
+ /** 请求返回的类型 */
956
+ const fetchResponseType = fetchResponse.headers.get("Content-Type");
957
+
958
+ /* 如果需要stream,且获取到的是stream,那直接返回 */
959
+ if (
960
+ option.responseType === "stream" ||
961
+ (fetchResponse.headers.has("Content-Type") &&
962
+ fetchResponse.headers.get("Content-Type")!.includes("text/event-stream"))
963
+ ) {
964
+ Reflect.set(httpxResponse, "isStream", true);
965
+ Reflect.set(httpxResponse, "response", fetchResponse.body);
966
+ Reflect.deleteProperty(httpxResponse, "responseText");
967
+ Reflect.deleteProperty(httpxResponse, "responseXML");
968
+ option.onload(httpxResponse);
969
+ return;
970
+ }
971
+
972
+ /** 响应 */
973
+ let response: any = "";
974
+ /** 响应字符串 */
975
+ let responseText: string = "";
976
+ /** 响应xml文档 */
977
+ let responseXML: XMLDocument | string = "";
978
+ /** 先获取二进制数据 */
979
+ const arrayBuffer = await fetchResponse.arrayBuffer();
980
+
981
+ /** 数据编码 */
982
+ let encoding = "utf-8";
983
+ if (fetchResponse.headers.has("Content-Type")) {
984
+ const charsetMatched = fetchResponse.headers.get("Content-Type")?.match(/charset=(.+)/);
985
+ if (charsetMatched) {
986
+ encoding = charsetMatched[1];
987
+ encoding = encoding.toLowerCase();
988
+ }
989
+ }
990
+ // Failed to construct 'TextDecoder': The encoding label provided ('"UTF-8"') is invalid.
991
+ // 去除引号
992
+ encoding = encoding.replace(/('|")/gi, "");
993
+ // 编码
994
+ const textDecoder = new TextDecoder(encoding);
995
+ responseText = textDecoder.decode(arrayBuffer);
996
+ response = responseText;
997
+
998
+ if (option.responseType === "arraybuffer") {
999
+ // response返回格式是二进制流
1000
+ response = arrayBuffer;
1001
+ } else if (option.responseType === "blob") {
1002
+ // response返回格式是blob
1003
+ response = new Blob([arrayBuffer]);
1004
+ } else if (
1005
+ option.responseType === "json" ||
1006
+ (typeof fetchResponseType === "string" && fetchResponseType.includes("application/json"))
1007
+ ) {
1008
+ // response返回格式是JSON格式
1009
+ response = CommonUtil.toJSON(responseText);
1010
+ } else if (option.responseType === "document" || option.responseType == null) {
1011
+ // response返回格式是文档格式
1012
+ const parser = new DOMParser();
1013
+ response = parser.parseFromString(responseText, "text/html");
1014
+ }
1015
+ // 转为XML结构
1016
+ const parser = new DOMParser();
1017
+ responseXML = parser.parseFromString(responseText, "text/xml");
1018
+
1019
+ httpxResponse.response = response;
1020
+ httpxResponse.responseText = responseText;
1021
+ httpxResponse.responseXML = responseXML;
1022
+
1023
+ // 执行回调
1024
+ option.onload(httpxResponse);
1025
+ })
1026
+ .catch((error: any) => {
1027
+ if (error.name === "AbortError") {
1028
+ return;
1029
+ }
1030
+ option.onerror({
1031
+ isFetch: true,
1032
+ finalUrl: option.url,
1033
+ readyState: 4,
1034
+ status: 0,
1035
+ statusText: "",
1036
+ responseHeaders: "",
1037
+ responseText: "",
1038
+ error: error,
1039
+ });
1040
+ });
1041
+ option.onloadstart({
1042
+ isFetch: true,
1043
+ finalUrl: option.url,
1044
+ readyState: 1,
1045
+ responseHeaders: "",
1046
+ responseText: "",
1047
+ status: 0,
1048
+ statusText: "",
1049
+ });
1050
+ return {
1051
+ abort() {
1052
+ abortController.abort();
1053
+ },
1054
+ };
1055
+ },
1056
+ };
1057
+ /**
1058
+ * 默认配置
1059
+ */
1060
+ #defaultRequestOption = <HttpxRequestOption>{
1061
+ url: void 0 as undefined | string,
1062
+ timeout: 5000,
1063
+ async: false,
1064
+ responseType: void 0,
1065
+ headers: void 0,
1066
+ data: void 0,
1067
+ redirect: void 0,
1068
+ cookie: void 0,
1069
+ cookiePartition: void 0,
1070
+ binary: void 0,
1071
+ nocache: void 0,
1072
+ revalidate: void 0,
1073
+ context: void 0,
1074
+ overrideMimeType: void 0,
1075
+ anonymous: void 0,
1076
+ fetch: void 0,
1077
+ fetchInit: void 0,
1078
+ allowInterceptConfig: {
1079
+ beforeRequest: true,
1080
+ afterResponseSuccess: true,
1081
+ afterResponseError: true,
1082
+ },
1083
+ user: void 0,
1084
+ password: void 0,
1085
+ onabort() {},
1086
+ onerror() {},
1087
+ ontimeout() {},
1088
+ onloadstart() {},
1089
+ onreadystatechange() {},
1090
+ onprogress() {},
1091
+ };
1092
+ /**
1093
+ * 实例化的默认配置
1094
+ */
1095
+ #defaultInitOption = {
1096
+ /**
1097
+ * `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
1098
+ */
1099
+ baseURL: void 0 as undefined | string,
1100
+ /**
1101
+ * 当前使用请求时,输出请求的配置,一般用于DEBUG|DEV
1102
+ */
1103
+ logDetails: false,
1104
+ };
1105
+ /**
1106
+ * 实例化
1107
+ * @param option 初始化配置
1108
+ */
1109
+ constructor(option: Partial<HttpxInitOption> = {}) {
1110
+ if (typeof option.xmlHttpRequest !== "function") {
1111
+ console.warn(
1112
+ "[Httpx-constructor] 未传入GM_xmlhttpRequest函数或传入的GM_xmlhttpRequest不是Function,将默认使用window.fetch"
1113
+ );
1114
+ }
1115
+ CommonUtil.coverObjectFunctionThis(this);
1116
+ this.interceptors.request.context = this;
1117
+ this.interceptors.response.context = this;
1118
+ this.config(option);
1119
+ }
1120
+ /**
1121
+ * 覆盖当前配置
1122
+ * @param option
1123
+ */
1124
+ config(option: Partial<HttpxInitOption> = {}) {
1125
+ if (typeof option.xmlHttpRequest === "function") {
1126
+ this.GM_Api.xmlHttpRequest = option.xmlHttpRequest;
1127
+ }
1128
+ this.#defaultRequestOption = CommonUtil.assign(this.#defaultRequestOption, option);
1129
+ this.#defaultInitOption = CommonUtil.assign(this.#defaultInitOption, option);
1130
+ }
1131
+ /**
1132
+ * 拦截器
1133
+ */
1134
+ interceptors = {
1135
+ /**
1136
+ * 请求拦截器
1137
+ */
1138
+ request: {
1139
+ context: null as any as Httpx,
1140
+ /**
1141
+ * 添加拦截器
1142
+ * @param fn 设置的请求前回调函数,如果返回配置,则使用返回的配置,如果返回null|undefined,则阻止请求
1143
+ */
1144
+ use(fn: <T extends Required<HttpxRequestOption>>(details: T) => void | T | Promise<void | T>) {
1145
+ if (typeof fn !== "function") {
1146
+ console.warn("[Httpx-interceptors-request] 请传入拦截器函数");
1147
+ return;
1148
+ }
1149
+ return this.context.HttpxRequestHook.add(fn);
1150
+ },
1151
+ /**
1152
+ * 移除拦截器
1153
+ * @param id 通过use返回的id
1154
+ */
1155
+ eject(id: string) {
1156
+ return this.context.HttpxRequestHook.delete(id);
1157
+ },
1158
+ /**
1159
+ * 移除所有拦截器
1160
+ */
1161
+ ejectAll() {
1162
+ this.context.HttpxRequestHook.clearAll();
1163
+ },
1164
+ },
1165
+ /**
1166
+ * 响应拦截器
1167
+ */
1168
+ response: {
1169
+ context: null as any as Httpx,
1170
+ /**
1171
+ * 添加拦截器
1172
+ * @param successFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
1173
+ * + 2xx 范围内的状态码都会触发该函数
1174
+ * @param errorFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
1175
+ * + 超出 2xx 范围的状态码都会触发该函数
1176
+ */
1177
+ use(
1178
+ successFn?: <T extends HttpxResponseData<HttpxRequestOption>>(
1179
+ response: T,
1180
+ details: HttpxRequestOption
1181
+ ) => void | T,
1182
+ errorFn?: <T extends HttpxHookErrorData>(data: T) => void | T | Promise<void | T>
1183
+ ) {
1184
+ if (typeof successFn !== "function" && typeof errorFn !== "function") {
1185
+ console.warn("[Httpx-interceptors-response] 必须传入一个拦截器函数");
1186
+ return;
1187
+ }
1188
+ return this.context.HttpxResponseHook.add(successFn!, errorFn!);
1189
+ },
1190
+ /**
1191
+ * 移除拦截器
1192
+ * @param id 通过use返回的id
1193
+ */
1194
+ eject(id: string) {
1195
+ return this.context.HttpxResponseHook.delete(id);
1196
+ },
1197
+ /**
1198
+ * 移除所有拦截器
1199
+ */
1200
+ ejectAll() {
1201
+ this.context.HttpxResponseHook.clearAll();
1202
+ },
1203
+ },
1204
+ };
1205
+ /**
1206
+ * 修改xmlHttpRequest
1207
+ * @param httpRequest 网络请求函数
1208
+ */
1209
+ setXMLHttpRequest(httpRequest: (...args: any[]) => any) {
1210
+ this.GM_Api.xmlHttpRequest = httpRequest;
1211
+ }
1212
+ /**
1213
+ * GET 请求
1214
+ * @param details 配置
1215
+ */
1216
+ get<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1217
+ /**
1218
+ * GET 请求
1219
+ * @param url 请求的url
1220
+ * @param details 配置
1221
+ */
1222
+ get<T extends Omit<HttpxRequestOption, "url">>(
1223
+ url: string,
1224
+ details?: T
1225
+ ): HttpxPromise<
1226
+ HttpxResponse<
1227
+ T & {
1228
+ /**
1229
+ * 请求的url
1230
+ */
1231
+ url: string;
1232
+ }
1233
+ >
1234
+ >;
1235
+ /**
1236
+ * GET 请求
1237
+ * @param url 请求的url
1238
+ * @param details 配置
1239
+ */
1240
+ get(...args: (string | HttpxRequestOption)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1241
+ const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1242
+ useRequestOption.method = "GET";
1243
+ return this.request(useRequestOption, (option) => {
1244
+ Reflect.deleteProperty(option, "onprogress");
1245
+ });
1246
+ }
1247
+ /**
1248
+ * POST 请求
1249
+ * @param details 配置
1250
+ */
1251
+ post<T extends HttpxRequestOption>(details?: T): HttpxPromise<HttpxResponse<T>>;
1252
+ /**
1253
+ * POST 请求
1254
+ * @param url 请求的url
1255
+ * @param details 配置
1256
+ */
1257
+ post<T extends Omit<HttpxRequestOption, "url">>(
1258
+ url: string,
1259
+ details?: T
1260
+ ): HttpxPromise<
1261
+ HttpxResponse<
1262
+ T & {
1263
+ /**
1264
+ * 请求的url
1265
+ */
1266
+ url: string;
1267
+ }
1268
+ >
1269
+ >;
1270
+ /**
1271
+ * POST 请求
1272
+ */
1273
+ post(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1274
+ const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1275
+ useRequestOption.method = "POST";
1276
+ return this.request(useRequestOption);
1277
+ }
1278
+ /**
1279
+ * HEAD 请求
1280
+ * @param details 配置
1281
+ */
1282
+ head<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1283
+ /**
1284
+ * HEAD 请求
1285
+ * @param url 请求的url
1286
+ * @param details 配置
1287
+ */
1288
+ head<T extends Omit<HttpxRequestOption, "url">>(
1289
+ url: string,
1290
+ details?: T
1291
+ ): HttpxPromise<
1292
+ HttpxResponse<
1293
+ T & {
1294
+ /**
1295
+ * 请求的url
1296
+ */
1297
+ url: string;
1298
+ }
1299
+ >
1300
+ >;
1301
+ /**
1302
+ * HEAD 请求
1303
+ */
1304
+ head(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1305
+ const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1306
+ useRequestOption.method = "HEAD";
1307
+ return this.request(useRequestOption, (option) => {
1308
+ Reflect.deleteProperty(option, "onprogress");
1309
+ });
1310
+ }
1311
+ /**
1312
+ * OPTIONS 请求
1313
+ * @param details 配置
1314
+ */
1315
+ options<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1316
+ /**
1317
+ * OPTIONS 请求
1318
+ * @param url 请求的url
1319
+ * @param details 配置
1320
+ */
1321
+ options<T extends Omit<HttpxRequestOption, "url">>(
1322
+ url: string,
1323
+ details?: T
1324
+ ): HttpxPromise<
1325
+ HttpxResponse<
1326
+ T & {
1327
+ /**
1328
+ * 请求的url
1329
+ */
1330
+ url: string;
1331
+ }
1332
+ >
1333
+ >;
1334
+ /**
1335
+ * OPTIONS 请求
1336
+ */
1337
+ options(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1338
+ const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1339
+ useRequestOption.method = "OPTIONS";
1340
+ return this.request(useRequestOption, (option) => {
1341
+ Reflect.deleteProperty(option, "onprogress");
1342
+ });
1343
+ }
1344
+ /**
1345
+ * DELETE 请求
1346
+ * @param details 配置
1347
+ */
1348
+ delete<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1349
+ /**
1350
+ * DELETE 请求
1351
+ * @param url 请求的url
1352
+ * @param details 配置
1353
+ */
1354
+ delete<T extends Omit<HttpxRequestOption, "url">>(
1355
+ url: string,
1356
+ details?: T
1357
+ ): HttpxPromise<
1358
+ HttpxResponse<
1359
+ T & {
1360
+ /**
1361
+ * 请求的url
1362
+ */
1363
+ url: string;
1364
+ }
1365
+ >
1366
+ >;
1367
+ /**
1368
+ * DELETE 请求
1369
+ */
1370
+ delete(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1371
+ const useRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1372
+ useRequestOption.method = "DELETE";
1373
+ return this.request(useRequestOption, (option) => {
1374
+ Reflect.deleteProperty(option, "onprogress");
1375
+ });
1376
+ }
1377
+ /**
1378
+ * PUT 请求
1379
+ * @param details 配置
1380
+ */
1381
+ put<T extends HttpxRequestOption>(details: T): HttpxPromise<HttpxResponse<T>>;
1382
+ /**
1383
+ * PUT 请求
1384
+ * @param url 请求的url
1385
+ * @param details 配置
1386
+ */
1387
+ put<T extends Omit<HttpxRequestOption, "url">>(
1388
+ url: string,
1389
+ details?: T
1390
+ ): HttpxPromise<
1391
+ HttpxResponse<
1392
+ T & {
1393
+ /**
1394
+ * 请求的url
1395
+ */
1396
+ url: string;
1397
+ }
1398
+ >
1399
+ >;
1400
+ /**
1401
+ * PUT 请求
1402
+ */
1403
+ put(...args: (HttpxRequestOption | string)[]): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
1404
+ const userRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(...args);
1405
+ userRequestOption.method = "PUT";
1406
+ return this.request(userRequestOption);
1407
+ }
1408
+ /**
1409
+ * 发送请求
1410
+ * @param details 配置
1411
+ * @param beforeRequestOption 处理请求前的配置
1412
+ */
1413
+ request<T extends HttpxRequestOption>(
1414
+ details: T,
1415
+ beforeRequestOption?: (option: Required<T | HttpxRequestOption>) => void
1416
+ ): HttpxPromise<HttpxResponse<T>> {
1417
+ // 对请求的参数进行合并处理
1418
+ const userRequestOption = this.HttpxRequestOption.handleBeforeRequestOptionArgs(details);
1419
+
1420
+ /**
1421
+ * 取消请求
1422
+ */
1423
+ let abortFn: ((...args: any[]) => any) | null = null;
1424
+ const promise = new globalThis.Promise<HttpxResponse<HttpxRequestOption>>(async (resolve, reject) => {
1425
+ // 请求配置
1426
+ let requestOption = this.HttpxRequestOption.getRequestOption(
1427
+ userRequestOption.method!,
1428
+ userRequestOption,
1429
+ (resultOption) => {
1430
+ resolve(resultOption);
1431
+ },
1432
+ (...args: any[]) => {
1433
+ reject(...args);
1434
+ }
1435
+ );
1436
+ requestOption = this.HttpxRequestOption.removeRequestNullOption(requestOption);
1437
+ if (typeof beforeRequestOption === "function") {
1438
+ beforeRequestOption(requestOption as Required<T>);
1439
+ }
1440
+
1441
+ // 处理重试逻辑
1442
+ const requestResult = await this.HttpxRequest.request(requestOption);
1443
+ if (requestResult != null && typeof requestResult.abort === "function") {
1444
+ abortFn = () => {
1445
+ // 取消请求
1446
+ requestResult.abort();
1447
+ };
1448
+ }
1449
+ }) as HttpxPromise<HttpxResponse<T>>;
1450
+ promise.abort = () => {
1451
+ if (typeof abortFn === "function") {
1452
+ abortFn();
1453
+ }
1454
+ };
1455
+ return promise;
1456
+ }
1457
+ }