@nsnanocat/util 2.6.1 → 2.6.3
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.
- package/README.md +11 -6
- package/getStorage.mjs +1 -1
- package/index.cjs +7 -0
- package/index.js +1 -1
- package/package.json +19 -1
- package/polyfill/fetch.cjs +128 -0
- package/polyfill/{fetch.js → fetch.mjs} +9 -133
- package/polyfill/fetch.mts +1 -1
- package/polyfill/index.d.ts +2 -2
- package/polyfill/index.js +1 -1
package/README.md
CHANGED
|
@@ -369,7 +369,11 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
369
369
|
|
|
370
370
|
### `polyfill/fetch.mjs`
|
|
371
371
|
|
|
372
|
-
`fetch`
|
|
372
|
+
`fetch` 现已拆分为 ESM / CJS 两条运行路径:
|
|
373
|
+
- `polyfill/fetch.mjs`:仅用于 iOS 脚本平台(Quantumult X / Loon / Surge / Stash / Egern / Shadowrocket)
|
|
374
|
+
- `polyfill/fetch.cjs`:用于 Worker / Node.js
|
|
375
|
+
|
|
376
|
+
`polyfill/fetch.mjs` 仍仿照 Web API `Window.fetch` 设计:
|
|
373
377
|
- 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
|
|
374
378
|
- 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch
|
|
375
379
|
- 目标:尽量保持 Web `fetch` 调用习惯,同时补齐各平台扩展参数映射
|
|
@@ -394,7 +398,7 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
394
398
|
- `timeout`
|
|
395
399
|
- `policy`
|
|
396
400
|
- `redirection` / `auto-redirect`
|
|
397
|
-
- `auto-cookie
|
|
401
|
+
- `auto-cookie`(仅 CJS 的 Worker / Node.js 分支识别;默认启用,传入 `false` / `0` / `-1` 可关闭)
|
|
398
402
|
|
|
399
403
|
说明:下表是各 App 原生 HTTP 接口的差异补充,以及本库 `fetch` 的内部映射方式。调用方使用统一入参即可。
|
|
400
404
|
|
|
@@ -408,8 +412,6 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
408
412
|
| Egern | `$httpClient[method]` | 秒 | 无专门映射 | `auto-redirect` | 同上 |
|
|
409
413
|
| Shadowrocket | `$httpClient[method]` | 秒 | `headers.X-Surge-Proxy` | `auto-redirect` | 同上 |
|
|
410
414
|
| Quantumult X | `$task.fetch` | 毫秒(内部乘 1000) | `opts.policy` | `opts.redirection` | `body(ArrayBuffer/TypedArray)` 转 `bodyBytes`;响应按 `Content-Type` 恢复到 `body` |
|
|
411
|
-
| Worker | `globalThis.fetch`(不存在时回退 `node-fetch`);共享 `auto-cookie` 处理 | 毫秒(内部乘 1000) | 无 | `redirect: follow/manual` | 返回 `body`(UTF-8 string) + `bodyBytes`(ArrayBuffer) |
|
|
412
|
-
| Node.js | `globalThis.fetch`(不存在时回退 `node-fetch`);默认按需包裹 `fetch-cookie` | 毫秒(内部乘 1000) | 无 | `redirect: follow/manual` | 返回 `body`(UTF-8 string) + `bodyBytes`(ArrayBuffer) |
|
|
413
415
|
|
|
414
416
|
返回对象(统一后)常见字段:
|
|
415
417
|
- `ok`
|
|
@@ -421,12 +423,15 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
421
423
|
- `bodyBytes`
|
|
422
424
|
|
|
423
425
|
不可用/差异点:
|
|
424
|
-
- `policy` 在 Surge / Egern
|
|
426
|
+
- `policy` 在 Surge / Egern 分支没有额外适配逻辑。
|
|
425
427
|
- `redirection` 在部分平台会映射为 `auto-redirect` 或 `opts.redirection`。
|
|
426
|
-
- Worker / Node.js 共享基于 `fetch` 的请求分支;若 `globalThis.fetch` 不存在则回退到 `node-fetch`,并在 `auto-cookie` 未关闭时按需包裹 `fetch-cookie`。
|
|
427
428
|
- 传入 `timeout` 时,`5` 和 `5000` 都会被接受;库会先将用户输入归一化,再按平台要求转换为秒或毫秒。
|
|
428
429
|
- 返回结构是统一兼容结构,不等同于浏览器 `Response` 对象。
|
|
429
430
|
|
|
431
|
+
Worker / Node.js 使用说明:
|
|
432
|
+
- 请通过 CJS 入口调用:`require("@nsnanocat/util").fetch` 或 `require("@nsnanocat/util/polyfill/fetch").fetch`
|
|
433
|
+
- CJS 分支会处理 `auto-cookie`,并将响应归一化为 `ok/status/statusText/body/bodyBytes`
|
|
434
|
+
|
|
430
435
|
### `polyfill/Storage.mjs`
|
|
431
436
|
|
|
432
437
|
`Storage` 是仿照 Web Storage 接口(`Storage`)设计的跨平台持久化适配器:
|
package/getStorage.mjs
CHANGED
package/index.cjs
ADDED
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export * from "./lib/notification.mjs";
|
|
|
5
5
|
export * from "./lib/time.mjs";
|
|
6
6
|
export * from "./lib/wait.mjs";
|
|
7
7
|
export * from "./polyfill/Console.mjs";
|
|
8
|
-
export * from "./polyfill/fetch.
|
|
8
|
+
export * from "./polyfill/fetch.mjs";
|
|
9
9
|
export * from "./polyfill/Lodash.mjs";
|
|
10
10
|
export * from "./polyfill/qs.mjs";
|
|
11
11
|
export * from "./polyfill/StatusTexts.mjs";
|
package/package.json
CHANGED
|
@@ -14,10 +14,23 @@
|
|
|
14
14
|
"license": "Apache-2.0",
|
|
15
15
|
"bugs": "https://github.com/NSNanoCat/util/issues",
|
|
16
16
|
"main": "index.js",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./index.js",
|
|
20
|
+
"require": "./index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./polyfill/fetch": {
|
|
23
|
+
"import": "./polyfill/fetch.mjs",
|
|
24
|
+
"require": "./polyfill/fetch.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./*": "./*"
|
|
27
|
+
},
|
|
17
28
|
"types": "types/nsnanocat-util.d.ts",
|
|
18
29
|
"scripts": {
|
|
19
30
|
"tsc:build": "npx tsc",
|
|
20
31
|
"test": "node --test test/*.test.js",
|
|
32
|
+
"test:fetch": "node --test test/fetch.test.js",
|
|
33
|
+
"test:fetch:cjs": "node --test test/fetch.test.cjs",
|
|
21
34
|
"test:merge": "node --test test/Lodash.merge.test.js",
|
|
22
35
|
"test:argument": "node --test test/argument.test.js",
|
|
23
36
|
"deprecate": "npm deprecate -f '@nsnanocat/util@0.0.0-preview' \"this package has been deprecated\""
|
|
@@ -27,12 +40,17 @@
|
|
|
27
40
|
"url": "git+https://github.com/NSNanoCat/util.git"
|
|
28
41
|
},
|
|
29
42
|
"files": [
|
|
43
|
+
"index.cjs",
|
|
30
44
|
"index.js",
|
|
31
45
|
"lib",
|
|
32
46
|
"polyfill",
|
|
33
47
|
"getStorage.mjs",
|
|
34
48
|
"types"
|
|
35
49
|
],
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"fetch-cookie": "^3.2.0",
|
|
52
|
+
"node-fetch": "^3.3.2"
|
|
53
|
+
},
|
|
36
54
|
"devDependencies": {
|
|
37
55
|
"@biomejs/biome": "2.4.6",
|
|
38
56
|
"typescript": "^5.9.3"
|
|
@@ -41,5 +59,5 @@
|
|
|
41
59
|
"registry": "https://registry.npmjs.org/",
|
|
42
60
|
"access": "public"
|
|
43
61
|
},
|
|
44
|
-
"version": "2.6.
|
|
62
|
+
"version": "2.6.3"
|
|
45
63
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 统一请求参数。
|
|
5
|
+
* Unified request payload.
|
|
6
|
+
*
|
|
7
|
+
* @typedef {object} FetchRequest
|
|
8
|
+
* @property {string} url 请求地址 / Request URL.
|
|
9
|
+
* @property {string} [method] 请求方法 / HTTP method.
|
|
10
|
+
* @property {Record<string, any>} [headers] 请求头 / Request headers.
|
|
11
|
+
* @property {string|ArrayBuffer|ArrayBufferView|object} [body] 请求体 / Request body.
|
|
12
|
+
* @property {ArrayBuffer} [bodyBytes] 二进制请求体 / Binary request body.
|
|
13
|
+
* @property {number|string} [timeout] 超时(秒或毫秒)/ Timeout (seconds or milliseconds).
|
|
14
|
+
* @property {boolean} [redirection] 是否跟随重定向 / Whether to follow redirects.
|
|
15
|
+
* @property {boolean|number|string} ["auto-cookie"] Worker / Node.js Cookie 开关 / Worker / Node.js Cookie toggle.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 统一响应结构。
|
|
20
|
+
* Unified response payload.
|
|
21
|
+
*
|
|
22
|
+
* @typedef {object} FetchResponse
|
|
23
|
+
* @property {boolean} ok 请求是否成功 / Whether request is successful.
|
|
24
|
+
* @property {number} status 状态码 / HTTP status code.
|
|
25
|
+
* @property {number} [statusCode] 状态码别名 / Status code alias.
|
|
26
|
+
* @property {string} [statusText] 状态文本 / HTTP status text.
|
|
27
|
+
* @property {Record<string, any>} [headers] 响应头 / Response headers.
|
|
28
|
+
* @property {string|ArrayBuffer} [body] 响应体 / Response body.
|
|
29
|
+
* @property {ArrayBuffer} [bodyBytes] 二进制响应体 / Binary response body.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 仅面向 Worker / Node.js 的 `fetch` 适配层(CJS 版本)。
|
|
34
|
+
* `fetch` adapter for Worker / Node.js only (CJS version).
|
|
35
|
+
*
|
|
36
|
+
* @async
|
|
37
|
+
* @param {FetchRequest|string} resource 请求对象或 URL / Request object or URL string.
|
|
38
|
+
* @param {Partial<FetchRequest>} [options={}] 追加参数 / Extra options.
|
|
39
|
+
* @returns {Promise<FetchResponse>}
|
|
40
|
+
*/
|
|
41
|
+
async function fetch(resource, options = {}) {
|
|
42
|
+
switch (typeof resource) {
|
|
43
|
+
case "object":
|
|
44
|
+
resource = { ...options, ...resource };
|
|
45
|
+
break;
|
|
46
|
+
case "string":
|
|
47
|
+
resource = { ...options, url: resource };
|
|
48
|
+
break;
|
|
49
|
+
case "undefined":
|
|
50
|
+
default:
|
|
51
|
+
throw new TypeError(`${Function.name}: 参数类型错误, resource 必须为对象或字符串`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!resource.method) {
|
|
55
|
+
resource.method = "GET";
|
|
56
|
+
if (resource.body ?? resource.bodyBytes) resource.method = "POST";
|
|
57
|
+
}
|
|
58
|
+
delete resource.headers?.Host;
|
|
59
|
+
delete resource.headers?.[":authority"];
|
|
60
|
+
delete resource.headers?.["Content-Length"];
|
|
61
|
+
delete resource.headers?.["content-length"];
|
|
62
|
+
|
|
63
|
+
if (!resource.timeout) resource.timeout = 5;
|
|
64
|
+
if (resource.timeout) {
|
|
65
|
+
resource.timeout = Number.parseInt(resource.timeout, 10);
|
|
66
|
+
if (resource.timeout > 500) resource.timeout = Math.round(resource.timeout / 1000);
|
|
67
|
+
resource.timeout = resource.timeout * 1000;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!globalThis.fetch) {
|
|
71
|
+
throw new Error(`${Function.name}: 当前 Node.js 运行时缺少全局 fetch,请升级 Node.js 版本`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
switch (resource["auto-cookie"]) {
|
|
75
|
+
case undefined:
|
|
76
|
+
case "true":
|
|
77
|
+
case true:
|
|
78
|
+
case "1":
|
|
79
|
+
case 1:
|
|
80
|
+
default:
|
|
81
|
+
if (!globalThis.fetch?.cookieJar) globalThis.fetch = require("fetch-cookie").default(globalThis.fetch);
|
|
82
|
+
break;
|
|
83
|
+
case "false":
|
|
84
|
+
case false:
|
|
85
|
+
case "0":
|
|
86
|
+
case 0:
|
|
87
|
+
case "-1":
|
|
88
|
+
case -1:
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
resource.redirect = resource.redirection ? "follow" : "manual";
|
|
93
|
+
const { url, ...fetchOptions } = resource;
|
|
94
|
+
|
|
95
|
+
return Promise.race([
|
|
96
|
+
globalThis
|
|
97
|
+
.fetch(url, fetchOptions)
|
|
98
|
+
.then(async response => {
|
|
99
|
+
const bodyBytes = await response.arrayBuffer();
|
|
100
|
+
let headers;
|
|
101
|
+
try {
|
|
102
|
+
headers = response.headers.raw();
|
|
103
|
+
} catch {
|
|
104
|
+
headers = Array.from(response.headers.entries()).reduce((acc, [key, value]) => {
|
|
105
|
+
acc[key] = acc[key] ? [...acc[key], value] : [value];
|
|
106
|
+
return acc;
|
|
107
|
+
}, {});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
ok: response.ok ?? /^2\d\d$/.test(response.status),
|
|
111
|
+
status: response.status,
|
|
112
|
+
statusCode: response.status,
|
|
113
|
+
statusText: response.statusText,
|
|
114
|
+
body: new TextDecoder("utf-8").decode(bodyBytes),
|
|
115
|
+
bodyBytes,
|
|
116
|
+
headers: Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, key.toLowerCase() !== "set-cookie" ? value.toString() : value])),
|
|
117
|
+
};
|
|
118
|
+
})
|
|
119
|
+
.catch(error => Promise.reject(error.message)),
|
|
120
|
+
new Promise((resolve, reject) => {
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
reject(new Error(`${Function.name}: 请求超时, 请检查网络后重试`));
|
|
123
|
+
}, resource.timeout);
|
|
124
|
+
}),
|
|
125
|
+
]);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { fetch };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { $app } from "../lib/app.mjs";
|
|
2
|
-
import { Console } from "./Console.mjs";
|
|
3
2
|
import { Lodash as _ } from "./Lodash.mjs";
|
|
4
3
|
import { StatusTexts } from "./StatusTexts.mjs";
|
|
5
4
|
|
|
@@ -17,7 +16,6 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
17
16
|
* @property {string} [policy] 指定策略 / Preferred policy.
|
|
18
17
|
* @property {boolean} [redirection] 是否跟随重定向 / Whether to follow redirects.
|
|
19
18
|
* @property {boolean} ["auto-redirect"] 平台重定向字段 / Platform redirect flag.
|
|
20
|
-
* @property {boolean|number|string} ["auto-cookie"] Worker / Node.js Cookie 开关 / Worker / Node.js Cookie toggle.
|
|
21
19
|
* @property {Record<string, any>} [opts] 平台扩展字段 / Platform extension fields.
|
|
22
20
|
*/
|
|
23
21
|
|
|
@@ -36,35 +34,8 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
36
34
|
*/
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* 设计目标:
|
|
43
|
-
* Design goal:
|
|
44
|
-
* - 仿照 Web API `fetch`(`Window.fetch`)接口设计
|
|
45
|
-
* - Modeled after Web API `fetch` (`Window.fetch`)
|
|
46
|
-
* - 统一 VPN App、Worker 与 Node.js 环境中的请求调用
|
|
47
|
-
* - Unify request calls across VPN apps, Worker, and Node.js
|
|
48
|
-
*
|
|
49
|
-
* 功能:
|
|
50
|
-
* Features:
|
|
51
|
-
* - 统一 Quantumult X / Loon / Surge / Stash / Egern / Shadowrocket / Worker / Node.js 请求接口
|
|
52
|
-
* - Normalize request APIs across Quantumult X / Loon / Surge / Stash / Egern / Shadowrocket / Worker / Node.js
|
|
53
|
-
* - 统一返回体字段(`ok/status/statusText/body/bodyBytes`)
|
|
54
|
-
* - Normalize response fields (`ok/status/statusText/body/bodyBytes`)
|
|
55
|
-
*
|
|
56
|
-
* 与 Web `fetch` 的已知差异:
|
|
57
|
-
* Known differences from Web `fetch`:
|
|
58
|
-
* - 支持 `policy`、`auto-redirect` 等平台扩展字段
|
|
59
|
-
* - Supports platform extension fields like `policy` and `auto-redirect`
|
|
60
|
-
* - Worker / Node.js 共享基于 `fetch` 的请求分支
|
|
61
|
-
* - Worker / Node.js share the `fetch`-based request branch
|
|
62
|
-
* - `auto-cookie` 在 Worker / Node.js 共享分支中识别
|
|
63
|
-
* - `auto-cookie` is recognized by the shared Worker / Node.js branch
|
|
64
|
-
* - 非浏览器平台通过 `$httpClient/$task` 实现,不是原生 Fetch 实现
|
|
65
|
-
* - Non-browser platforms use `$httpClient/$task` instead of native Fetch engine
|
|
66
|
-
* - 返回结构包含 `statusCode/bodyBytes` 等兼容字段
|
|
67
|
-
* - Response includes compatibility fields like `statusCode/bodyBytes`
|
|
37
|
+
* 仅面向 iOS 脚本平台的 `fetch` 适配层(ESM 版本)。
|
|
38
|
+
* `fetch` adapter for iOS script platforms only (ESM version).
|
|
68
39
|
*
|
|
69
40
|
* @link https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
|
|
70
41
|
* @link https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch
|
|
@@ -74,8 +45,6 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
74
45
|
* @returns {Promise<FetchResponse>}
|
|
75
46
|
*/
|
|
76
47
|
export async function fetch(resource, options = {}) {
|
|
77
|
-
// 初始化参数。
|
|
78
|
-
// Initialize request input.
|
|
79
48
|
switch (typeof resource) {
|
|
80
49
|
case "object":
|
|
81
50
|
resource = { ...options, ...resource };
|
|
@@ -87,63 +56,39 @@ export async function fetch(resource, options = {}) {
|
|
|
87
56
|
default:
|
|
88
57
|
throw new TypeError(`${Function.name}: 参数类型错误, resource 必须为对象或字符串`);
|
|
89
58
|
}
|
|
90
|
-
|
|
91
|
-
// Infer the HTTP method automatically.
|
|
59
|
+
|
|
92
60
|
if (!resource.method) {
|
|
93
61
|
resource.method = "GET";
|
|
94
62
|
if (resource.body ?? resource.bodyBytes) resource.method = "POST";
|
|
95
63
|
}
|
|
96
|
-
// 移除需要由底层实现自动生成的请求头。
|
|
97
|
-
// Remove headers that should be generated by the underlying runtime.
|
|
98
64
|
delete resource.headers?.Host;
|
|
99
65
|
delete resource.headers?.[":authority"];
|
|
100
66
|
delete resource.headers?.["Content-Length"];
|
|
101
67
|
delete resource.headers?.["content-length"];
|
|
102
|
-
// 统一请求方法为小写,方便后续索引平台 API。
|
|
103
|
-
// Normalize the method to lowercase for platform API lookups.
|
|
104
68
|
const method = resource.method.toLocaleLowerCase();
|
|
105
|
-
|
|
106
|
-
// Default request timeout to 5 seconds.
|
|
69
|
+
|
|
107
70
|
if (!resource.timeout) resource.timeout = 5;
|
|
108
|
-
// 智能矫正请求超时时间,兼容用户输入的秒或毫秒。
|
|
109
|
-
// Normalize timeout input so both seconds and milliseconds are accepted.
|
|
110
71
|
if (resource.timeout) {
|
|
111
72
|
resource.timeout = Number.parseInt(resource.timeout, 10);
|
|
112
|
-
// 统一先转换为秒,大于 500 视为毫秒输入。
|
|
113
|
-
// Convert to seconds first and treat values above 500 as milliseconds.
|
|
114
73
|
if (resource.timeout > 500) resource.timeout = Math.round(resource.timeout / 1000);
|
|
115
74
|
}
|
|
116
|
-
// 某些平台要求毫秒级超时,进行二次换算。
|
|
117
|
-
// Some platforms expect timeout in milliseconds, so convert again.
|
|
118
75
|
if (resource.timeout) {
|
|
119
76
|
switch ($app) {
|
|
120
77
|
case "Loon":
|
|
121
78
|
case "Quantumult X":
|
|
122
|
-
case "Worker":
|
|
123
|
-
case "Node.js":
|
|
124
|
-
// 这些平台要求毫秒,因此把秒重新换算为毫秒。
|
|
125
|
-
// These platforms expect milliseconds, so convert seconds back to milliseconds.
|
|
126
79
|
resource.timeout = resource.timeout * 1000;
|
|
127
80
|
break;
|
|
128
|
-
case "Shadowrocket":
|
|
129
|
-
case "Stash":
|
|
130
|
-
case "Egern":
|
|
131
|
-
case "Surge":
|
|
132
81
|
default:
|
|
133
82
|
break;
|
|
134
83
|
}
|
|
135
84
|
}
|
|
136
|
-
|
|
137
|
-
// Select the request engine for the current platform.
|
|
85
|
+
|
|
138
86
|
switch ($app) {
|
|
139
87
|
case "Loon":
|
|
140
88
|
case "Surge":
|
|
141
89
|
case "Stash":
|
|
142
90
|
case "Egern":
|
|
143
91
|
case "Shadowrocket":
|
|
144
|
-
default:
|
|
145
|
-
// 转换通用请求参数到 `$httpClient` 语义。
|
|
146
|
-
// Map shared request fields to `$httpClient` semantics.
|
|
147
92
|
if (resource.policy) {
|
|
148
93
|
switch ($app) {
|
|
149
94
|
case "Loon":
|
|
@@ -158,14 +103,10 @@ export async function fetch(resource, options = {}) {
|
|
|
158
103
|
}
|
|
159
104
|
}
|
|
160
105
|
if (typeof resource.redirection === "boolean") resource["auto-redirect"] = resource.redirection;
|
|
161
|
-
// 优先把 `bodyBytes` 映射回 `$httpClient` 能接受的 `body`。
|
|
162
|
-
// Prefer mapping `bodyBytes` back to the `body` field expected by `$httpClient`.
|
|
163
106
|
if (resource.bodyBytes && !resource.body) {
|
|
164
107
|
resource.body = resource.bodyBytes;
|
|
165
108
|
resource.bodyBytes = undefined;
|
|
166
109
|
}
|
|
167
|
-
// 根据 `Accept` 推断是否需要二进制响应体。
|
|
168
|
-
// Infer whether the response should be treated as binary from `Accept`.
|
|
169
110
|
switch ((resource.headers?.Accept || resource.headers?.accept)?.split(";")?.[0]) {
|
|
170
111
|
case "application/protobuf":
|
|
171
112
|
case "application/x-protobuf":
|
|
@@ -177,8 +118,6 @@ export async function fetch(resource, options = {}) {
|
|
|
177
118
|
resource["binary-mode"] = true;
|
|
178
119
|
break;
|
|
179
120
|
}
|
|
180
|
-
// 发送 `$httpClient` 请求并归一化返回结构。
|
|
181
|
-
// Send the `$httpClient` request and normalize the response payload.
|
|
182
121
|
return new Promise((resolve, reject) => {
|
|
183
122
|
globalThis.$httpClient[method](resource, (error, response, body) => {
|
|
184
123
|
if (error) reject(error);
|
|
@@ -195,12 +134,8 @@ export async function fetch(resource, options = {}) {
|
|
|
195
134
|
});
|
|
196
135
|
});
|
|
197
136
|
case "Quantumult X":
|
|
198
|
-
// 转换 Quantumult X 专有请求参数。
|
|
199
|
-
// Map request fields to Quantumult X specific options.
|
|
200
137
|
if (resource.policy) _.set(resource, "opts.policy", resource.policy);
|
|
201
138
|
if (typeof resource["auto-redirect"] === "boolean") _.set(resource, "opts.redirection", resource["auto-redirect"]);
|
|
202
|
-
// Quantumult X 使用 `bodyBytes` 传输二进制请求体。
|
|
203
|
-
// Quantumult X uses `bodyBytes` for binary request payloads.
|
|
204
139
|
if (resource.body instanceof ArrayBuffer) {
|
|
205
140
|
resource.bodyBytes = resource.body;
|
|
206
141
|
resource.body = undefined;
|
|
@@ -208,8 +143,6 @@ export async function fetch(resource, options = {}) {
|
|
|
208
143
|
resource.bodyBytes = resource.body.buffer.slice(resource.body.byteOffset, resource.body.byteLength + resource.body.byteOffset);
|
|
209
144
|
resource.body = undefined;
|
|
210
145
|
} else if (resource.body) resource.bodyBytes = undefined;
|
|
211
|
-
// 发送请求,并用 `Promise.race` 提供统一超时保护。
|
|
212
|
-
// Send the request and enforce timeout with `Promise.race`.
|
|
213
146
|
return Promise.race([
|
|
214
147
|
globalThis.$task.fetch(resource).then(
|
|
215
148
|
response => {
|
|
@@ -242,66 +175,9 @@ export async function fetch(resource, options = {}) {
|
|
|
242
175
|
}),
|
|
243
176
|
]);
|
|
244
177
|
case "Worker":
|
|
245
|
-
case "Node.js":
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
switch (resource["auto-cookie"]) {
|
|
250
|
-
case undefined:
|
|
251
|
-
case "true":
|
|
252
|
-
case true:
|
|
253
|
-
case "1":
|
|
254
|
-
case 1:
|
|
255
|
-
default:
|
|
256
|
-
// 仅在尚未包裹 CookieJar 时注入 `fetch-cookie`,避免重复包装。
|
|
257
|
-
// Inject `fetch-cookie` only once when a cookie jar is not already attached.
|
|
258
|
-
if (!globalThis.fetch?.cookieJar) globalThis.fetch = require("fetch-cookie").default(globalThis.fetch);
|
|
259
|
-
break;
|
|
260
|
-
case "false":
|
|
261
|
-
case false:
|
|
262
|
-
case "0":
|
|
263
|
-
case 0:
|
|
264
|
-
case "-1":
|
|
265
|
-
case -1:
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
// 将通用字段映射到 Worker / Node.js Fetch 语义。
|
|
269
|
-
// Map shared fields to Worker / Node.js Fetch semantics.
|
|
270
|
-
resource.redirect = resource.redirection ? "follow" : "manual";
|
|
271
|
-
const { url, ...options } = resource;
|
|
272
|
-
// 发起请求并归一化响应头、文本与二进制响应体。
|
|
273
|
-
// Send the request and normalize headers, text, and binary response data.
|
|
274
|
-
return Promise.race([
|
|
275
|
-
globalThis
|
|
276
|
-
.fetch(url, options)
|
|
277
|
-
.then(async response => {
|
|
278
|
-
const bodyBytes = await response.arrayBuffer();
|
|
279
|
-
let headers;
|
|
280
|
-
try {
|
|
281
|
-
headers = response.headers.raw();
|
|
282
|
-
} catch {
|
|
283
|
-
headers = Array.from(response.headers.entries()).reduce((acc, [key, value]) => {
|
|
284
|
-
acc[key] = acc[key] ? [...acc[key], value] : [value];
|
|
285
|
-
return acc;
|
|
286
|
-
}, {});
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
ok: response.ok ?? /^2\d\d$/.test(response.status),
|
|
290
|
-
status: response.status,
|
|
291
|
-
statusCode: response.status,
|
|
292
|
-
statusText: response.statusText,
|
|
293
|
-
body: new TextDecoder("utf-8").decode(bodyBytes),
|
|
294
|
-
bodyBytes: bodyBytes,
|
|
295
|
-
headers: Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, key.toLowerCase() !== "set-cookie" ? value.toString() : value])),
|
|
296
|
-
};
|
|
297
|
-
})
|
|
298
|
-
.catch(error => Promise.reject(error.message)),
|
|
299
|
-
new Promise((resolve, reject) => {
|
|
300
|
-
setTimeout(() => {
|
|
301
|
-
reject(new Error(`${Function.name}: 请求超时, 请检查网络后重试`));
|
|
302
|
-
}, resource.timeout);
|
|
303
|
-
}),
|
|
304
|
-
]);
|
|
305
|
-
}
|
|
178
|
+
case "Node.js":
|
|
179
|
+
throw new Error(`${Function.name}: ESM 版本不支持 Worker/Node.js,请改用 CJS 入口`);
|
|
180
|
+
default:
|
|
181
|
+
throw new Error(`${Function.name}: 当前平台不支持`);
|
|
306
182
|
}
|
|
307
183
|
}
|
package/polyfill/fetch.mts
CHANGED
package/polyfill/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Aggregated exports for polyfill modules.
|
|
4
4
|
*/
|
|
5
5
|
export { Console } from "./Console.mjs";
|
|
6
|
-
export { fetch } from "./fetch.
|
|
7
|
-
export type { Fetch, FetchRequest, FetchResponse } from "./fetch.
|
|
6
|
+
export { fetch } from "./fetch.mjs";
|
|
7
|
+
export type { Fetch, FetchRequest, FetchResponse } from "./fetch.d.ts";
|
|
8
8
|
export { Lodash } from "./Lodash.mjs";
|
|
9
9
|
export { qs } from "./qs.mjs";
|
|
10
10
|
export { StatusTexts } from "./StatusTexts.mjs";
|
package/polyfill/index.js
CHANGED