@nsnanocat/util 2.2.0 → 2.2.4
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 +40 -25
- package/lib/app.mjs +8 -4
- package/lib/done.mjs +5 -0
- package/lib/environment.mjs +6 -0
- package/lib/notification.mjs +5 -0
- package/package.json +3 -2
- package/polyfill/Console.mjs +4 -3
- package/polyfill/Storage.mjs +24 -4
- package/polyfill/fetch.mjs +37 -29
- package/types/nsnanocat-util.d.ts +1 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @nsnanocat/util
|
|
2
2
|
|
|
3
|
-
用于统一 Quantumult X / Loon / Shadowrocket / Node.js / Egern / Surge / Stash 脚本接口的通用工具库。
|
|
3
|
+
用于统一 Quantumult X / Loon / Shadowrocket / Worker / Node.js / Egern / Surge / Stash 脚本接口的通用工具库。
|
|
4
4
|
|
|
5
5
|
核心目标:
|
|
6
6
|
- 统一不同平台的 HTTP、通知、持久化、结束脚本等调用方式。
|
|
@@ -56,16 +56,16 @@ npm i @nsnanocat/util@latest
|
|
|
56
56
|
|
|
57
57
|
```js
|
|
58
58
|
import {
|
|
59
|
-
$app, // 当前平台名(如 "Surge" / "Loon" / "Quantumult X" / "Node.js")
|
|
59
|
+
$app, // 当前平台名(如 "Surge" / "Loon" / "Quantumult X" / "Worker" / "Node.js")
|
|
60
60
|
$argument, // 已标准化的模块参数对象(导入包时自动处理字符串 -> 对象)
|
|
61
61
|
done, // 统一结束脚本函数(内部自动适配各平台 $done 差异)
|
|
62
|
-
fetch, // 统一 HTTP 请求函数(内部自动适配 $httpClient / $task /
|
|
62
|
+
fetch, // 统一 HTTP 请求函数(内部自动适配 $httpClient / $task / fetch)
|
|
63
63
|
notification, // 统一通知函数(内部自动适配 $notify / $notification.post)
|
|
64
64
|
time, // 时间格式化工具
|
|
65
65
|
wait, // 延时等待工具(Promise)
|
|
66
66
|
Console, // 统一日志工具(支持 logLevel)
|
|
67
67
|
Lodash as _, // Lodash 建议按官方示例惯例使用 `_` 作为工具对象别名
|
|
68
|
-
Storage, // 统一持久化存储接口(适配 $prefs / $persistentStore / 文件)
|
|
68
|
+
Storage, // 统一持久化存储接口(适配 $prefs / $persistentStore / 内存 / 文件)
|
|
69
69
|
} from "@nsnanocat/util";
|
|
70
70
|
```
|
|
71
71
|
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
| `lib/notification.mjs` | `lib/app.mjs`, `polyfill/Console.mjs` | `$app`, `Console.group`, `Console.log`, `Console.groupEnd`, `Console.error` | 将通知参数映射到各平台通知接口并统一日志输出 |
|
|
105
105
|
| `lib/runScript.mjs` | `polyfill/Console.mjs`, `polyfill/fetch.mjs`, `polyfill/Storage.mjs`, `polyfill/Lodash.mjs` | `Console.error`, `fetch`, `Storage.getItem`(`Lodash` 当前版本未实际调用) | 读取 BoxJS 配置并发起统一 HTTP 调用执行脚本 |
|
|
106
106
|
| `getStorage.mjs` | `lib/argument.mjs`, `polyfill/Console.mjs`, `polyfill/Lodash.mjs`, `polyfill/Storage.mjs` | `Console.debug`, `Console.logLevel`, `Lodash.merge`, `Storage.getItem` | 先标准化 `$argument`,再合并默认配置/持久化配置/运行参数 |
|
|
107
|
-
| `polyfill/Console.mjs` | `lib/app.mjs` | `$app` | 日志在 Node.js 与 iOS 脚本环境使用不同错误输出策略 |
|
|
107
|
+
| `polyfill/Console.mjs` | `lib/app.mjs` | `$app` | 日志在 Worker / Node.js 与 iOS 脚本环境使用不同错误输出策略 |
|
|
108
108
|
| `polyfill/fetch.mjs` | `lib/app.mjs`, `polyfill/Lodash.mjs`, `polyfill/StatusTexts.mjs`, `polyfill/Console.mjs` | `$app`, `Lodash.set`, `StatusTexts`(`Console` 当前版本未实际调用) | 按平台选请求引擎并做参数映射、响应结构统一 |
|
|
109
109
|
| `polyfill/Storage.mjs` | `lib/app.mjs`, `polyfill/Lodash.mjs` | `$app`, `Lodash.get`, `Lodash.set`, `Lodash.unset` | 按平台选持久化后端并支持 `@key.path` 读写 |
|
|
110
110
|
| `polyfill/Lodash.mjs` | 无 | 无 | 提供路径/合并等基础能力,被多个模块复用 |
|
|
@@ -116,7 +116,7 @@ import {
|
|
|
116
116
|
### `lib/app.mjs` 与 `lib/environment.mjs`(平台识别与环境)
|
|
117
117
|
|
|
118
118
|
#### `$app`
|
|
119
|
-
- 类型:`"Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Node.js" | undefined`
|
|
119
|
+
- 类型:`"Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Worker" | "Node.js" | undefined`
|
|
120
120
|
- 角色:核心模块。库内所有存在平台行为差异的模块都会先读取 `$app` 再分流(如 `done`、`notification`、`fetch`、`Storage`、`Console`、`environment`)。
|
|
121
121
|
- 读取方式:
|
|
122
122
|
|
|
@@ -133,9 +133,10 @@ console.log(appName);
|
|
|
133
133
|
4. 存在 `Egern` -> `Egern`
|
|
134
134
|
5. 存在 `$environment` 且有 `surge-version` -> `Surge`
|
|
135
135
|
6. 存在 `$environment` 且有 `stash-version` -> `Stash`
|
|
136
|
-
7. 存在 `
|
|
137
|
-
8.
|
|
138
|
-
|
|
136
|
+
7. 存在 `Cloudflare` -> `Worker`
|
|
137
|
+
8. 存在 `process.versions.node` -> `Node.js`
|
|
138
|
+
9. 默认回落 -> `undefined`
|
|
139
|
+
- 实现细节:内部使用 `'key' in globalThis` 检测平台标记,避免 `Object.keys(globalThis)` 漏掉不可枚举全局变量;当前 Worker 识别以 `Cloudflare` 全局标记为准。
|
|
139
140
|
|
|
140
141
|
#### `$environment` / `environment()`
|
|
141
142
|
- 路径:`lib/environment.mjs`(未从包主入口导出)
|
|
@@ -157,6 +158,7 @@ console.log(environment()); // 当前环境对象
|
|
|
157
158
|
| Egern | 读取全局 `$environment`,再写入 `app` | `{ ..., app: "Egern" }` |
|
|
158
159
|
| Loon | 读取全局 `$loon` 字符串并拆分 | `{ device, ios, "loon-version", app: "Loon" }` |
|
|
159
160
|
| Quantumult X | 不读取额外环境字段,直接构造对象 | `{ app: "Quantumult X" }` |
|
|
161
|
+
| Worker | 直接构造对象 | `{ app: "Worker" }` |
|
|
160
162
|
| Node.js | 读取 `process.env` 并写入 `process.env.app` | `{ ..., app: "Node.js" }` |
|
|
161
163
|
| 其他 | 无 | `{}` |
|
|
162
164
|
|
|
@@ -194,7 +196,7 @@ console.log($argument); // { mode: "on", a: { b: "1" } }
|
|
|
194
196
|
|
|
195
197
|
#### `done(object = {})`
|
|
196
198
|
- 签名:`done(object?: object): void`
|
|
197
|
-
- 作用:统一不同平台的脚本结束接口(`$done` / Node 退出)。
|
|
199
|
+
- 作用:统一不同平台的脚本结束接口(`$done` / Worker 日志结束 / Node 退出)。
|
|
198
200
|
|
|
199
201
|
说明:下表描述的是各 App 原生接口差异与本库内部映射逻辑。调用方只需要按 `done` 的统一参数传值即可,不需要自己再写平台分支。
|
|
200
202
|
|
|
@@ -216,12 +218,14 @@ console.log($argument); // { mode: "on", a: { b: "1" } }
|
|
|
216
218
|
| Egern | 不转换 | 透传 | 透传 | `$done(object)` |
|
|
217
219
|
| Shadowrocket | 不转换 | 透传 | 透传 | `$done(object)` |
|
|
218
220
|
| Quantumult X | 写入 `opts.policy` | `number` 会转 `HTTP/1.1 200 OK` 字符串 | 仅保留 `status/url/headers/body/bodyBytes`;`ArrayBuffer/TypedArray` 转 `bodyBytes` | `$done(object)` |
|
|
221
|
+
| Worker | 不适用 | 不适用 | 不适用 | 仅记录结束日志 |
|
|
219
222
|
| Node.js | 不适用 | 不适用 | 不适用 | `process.exit(1)` |
|
|
220
223
|
|
|
221
224
|
不可用/差异点:
|
|
222
225
|
- `policy` 在 Egern / Shadowrocket 分支不做映射。
|
|
223
226
|
- Quantumult X 会丢弃未在白名单内的字段。
|
|
224
227
|
- Quantumult X 的 `status` 在部分场景要求完整状态行(如 `HTTP/1.1 200 OK`),本库会在传入数字状态码时自动拼接(依赖 `StatusTexts`)。
|
|
228
|
+
- Worker 不调用 `$done`,仅记录结束日志。
|
|
225
229
|
- Node.js 不调用 `$done`,而是直接退出进程,且退出码固定为 `1`。
|
|
226
230
|
- 未识别平台(`$app === undefined`)只记录结束日志,不会尝试调用 `$done` 或退出进程。
|
|
227
231
|
|
|
@@ -252,11 +256,13 @@ console.log($argument); // { mode: "on", a: { b: "1" } }
|
|
|
252
256
|
| Shadowrocket | `$notification.post` | `{ openUrl: content }` | 走 Surge 分支的 action/url/text/media 字段 |
|
|
253
257
|
| Loon | `$notification.post` | `{ openUrl: content }` | `openUrl`、`mediaUrl`(仅 http/https) |
|
|
254
258
|
| Quantumult X | `$notify` | `{ "open-url": content }` | `open-url`、`media-url`(仅 http/https)、`update-pasteboard` |
|
|
259
|
+
| Worker | 不发送通知(非 iOS App 环境) | 无 | 无 |
|
|
255
260
|
| Node.js | 不发送通知(非 iOS App 环境) | 无 | 无 |
|
|
256
261
|
|
|
257
262
|
不可用/差异点:
|
|
258
263
|
- `copy/update-pasteboard` 在 Loon 分支不会生效。
|
|
259
264
|
- Loon / Quantumult X 对 `media` 仅接受网络 URL;Base64 媒体不会自动映射。
|
|
265
|
+
- Worker 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
|
|
260
266
|
- Node.js 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
|
|
261
267
|
|
|
262
268
|
### `lib/time.mjs`
|
|
@@ -384,7 +390,7 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
384
390
|
- `timeout`
|
|
385
391
|
- `policy`
|
|
386
392
|
- `redirection` / `auto-redirect`
|
|
387
|
-
- `auto-cookie
|
|
393
|
+
- `auto-cookie`(Worker / Node.js 共享分支识别;默认启用,传入 `false` / `0` / `-1` 可关闭)
|
|
388
394
|
|
|
389
395
|
说明:下表是各 App 原生 HTTP 接口的差异补充,以及本库 `fetch` 的内部映射方式。调用方使用统一入参即可。
|
|
390
396
|
|
|
@@ -398,6 +404,7 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
398
404
|
| Egern | `$httpClient[method]` | 秒 | 无专门映射 | `auto-redirect` | 同上 |
|
|
399
405
|
| Shadowrocket | `$httpClient[method]` | 秒 | `headers.X-Surge-Proxy` | `auto-redirect` | 同上 |
|
|
400
406
|
| Quantumult X | `$task.fetch` | 毫秒(内部乘 1000) | `opts.policy` | `opts.redirection` | `body(ArrayBuffer/TypedArray)` 转 `bodyBytes`;响应按 `Content-Type` 恢复到 `body` |
|
|
407
|
+
| Worker | `globalThis.fetch`(不存在时回退 `node-fetch`);共享 `auto-cookie` 处理 | 毫秒(内部乘 1000) | 无 | `redirect: follow/manual` | 返回 `body`(UTF-8 string) + `bodyBytes`(ArrayBuffer) |
|
|
401
408
|
| Node.js | `globalThis.fetch`(不存在时回退 `node-fetch`);默认按需包裹 `fetch-cookie` | 毫秒(内部乘 1000) | 无 | `redirect: follow/manual` | 返回 `body`(UTF-8 string) + `bodyBytes`(ArrayBuffer) |
|
|
402
409
|
|
|
403
410
|
返回对象(统一后)常见字段:
|
|
@@ -410,9 +417,9 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
410
417
|
- `bodyBytes`
|
|
411
418
|
|
|
412
419
|
不可用/差异点:
|
|
413
|
-
- `policy` 在 Surge / Egern / Node.js 分支没有额外适配逻辑。
|
|
420
|
+
- `policy` 在 Surge / Egern / Worker / Node.js 分支没有额外适配逻辑。
|
|
414
421
|
- `redirection` 在部分平台会映射为 `auto-redirect` 或 `opts.redirection`。
|
|
415
|
-
- Node.js
|
|
422
|
+
- Worker / Node.js 共享基于 `fetch` 的请求分支;若 `globalThis.fetch` 不存在则回退到 `node-fetch`,并在 `auto-cookie` 未关闭时按需包裹 `fetch-cookie`。
|
|
416
423
|
- 传入 `timeout` 时,`5` 和 `5000` 都会被接受;库会先将用户输入归一化,再按平台要求转换为秒或毫秒。
|
|
417
424
|
- 返回结构是统一兼容结构,不等同于浏览器 `Response` 对象。
|
|
418
425
|
|
|
@@ -435,21 +442,24 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
435
442
|
#### `Storage.removeItem(keyName)`
|
|
436
443
|
- Quantumult X:可用(`$prefs.removeValueForKey`)。
|
|
437
444
|
- Surge:通过 `$persistentStore.write(null, keyName)` 删除。
|
|
445
|
+
- Worker:可用(仅删除内存缓存中的对应 key,不持久化)。
|
|
438
446
|
- Node.js:可用(删除 `box.dat` 中对应 key 并落盘)。
|
|
439
447
|
- Loon / Stash / Egern / Shadowrocket:返回 `false`。
|
|
440
448
|
|
|
441
449
|
#### `Storage.clear()`
|
|
442
450
|
- Quantumult X:可用(`$prefs.removeAllValues`)。
|
|
451
|
+
- Worker:可用(仅清空内存缓存,不持久化)。
|
|
443
452
|
- Node.js:可用(清空 `box.dat` 并落盘)。
|
|
444
453
|
- 其他平台:返回 `false`。
|
|
445
454
|
|
|
446
|
-
#### Node.js 特性
|
|
455
|
+
#### Worker / Node.js 特性
|
|
456
|
+
- Worker:使用进程内内存缓存,不写文件。
|
|
447
457
|
- 数据文件默认:`box.dat`。
|
|
448
458
|
- 读取路径优先级:当前目录 -> `process.cwd()`。
|
|
449
459
|
|
|
450
460
|
与 Web Storage 的行为差异:
|
|
451
461
|
- 支持 `@key.path` 深路径读写(Web Storage 原生不支持)。
|
|
452
|
-
- `removeItem/clear` 仅部分平台可用(目前为 Quantumult X、Node.js,以及 Surge 的 `removeItem`)。
|
|
462
|
+
- `removeItem/clear` 仅部分平台可用(目前为 Quantumult X、Worker、Node.js,以及 Surge 的 `removeItem`)。
|
|
453
463
|
- `getItem` 会尝试 `JSON.parse`,`setItem` 写入对象会 `JSON.stringify`。
|
|
454
464
|
|
|
455
465
|
平台后端映射:
|
|
@@ -458,6 +468,7 @@ const store = getStorage("@my_box", ["YouTube", "Global"], database);
|
|
|
458
468
|
| --- | --- |
|
|
459
469
|
| Surge / Loon / Stash / Egern / Shadowrocket | `$persistentStore.read/write` |
|
|
460
470
|
| Quantumult X | `$prefs.valueForKey/setValueForKey` |
|
|
471
|
+
| Worker | 进程内内存缓存 |
|
|
461
472
|
| Node.js | 本地 `box.dat` |
|
|
462
473
|
|
|
463
474
|
### `polyfill/Console.mjs`
|
|
@@ -507,7 +518,7 @@ console.log(Console.logLevel); // "WARN"
|
|
|
507
518
|
| `count(label)` | `label?: string` | `void` | 计数并输出 |
|
|
508
519
|
| `countReset(label)` | `label?: string` | `void` | 重置计数器 |
|
|
509
520
|
| `debug(...msg)` | `...msg: any[]` | `void` | 仅 `DEBUG/ALL` 级别输出 |
|
|
510
|
-
| `error(...msg)` | `...msg: any[]` | `void` | Node.js 优先输出 `stack` |
|
|
521
|
+
| `error(...msg)` | `...msg: any[]` | `void` | Worker / Node.js 优先输出 `stack` |
|
|
511
522
|
| `exception(...msg)` | `...msg: any[]` | `void` | `error` 别名 |
|
|
512
523
|
| `group(label)` | `label: string` | `void` | 压栈分组 |
|
|
513
524
|
| `groupEnd()` | 无 | `void` | 出栈分组 |
|
|
@@ -519,7 +530,7 @@ console.log(Console.logLevel); // "WARN"
|
|
|
519
530
|
| `warn(...msg)` | `...msg: any[]` | `void` | `WARN` 及以上 |
|
|
520
531
|
|
|
521
532
|
平台差异:
|
|
522
|
-
- Node.js 下 `error` 会优先打印 `Error.stack`。
|
|
533
|
+
- Worker / Node.js 下 `error` 会优先打印 `Error.stack`。
|
|
523
534
|
- 其他平台统一加前缀符号输出(`❌/⚠️/ℹ️/🅱️`)。
|
|
524
535
|
|
|
525
536
|
### `polyfill/Lodash.mjs`
|
|
@@ -623,18 +634,19 @@ console.log(value); // 1
|
|
|
623
634
|
|
|
624
635
|
说明:本节展示的是各平台原生脚本接口差异。实际在本库中,这些差异已由 `done`、`fetch`、`notification`、`Storage` 等模块做了统一适配。
|
|
625
636
|
|
|
626
|
-
| 能力 | Quantumult X | Loon | Surge | Stash | Egern | Shadowrocket | Node.js |
|
|
627
|
-
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
628
|
-
| HTTP 请求 | `$task.fetch` | `$httpClient` | `$httpClient` | `$httpClient` | `$httpClient` | `$httpClient` | `fetch` |
|
|
629
|
-
| 通知 | `$notify` | `$notification.post` | `$notification.post` | `$notification.post` | `$notification.post` | `$notification.post` | 无 |
|
|
630
|
-
| 持久化 | `$prefs` | `$persistentStore` | `$persistentStore` | `$persistentStore` | `$persistentStore` | `$persistentStore` | `box.dat` |
|
|
631
|
-
| 结束脚本 | `$done` | `$done` | `$done` | `$done` | `$done` | `$done` | `process.exit(1)` |
|
|
632
|
-
| `removeItem/clear` | 可用 | 不可用 | `removeItem` 可用 / `clear` 不可用 | 不可用 | 不可用 | 不可用 | 可用 |
|
|
633
|
-
| `policy` 注入(`fetch/done`) | `opts.policy` | `node` | `X-Surge-Policy`(done) | `X-Stash-Selected-Proxy` | 无专门映射 | `X-Surge-Proxy`(fetch) | 无 |
|
|
637
|
+
| 能力 | Quantumult X | Loon | Surge | Stash | Egern | Shadowrocket | Worker | Node.js |
|
|
638
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
639
|
+
| HTTP 请求 | `$task.fetch` | `$httpClient` | `$httpClient` | `$httpClient` | `$httpClient` | `$httpClient` | `fetch` | `fetch` |
|
|
640
|
+
| 通知 | `$notify` | `$notification.post` | `$notification.post` | `$notification.post` | `$notification.post` | `$notification.post` | 无 | 无 |
|
|
641
|
+
| 持久化 | `$prefs` | `$persistentStore` | `$persistentStore` | `$persistentStore` | `$persistentStore` | `$persistentStore` | 内存缓存 | `box.dat` |
|
|
642
|
+
| 结束脚本 | `$done` | `$done` | `$done` | `$done` | `$done` | `$done` | 仅日志 | `process.exit(1)` |
|
|
643
|
+
| `removeItem/clear` | 可用 | 不可用 | `removeItem` 可用 / `clear` 不可用 | 不可用 | 不可用 | 不可用 | 可用 | 可用 |
|
|
644
|
+
| `policy` 注入(`fetch/done`) | `opts.policy` | `node` | `X-Surge-Policy`(done) | `X-Stash-Selected-Proxy` | 无专门映射 | `X-Surge-Proxy`(fetch) | 无 | 无 |
|
|
634
645
|
|
|
635
646
|
## 已知限制与注意事项
|
|
636
647
|
|
|
637
648
|
- `lib/argument.mjs` 为 `$argument` 标准化模块,`import` 时会按规则重写全局 `$argument`。
|
|
649
|
+
- `lib/done.mjs` 在 Worker 仅记录结束日志。
|
|
638
650
|
- `lib/done.mjs` 在 Node.js 固定 `process.exit(1)`。
|
|
639
651
|
- `Storage.removeItem("@a.b")` 分支存在未声明变量写入风险;如要大量使用路径删除,建议先本地验证。
|
|
640
652
|
- `lib/runScript.mjs` 未从包主入口导出,需要按文件路径直接导入。
|
|
@@ -662,6 +674,9 @@ console.log(value); // 1
|
|
|
662
674
|
- [crossutility/Quantumult-X - sample-fetch-opts-policy.js](https://raw.githubusercontent.com/crossutility/Quantumult-X/master/sample-fetch-opts-policy.js)
|
|
663
675
|
- [crossutility/Quantumult-X - sample-rewrite-response-header.js](https://github.com/crossutility/Quantumult-X/raw/refs/heads/master/sample-rewrite-response-header.js)
|
|
664
676
|
|
|
677
|
+
### Worker
|
|
678
|
+
- 以 `Cloudflare` 全局标记识别 Worker 运行时。
|
|
679
|
+
|
|
665
680
|
### Node.js
|
|
666
681
|
- [Node.js Globals - fetch](https://nodejs.org/api/globals.html#fetch)
|
|
667
682
|
|
package/lib/app.mjs
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* 4) `Egern` -> Egern
|
|
11
11
|
* 5) `$environment["surge-version"]` -> Surge
|
|
12
12
|
* 6) `$environment["stash-version"]` -> Stash
|
|
13
|
-
* 7) `
|
|
14
|
-
* 8)
|
|
13
|
+
* 7) `Cloudflare` -> Worker
|
|
14
|
+
* 8) `process.versions.node` -> Node.js
|
|
15
|
+
* 9) 默认回落 -> undefined
|
|
15
16
|
* default fallback -> undefined
|
|
16
17
|
*
|
|
17
18
|
* 说明:
|
|
@@ -19,10 +20,10 @@
|
|
|
19
20
|
* - 使用 `'key' in globalThis`,避免 `Object.keys` 对不可枚举全局变量漏检。
|
|
20
21
|
* - Use `'key' in globalThis` to avoid missing non-enumerable globals with `Object.keys`.
|
|
21
22
|
*
|
|
22
|
-
* @type {("Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Node.js" | undefined)}
|
|
23
|
+
* @type {("Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Worker" | "Node.js" | undefined)}
|
|
23
24
|
*/
|
|
24
25
|
export const $app = (() => {
|
|
25
|
-
const has =
|
|
26
|
+
const has = key => key in globalThis;
|
|
26
27
|
switch (true) {
|
|
27
28
|
case has("$task"):
|
|
28
29
|
return "Quantumult X";
|
|
@@ -36,6 +37,9 @@ export const $app = (() => {
|
|
|
36
37
|
return "Surge";
|
|
37
38
|
case Boolean(globalThis.$environment?.["stash-version"]):
|
|
38
39
|
return "Stash";
|
|
40
|
+
case has("Cloudflare"):
|
|
41
|
+
//case has("ServiceWorkerGlobalScope") && has("self") && has("caches") && has("scheduler"):
|
|
42
|
+
return "Worker";
|
|
39
43
|
case Boolean(globalThis.process?.versions?.node):
|
|
40
44
|
return "Node.js";
|
|
41
45
|
default:
|
package/lib/done.mjs
CHANGED
|
@@ -24,6 +24,8 @@ import { StatusTexts } from "../polyfill/StatusTexts.mjs";
|
|
|
24
24
|
* Notes:
|
|
25
25
|
* - 这是调用入口,平台原生 `$done` 差异在内部处理
|
|
26
26
|
* - This is the call entry and native `$done` differences are handled internally
|
|
27
|
+
* - Worker 不调用 `$done` 或退出进程,仅记录日志
|
|
28
|
+
* - Worker neither calls `$done` nor exits the process; it only logs
|
|
27
29
|
* - Node.js 不调用 `$done`,而是直接退出进程
|
|
28
30
|
* - Node.js does not call `$done`; it exits the process directly
|
|
29
31
|
* - 未识别平台仅记录结束日志,不会强制退出
|
|
@@ -80,6 +82,9 @@ export function done(object = {}) {
|
|
|
80
82
|
Console.log("🚩 执行结束!");
|
|
81
83
|
$done(object);
|
|
82
84
|
break;
|
|
85
|
+
case "Worker":
|
|
86
|
+
Console.log("🚩 执行结束!");
|
|
87
|
+
break;
|
|
83
88
|
case "Node.js":
|
|
84
89
|
Console.log("🚩 执行结束!");
|
|
85
90
|
process.exit(1);
|
package/lib/environment.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import { $app } from "./app.mjs";
|
|
|
10
10
|
* - Loon: parse `$loon` into device/version fields
|
|
11
11
|
* - Quantumult X: 仅返回 `{ app: "Quantumult X" }`
|
|
12
12
|
* - Quantumult X: returns `{ app: "Quantumult X" }` only
|
|
13
|
+
* - Worker: 返回 `{ app: "Worker" }`
|
|
14
|
+
* - Worker: returns `{ app: "Worker" }`
|
|
13
15
|
* - Node.js: 复用 `process.env` 并写入 `process.env.app`
|
|
14
16
|
* - Node.js: reuses `process.env` and writes `process.env.app`
|
|
15
17
|
*
|
|
@@ -47,6 +49,10 @@ export function environment() {
|
|
|
47
49
|
return {
|
|
48
50
|
app: "Quantumult X",
|
|
49
51
|
};
|
|
52
|
+
case "Worker":
|
|
53
|
+
return {
|
|
54
|
+
app: "Worker",
|
|
55
|
+
};
|
|
50
56
|
case "Node.js":
|
|
51
57
|
process.env.app = "Node.js";
|
|
52
58
|
return process.env;
|
package/lib/notification.mjs
CHANGED
|
@@ -29,6 +29,8 @@ import { Console } from "../polyfill/Console.mjs";
|
|
|
29
29
|
* Notes:
|
|
30
30
|
* - iOS App 平台调用 `$notification.post` 或 `$notify`
|
|
31
31
|
* - iOS app platforms call `$notification.post` or `$notify`
|
|
32
|
+
* - Worker 不支持 iOS 通知接口,仅输出日志
|
|
33
|
+
* - Worker does not support iOS notification APIs; it logs only
|
|
32
34
|
* - Node.js 不支持 iOS 通知接口,仅输出日志
|
|
33
35
|
* - Node.js does not support iOS notification APIs; it logs only
|
|
34
36
|
*
|
|
@@ -52,6 +54,7 @@ export function notification(title = `ℹ️ ${$app} 通知`, subtitle = "", bod
|
|
|
52
54
|
case "Quantumult X":
|
|
53
55
|
$notify(title, subtitle, body, mutableContent);
|
|
54
56
|
break;
|
|
57
|
+
case "Worker":
|
|
55
58
|
case "Node.js":
|
|
56
59
|
break;
|
|
57
60
|
}
|
|
@@ -90,6 +93,7 @@ const MutableContent = content => {
|
|
|
90
93
|
case "Quantumult X":
|
|
91
94
|
mutableContent["open-url"] = content;
|
|
92
95
|
break;
|
|
96
|
+
case "Worker":
|
|
93
97
|
case "Node.js":
|
|
94
98
|
break;
|
|
95
99
|
}
|
|
@@ -167,6 +171,7 @@ const MutableContent = content => {
|
|
|
167
171
|
if (copyUrl) mutableContent["update-pasteboard"] = copyUrl;
|
|
168
172
|
break;
|
|
169
173
|
}
|
|
174
|
+
case "Worker":
|
|
170
175
|
case "Node.js":
|
|
171
176
|
break;
|
|
172
177
|
}
|
package/package.json
CHANGED
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
"types"
|
|
36
36
|
],
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"
|
|
38
|
+
"@biomejs/biome": "2.4.6",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
39
40
|
},
|
|
40
41
|
"publishConfig": {
|
|
41
42
|
"registry": "https://registry.npmjs.org/",
|
|
42
43
|
"access": "public"
|
|
43
44
|
},
|
|
44
|
-
"version": "2.2.
|
|
45
|
+
"version": "2.2.4"
|
|
45
46
|
}
|
package/polyfill/Console.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { $app } from "../lib/app.mjs";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Unified logger compatible with script platforms and Node.js.
|
|
4
|
+
* 统一日志工具,兼容各脚本平台、Worker 与 Node.js。
|
|
5
|
+
* Unified logger compatible with script platforms, Worker, and Node.js.
|
|
6
6
|
*
|
|
7
7
|
* logLevel 用法:
|
|
8
8
|
* logLevel usage:
|
|
@@ -99,8 +99,9 @@ export class Console {
|
|
|
99
99
|
default:
|
|
100
100
|
msg = msg.map(m => `❌ ${m}`);
|
|
101
101
|
break;
|
|
102
|
+
case "Worker":
|
|
102
103
|
case "Node.js":
|
|
103
|
-
msg = msg.map(m => `❌ ${m
|
|
104
|
+
msg = msg.map(m => `❌ ${m?.stack ?? m}`);
|
|
104
105
|
break;
|
|
105
106
|
}
|
|
106
107
|
Console.log(...msg);
|
package/polyfill/Storage.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import { Lodash as _ } from "./Lodash.mjs";
|
|
|
16
16
|
* Supported backends:
|
|
17
17
|
* - Surge/Loon/Stash/Egern/Shadowrocket: `$persistentStore`
|
|
18
18
|
* - Quantumult X: `$prefs`
|
|
19
|
+
* - Worker: 内存缓存(非持久化)
|
|
20
|
+
* - Worker: in-memory cache (non-persistent)
|
|
19
21
|
* - Node.js: 本地 `box.dat`
|
|
20
22
|
* - Node.js: local `box.dat`
|
|
21
23
|
*
|
|
@@ -37,8 +39,8 @@ import { Lodash as _ } from "./Lodash.mjs";
|
|
|
37
39
|
*/
|
|
38
40
|
export class Storage {
|
|
39
41
|
/**
|
|
40
|
-
* Node.js 环境下的内存数据缓存。
|
|
41
|
-
* In-memory data cache for Node.js runtime.
|
|
42
|
+
* Worker / Node.js 环境下的内存数据缓存。
|
|
43
|
+
* In-memory data cache for Worker / Node.js runtime.
|
|
42
44
|
*
|
|
43
45
|
* @type {Record<string, any>|null}
|
|
44
46
|
*/
|
|
@@ -95,6 +97,10 @@ export class Storage {
|
|
|
95
97
|
case "Quantumult X":
|
|
96
98
|
keyValue = $prefs.valueForKey(keyName);
|
|
97
99
|
break;
|
|
100
|
+
case "Worker":
|
|
101
|
+
Storage.data = Storage.data ?? {};
|
|
102
|
+
keyValue = Storage.data[keyName];
|
|
103
|
+
break;
|
|
98
104
|
case "Node.js":
|
|
99
105
|
Storage.data = Storage.#loaddata(Storage.dataFile);
|
|
100
106
|
keyValue = Storage.data?.[keyName];
|
|
@@ -153,6 +159,11 @@ export class Storage {
|
|
|
153
159
|
case "Quantumult X":
|
|
154
160
|
result = $prefs.setValueForKey(keyValue, keyName);
|
|
155
161
|
break;
|
|
162
|
+
case "Worker":
|
|
163
|
+
Storage.data = Storage.data ?? {};
|
|
164
|
+
Storage.data[keyName] = keyValue;
|
|
165
|
+
result = true;
|
|
166
|
+
break;
|
|
156
167
|
case "Node.js":
|
|
157
168
|
Storage.data = Storage.#loaddata(Storage.dataFile);
|
|
158
169
|
Storage.data[keyName] = keyValue;
|
|
@@ -207,6 +218,11 @@ export class Storage {
|
|
|
207
218
|
case "Quantumult X":
|
|
208
219
|
result = $prefs.removeValueForKey(keyName);
|
|
209
220
|
break;
|
|
221
|
+
case "Worker":
|
|
222
|
+
Storage.data = Storage.data ?? {};
|
|
223
|
+
delete Storage.data[keyName];
|
|
224
|
+
result = true;
|
|
225
|
+
break;
|
|
210
226
|
case "Node.js":
|
|
211
227
|
// result = false;
|
|
212
228
|
Storage.data = Storage.#loaddata(Storage.dataFile);
|
|
@@ -224,8 +240,8 @@ export class Storage {
|
|
|
224
240
|
}
|
|
225
241
|
|
|
226
242
|
/**
|
|
227
|
-
*
|
|
228
|
-
* Clear storage
|
|
243
|
+
* 清空存储。
|
|
244
|
+
* Clear storage.
|
|
229
245
|
*
|
|
230
246
|
* @returns {boolean}
|
|
231
247
|
*/
|
|
@@ -242,6 +258,10 @@ export class Storage {
|
|
|
242
258
|
case "Quantumult X":
|
|
243
259
|
result = $prefs.removeAllValues();
|
|
244
260
|
break;
|
|
261
|
+
case "Worker":
|
|
262
|
+
Storage.data = {};
|
|
263
|
+
result = true;
|
|
264
|
+
break;
|
|
245
265
|
case "Node.js":
|
|
246
266
|
// result = false;
|
|
247
267
|
Storage.data = Storage.#loaddata(Storage.dataFile);
|
package/polyfill/fetch.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
17
17
|
* @property {string} [policy] 指定策略 / Preferred policy.
|
|
18
18
|
* @property {boolean} [redirection] 是否跟随重定向 / Whether to follow redirects.
|
|
19
19
|
* @property {boolean} ["auto-redirect"] 平台重定向字段 / Platform redirect flag.
|
|
20
|
-
* @property {boolean|number|string} ["auto-cookie"] Node.js Cookie 开关 / Node.js Cookie toggle.
|
|
20
|
+
* @property {boolean|number|string} ["auto-cookie"] Worker / Node.js Cookie 开关 / Worker / Node.js Cookie toggle.
|
|
21
21
|
* @property {Record<string, any>} [opts] 平台扩展字段 / Platform extension fields.
|
|
22
22
|
*/
|
|
23
23
|
|
|
@@ -43,13 +43,13 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
43
43
|
* Design goal:
|
|
44
44
|
* - 仿照 Web API `fetch`(`Window.fetch`)接口设计
|
|
45
45
|
* - Modeled after Web API `fetch` (`Window.fetch`)
|
|
46
|
-
* - 统一 VPN App 与 Node.js 环境中的请求调用
|
|
47
|
-
* - Unify request calls across VPN apps and Node.js
|
|
46
|
+
* - 统一 VPN App、Worker 与 Node.js 环境中的请求调用
|
|
47
|
+
* - Unify request calls across VPN apps, Worker, and Node.js
|
|
48
48
|
*
|
|
49
49
|
* 功能:
|
|
50
50
|
* Features:
|
|
51
|
-
* - 统一 Quantumult X / Loon / Surge / Stash / Egern / Shadowrocket / Node.js 请求接口
|
|
52
|
-
* - Normalize request APIs across Quantumult X / Loon / Surge / Stash / Egern / Shadowrocket / Node.js
|
|
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
53
|
* - 统一返回体字段(`ok/status/statusText/body/bodyBytes`)
|
|
54
54
|
* - Normalize response fields (`ok/status/statusText/body/bodyBytes`)
|
|
55
55
|
*
|
|
@@ -57,8 +57,10 @@ import { StatusTexts } from "./StatusTexts.mjs";
|
|
|
57
57
|
* Known differences from Web `fetch`:
|
|
58
58
|
* - 支持 `policy`、`auto-redirect` 等平台扩展字段
|
|
59
59
|
* - Supports platform extension fields like `policy` and `auto-redirect`
|
|
60
|
-
* - Node.js
|
|
61
|
-
* - Node.js
|
|
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
|
|
62
64
|
* - 非浏览器平台通过 `$httpClient/$task` 实现,不是原生 Fetch 实现
|
|
63
65
|
* - Non-browser platforms use `$httpClient/$task` instead of native Fetch engine
|
|
64
66
|
* - 返回结构包含 `statusCode/bodyBytes` 等兼容字段
|
|
@@ -111,6 +113,26 @@ export async function fetch(resource, options = {}) {
|
|
|
111
113
|
// Convert to seconds first and treat values above 500 as milliseconds.
|
|
112
114
|
if (resource.timeout > 500) resource.timeout = Math.round(resource.timeout / 1000);
|
|
113
115
|
}
|
|
116
|
+
// 某些平台要求毫秒级超时,进行二次换算。
|
|
117
|
+
// Some platforms expect timeout in milliseconds, so convert again.
|
|
118
|
+
if (resource.timeout) {
|
|
119
|
+
switch ($app) {
|
|
120
|
+
case "Loon":
|
|
121
|
+
case "Quantumult X":
|
|
122
|
+
case "Worker":
|
|
123
|
+
case "Node.js":
|
|
124
|
+
// 这些平台要求毫秒,因此把秒重新换算为毫秒。
|
|
125
|
+
// These platforms expect milliseconds, so convert seconds back to milliseconds.
|
|
126
|
+
resource.timeout = resource.timeout * 1000;
|
|
127
|
+
break;
|
|
128
|
+
case "Shadowrocket":
|
|
129
|
+
case "Stash":
|
|
130
|
+
case "Egern":
|
|
131
|
+
case "Surge":
|
|
132
|
+
default:
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
114
136
|
// 根据当前平台选择请求实现。
|
|
115
137
|
// Select the request engine for the current platform.
|
|
116
138
|
switch ($app) {
|
|
@@ -122,23 +144,6 @@ export async function fetch(resource, options = {}) {
|
|
|
122
144
|
default:
|
|
123
145
|
// 转换通用请求参数到 `$httpClient` 语义。
|
|
124
146
|
// Map shared request fields to `$httpClient` semantics.
|
|
125
|
-
if (resource.timeout) {
|
|
126
|
-
switch ($app) {
|
|
127
|
-
case "Loon":
|
|
128
|
-
case "Quantumult X":
|
|
129
|
-
case "Node.js":
|
|
130
|
-
// 这些平台要求毫秒,因此把秒重新换算为毫秒。
|
|
131
|
-
// These platforms expect milliseconds, so convert seconds back to milliseconds.
|
|
132
|
-
resource.timeout = resource.timeout * 1000;
|
|
133
|
-
break;
|
|
134
|
-
case "Shadowrocket":
|
|
135
|
-
case "Stash":
|
|
136
|
-
case "Egern":
|
|
137
|
-
case "Surge":
|
|
138
|
-
default:
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
147
|
if (resource.policy) {
|
|
143
148
|
switch ($app) {
|
|
144
149
|
case "Loon":
|
|
@@ -236,10 +241,13 @@ export async function fetch(resource, options = {}) {
|
|
|
236
241
|
}, resource.timeout);
|
|
237
242
|
}),
|
|
238
243
|
]);
|
|
244
|
+
case "Worker":
|
|
239
245
|
case "Node.js": {
|
|
240
|
-
// Node.js
|
|
241
|
-
//
|
|
242
|
-
if (!globalThis.fetch)
|
|
246
|
+
// Worker 复用宿主 `fetch`;Node.js 优先复用原生 `fetch`,缺失时再回退到 `node-fetch`。
|
|
247
|
+
// Worker reuses host `fetch`; Node.js reuses native `fetch` first and falls back to `node-fetch`.
|
|
248
|
+
if (!globalThis.fetch) {
|
|
249
|
+
globalThis.fetch = require("node-fetch");
|
|
250
|
+
}
|
|
243
251
|
switch (resource["auto-cookie"]) {
|
|
244
252
|
case undefined:
|
|
245
253
|
case "true":
|
|
@@ -259,8 +267,8 @@ export async function fetch(resource, options = {}) {
|
|
|
259
267
|
case -1:
|
|
260
268
|
break;
|
|
261
269
|
}
|
|
262
|
-
// 将通用字段映射到 Node.js Fetch 语义。
|
|
263
|
-
// Map shared fields to Node.js Fetch semantics.
|
|
270
|
+
// 将通用字段映射到 Worker / Node.js Fetch 语义。
|
|
271
|
+
// Map shared fields to Worker / Node.js Fetch semantics.
|
|
264
272
|
resource.redirect = resource.redirection ? "follow" : "manual";
|
|
265
273
|
const { url, ...options } = resource;
|
|
266
274
|
// 发起请求并归一化响应头、文本与二进制响应体。
|