用于统一 Quantumult X / Loon / Shadowrocket / Node.js / Egern / Surge / Stash 脚本接口的通用工具库。
核心目标:
- 统一不同平台的 HTTP、通知、持久化、结束脚本等调用方式。
- 在一个脚本里尽量少写平台分支。
- 提供一组可直接复用的 polyfill(
fetch/Storage/Console/Lodash)。
发布源:
- npm(推荐):https://www.npmjs.com/package/@nsnanocat/util
- GitHub Packages(同步发布):https://github.com/NSNanoCat/util/pkgs/npm/util
如果你不确定该选哪个,直接用 npm 源即可。 如果你从 GitHub Packages 安装,需要先配置 GitHub 认证(PAT Token)。
# 首次安装:拉取并安装这个包
npm i @nsnanocat/util
# 更新到最新版本:升级已安装的 util
npm i @nsnanocat/util@latest
# 你也可以使用 update(效果类似)
# npm update @nsnanocat/util# 把 @nsnanocat 作用域的包下载源切到 GitHub Packages
npm config set @nsnanocat:registry https://npm.pkg.github.com
# 配置 GitHub Token(用于下载 GitHub Packages)
# 建议把 YOUR_GITHUB_PAT 换成你的真实 Token,再执行
# echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT" >> ~/.npmrc
# 首次安装:从 GitHub Packages 安装 util
npm i @nsnanocat/util
# 更新到最新版本:从 GitHub Packages 拉取最新 util
npm i @nsnanocat/util@latestimport {
$app, // 当前平台名(如 "Surge" / "Loon" / "Quantumult X" / "Node.js")
$argument, // 已标准化的模块参数对象(导入包时自动处理字符串 -> 对象)
done, // 统一结束脚本函数(内部自动适配各平台 $done 差异)
fetch, // 统一 HTTP 请求函数(内部自动适配 $httpClient / $task / Node fetch)
notification, // 统一通知函数(内部自动适配 $notify / $notification.post)
time, // 时间格式化工具
wait, // 延时等待工具(Promise)
Console, // 统一日志工具(支持 logLevel)
Lodash as _, // Lodash 建议按官方示例惯例使用 `_` 作为工具对象别名
Storage, // 统一持久化存储接口(适配 $prefs / $persistentStore / 文件)
} from "@nsnanocat/util";lib/app.mjslib/argument.mjs($argument参数标准化模块,导入时自动执行)lib/done.mjslib/notification.mjslib/time.mjslib/wait.mjspolyfill/Console.mjspolyfill/fetch.mjspolyfill/Lodash.mjspolyfill/StatusTexts.mjspolyfill/Storage.mjs
lib/environment.mjslib/runScript.mjsgetStorage.mjs(薯条项目自用,仅当你的存储结构与薯条项目一致时再使用;请通过子路径@nsnanocat/util/getStorage.mjs导入)
说明:
- 下表只描述“模块之间”的依赖关系、调用到的函数/常量、以及依赖原因。
- 你在业务脚本中通常只需要调用对外 API;底层跨平台差异已在这些依赖链里处理。
| 模块 | 依赖模块 | 使用的函数/常量 | 为什么依赖 |
|---|---|---|---|
lib/app.mjs |
无 | 无 | 核心平台识别源头,供其他差异模块分流 |
lib/environment.mjs |
lib/app.mjs |
$app |
按平台生成统一 $environment(尤其补齐 app 字段) |
lib/argument.mjs |
polyfill/Console.mjs, polyfill/Lodash.mjs |
Console.debug, Console.logLevel, Lodash.set |
统一 $argument 结构并支持深路径写入 |
lib/done.mjs |
lib/app.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs |
$app, Console.log, Lodash.set, Lodash.pick, StatusTexts |
将各平台 $done 参数格式拉平并兼容状态码/策略字段 |
lib/notification.mjs |
lib/app.mjs, polyfill/Console.mjs |
$app, Console.group, Console.log, Console.groupEnd, Console.error |
将通知参数映射到各平台通知接口并统一日志输出 |
lib/runScript.mjs |
polyfill/Console.mjs, polyfill/fetch.mjs, polyfill/Storage.mjs, polyfill/Lodash.mjs |
Console.error, fetch, Storage.getItem(Lodash 当前版本未实际调用) |
读取 BoxJS 配置并发起统一 HTTP 调用执行脚本 |
getStorage.mjs |
lib/argument.mjs, polyfill/Console.mjs, polyfill/Lodash.mjs, polyfill/Storage.mjs |
Console.debug, Console.logLevel, Lodash.merge, Storage.getItem |
先标准化 $argument,再合并默认配置/持久化配置/运行参数 |
polyfill/Console.mjs |
lib/app.mjs |
$app |
日志在 Node.js 与 iOS 脚本环境使用不同错误输出策略 |
polyfill/fetch.mjs |
lib/app.mjs, polyfill/Lodash.mjs, polyfill/StatusTexts.mjs, polyfill/Console.mjs |
$app, Lodash.set, StatusTexts(Console 当前版本未实际调用) |
按平台选请求引擎并做参数映射、响应结构统一 |
polyfill/Storage.mjs |
lib/app.mjs, polyfill/Lodash.mjs |
$app, Lodash.get, Lodash.set, Lodash.unset |
按平台选持久化后端并支持 @key.path 读写 |
polyfill/Lodash.mjs |
无 | 无 | 提供路径/合并等基础能力,被多个模块复用 |
polyfill/StatusTexts.mjs |
无 | 无 | 提供 HTTP 状态文案,供 fetch/done 使用 |
index.js / lib/index.js / polyfill/index.js |
多个模块 | export * |
聚合导出,不含业务逻辑 |
- 类型:
"Quantumult X" | "Loon" | "Shadowrocket" | "Node.js" | "Egern" | "Surge" | "Stash" | undefined - 角色:核心模块。库内所有存在平台行为差异的模块都会先读取
$app再分流(如done、notification、fetch、Storage、Console、environment)。 - 读取方式:
import { $app } from "@nsnanocat/util";
const appName = $app; // 读取 $app,返回平台字符串
console.log(appName);- 识别顺序(
lib/app.mjs):
- 存在
$task->Quantumult X - 存在
$loon->Loon - 存在
$rocket->Shadowrocket - 存在
module->Node.js - 存在
Egern->Egern - 存在
$environment且有surge-version->Surge - 存在
$environment且有stash-version->Stash
- 路径:
lib/environment.mjs(未从包主入口导出) - 签名:
environment(): object - 调用方式:
import { $environment, environment } from "@nsnanocat/util/lib/environment.mjs";
console.log($environment.app); // 统一平台名
console.log(environment()); // 当前环境对象- 规则:会为已识别平台统一生成
$environment.app = "平台名称"。
| 平台 | 调用路径(读取来源) | 读取结果示例 |
|---|---|---|
| Surge | 读取全局 $environment,再写入 app |
{ ..., "surge-version": "x", app: "Surge" } |
| Stash | 读取全局 $environment,再写入 app |
{ ..., "stash-version": "x", app: "Stash" } |
| Egern | 读取全局 $environment,再写入 app |
{ ..., app: "Egern" } |
| Loon | 读取全局 $loon 字符串并拆分 |
{ device, ios, "loon-version", app: "Loon" } |
| Quantumult X | 不读取额外环境字段,直接构造对象 | { app: "Quantumult X" } |
| Node.js | 读取 process.env 并写入 process.env.app |
{ ..., app: "Node.js" } |
| 其他 | 无 | {} |
此文件无显式导出;import 后立即执行。这是为了统一各平台 $argument 的输入差异。
- 通过包入口导入(
import ... from "@nsnanocat/util")时会自动执行本模块。 - JSCore 环境不支持
await import,请使用静态导入或直接走包入口导入。 - 读取到的
$argument会按 URL Params 样式格式化为对象,并支持深路径。 - 你也可以通过
import { $argument } from "@nsnanocat/util"读取当前已标准化的$argument快照。 - 平台输入差异说明:
- Surge / Stash / Egern:脚本参数通常以字符串形式传入(如
a=1&b=2)。 - Loon:支持字符串和对象两种
$argument形式。 - Quantumult X / Shadowrocket:不提供
$argument。
- Surge / Stash / Egern:脚本参数通常以字符串形式传入(如
- 当全局
$argument为string(如"a.b=1&x=2")时:- 按
&/=切分。 - 去掉值中的双引号。
- 使用点路径展开对象(
a.b=1 -> { a: { b: "1" } })。
- 按
- 当全局
$argument为object时:- 将 key 当路径写回新对象(
{"a.b":"1"}->{a:{b:"1"}})。
- 将 key 当路径写回新对象(
- 当
$argument为null或undefined:会归一化为{}。 - 若
$argument.LogLevel存在:同步到Console.logLevel。
import { $argument } from "@nsnanocat/util";
// $argument = "mode=on&a.b=1"; // 示例入参,实际由模块参数注入
console.log($argument); // { mode: "on", a: { b: "1" } }- 签名:
done(object?: object): void - 作用:统一不同平台的脚本结束接口(
$done/ Node 退出)。
说明:下表描述的是各 App 原生接口差异与本库内部映射逻辑。调用方只需要按 done 的统一参数传值即可,不需要自己再写平台分支。
支持字段(输入):
status:number | stringurl:stringheaders:objectbody:string | ArrayBuffer | TypedArraybodyBytes:ArrayBufferpolicy:string
平台行为差异:
| 平台 | policy 处理 |
status 处理 |
body/bodyBytes 处理 |
最终行为 |
|---|---|---|---|---|
| Surge | 写入 headers.X-Surge-Policy |
透传 | 透传 | $done(object) |
| Loon | object.node = policy |
透传 | 透传 | $done(object) |
| Stash | 写入 headers.X-Stash-Selected-Proxy(URL 编码) |
透传 | 透传 | $done(object) |
| Egern | 不转换 | 透传 | 透传 | $done(object) |
| Shadowrocket | 不转换 | 透传 | 透传 | $done(object) |
| Quantumult X | 写入 opts.policy |
number 会转 HTTP/1.1 200 OK 字符串 |
仅保留 status/url/headers/body/bodyBytes;ArrayBuffer/TypedArray 转 bodyBytes |
$done(object) |
| Node.js | 不适用 | 不适用 | 不适用 | process.exit(1) |
不可用/差异点:
policy在 Egern / Shadowrocket 分支不做映射。- Quantumult X 会丢弃未在白名单内的字段。
- Quantumult X 的
status在部分场景要求完整状态行(如HTTP/1.1 200 OK),本库会在传入数字状态码时自动拼接(依赖StatusTexts)。 - Node.js 不调用
$done,而是直接退出进程,且退出码固定为1。
- 签名:
title?: stringsubtitle?: stringbody?: stringcontent?: string | number | boolean | object
- 默认值:
title = "ℹ️ ${$app} 通知" - 作用:统一
notify/notification参数格式并发送通知。
content 可用 key(对象形式):
- 跳转:
open/open-url/url/openUrl - 复制:
copy/update-pasteboard/updatePasteboard - 媒体:
media/media-url/mediaUrl - 其他:
auto-dismiss、sound、mime
平台映射:
| 平台 | 调用接口 | 字符串 content 行为 |
对象字段支持 |
|---|---|---|---|
| Surge | $notification.post |
{ url: content } |
open-url/clipboard 动作、media-url、media-base64、auto-dismiss、sound |
| Stash | $notification.post |
{ url: content } |
同 Surge 分支(是否全部展示取决于 Stash 支持) |
| Egern | $notification.post |
{ url: content } |
同 Surge 分支(是否全部展示取决于 Egern 支持) |
| Shadowrocket | $notification.post |
{ openUrl: content } |
走 Surge 分支的 action/url/text/media 字段 |
| Loon | $notification.post |
{ openUrl: content } |
openUrl、mediaUrl(仅 http/https) |
| Quantumult X | $notify |
{ "open-url": content } |
open-url、media-url(仅 http/https)、update-pasteboard |
| Node.js | 不发送通知(非 iOS App 环境) | 无 | 无 |
不可用/差异点:
copy/update-pasteboard在 Loon 分支不会生效。- Loon / Quantumult X 对
media仅接受网络 URL;Base64 媒体不会自动映射。 - Node.js 不是 iOS App 脚本环境,不支持 iOS 通知行为;当前分支仅日志输出。
- 签名:
time(format: string, ts?: number): string ts:可选时间戳(传给new Date(ts))。- 支持占位符:
YY、yyyy、MM、dd、HH、mm、ss、sss、S(季度)。
time("yyyy-MM-dd HH:mm:ss.sss");
time("yyyyMMddHHmmss", Date.now());注意:当前实现对每个 token 只替换一次(String.replace 非全局)。
- 签名:
wait(delay?: number): Promise<void> - 用法:
await wait(500);- 签名:
runScript(script: string, runOpts?: { timeout?: number }): Promise<void> - 作用:通过 BoxJS
httpapi调用本地脚本执行接口:/v1/scripting/evaluate。 - 读取存储键:
@chavy_boxjs_userCfgs.httpapi@chavy_boxjs_userCfgs.httpapi_timeout
- 请求体:
script_textmock_type: "cron"timeout
示例:
import { runScript } from "./lib/runScript.mjs";
await runScript("$done({})", { timeout: 20 });注意:
- 依赖你本地已正确配置
httpapi(password@host:port)。 - 函数不返回接口响应,仅在失败时
Console.error。
仅当你的持久化结构与薯条项目一致时才建议使用。
- 签名:
key: string(持久化主键)names: string | string[](平台名/配置组名,可嵌套数组)database: object(默认数据库)
- 返回:
{ Settings, Configs, Caches }
合并顺序由 $argument.Storage 控制(持久化读取统一使用 PersistentStore = Storage.getItem(key, {});支持别名):
- 可用值(大小写敏感):
undefined|Argument|$argument|PersistentStore|BoxJs|boxjs|$persistentStore|database
undefined:database[name]->$argument->PersistentStore[name]Argument/$argument:database[name]->PersistentStore[name]->$argumentPersistentStore/BoxJs/$persistentStore(默认):database[name]->PersistentStore[name]database:仅database[name]
注意:Configs 与 Caches 始终按每个 name 合并(与 $argument.Storage 无关)。
自动类型转换(Root.Settings):
- 字符串
"true"/"false"->boolean - 纯数字字符串 ->
number - 含逗号字符串 ->
array,并尝试逐项转数字
示例:
import getStorage from "@nsnanocat/util/getStorage.mjs";
const store = getStorage("@my_box", ["YouTube", "Global"], database);getStorage.mjs 同时导出以下辅助函数:
traverseObject(o, c):深度遍历对象并替换叶子值string2number(string):将纯数字字符串转换为数字value2array(value):字符串按逗号拆分;数字/布尔值会被包装为单元素数组
示例:
import getStorage, {
traverseObject,
string2number,
value2array,
} from "@nsnanocat/util/getStorage.mjs";
const store = getStorage("@my_box", ["YouTube", "Global"], database);fetch 是仿照 Web API Window.fetch 设计的跨平台适配实现:
- 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch
- 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/fetch
- 目标:尽量保持 Web
fetch调用习惯,同时补齐各平台扩展参数映射
- 签名:
fetch(resource: object | string, options?: object): Promise<object> - 参数合并:
resource为对象:{ ...options, ...resource }resource为字符串:{ ...options, url: resource }
- 默认方法:无
method时,若有body/bodyBytes->POST,否则GET - 会删除 headers:
Host、:authority、Content-Length/content-length timeout规则:- 缺省 ->
5(秒) > 500视为毫秒并转为秒
- 缺省 ->
通用请求字段:
urlmethodheadersbodybodyBytestimeoutpolicyredirection/auto-redirect
说明:下表是各 App 原生 HTTP 接口的差异补充,以及本库 fetch 的内部映射方式。调用方使用统一入参即可。
平台行为差异:
| 平台 | 请求发送接口 | timeout 单位 |
policy 映射 |
重定向字段 | 二进制处理 |
|---|---|---|---|---|---|
| Surge | $httpClient[method] |
秒 | 无专门映射 | auto-redirect |
Accept 命中二进制类型时设置 binary-mode |
| Loon | $httpClient[method] |
毫秒(内部乘 1000) | node = policy |
auto-redirect |
同上 |
| Stash | $httpClient[method] |
秒 | headers.X-Stash-Selected-Proxy |
auto-redirect |
同上 |
| Egern | $httpClient[method] |
秒 | 无专门映射 | auto-redirect |
同上 |
| Shadowrocket | $httpClient[method] |
秒 | headers.X-Surge-Proxy |
auto-redirect |
同上 |
| Quantumult X | $task.fetch |
毫秒(内部乘 1000) | opts.policy |
opts.redirection |
body(ArrayBuffer/TypedArray) 转 bodyBytes;响应按 Content-Type 恢复到 body |
| Node.js | fetch + fetch-cookie |
毫秒(内部乘 1000) | 无 | redirect: follow/manual |
返回 body(UTF-8 string) + bodyBytes(ArrayBuffer) |
返回对象(统一后)常见字段:
okstatusstatusCodestatusTextheadersbodybodyBytes
不可用/差异点:
policy在 Surge / Egern / Node.js 分支没有额外适配逻辑。redirection在部分平台会映射为auto-redirect或opts.redirection。- Node.js 分支依赖
globalThis.fetch/globalThis.fetchCookie或node-fetch+fetch-cookie。 - 返回结构是统一兼容结构,不等同于浏览器
Response对象。
Storage 是仿照 Web Storage 接口(Storage)设计的跨平台持久化适配器:
- 参考文档:https://developer.mozilla.org/en-US/docs/Web/API/Storage
- 中文文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Storage
- 目标:统一 VPN App 脚本环境中的持久化读写接口,并尽量贴近 Web Storage 行为
- 支持普通 key:按平台读持久化。
- 支持路径 key:
@root.path.to.key。
- 普通 key:按平台写持久化。
- 路径 key:
@root.path写入嵌套对象。 keyValue为对象时自动JSON.stringify。
- Quantumult X:可用(
$prefs.removeValueForKey)。 - Surge:通过
$persistentStore.write(null, keyName)删除。 - Node.js:可用(删除
box.dat中对应 key 并落盘)。 - Loon / Stash / Egern / Shadowrocket:返回
false。
- Quantumult X:可用(
$prefs.removeAllValues)。 - Node.js:可用(清空
box.dat并落盘)。 - 其他平台:返回
false。
- 数据文件默认:
box.dat。 - 读取路径优先级:当前目录 ->
process.cwd()。
与 Web Storage 的行为差异:
- 支持
@key.path深路径读写(Web Storage 原生不支持)。 removeItem/clear仅部分平台可用(目前为 Quantumult X、Node.js,以及 Surge 的removeItem)。getItem会尝试JSON.parse,setItem写入对象会JSON.stringify。
平台后端映射:
| 平台 | 读写接口 |
|---|---|
| Surge / Loon / Stash / Egern / Shadowrocket | $persistentStore.read/write |
| Quantumult X | $prefs.valueForKey/setValueForKey |
| Node.js | 本地 box.dat |
Console 是统一日志工具(静态类)。
Console.logLevel可读写。- 支持:
OFF(0)/ERROR(1)/WARN(2)/INFO(3)/DEBUG(4)/ALL(5)。
logLevel 用法示例:
import { Console } from "@nsnanocat/util";
Console.logLevel = "debug"; // 或 4
Console.debug("debug message");
Console.logLevel = 2; // WARN
Console.info("won't print at WARN level");
Console.warn("will print");
console.log(Console.logLevel); // "WARN"clear()count(label = "default")countReset(label = "default")debug(...msg)error(...msg)exception(...msg)group(label)groupEnd()info(...msg)log(...msg)time(label = "default")timeLog(label = "default")timeEnd(label = "default")warn(...msg)
参数与返回值:
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
clear() |
无 | void |
当前实现为空函数 |
count(label) |
label?: string |
void |
计数并输出 |
countReset(label) |
label?: string |
void |
重置计数器 |
debug(...msg) |
...msg: any[] |
void |
仅 DEBUG/ALL 级别输出 |
error(...msg) |
...msg: any[] |
void |
Node.js 优先输出 stack |
exception(...msg) |
...msg: any[] |
void |
error 别名 |
group(label) |
label: string |
void |
压栈分组 |
groupEnd() |
无 | void |
出栈分组 |
info(...msg) |
...msg: any[] |
void |
INFO 及以上 |
log(...msg) |
...msg: any[] |
void |
通用日志 |
time(label) |
label?: string |
void |
记录起始时间 |
timeLog(label) |
label?: string |
void |
输出耗时 |
timeEnd(label) |
label?: string |
void |
清除计时器 |
warn(...msg) |
...msg: any[] |
void |
WARN 及以上 |
平台差异:
- Node.js 下
error会优先打印Error.stack。 - 其他平台统一加前缀符号输出(
❌/⚠️/ℹ️/🅱️)。
Lodash 为“部分方法的简化实现”,不是完整 Lodash。各方法语义可参考:
导入约定(建议):
- 这是 lodash 官方示例中常见的惯例写法:使用
_作为工具对象别名。
import { Lodash as _ } from "@nsnanocat/util";
const data = {};
_.set(data, "a.b", 1);
console.log(data); // { a: { b: 1 } }
const value = _.get(data, "a.b", 0);
console.log(value); // 1示例对应的 lodash 官方文档页面:
set(object, path, value)get(object, path, defaultValue)
当前实现包含:
escape(string)unescape(string)toPath(value)get(object, path, defaultValue)set(object, path, value)unset(object, path)pick(object, paths)omit(object, paths)merge(object, ...sources)
对应 lodash 官方文档页面:
escape(string)unescape(string)toPath(value)get(object, path, defaultValue)set(object, path, value)unset(object, path)pick(object, paths)omit(object, paths)merge(object, ...sources)
参数与返回值:
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
escape |
string: string |
string |
HTML 转义 |
unescape |
string: string |
string |
HTML 反转义 |
toPath |
value: string |
string[] |
a[0].b -> ['a','0','b'] |
get |
object?: object, path?: string\|string[], defaultValue?: any |
any |
路径读取 |
set |
object: object, path: string\|string[], value: any |
object |
路径写入(会创建中间层) |
unset |
object?: object, path?: string\|string[] |
boolean |
删除路径并返回结果 |
pick |
object?: object, paths?: string\|string[] |
object |
挑选 key(仅第一层) |
omit |
object?: object, paths?: string\|string[] |
object |
删除 key(会修改原对象) |
merge |
object: object, ...sources: object[] |
object |
深合并(非完整 lodash 行为) |
merge 行为(与 lodash 官方有差异):
- 深度合并 Plain Object。
- Array 直接覆盖;空数组不覆盖已存在值。
- Map/Set 支持同类型合并;空 Map/Set 不覆盖已存在值。
undefined不覆盖,null会覆盖。- 直接修改目标对象(mutates target)。
- 类型:
Record<number, string> - 内容:HTTP 状态码到状态文本映射(100~511 的常见码)。
- 主要用途:给 Quantumult X 的
$done状态行补全文本(如HTTP/1.1 200 OK)。 - 参考示例:https://github.com/crossutility/Quantumult-X/raw/refs/heads/master/sample-rewrite-response-header.js
说明:本节展示的是各平台原生脚本接口差异。实际在本库中,这些差异已由 done、fetch、notification、Storage 等模块做了统一适配。
| 能力 | Quantumult X | Loon | Surge | Stash | Egern | Shadowrocket | Node.js |
|---|---|---|---|---|---|---|---|
| HTTP 请求 | $task.fetch |
$httpClient |
$httpClient |
$httpClient |
$httpClient |
$httpClient |
fetch |
| 通知 | $notify |
$notification.post |
$notification.post |
$notification.post |
$notification.post |
$notification.post |
无 |
| 持久化 | $prefs |
$persistentStore |
$persistentStore |
$persistentStore |
$persistentStore |
$persistentStore |
box.dat |
| 结束脚本 | $done |
$done |
$done |
$done |
$done |
$done |
process.exit(1) |
removeItem/clear |
可用 | 不可用 | removeItem 可用 / clear 不可用 |
不可用 | 不可用 | 不可用 | 可用 |
policy 注入(fetch/done) |
opts.policy |
node |
X-Surge-Policy(done) |
X-Stash-Selected-Proxy |
无专门映射 | X-Surge-Proxy(fetch) |
无 |
lib/argument.mjs为$argument标准化模块,import时会按规则重写全局$argument。lib/done.mjs在 Node.js 固定process.exit(1)。polyfill/fetch.mjs的超时保护使用了Promise.race,但当前实现里请求 Promise 先被await,可能导致超时行为与预期不完全一致。Storage.removeItem("@a.b")分支存在未声明变量写入风险;如要大量使用路径删除,建议先本地验证。lib/runScript.mjs未从包主入口导出,需要按文件路径直接导入。
以下资料用于对齐不同平台 $ API 语义;README 的“平台差异”优先以本仓库实现为准。
- crossutility/Quantumult-X - sample-task.js
- crossutility/Quantumult-X - sample-rewrite-with-script.js
- crossutility/Quantumult-X - sample-fetch-opts-policy.js
- crossutility/Quantumult-X - sample-rewrite-response-header.js
说明:Egern 与 Shadowrocket 暂未检索到等价于 Surge/Loon/Stash 的完整公开脚本 API 页面;相关差异说明以本库实际代码分支行为为准。