@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 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 / Node fetch)
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. 存在 `process.versions.node` -> `Node.js`
137
- 8. 默认回落 -> `undefined`
138
- - 实现细节:内部使用 `'key' in globalThis` 检测平台标记,避免 `Object.keys(globalThis)` 漏掉不可枚举全局变量;因此在 Workers / Vercel 风格全局对象存在时,只要 `process.versions.node` 可用,仍会识别为 `Node.js`。
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`(仅 Node.js 分支识别;默认启用,传入 `false` / `0` / `-1` 可关闭)
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 分支优先复用 `globalThis.fetch`;若不存在则回退到 `node-fetch`,并在 `auto-cookie` 未关闭时按需包裹 `fetch-cookie`。
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) `process.versions.node` -> Node.js
14
- * 8) 默认回落 -> undefined
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 = (key) => key in globalThis;
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);
@@ -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;
@@ -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
- "typescript": "^5.6.3"
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.0"
45
+ "version": "2.2.4"
45
46
  }
@@ -1,8 +1,8 @@
1
1
  import { $app } from "../lib/app.mjs";
2
2
 
3
3
  /**
4
- * 统一日志工具,兼容各脚本平台与 Node.js。
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.stack}`);
104
+ msg = msg.map(m => `❌ ${m?.stack ?? m}`);
104
105
  break;
105
106
  }
106
107
  Console.log(...msg);
@@ -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
- * 清空存储(仅 Quantumult X 支持)。
228
- * Clear storage (supported by Quantumult X only).
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);
@@ -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 分支默认启用 Cookie 透传,可通过 `auto-cookie` 关闭
61
- * - Node.js enables Cookie forwarding by default and can disable it via `auto-cookie`
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 优先复用原生/宿主 `fetch`,缺失时再回退到 `node-fetch`。
241
- // Reuse host `fetch` in Node.js when available and fall back to `node-fetch` otherwise.
242
- if (!globalThis.fetch) globalThis.fetch = require("node-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
  // 发起请求并归一化响应头、文本与二进制响应体。
@@ -6,6 +6,7 @@ declare module "@nsnanocat/util" {
6
6
  | "Egern"
7
7
  | "Surge"
8
8
  | "Stash"
9
+ | "Worker"
9
10
  | "Node.js";
10
11
 
11
12
  export const $app: AppName | undefined;