@nsnanocat/preference-panes 0.7.2 → 0.8.0
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 +23 -78
- package/dist/api.js +1622 -0
- package/dist/module/app.mjs +22 -30
- package/dist/module/index.html +1 -1
- package/dist/module/navigation.mjs +1 -1
- package/dist/preference-panes.mjs +21 -29
- package/package.json +1 -1
- package/src/Store.mjs +40 -53
- package/src/browser/client.d.mts +2 -2
- package/src/browser/client.mjs +21 -21
- package/src/build.mjs +3 -13
- package/src/index.d.ts +6 -6
- package/src/lib/page-inputs.mjs +1 -1
- package/src/lib/settings-path.mjs +0 -18
- package/src/proxy/handler.mjs +9 -22
- package/dist/preference-panes.config.js +0 -842
- package/dist/preference-panes.proxy.js +0 -1754
- package/src/proxy/config.mjs +0 -14
|
@@ -1,842 +0,0 @@
|
|
|
1
|
-
var PreferencePanes = (function (exports) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 统一生成不可缓存的响应,HEAD 始终省略正文。
|
|
6
|
-
* Create an uncached response, always omitting the body for HEAD.
|
|
7
|
-
* @param {import("../index.js").SettingsRequest} request 宿主请求 / Host request.
|
|
8
|
-
* @param {number} status HTTP 状态 / HTTP status.
|
|
9
|
-
* @param {unknown} body JSON 数据或资源正文 / JSON data or resource body.
|
|
10
|
-
* @param {string} [type] 媒体类型 / Media type.
|
|
11
|
-
* @returns {import("../index.js").SettingsResponse} 通用响应 / Common response.
|
|
12
|
-
*/
|
|
13
|
-
function response(request, status, body, type = "application/json") {
|
|
14
|
-
return {
|
|
15
|
-
status,
|
|
16
|
-
headers: { "Content-Type": `${type}; charset=utf-8`, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" },
|
|
17
|
-
body: request.method === "HEAD" ? "" : type === "application/json" ? JSON.stringify(body) : body,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 当前运行平台名称(脚本平台优先,模块系统次之)。
|
|
23
|
-
* Current runtime platform name (script platform first, module system second).
|
|
24
|
-
*
|
|
25
|
-
* 识别顺序:
|
|
26
|
-
* Detection order:
|
|
27
|
-
* 1) `$task` -> Quantumult X
|
|
28
|
-
* 2) `$loon` -> Loon
|
|
29
|
-
* 3) `$rocket` -> Shadowrocket
|
|
30
|
-
* 4) `Egern` -> Egern
|
|
31
|
-
* 5) `$environment["surge-version"]` -> Surge
|
|
32
|
-
* 6) `$environment["stash-version"]` -> Stash
|
|
33
|
-
* 7) `Cloudflare` -> Worker
|
|
34
|
-
* 8) `process.versions.node` -> Node.js
|
|
35
|
-
* 9) 默认回落 -> undefined
|
|
36
|
-
* default fallback -> undefined
|
|
37
|
-
*
|
|
38
|
-
* 说明:
|
|
39
|
-
* Notes:
|
|
40
|
-
* - 使用 `'key' in globalThis`,避免 `Object.keys` 对不可枚举全局变量漏检。
|
|
41
|
-
* - Use `'key' in globalThis` to avoid missing non-enumerable globals with `Object.keys`.
|
|
42
|
-
*
|
|
43
|
-
* @type {("Quantumult X" | "Loon" | "Shadowrocket" | "Egern" | "Surge" | "Stash" | "Worker" | "Node.js" | undefined)}
|
|
44
|
-
*/
|
|
45
|
-
const $app = (() => {
|
|
46
|
-
const has = key => key in globalThis;
|
|
47
|
-
switch (true) {
|
|
48
|
-
case has("$task"):
|
|
49
|
-
return "Quantumult X";
|
|
50
|
-
case has("$loon"):
|
|
51
|
-
return "Loon";
|
|
52
|
-
case has("$rocket"):
|
|
53
|
-
return "Shadowrocket";
|
|
54
|
-
case has("Egern"):
|
|
55
|
-
return "Egern";
|
|
56
|
-
case Boolean(globalThis.$environment?.["surge-version"]):
|
|
57
|
-
return "Surge";
|
|
58
|
-
case Boolean(globalThis.$environment?.["stash-version"]):
|
|
59
|
-
return "Stash";
|
|
60
|
-
case has("Cloudflare"):
|
|
61
|
-
//case has("ServiceWorkerGlobalScope") && has("self") && has("caches") && has("scheduler"):
|
|
62
|
-
return "Worker";
|
|
63
|
-
case Boolean(globalThis.process?.versions?.node):
|
|
64
|
-
return "Node.js";
|
|
65
|
-
default:
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
})();
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 统一日志工具,兼容各脚本平台、Worker 与 Node.js。
|
|
72
|
-
* Unified logger compatible with script platforms, Worker, and Node.js.
|
|
73
|
-
*
|
|
74
|
-
* logLevel 用法:
|
|
75
|
-
* logLevel usage:
|
|
76
|
-
* - 可读: `Console.logLevel` 返回 `OFF|ERROR|WARN|INFO|DEBUG|ALL`
|
|
77
|
-
* - Read: `Console.logLevel` returns `OFF|ERROR|WARN|INFO|DEBUG|ALL`
|
|
78
|
-
* - 可写: 数字 `0~5` 或字符串 `off/error/warn/info/debug/all`
|
|
79
|
-
* - Write: number `0~5` or string `off/error/warn/info/debug/all`
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* Console.logLevel = "debug";
|
|
83
|
-
* Console.debug("only shown when level >= DEBUG");
|
|
84
|
-
* Console.logLevel = 2; // WARN
|
|
85
|
-
*/
|
|
86
|
-
class Console {
|
|
87
|
-
static #counts = new Map([]);
|
|
88
|
-
static #groups = [];
|
|
89
|
-
static #times = new Map([]);
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* 清空控制台(当前为空实现)。
|
|
93
|
-
* Clear console (currently a no-op).
|
|
94
|
-
*
|
|
95
|
-
* @returns {void}
|
|
96
|
-
*/
|
|
97
|
-
static clear = () => {};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* 增加计数器并打印当前值。
|
|
101
|
-
* Increment counter and print the current value.
|
|
102
|
-
*
|
|
103
|
-
* @param {string} [label="default"] 计数器名称 / Counter label.
|
|
104
|
-
* @returns {void}
|
|
105
|
-
*/
|
|
106
|
-
static count = (label = "default") => {
|
|
107
|
-
switch (Console.#counts.has(label)) {
|
|
108
|
-
case true:
|
|
109
|
-
Console.#counts.set(label, Console.#counts.get(label) + 1);
|
|
110
|
-
break;
|
|
111
|
-
case false:
|
|
112
|
-
Console.#counts.set(label, 0);
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
Console.log(`${label}: ${Console.#counts.get(label)}`);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 重置计数器。
|
|
120
|
-
* Reset a counter.
|
|
121
|
-
*
|
|
122
|
-
* @param {string} [label="default"] 计数器名称 / Counter label.
|
|
123
|
-
* @returns {void}
|
|
124
|
-
*/
|
|
125
|
-
static countReset = (label = "default") => {
|
|
126
|
-
switch (Console.#counts.has(label)) {
|
|
127
|
-
case true:
|
|
128
|
-
Console.#counts.set(label, 0);
|
|
129
|
-
Console.log(`${label}: ${Console.#counts.get(label)}`);
|
|
130
|
-
break;
|
|
131
|
-
case false:
|
|
132
|
-
Console.warn(`Counter "${label}" doesn’t exist`);
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 输出调试日志。
|
|
139
|
-
* Print debug logs.
|
|
140
|
-
*
|
|
141
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
142
|
-
* @returns {void}
|
|
143
|
-
*/
|
|
144
|
-
static debug = (...msg) => {
|
|
145
|
-
if (Console.#level < 4) return;
|
|
146
|
-
msg = msg.map(m => `🅱️ ${m}`);
|
|
147
|
-
Console.log(...msg);
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* 输出错误日志。
|
|
152
|
-
* Print error logs.
|
|
153
|
-
*
|
|
154
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
155
|
-
* @returns {void}
|
|
156
|
-
*/
|
|
157
|
-
static error(...msg) {
|
|
158
|
-
if (Console.#level < 1) return;
|
|
159
|
-
switch ($app) {
|
|
160
|
-
case "Surge":
|
|
161
|
-
case "Loon":
|
|
162
|
-
case "Stash":
|
|
163
|
-
case "Egern":
|
|
164
|
-
case "Shadowrocket":
|
|
165
|
-
case "Quantumult X":
|
|
166
|
-
default:
|
|
167
|
-
msg = msg.map(m => `❌ ${m}`);
|
|
168
|
-
break;
|
|
169
|
-
case "Worker":
|
|
170
|
-
case "Node.js":
|
|
171
|
-
msg = msg.map(m => `❌ ${m?.stack ?? m}`);
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
Console.log(...msg);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* `error` 的别名。
|
|
179
|
-
* Alias of `error`.
|
|
180
|
-
*
|
|
181
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
182
|
-
* @returns {void}
|
|
183
|
-
*/
|
|
184
|
-
static exception = (...msg) => Console.error(...msg);
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 进入日志分组。
|
|
188
|
-
* Enter a log group.
|
|
189
|
-
*
|
|
190
|
-
* @param {string} label 分组名 / Group label.
|
|
191
|
-
* @returns {number}
|
|
192
|
-
*/
|
|
193
|
-
static group = label => Console.#groups.unshift(label);
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* 退出日志分组。
|
|
197
|
-
* Exit the latest log group.
|
|
198
|
-
*
|
|
199
|
-
* @returns {*}
|
|
200
|
-
*/
|
|
201
|
-
static groupEnd = () => Console.#groups.shift();
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 输出信息日志。
|
|
205
|
-
* Print info logs.
|
|
206
|
-
*
|
|
207
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
208
|
-
* @returns {void}
|
|
209
|
-
*/
|
|
210
|
-
static info(...msg) {
|
|
211
|
-
if (Console.#level < 3) return;
|
|
212
|
-
msg = msg.map(m => `ℹ️ ${m}`);
|
|
213
|
-
Console.log(...msg);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
static #level = 3;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 获取日志级别文本。
|
|
220
|
-
* Get current log level text.
|
|
221
|
-
*
|
|
222
|
-
* @returns {"OFF"|"ERROR"|"WARN"|"INFO"|"DEBUG"|"ALL"}
|
|
223
|
-
*/
|
|
224
|
-
static get logLevel() {
|
|
225
|
-
switch (Console.#level) {
|
|
226
|
-
case 0:
|
|
227
|
-
return "OFF";
|
|
228
|
-
case 1:
|
|
229
|
-
return "ERROR";
|
|
230
|
-
case 2:
|
|
231
|
-
return "WARN";
|
|
232
|
-
case 3:
|
|
233
|
-
default:
|
|
234
|
-
return "INFO";
|
|
235
|
-
case 4:
|
|
236
|
-
return "DEBUG";
|
|
237
|
-
case 5:
|
|
238
|
-
return "ALL";
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 设置日志级别。
|
|
244
|
-
* Set current log level.
|
|
245
|
-
*
|
|
246
|
-
* @param {number|string} level 级别值 / Level value.
|
|
247
|
-
*/
|
|
248
|
-
static set logLevel(level) {
|
|
249
|
-
switch (typeof level) {
|
|
250
|
-
case "string":
|
|
251
|
-
level = level.toLowerCase();
|
|
252
|
-
break;
|
|
253
|
-
case "number":
|
|
254
|
-
break;
|
|
255
|
-
case "undefined":
|
|
256
|
-
default:
|
|
257
|
-
level = "warn";
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
switch (level) {
|
|
261
|
-
case 0:
|
|
262
|
-
case "off":
|
|
263
|
-
Console.#level = 0;
|
|
264
|
-
break;
|
|
265
|
-
case 1:
|
|
266
|
-
case "error":
|
|
267
|
-
Console.#level = 1;
|
|
268
|
-
break;
|
|
269
|
-
case 2:
|
|
270
|
-
case "warn":
|
|
271
|
-
case "warning":
|
|
272
|
-
default:
|
|
273
|
-
Console.#level = 2;
|
|
274
|
-
break;
|
|
275
|
-
case 3:
|
|
276
|
-
case "info":
|
|
277
|
-
Console.#level = 3;
|
|
278
|
-
break;
|
|
279
|
-
case 4:
|
|
280
|
-
case "debug":
|
|
281
|
-
Console.#level = 4;
|
|
282
|
-
break;
|
|
283
|
-
case 5:
|
|
284
|
-
case "all":
|
|
285
|
-
Console.#level = 5;
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* 输出通用日志。
|
|
292
|
-
* Print generic logs.
|
|
293
|
-
*
|
|
294
|
-
* 说明:
|
|
295
|
-
* Notes:
|
|
296
|
-
* - 多行字符串参数会按换行拆分为多个独立日志项。
|
|
297
|
-
* - Multi-line string arguments are split into multiple log entries by line breaks.
|
|
298
|
-
*
|
|
299
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
300
|
-
* @returns {void}
|
|
301
|
-
*/
|
|
302
|
-
static log = (...msg) => {
|
|
303
|
-
if (Console.#level === 0) return;
|
|
304
|
-
msg = msg.flatMap(log => {
|
|
305
|
-
switch (typeof log) {
|
|
306
|
-
case "object":
|
|
307
|
-
return [JSON.stringify(log)];
|
|
308
|
-
case "bigint":
|
|
309
|
-
case "number":
|
|
310
|
-
case "boolean":
|
|
311
|
-
return [log.toString()];
|
|
312
|
-
case "string":
|
|
313
|
-
return log.split(/\r?\n/u);
|
|
314
|
-
case "undefined":
|
|
315
|
-
default:
|
|
316
|
-
return [log];
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
Console.#groups.forEach(group => {
|
|
320
|
-
msg = msg.map(log => ` ${log}`);
|
|
321
|
-
msg.unshift(`▼ ${group}:`);
|
|
322
|
-
});
|
|
323
|
-
msg = ["", ...msg];
|
|
324
|
-
console.log(msg.join("\n"));
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* 开始计时。
|
|
329
|
-
* Start timer.
|
|
330
|
-
*
|
|
331
|
-
* @param {string} [label="default"] 计时器名称 / Timer label.
|
|
332
|
-
* @returns {Map<string, number>}
|
|
333
|
-
*/
|
|
334
|
-
static time = (label = "default") => Console.#times.set(label, Date.now());
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* 结束计时并移除计时器。
|
|
338
|
-
* End timer and remove it.
|
|
339
|
-
*
|
|
340
|
-
* @param {string} [label="default"] 计时器名称 / Timer label.
|
|
341
|
-
* @returns {boolean}
|
|
342
|
-
*/
|
|
343
|
-
static timeEnd = (label = "default") => Console.#times.delete(label);
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* 输出当前计时器耗时。
|
|
347
|
-
* Print elapsed time for a timer.
|
|
348
|
-
*
|
|
349
|
-
* @param {string} [label="default"] 计时器名称 / Timer label.
|
|
350
|
-
* @returns {void}
|
|
351
|
-
*/
|
|
352
|
-
static timeLog = (label = "default") => {
|
|
353
|
-
const time = Console.#times.get(label);
|
|
354
|
-
if (time) Console.log(`${label}: ${Date.now() - time}ms`);
|
|
355
|
-
else Console.warn(`Timer "${label}" doesn’t exist`);
|
|
356
|
-
};
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* 输出警告日志。
|
|
360
|
-
* Print warning logs.
|
|
361
|
-
*
|
|
362
|
-
* @param {...any} msg 日志内容 / Log messages.
|
|
363
|
-
* @returns {void}
|
|
364
|
-
*/
|
|
365
|
-
static warn(...msg) {
|
|
366
|
-
if (Console.#level < 2) return;
|
|
367
|
-
msg = msg.map(m => `⚠️ ${m}`);
|
|
368
|
-
Console.log(...msg);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/* https://www.lodashjs.com */
|
|
373
|
-
/**
|
|
374
|
-
* 轻量 Lodash 工具集。
|
|
375
|
-
* Lightweight Lodash-like utilities.
|
|
376
|
-
*
|
|
377
|
-
* 说明:
|
|
378
|
-
* Notes:
|
|
379
|
-
* - 这是 Lodash 的“部分方法”简化实现,不等价于完整 Lodash
|
|
380
|
-
* - This is a simplified subset, not a full Lodash implementation
|
|
381
|
-
* - 各方法语义可参考 Lodash 官方文档
|
|
382
|
-
* - Method semantics can be referenced from official Lodash docs
|
|
383
|
-
* - 导入时建议使用 `Lodash as _`,遵循 lodash 官方示例惯例
|
|
384
|
-
* - Use `Lodash as _` when importing, following official lodash example convention
|
|
385
|
-
*
|
|
386
|
-
* 参考:
|
|
387
|
-
* Reference:
|
|
388
|
-
* - https://www.lodashjs.com
|
|
389
|
-
* - https://lodash.com
|
|
390
|
-
*/
|
|
391
|
-
class Lodash {
|
|
392
|
-
/**
|
|
393
|
-
* HTML 特殊字符转义。
|
|
394
|
-
* Escape HTML special characters.
|
|
395
|
-
*
|
|
396
|
-
* @param {string} string 输入文本 / Input text.
|
|
397
|
-
* @returns {string}
|
|
398
|
-
* @see {@link https://lodash.com/docs/#escape lodash.escape}
|
|
399
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.escape lodash.escape (中文)}
|
|
400
|
-
*/
|
|
401
|
-
static escape(string) {
|
|
402
|
-
const map = {
|
|
403
|
-
"&": "&",
|
|
404
|
-
"<": "<",
|
|
405
|
-
">": ">",
|
|
406
|
-
'"': """,
|
|
407
|
-
"'": "'",
|
|
408
|
-
};
|
|
409
|
-
return string.replace(/[&<>"']/g, m => map[m]);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* 按路径读取对象值。
|
|
414
|
-
* Get object value by path.
|
|
415
|
-
*
|
|
416
|
-
* @param {object} [object={}] 目标对象 / Target object.
|
|
417
|
-
* @param {string|string[]} [path=""] 路径 / Path.
|
|
418
|
-
* @param {*} [defaultValue=undefined] 默认值 / Default value.
|
|
419
|
-
* @returns {*}
|
|
420
|
-
* @see {@link https://lodash.com/docs/#get lodash.get}
|
|
421
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.get lodash.get (中文)}
|
|
422
|
-
*/
|
|
423
|
-
static get(object = {}, path = "", defaultValue = undefined) {
|
|
424
|
-
// translate array case to dot case, then split with .
|
|
425
|
-
// a[0].b -> a.0.b -> ['a', '0', 'b']
|
|
426
|
-
if (!Array.isArray(path)) path = Lodash.toPath(path);
|
|
427
|
-
|
|
428
|
-
const result = path.reduce((previousValue, currentValue) => {
|
|
429
|
-
return Object(previousValue)[currentValue]; // null undefined get attribute will throwError, Object() can return a object
|
|
430
|
-
}, object);
|
|
431
|
-
return result === undefined ? defaultValue : result;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* 递归合并源对象的自身可枚举属性到目标对象
|
|
436
|
-
* Recursively merge source enumerable properties into target object.
|
|
437
|
-
* @description 简化版 lodash.merge,用于合并配置对象
|
|
438
|
-
* @description A simplified lodash.merge for config merging.
|
|
439
|
-
*
|
|
440
|
-
* 适用情况:
|
|
441
|
-
* - 合并嵌套的配置/设置对象
|
|
442
|
-
* - 需要深度合并而非浅层覆盖的场景
|
|
443
|
-
* - 多个源对象依次合并到目标对象
|
|
444
|
-
*
|
|
445
|
-
* 限制:
|
|
446
|
-
* - 仅处理普通对象 (Plain Object),不处理 Date/RegExp 等特殊对象
|
|
447
|
-
* - Map/Set 仅支持同类型合并,不递归内部值
|
|
448
|
-
* - 数组会被直接覆盖,不会合并数组元素
|
|
449
|
-
* - 不处理循环引用,可能导致栈溢出
|
|
450
|
-
* - 不复制 Symbol 属性和不可枚举属性
|
|
451
|
-
* - 不保留原型链,仅处理自身属性
|
|
452
|
-
* - 会修改原始目标对象 (mutates target)
|
|
453
|
-
*
|
|
454
|
-
* @param {object} object - 目标对象
|
|
455
|
-
* @param {object} object - Target object.
|
|
456
|
-
* @param {...object} sources - 源对象(可多个)
|
|
457
|
-
* @param {...object} sources - Source objects.
|
|
458
|
-
* @returns {object} 返回合并后的目标对象
|
|
459
|
-
* @returns {object} Merged target object.
|
|
460
|
-
* @see {@link https://lodash.com/docs/#merge lodash.merge}
|
|
461
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.merge lodash.merge (中文)}
|
|
462
|
-
* @example
|
|
463
|
-
* const target = { a: { b: 1 }, c: 2 };
|
|
464
|
-
* const source = { a: { d: 3 }, e: 4 };
|
|
465
|
-
* Lodash.merge(target, source);
|
|
466
|
-
* // => { a: { b: 1, d: 3 }, c: 2, e: 4 }
|
|
467
|
-
*/
|
|
468
|
-
static merge(object, ...sources) {
|
|
469
|
-
if (object === null || object === undefined) return object;
|
|
470
|
-
|
|
471
|
-
for (const source of sources) {
|
|
472
|
-
if (source === null || source === undefined) continue;
|
|
473
|
-
|
|
474
|
-
for (const key of Object.keys(source)) {
|
|
475
|
-
const sourceValue = source[key];
|
|
476
|
-
const targetValue = object[key];
|
|
477
|
-
|
|
478
|
-
switch (true) {
|
|
479
|
-
case Lodash.#isPlainObject(sourceValue) && Lodash.#isPlainObject(targetValue):
|
|
480
|
-
// 递归合并对象
|
|
481
|
-
object[key] = Lodash.merge(targetValue, sourceValue);
|
|
482
|
-
break;
|
|
483
|
-
case sourceValue instanceof Map && targetValue instanceof Map:
|
|
484
|
-
// 合并 Map(空 Map 跳过)
|
|
485
|
-
if (sourceValue.size > 0) {
|
|
486
|
-
for (const [k, v] of sourceValue) {
|
|
487
|
-
targetValue.set(k, v);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
break;
|
|
491
|
-
case sourceValue instanceof Set && targetValue instanceof Set:
|
|
492
|
-
// 合并 Set(空 Set 跳过)
|
|
493
|
-
if (sourceValue.size > 0) {
|
|
494
|
-
for (const v of sourceValue) {
|
|
495
|
-
targetValue.add(v);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
break;
|
|
499
|
-
case Array.isArray(sourceValue) && sourceValue.length === 0 && targetValue !== undefined:
|
|
500
|
-
// 空数组不覆盖已有值
|
|
501
|
-
break;
|
|
502
|
-
case (sourceValue instanceof Map && sourceValue.size === 0 && targetValue !== undefined):
|
|
503
|
-
case (sourceValue instanceof Set && sourceValue.size === 0 && targetValue !== undefined):
|
|
504
|
-
// 空 Map/Set 不覆盖已有值
|
|
505
|
-
break;
|
|
506
|
-
case sourceValue !== undefined:
|
|
507
|
-
object[key] = sourceValue;
|
|
508
|
-
break;
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return object;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* 判断值是否为普通对象 (Plain Object)
|
|
518
|
-
* Check whether a value is a plain object.
|
|
519
|
-
* @param {*} value - 要检查的值
|
|
520
|
-
* @param {*} value - Value to check.
|
|
521
|
-
* @returns {boolean} 如果是普通对象返回 true
|
|
522
|
-
* @returns {boolean} Returns true when value is a plain object.
|
|
523
|
-
* @see {@link https://lodash.com/docs/#isPlainObject lodash.isPlainObject}
|
|
524
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.isPlainObject lodash.isPlainObject (中文)}
|
|
525
|
-
*/
|
|
526
|
-
static #isPlainObject(value) {
|
|
527
|
-
if (value === null || typeof value !== "object") return false;
|
|
528
|
-
const proto = Object.getPrototypeOf(value);
|
|
529
|
-
return proto === null || proto === Object.prototype;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/**
|
|
533
|
-
* 删除对象指定路径并返回对象。
|
|
534
|
-
* Omit paths from object and return the same object.
|
|
535
|
-
*
|
|
536
|
-
* @param {object} [object={}] 目标对象 / Target object.
|
|
537
|
-
* @param {string|string[]} [paths=[]] 要删除的路径 / Paths to remove.
|
|
538
|
-
* @returns {object}
|
|
539
|
-
* @see {@link https://lodash.com/docs/#omit lodash.omit}
|
|
540
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.omit lodash.omit (中文)}
|
|
541
|
-
*/
|
|
542
|
-
static omit(object = {}, paths = []) {
|
|
543
|
-
if (!Array.isArray(paths)) paths = [paths.toString()];
|
|
544
|
-
paths.forEach(path => Lodash.unset(object, path));
|
|
545
|
-
return object;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
/**
|
|
549
|
-
* 仅保留对象指定键(第一层)。
|
|
550
|
-
* Pick selected keys from object (top level only).
|
|
551
|
-
*
|
|
552
|
-
* @param {object} [object={}] 目标对象 / Target object.
|
|
553
|
-
* @param {string|string[]} [paths=[]] 需要保留的键 / Keys to keep.
|
|
554
|
-
* @returns {object}
|
|
555
|
-
* @see {@link https://lodash.com/docs/#pick lodash.pick}
|
|
556
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.pick lodash.pick (中文)}
|
|
557
|
-
*/
|
|
558
|
-
static pick(object = {}, paths = []) {
|
|
559
|
-
if (!Array.isArray(paths)) paths = [paths.toString()];
|
|
560
|
-
const filteredEntries = Object.entries(object).filter(([key, value]) => paths.includes(key));
|
|
561
|
-
return Object.fromEntries(filteredEntries);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
/**
|
|
565
|
-
* 按路径写入对象值。
|
|
566
|
-
* Set object value by path.
|
|
567
|
-
*
|
|
568
|
-
* @param {object} object 目标对象 / Target object.
|
|
569
|
-
* @param {string|string[]} path 路径 / Path.
|
|
570
|
-
* @param {*} value 写入值 / Value.
|
|
571
|
-
* @returns {object}
|
|
572
|
-
* @see {@link https://lodash.com/docs/#set lodash.set}
|
|
573
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.set lodash.set (中文)}
|
|
574
|
-
*/
|
|
575
|
-
static set(object, path, value) {
|
|
576
|
-
if (!Array.isArray(path)) path = Lodash.toPath(path);
|
|
577
|
-
path.slice(0, -1).reduce((previousValue, currentValue, currentIndex) => (Object(previousValue[currentValue]) === previousValue[currentValue] ? previousValue[currentValue] : (previousValue[currentValue] = /^\d+$/.test(path[currentIndex + 1]) ? [] : {})), object)[path[path.length - 1]] = value;
|
|
578
|
-
return object;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* 将点路径或数组下标路径转换为数组。
|
|
583
|
-
* Convert dot/array-index path string into path segments.
|
|
584
|
-
*
|
|
585
|
-
* @param {string} value 路径字符串 / Path string.
|
|
586
|
-
* @returns {string[]}
|
|
587
|
-
* @see {@link https://lodash.com/docs/#toPath lodash.toPath}
|
|
588
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.toPath lodash.toPath (中文)}
|
|
589
|
-
*/
|
|
590
|
-
static toPath(value) {
|
|
591
|
-
return value
|
|
592
|
-
.replace(/\[(\d+)\]/g, ".$1")
|
|
593
|
-
.split(".")
|
|
594
|
-
.filter(Boolean);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* HTML 实体反转义。
|
|
599
|
-
* Unescape HTML entities.
|
|
600
|
-
*
|
|
601
|
-
* @param {string} string 输入文本 / Input text.
|
|
602
|
-
* @returns {string}
|
|
603
|
-
* @see {@link https://lodash.com/docs/#unescape lodash.unescape}
|
|
604
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.unescape lodash.unescape (中文)}
|
|
605
|
-
*/
|
|
606
|
-
static unescape(string) {
|
|
607
|
-
const map = {
|
|
608
|
-
"&": "&",
|
|
609
|
-
"<": "<",
|
|
610
|
-
">": ">",
|
|
611
|
-
""": '"',
|
|
612
|
-
"'": "'",
|
|
613
|
-
};
|
|
614
|
-
return string.replace(/&|<|>|"|'/g, m => map[m]);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* 删除对象路径对应的值。
|
|
619
|
-
* Remove value by object path.
|
|
620
|
-
*
|
|
621
|
-
* @param {object} [object={}] 目标对象 / Target object.
|
|
622
|
-
* @param {string|string[]} [path=""] 路径 / Path.
|
|
623
|
-
* @returns {boolean}
|
|
624
|
-
* @see {@link https://lodash.com/docs/#unset lodash.unset}
|
|
625
|
-
* @see {@link https://www.lodashjs.com/docs/lodash.unset lodash.unset (中文)}
|
|
626
|
-
*/
|
|
627
|
-
static unset(object = {}, path = "") {
|
|
628
|
-
if (!Array.isArray(path)) path = Lodash.toPath(path);
|
|
629
|
-
const result = path.reduce((previousValue, currentValue, currentIndex) => {
|
|
630
|
-
if (currentIndex === path.length - 1) {
|
|
631
|
-
delete previousValue[currentValue];
|
|
632
|
-
return true;
|
|
633
|
-
}
|
|
634
|
-
return Object(previousValue)[currentValue];
|
|
635
|
-
}, object);
|
|
636
|
-
return result;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* HTTP 状态码文本映射表。
|
|
642
|
-
* HTTP status code to status text map.
|
|
643
|
-
*
|
|
644
|
-
* 主要用途:
|
|
645
|
-
* Primary usage:
|
|
646
|
-
* - 为 Quantumult X 的 `$done` 状态行拼接提供状态文本
|
|
647
|
-
* - Provide status text for Quantumult X `$done` status-line composition
|
|
648
|
-
* - QX 在部分场景要求 `status` 为完整状态行(如 `HTTP/1.1 200 OK`)
|
|
649
|
-
* - QX may require full status line (e.g. `HTTP/1.1 200 OK`) in some cases
|
|
650
|
-
*
|
|
651
|
-
* 参考:
|
|
652
|
-
* Reference:
|
|
653
|
-
* - https://github.com/crossutility/Quantumult-X/raw/refs/heads/master/sample-rewrite-response-header.js
|
|
654
|
-
*
|
|
655
|
-
* @type {Record<number, string>}
|
|
656
|
-
*/
|
|
657
|
-
const StatusTexts = {
|
|
658
|
-
100: "Continue",
|
|
659
|
-
101: "Switching Protocols",
|
|
660
|
-
102: "Processing",
|
|
661
|
-
103: "Early Hints",
|
|
662
|
-
200: "OK",
|
|
663
|
-
201: "Created",
|
|
664
|
-
202: "Accepted",
|
|
665
|
-
203: "Non-Authoritative Information",
|
|
666
|
-
204: "No Content",
|
|
667
|
-
205: "Reset Content",
|
|
668
|
-
206: "Partial Content",
|
|
669
|
-
207: "Multi-Status",
|
|
670
|
-
208: "Already Reported",
|
|
671
|
-
226: "IM Used",
|
|
672
|
-
300: "Multiple Choices",
|
|
673
|
-
301: "Moved Permanently",
|
|
674
|
-
302: "Found",
|
|
675
|
-
304: "Not Modified",
|
|
676
|
-
307: "Temporary Redirect",
|
|
677
|
-
308: "Permanent Redirect",
|
|
678
|
-
400: "Bad Request",
|
|
679
|
-
401: "Unauthorized",
|
|
680
|
-
402: "Payment Required",
|
|
681
|
-
403: "Forbidden",
|
|
682
|
-
404: "Not Found",
|
|
683
|
-
405: "Method Not Allowed",
|
|
684
|
-
406: "Not Acceptable",
|
|
685
|
-
407: "Proxy Authentication Required",
|
|
686
|
-
408: "Request Timeout",
|
|
687
|
-
409: "Conflict",
|
|
688
|
-
410: "Gone",
|
|
689
|
-
411: "Length Required",
|
|
690
|
-
412: "Precondition Failed",
|
|
691
|
-
413: "Content Too Large",
|
|
692
|
-
414: "URI Too Long",
|
|
693
|
-
415: "Unsupported Media Type",
|
|
694
|
-
416: "Range Not Satisfiable",
|
|
695
|
-
417: "Expectation Failed",
|
|
696
|
-
418: "I'm a teapot",
|
|
697
|
-
421: "Misdirected Request",
|
|
698
|
-
422: "Unprocessable Entity",
|
|
699
|
-
423: "Locked",
|
|
700
|
-
424: "Failed Dependency",
|
|
701
|
-
425: "Too Early",
|
|
702
|
-
426: "Upgrade Required",
|
|
703
|
-
428: "Precondition Required",
|
|
704
|
-
429: "Too Many Requests",
|
|
705
|
-
431: "Request Header Fields Too Large",
|
|
706
|
-
451: "Unavailable For Legal Reasons",
|
|
707
|
-
500: "Internal Server Error",
|
|
708
|
-
501: "Not Implemented",
|
|
709
|
-
502: "Bad Gateway",
|
|
710
|
-
503: "Service Unavailable",
|
|
711
|
-
504: "Gateway Timeout",
|
|
712
|
-
505: "HTTP Version Not Supported",
|
|
713
|
-
506: "Variant Also Negotiates",
|
|
714
|
-
507: "Insufficient Storage",
|
|
715
|
-
508: "Loop Detected",
|
|
716
|
-
510: "Not Extended",
|
|
717
|
-
511: "Network Authentication Required",
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* `done` 的统一入参结构。
|
|
722
|
-
* Unified `done` input payload.
|
|
723
|
-
*
|
|
724
|
-
* @typedef {object} DonePayload
|
|
725
|
-
* @property {number|string} [status] 响应状态码或状态行 / Response status code or status line.
|
|
726
|
-
* @property {string} [url] 响应 URL / Response URL.
|
|
727
|
-
* @property {Record<string, any>} [headers] 响应头 / Response headers.
|
|
728
|
-
* @property {string|ArrayBuffer|ArrayBufferView} [body] 响应体 / Response body.
|
|
729
|
-
* @property {ArrayBuffer} [bodyBytes] 二进制响应体 / Binary response body.
|
|
730
|
-
* @property {string} [policy] 指定策略名 / Preferred policy name.
|
|
731
|
-
*/
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* 结束脚本执行并按平台转换参数。
|
|
735
|
-
* Complete script execution with platform-specific parameter mapping.
|
|
736
|
-
*
|
|
737
|
-
* 说明:
|
|
738
|
-
* Notes:
|
|
739
|
-
* - 这是调用入口,平台原生 `$done` 差异在内部处理
|
|
740
|
-
* - This is the call entry and native `$done` differences are handled internally
|
|
741
|
-
* - Worker 不调用 `$done` 或退出进程,仅记录日志
|
|
742
|
-
* - Worker neither calls `$done` nor exits the process; it only logs
|
|
743
|
-
* - Node.js 不调用 `$done`,而是直接退出进程
|
|
744
|
-
* - Node.js does not call `$done`; it exits the process directly
|
|
745
|
-
* - 未识别平台仅记录结束日志,不会强制退出
|
|
746
|
-
* - Unknown runtimes only log completion and do not force an exit
|
|
747
|
-
*
|
|
748
|
-
* @param {DonePayload} [object={}] 统一响应对象 / Unified response object.
|
|
749
|
-
* @returns {void}
|
|
750
|
-
*/
|
|
751
|
-
function done(object = {}) {
|
|
752
|
-
switch ($app) {
|
|
753
|
-
case "Surge":
|
|
754
|
-
if (object.policy) Lodash.set(object, "headers.X-Surge-Policy", object.policy);
|
|
755
|
-
Console.log("🚩 执行结束!", `🕛 ${new Date().getTime() / 1000 - $script.startTime} 秒`);
|
|
756
|
-
$done(object);
|
|
757
|
-
break;
|
|
758
|
-
case "Loon":
|
|
759
|
-
if (object.policy) object.node = object.policy;
|
|
760
|
-
Console.log("🚩 执行结束!", `🕛 ${(new Date() - $script.startTime) / 1000} 秒`);
|
|
761
|
-
$done(object);
|
|
762
|
-
break;
|
|
763
|
-
case "Stash":
|
|
764
|
-
if (object.policy) Lodash.set(object, "headers.X-Stash-Selected-Proxy", encodeURI(object.policy));
|
|
765
|
-
Console.log("🚩 执行结束!", `🕛 ${(new Date() - $script.startTime) / 1000} 秒`);
|
|
766
|
-
$done(object);
|
|
767
|
-
break;
|
|
768
|
-
case "Egern":
|
|
769
|
-
Console.log("🚩 执行结束!");
|
|
770
|
-
$done(object);
|
|
771
|
-
break;
|
|
772
|
-
case "Shadowrocket":
|
|
773
|
-
Console.log("🚩 执行结束!");
|
|
774
|
-
$done(object);
|
|
775
|
-
break;
|
|
776
|
-
case "Quantumult X":
|
|
777
|
-
if (object.policy) Lodash.set(object, "opts.policy", object.policy);
|
|
778
|
-
object = Lodash.pick(object, ["status", "url", "headers", "body", "bodyBytes"]);
|
|
779
|
-
switch (typeof object.status) {
|
|
780
|
-
case "number":
|
|
781
|
-
object.status = `HTTP/1.1 ${object.status} ${StatusTexts[object.status]}`;
|
|
782
|
-
break;
|
|
783
|
-
case "string":
|
|
784
|
-
case "undefined":
|
|
785
|
-
break;
|
|
786
|
-
default:
|
|
787
|
-
throw new TypeError(`${Function.name}: 参数类型错误, status 必须为数字或字符串`);
|
|
788
|
-
}
|
|
789
|
-
if (object.body instanceof ArrayBuffer) {
|
|
790
|
-
object.bodyBytes = object.body;
|
|
791
|
-
object.body = undefined;
|
|
792
|
-
} else if (ArrayBuffer.isView(object.body)) {
|
|
793
|
-
object.bodyBytes = object.body.buffer.slice(object.body.byteOffset, object.body.byteLength + object.body.byteOffset);
|
|
794
|
-
object.body = undefined;
|
|
795
|
-
} else if (object.body) object.bodyBytes = undefined;
|
|
796
|
-
Console.log("🚩 执行结束!");
|
|
797
|
-
$done(object);
|
|
798
|
-
break;
|
|
799
|
-
case "Worker":
|
|
800
|
-
Console.log("🚩 执行结束!");
|
|
801
|
-
break;
|
|
802
|
-
case "Node.js":
|
|
803
|
-
Console.log("🚩 执行结束!");
|
|
804
|
-
process.exit(1);
|
|
805
|
-
break;
|
|
806
|
-
default:
|
|
807
|
-
Console.log("🚩 执行结束!");
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
/**
|
|
813
|
-
* 统一适配代理的完成格式;未接管的请求原样继续。
|
|
814
|
-
* Adapt the host completion format and pass through unhandled requests.
|
|
815
|
-
* @param {import("../index.js").SettingsResponse | undefined} result 通用响应 / Common response.
|
|
816
|
-
* @returns {void} 响应已交给宿主 / Response delivered to the host.
|
|
817
|
-
*/
|
|
818
|
-
function complete(result) {
|
|
819
|
-
if (!result) {
|
|
820
|
-
done({});
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
done($app === "Quantumult X" ? result : { response: result });
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
/**
|
|
827
|
-
* 为没有原生远程 Mock 的代理返回 BoxJS JSON,不提供存储或页面处理。
|
|
828
|
-
* Return BoxJS JSON on proxies without native remote Mock, without storage or page handling.
|
|
829
|
-
* @param {unknown} config 构建时嵌入的 BoxJS JSON / BoxJS JSON embedded at build time.
|
|
830
|
-
* @returns {void} 将响应交给宿主 / Deliver the response to the host.
|
|
831
|
-
*/
|
|
832
|
-
function mock(config) {
|
|
833
|
-
const method = globalThis.$request.method;
|
|
834
|
-
const allowed = method === "GET" || method === "HEAD";
|
|
835
|
-
complete(response(globalThis.$request, allowed ? 200 : 405, allowed ? config : { error: "Method not allowed" }));
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
exports.mock = mock;
|
|
839
|
-
|
|
840
|
-
return exports;
|
|
841
|
-
|
|
842
|
-
})({});
|