@morndream/mihomo-tui 0.5.0-rc.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/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/README.md +462 -0
- package/bin/mihomo-tui +34 -0
- package/dist/App.js +161 -0
- package/dist/App.js.map +1 -0
- package/dist/api/client.js +239 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/status.js +43 -0
- package/dist/api/status.js.map +1 -0
- package/dist/api/stream.js +139 -0
- package/dist/api/stream.js.map +1 -0
- package/dist/api/types.js +6 -0
- package/dist/api/types.js.map +1 -0
- package/dist/cli.js +221 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/conn.js +122 -0
- package/dist/commands/conn.js.map +1 -0
- package/dist/commands/logs.js +99 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/output.js +177 -0
- package/dist/commands/output.js.map +1 -0
- package/dist/commands/provider.js +140 -0
- package/dist/commands/provider.js.map +1 -0
- package/dist/commands/proxy.js +161 -0
- package/dist/commands/proxy.js.map +1 -0
- package/dist/commands/proxyEnv.js +193 -0
- package/dist/commands/proxyEnv.js.map +1 -0
- package/dist/commands/rules.js +87 -0
- package/dist/commands/rules.js.map +1 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/components/ConfirmDialog.js +31 -0
- package/dist/components/ConfirmDialog.js.map +1 -0
- package/dist/components/DelayBadge.js +27 -0
- package/dist/components/DelayBadge.js.map +1 -0
- package/dist/components/InputDialog.js +325 -0
- package/dist/components/InputDialog.js.map +1 -0
- package/dist/components/ListDialog.js +86 -0
- package/dist/components/ListDialog.js.map +1 -0
- package/dist/components/ProgressDialog.js +34 -0
- package/dist/components/ProgressDialog.js.map +1 -0
- package/dist/components/ScrollList.js +26 -0
- package/dist/components/ScrollList.js.map +1 -0
- package/dist/components/StatusBar.js +58 -0
- package/dist/components/StatusBar.js.map +1 -0
- package/dist/components/TextEditor.js +112 -0
- package/dist/components/TextEditor.js.map +1 -0
- package/dist/components/textEditorModel.js +154 -0
- package/dist/components/textEditorModel.js.map +1 -0
- package/dist/config/manager.js +296 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/config/preserveGroups.js +9 -0
- package/dist/config/preserveGroups.js.map +1 -0
- package/dist/config/ruleDocument.js +88 -0
- package/dist/config/ruleDocument.js.map +1 -0
- package/dist/config/ruleService.js +256 -0
- package/dist/config/ruleService.js.map +1 -0
- package/dist/config/ruleValidation.js +98 -0
- package/dist/config/ruleValidation.js.map +1 -0
- package/dist/config/service.js +63 -0
- package/dist/config/service.js.map +1 -0
- package/dist/config/settingsService.js +162 -0
- package/dist/config/settingsService.js.map +1 -0
- package/dist/config/skeleton.js +324 -0
- package/dist/config/skeleton.js.map +1 -0
- package/dist/config/subscriptionService.js +284 -0
- package/dist/config/subscriptionService.js.map +1 -0
- package/dist/config/subscriptions.js +200 -0
- package/dist/config/subscriptions.js.map +1 -0
- package/dist/config/types.js +5 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.js +101 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/useProviders.js +138 -0
- package/dist/hooks/useProviders.js.map +1 -0
- package/dist/hooks/useProxies.js +298 -0
- package/dist/hooks/useProxies.js.map +1 -0
- package/dist/hooks/useStream.js +127 -0
- package/dist/hooks/useStream.js.map +1 -0
- package/dist/kernel/installer.js +317 -0
- package/dist/kernel/installer.js.map +1 -0
- package/dist/kernel/releases.js +111 -0
- package/dist/kernel/releases.js.map +1 -0
- package/dist/kernel/sources.js +98 -0
- package/dist/kernel/sources.js.map +1 -0
- package/dist/rules/editor.js +181 -0
- package/dist/rules/editor.js.map +1 -0
- package/dist/rules/matcher.js +83 -0
- package/dist/rules/matcher.js.map +1 -0
- package/dist/rules/model.js +25 -0
- package/dist/rules/model.js.map +1 -0
- package/dist/rules/runtime.js +46 -0
- package/dist/rules/runtime.js.map +1 -0
- package/dist/rules/types.js +10 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/ui/FooterLine.js +62 -0
- package/dist/ui/FooterLine.js.map +1 -0
- package/dist/ui/Panel.js +47 -0
- package/dist/ui/Panel.js.map +1 -0
- package/dist/ui/RowIndicator.js +25 -0
- package/dist/ui/RowIndicator.js.map +1 -0
- package/dist/ui/SectionHeader.js +14 -0
- package/dist/ui/SectionHeader.js.map +1 -0
- package/dist/ui/StatusDot.js +15 -0
- package/dist/ui/StatusDot.js.map +1 -0
- package/dist/ui/TopBar.js +81 -0
- package/dist/ui/TopBar.js.map +1 -0
- package/dist/ui/exitGuard.js +20 -0
- package/dist/ui/exitGuard.js.map +1 -0
- package/dist/ui/keyCapture.js +28 -0
- package/dist/ui/keyCapture.js.map +1 -0
- package/dist/ui/layout.js +32 -0
- package/dist/ui/layout.js.map +1 -0
- package/dist/ui/rowIndicator.js +42 -0
- package/dist/ui/rowIndicator.js.map +1 -0
- package/dist/ui/tableHeader.js +33 -0
- package/dist/ui/tableHeader.js.map +1 -0
- package/dist/ui/theme.js +47 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/tone.js +131 -0
- package/dist/ui/tone.js.map +1 -0
- package/dist/version.js +27 -0
- package/dist/version.js.map +1 -0
- package/dist/views/Conns.js +107 -0
- package/dist/views/Conns.js.map +1 -0
- package/dist/views/Logs.js +89 -0
- package/dist/views/Logs.js.map +1 -0
- package/dist/views/Providers.js +392 -0
- package/dist/views/Providers.js.map +1 -0
- package/dist/views/Proxies.js +233 -0
- package/dist/views/Proxies.js.map +1 -0
- package/dist/views/RuleEditor.js +356 -0
- package/dist/views/RuleEditor.js.map +1 -0
- package/dist/views/Rules.js +286 -0
- package/dist/views/Rules.js.map +1 -0
- package/dist/views/Settings.js +734 -0
- package/dist/views/Settings.js.map +1 -0
- package/dist/views/groupDisplay.js +53 -0
- package/dist/views/groupDisplay.js.map +1 -0
- package/dist/views/subscriptionFields.js +59 -0
- package/dist/views/subscriptionFields.js.map +1 -0
- package/dist/views/yamlFile.js +32 -0
- package/dist/views/yamlFile.js.map +1 -0
- package/package.json +66 -0
package/dist/App.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Ink 根组件:六个标签页 + 全局快捷键 + 底部状态栏。
|
|
4
|
+
*
|
|
5
|
+
* 退出必须显式 process.exit —— mihomo 不回应 WebSocket close 帧,
|
|
6
|
+
* 句柄不释放,事件循环永不为空(SPEC 3.6)。
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useState } from 'react';
|
|
9
|
+
import { Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
10
|
+
import { MihomoClient } from './api/client.js';
|
|
11
|
+
import { useProxies } from './hooks/useProxies.js';
|
|
12
|
+
import { useProviders } from './hooks/useProviders.js';
|
|
13
|
+
import { useConnectionsStream, useLogStream, useStatusStream } from './hooks/useStream.js';
|
|
14
|
+
import { StatusBar, stateTone } from './components/StatusBar.js';
|
|
15
|
+
import { TopBar } from './ui/TopBar.js';
|
|
16
|
+
import { Panel } from './ui/Panel.js';
|
|
17
|
+
import { FooterLine } from './ui/FooterLine.js';
|
|
18
|
+
import { colors } from './ui/theme.js';
|
|
19
|
+
import { keysCaptured } from './ui/keyCapture.js';
|
|
20
|
+
import { requestGuardedExit } from './ui/exitGuard.js';
|
|
21
|
+
import { ConfirmDialog } from './components/ConfirmDialog.js';
|
|
22
|
+
import { ProxiesView } from './views/Proxies.js';
|
|
23
|
+
import { ProvidersView } from './views/Providers.js';
|
|
24
|
+
import { RulesView } from './views/Rules.js';
|
|
25
|
+
import { LogsView } from './views/Logs.js';
|
|
26
|
+
import { ConnsView } from './views/Conns.js';
|
|
27
|
+
import { SettingsView } from './views/Settings.js';
|
|
28
|
+
const TABS = ['节点', '订阅', '规则', '日志', '连接', '设置'];
|
|
29
|
+
const MODE_NAMES = { rule: '规则', global: '全局', direct: '直连' };
|
|
30
|
+
export function App({ config, version, mode }) {
|
|
31
|
+
const { exit } = useApp();
|
|
32
|
+
const { stdout } = useStdout();
|
|
33
|
+
const [tab, setTab] = useState(0);
|
|
34
|
+
const [message, setMessage] = useState('');
|
|
35
|
+
const [logLevel, setLogLevel] = useState('info');
|
|
36
|
+
const [currentMode, setCurrentMode] = useState(mode || 'rule');
|
|
37
|
+
const [tick, setTick] = useState(0);
|
|
38
|
+
// 退出确认层:主界面 ESC 先弹确认(分级退出,spec 2026-09-13)
|
|
39
|
+
const [confirmExit, setConfirmExit] = useState(false);
|
|
40
|
+
const [size, setSize] = useState({
|
|
41
|
+
columns: stdout?.columns ?? 80,
|
|
42
|
+
rows: stdout?.rows ?? 24,
|
|
43
|
+
});
|
|
44
|
+
// 终端尺寸变化时重算布局(窄屏降级为单栏)
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!stdout)
|
|
47
|
+
return;
|
|
48
|
+
const onResize = () => setSize({ columns: stdout.columns, rows: stdout.rows });
|
|
49
|
+
stdout.on('resize', onResize);
|
|
50
|
+
return () => {
|
|
51
|
+
stdout.off('resize', onResize);
|
|
52
|
+
};
|
|
53
|
+
}, [stdout]);
|
|
54
|
+
// 提示信息 4 秒后自动消失
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (!message)
|
|
57
|
+
return;
|
|
58
|
+
const timer = setTimeout(() => setMessage(''), 4000);
|
|
59
|
+
return () => clearTimeout(timer);
|
|
60
|
+
}, [message]);
|
|
61
|
+
const proxies = useProxies(config);
|
|
62
|
+
const providers = useProviders(config);
|
|
63
|
+
const status = useStatusStream(config);
|
|
64
|
+
// 日志与连接流只在对应标签页可见时渲染,避免在其他页被高频数据带着重渲染
|
|
65
|
+
const logs = useLogStream(config, logLevel, tab === 3);
|
|
66
|
+
const connections = useConnectionsStream(config, tab === 4);
|
|
67
|
+
// spinner 心跳只在真的有东西转的时候才跑 —— 否则整个 App 每 120ms 重渲染一次,
|
|
68
|
+
// 纯属白烧 CPU 与制造垃圾
|
|
69
|
+
const spinning = Boolean(proxies.testingGroup) || providers.providers.some((p) => p.updating);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!spinning)
|
|
72
|
+
return;
|
|
73
|
+
const timer = setInterval(() => setTick((t) => t + 1), 120);
|
|
74
|
+
return () => clearInterval(timer);
|
|
75
|
+
}, [spinning]);
|
|
76
|
+
useInput((input, key) => {
|
|
77
|
+
// Rule transactions register a separate guard; ordinary sessions retain hard exit.
|
|
78
|
+
if (key.ctrl && input === 'c') {
|
|
79
|
+
if (!requestGuardedExit(exit))
|
|
80
|
+
exit();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// 内层优先:对话框/文本编辑态/二次确认态捕获按键时全局键一律让路,
|
|
84
|
+
// 否则对话框里的 ESC 会被这里再消费一次把整只 TUI 退掉(广播式 useInput)
|
|
85
|
+
if (keysCaptured()) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// 日志页在编辑过滤词时会吞掉普通按键,这里只处理明确的全局键
|
|
89
|
+
if (key.tab) {
|
|
90
|
+
setTab((t) => (t + 1) % TABS.length);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// [ 向左切一个标签、] 向右切(循环式,与 Tab 同语义;TUI 惯例键位)
|
|
94
|
+
if (input === '[') {
|
|
95
|
+
setTab((t) => (t + TABS.length - 1) % TABS.length);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (input === ']') {
|
|
99
|
+
setTab((t) => (t + 1) % TABS.length);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// 数字键 1-6 直接切换到对应标签(1=节点, 2=订阅, 3=规则, 4=日志, 5=连接, 6=设置)
|
|
103
|
+
if (input >= '1' && input <= '6') {
|
|
104
|
+
const index = parseInt(input, 10) - 1;
|
|
105
|
+
if (index < TABS.length) {
|
|
106
|
+
setTab(index);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (key.escape) {
|
|
111
|
+
// 分级退出:主界面 ESC 不再直接退,先弹退出确认(Enter 才真退)
|
|
112
|
+
setConfirmExit(true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// m 键切换模式: rule -> global -> direct -> rule
|
|
116
|
+
// 规则页 m 留给本地标记,不能同时切换内核运行模式。
|
|
117
|
+
if (input === 'm' && tab !== 2) {
|
|
118
|
+
const modes = ['rule', 'global', 'direct'];
|
|
119
|
+
const currentIndex = modes.indexOf(currentMode);
|
|
120
|
+
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % modes.length;
|
|
121
|
+
const nextMode = modes[nextIndex];
|
|
122
|
+
const client = new MihomoClient(config);
|
|
123
|
+
client
|
|
124
|
+
.setMode(nextMode)
|
|
125
|
+
.then(() => {
|
|
126
|
+
setCurrentMode(nextMode);
|
|
127
|
+
const modeNames = {
|
|
128
|
+
rule: '规则',
|
|
129
|
+
global: '全局',
|
|
130
|
+
direct: '直连'
|
|
131
|
+
};
|
|
132
|
+
setMessage(`已切换到 ${modeNames[nextMode]} 模式`);
|
|
133
|
+
})
|
|
134
|
+
.catch((err) => {
|
|
135
|
+
setMessage(`切换模式失败: ${err.message}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const apiPort = (() => {
|
|
140
|
+
try {
|
|
141
|
+
return new URL(new MihomoClient(config).api).port || '19090';
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return '19090';
|
|
145
|
+
}
|
|
146
|
+
})();
|
|
147
|
+
// 布局预算:顶栏卡片 3(标题线+内容+底边)+ 键提示/消息共用 1 + 状态栏 2
|
|
148
|
+
const bodyHeight = Math.max(6, size.rows - 6);
|
|
149
|
+
const disconnected = status.state === 'closed' || status.state === 'reconnecting';
|
|
150
|
+
// 连接中视同已连接(沿用既有行为;真正的断开/重连才降级)
|
|
151
|
+
const shownState = disconnected ? status.state : 'open';
|
|
152
|
+
return (_jsxs(Box, { flexDirection: "column", width: size.columns, minHeight: size.rows, children: [_jsx(TopBar, { tabs: TABS, active: tab, modeLabel: MODE_NAMES[currentMode] ?? currentMode, apiPort: apiPort, statusTone: stateTone(shownState), width: size.columns }), _jsx(Box, { paddingX: 1, height: 1, children: message ? (_jsx(Text, { color: colors.success, children: ` ${message}` })) : (_jsx(FooterLine, { hints: [
|
|
153
|
+
{ key: 'Tab/[]', label: '切换' },
|
|
154
|
+
{ key: 'M', label: '切模式' },
|
|
155
|
+
{ key: 'ESC', label: '退出' },
|
|
156
|
+
], width: size.columns - 2 })) }), proxies.error && tab === 0 ? (_jsx(Panel, { danger: true, title: "\u5185\u6838\u4E0D\u53EF\u8FBE", width: size.columns, children: _jsx(Text, { color: colors.danger, children: `${proxies.error} 请检查 systemctl --user status mihomo` }) })) : null, confirmExit ? (
|
|
157
|
+
// 退出确认居中独占 body(cc-switch 确认卡片语言:边框标题「退出」、
|
|
158
|
+
// 键帽上置、文案居中、大留白)——模态期间页面内容隐藏,按键由 ConfirmDialog 独占
|
|
159
|
+
_jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsx(ConfirmDialog, { borderTitle: "\u9000\u51FA", message: ['确认退出?', '内核服务不受影响,仍在后台运行'], enterConfirms: true, width: Math.min(44, size.columns - 4), onConfirm: exit, onCancel: () => setConfirmExit(false) }) })) : (_jsxs(Box, { flexDirection: "column", flexGrow: 1, height: bodyHeight, children: [tab === 0 ? (_jsx(ProxiesView, { proxies: proxies, height: bodyHeight, width: size.columns, tick: tick, active: tab === 0, onMessage: setMessage })) : null, tab === 1 ? (_jsx(ProvidersView, { providers: providers, config: config, onChanged: () => proxies.refresh(), height: bodyHeight, width: size.columns, tick: tick, active: tab === 1, onMessage: setMessage })) : null, tab === 2 ? (_jsx(RulesView, { config: config, height: bodyHeight, width: size.columns, active: tab === 2, onMessage: setMessage })) : null, tab === 3 ? (_jsx(LogsView, { logs: logs, level: logLevel, onLevelChange: setLogLevel, height: bodyHeight, width: size.columns, active: tab === 3, onMessage: setMessage })) : null, tab === 4 ? (_jsx(ConnsView, { config: config, data: connections.data, height: bodyHeight, width: size.columns, active: tab === 4, onMessage: setMessage })) : null, tab === 5 ? (_jsx(SettingsView, { config: config, version: version, height: bodyHeight, width: size.columns, active: tab === 5, onMessage: setMessage })) : null] })), _jsx(StatusBar, { version: version, mode: currentMode, up: status.up, down: status.down, upTotal: status.upTotal, downTotal: status.downTotal, memory: status.memory, state: shownState, currentNode: proxies.currentNode })] }));
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=App.js.map
|
package/dist/App.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/** 内核连不上(服务未启动 / 端口不对),对应退出码 3 */
|
|
2
|
+
export class KernelUnreachableError extends Error {
|
|
3
|
+
api;
|
|
4
|
+
cause;
|
|
5
|
+
constructor(api, cause) {
|
|
6
|
+
super(`无法连接 mihomo 控制口 ${api}`);
|
|
7
|
+
this.api = api;
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
this.name = 'KernelUnreachableError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** HTTP 状态码错误(401 密钥错误、404 组名不存在等) */
|
|
13
|
+
export class HttpStatusError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
path;
|
|
16
|
+
body;
|
|
17
|
+
constructor(status, path, body) {
|
|
18
|
+
super(`HTTP ${status} ${path}${body ? `: ${body}` : ''}`);
|
|
19
|
+
this.status = status;
|
|
20
|
+
this.path = path;
|
|
21
|
+
this.body = body;
|
|
22
|
+
this.name = 'HttpStatusError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 内核返回 {"message": "..."} 的业务错误。
|
|
27
|
+
* 典型来源是延迟测试失败 —— 属于「节点不可用」这一正常结果,调用方不得当成崩溃。
|
|
28
|
+
*/
|
|
29
|
+
export class ApiBusinessError extends Error {
|
|
30
|
+
path;
|
|
31
|
+
constructor(path, message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.path = path;
|
|
34
|
+
this.name = 'ApiBusinessError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const DEFAULT_TIMEOUT = 8000;
|
|
38
|
+
export class MihomoClient {
|
|
39
|
+
api;
|
|
40
|
+
secret;
|
|
41
|
+
testUrl;
|
|
42
|
+
testTimeout;
|
|
43
|
+
constructor(config) {
|
|
44
|
+
this.api = config.api.replace(/\/+$/, '');
|
|
45
|
+
this.secret = config.secret;
|
|
46
|
+
this.testUrl = config.testUrl;
|
|
47
|
+
this.testTimeout = config.testTimeout;
|
|
48
|
+
}
|
|
49
|
+
/** 拼 URL:path 由调用方给出且各段已编码,query 交给 URLSearchParams */
|
|
50
|
+
buildUrl(path, query) {
|
|
51
|
+
const url = new URL(`${this.api}${path}`);
|
|
52
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
53
|
+
if (value !== undefined)
|
|
54
|
+
url.searchParams.set(key, String(value));
|
|
55
|
+
}
|
|
56
|
+
return url.toString();
|
|
57
|
+
}
|
|
58
|
+
async request(path, options = {}) {
|
|
59
|
+
const { method = 'GET', query, body, timeout = DEFAULT_TIMEOUT } = options;
|
|
60
|
+
const headers = {};
|
|
61
|
+
if (this.secret)
|
|
62
|
+
headers.Authorization = `Bearer ${this.secret}`;
|
|
63
|
+
if (body !== undefined)
|
|
64
|
+
headers['Content-Type'] = 'application/json';
|
|
65
|
+
let response;
|
|
66
|
+
try {
|
|
67
|
+
response = await fetch(this.buildUrl(path, query), {
|
|
68
|
+
method,
|
|
69
|
+
headers,
|
|
70
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
71
|
+
signal: AbortSignal.timeout(timeout),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
// fetch 只在网络层失败或超时时抛异常,HTTP 错误状态不抛
|
|
76
|
+
throw new KernelUnreachableError(this.api, err);
|
|
77
|
+
}
|
|
78
|
+
const text = await response.text();
|
|
79
|
+
const parsed = text ? safeJsonParse(text) : undefined;
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const message = isErrorBody(parsed) ? parsed.message : '';
|
|
82
|
+
// 401 与 404 是调用方用错了(密钥/名称),归入 HTTP 错误。
|
|
83
|
+
// 其余带 message 的状态码是内核对本次操作的明确表态,属业务错误:
|
|
84
|
+
// 503 {"message":"An error occurred in the delay test"} —— 节点不可用(实测)
|
|
85
|
+
// 400 {"message":"Selector update error: proxy not exist"} —— 节点名不在组内(实测)
|
|
86
|
+
if (message && ![401, 403, 404, 405].includes(response.status)) {
|
|
87
|
+
throw new ApiBusinessError(path, message);
|
|
88
|
+
}
|
|
89
|
+
throw new HttpStatusError(response.status, path, message || text.slice(0, 200));
|
|
90
|
+
}
|
|
91
|
+
return parsed;
|
|
92
|
+
}
|
|
93
|
+
// ---- 只读 ----
|
|
94
|
+
version() {
|
|
95
|
+
return this.request('/version');
|
|
96
|
+
}
|
|
97
|
+
configs() {
|
|
98
|
+
return this.request('/configs');
|
|
99
|
+
}
|
|
100
|
+
async proxies() {
|
|
101
|
+
const data = await this.request('/proxies');
|
|
102
|
+
return data.proxies ?? {};
|
|
103
|
+
}
|
|
104
|
+
proxy(name) {
|
|
105
|
+
return this.request(`/proxies/${encodeURIComponent(name)}`);
|
|
106
|
+
}
|
|
107
|
+
async providers() {
|
|
108
|
+
const data = await this.request('/providers/proxies');
|
|
109
|
+
return data.providers ?? {};
|
|
110
|
+
}
|
|
111
|
+
connections() {
|
|
112
|
+
return this.request('/connections');
|
|
113
|
+
}
|
|
114
|
+
/** 当前生效的规则列表(按匹配优先级排序) */
|
|
115
|
+
async rules() {
|
|
116
|
+
const data = await this.request('/rules');
|
|
117
|
+
return data.rules ?? [];
|
|
118
|
+
}
|
|
119
|
+
/** 规则集 provider 列表 */
|
|
120
|
+
async ruleProviders() {
|
|
121
|
+
const data = await this.request('/providers/rules');
|
|
122
|
+
return data.providers ?? {};
|
|
123
|
+
}
|
|
124
|
+
/** 触发规则集 provider 重新拉取 */
|
|
125
|
+
updateRuleProvider(name) {
|
|
126
|
+
return this.request(`/providers/rules/${encodeURIComponent(name)}`, {
|
|
127
|
+
method: 'PUT',
|
|
128
|
+
timeout: 60_000,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// ---- 运行时写操作(均不触碰配置文件)----
|
|
132
|
+
/** index 必须来自最新 GET /rules。 */
|
|
133
|
+
setRuleDisabled(index, disabled) {
|
|
134
|
+
if (!Number.isInteger(index) || index < 0)
|
|
135
|
+
throw new Error('规则索引必须为非负整数');
|
|
136
|
+
return this.request('/rules/disable', {
|
|
137
|
+
method: 'PATCH',
|
|
138
|
+
body: { [index]: disabled },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 切换代理组当前选中节点。
|
|
143
|
+
* 实测:对 Selector 组会改 now;对 URLTest/Fallback 组则写 fixed 字段
|
|
144
|
+
* 把该组钉死在指定节点(now 仍由测速决定,直到用 unfixProxy 解除)。
|
|
145
|
+
*/
|
|
146
|
+
selectProxy(group, name) {
|
|
147
|
+
return this.request(`/proxies/${encodeURIComponent(group)}`, {
|
|
148
|
+
method: 'PUT',
|
|
149
|
+
body: { name },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** 解除 URLTest/Fallback 组的 fixed 钉选,恢复自动测速选路 */
|
|
153
|
+
unfixProxy(group) {
|
|
154
|
+
return this.request(`/proxies/${encodeURIComponent(group)}`, { method: 'DELETE' });
|
|
155
|
+
}
|
|
156
|
+
/** 单节点延迟测试。节点不可用时内核返回 503 + message,抛 ApiBusinessError */
|
|
157
|
+
testProxyDelay(name, url = this.testUrl, timeout = this.testTimeout) {
|
|
158
|
+
return this.request(`/proxies/${encodeURIComponent(name)}/delay`, {
|
|
159
|
+
query: { url, timeout },
|
|
160
|
+
timeout: timeout + 2000,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/** 整组延迟测试,返回 {节点名: 延迟},失效节点不出现在结果里 */
|
|
164
|
+
testGroupDelay(group, url = this.testUrl, timeout = this.testTimeout) {
|
|
165
|
+
return this.request(`/group/${encodeURIComponent(group)}/delay`, {
|
|
166
|
+
query: { url, timeout },
|
|
167
|
+
timeout: timeout + 2000,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/** 更新指定订阅(provider) */
|
|
171
|
+
updateProvider(name) {
|
|
172
|
+
return this.request(`/providers/proxies/${encodeURIComponent(name)}`, {
|
|
173
|
+
method: 'PUT',
|
|
174
|
+
timeout: 60_000,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** 触发 provider 健康检查 */
|
|
178
|
+
healthCheckProvider(name) {
|
|
179
|
+
return this.request(`/providers/proxies/${encodeURIComponent(name)}/healthcheck`, {
|
|
180
|
+
timeout: 60_000,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
closeConnection(id) {
|
|
184
|
+
return this.request(`/connections/${encodeURIComponent(id)}`, { method: 'DELETE' });
|
|
185
|
+
}
|
|
186
|
+
closeAllConnections() {
|
|
187
|
+
return this.request('/connections', { method: 'DELETE' });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 切换运行模式:rule(规则)、global(全局)、direct(直连)
|
|
191
|
+
*/
|
|
192
|
+
setMode(mode) {
|
|
193
|
+
return this.request('/configs', {
|
|
194
|
+
method: 'PATCH',
|
|
195
|
+
body: { mode },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 热重载配置。内核自行从磁盘重新读取,本程序不写入任何内容。
|
|
200
|
+
* path 为空时内核用启动时的配置路径。
|
|
201
|
+
*/
|
|
202
|
+
reload(path = '') {
|
|
203
|
+
return this.request('/configs', {
|
|
204
|
+
method: 'PUT',
|
|
205
|
+
query: { force: 'false' },
|
|
206
|
+
body: { path, payload: '' },
|
|
207
|
+
timeout: 30_000,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/** WebSocket 地址;secret 只能走 query 参数(WebSocket 无法自定义请求头) */
|
|
211
|
+
wsUrl(path, query = {}) {
|
|
212
|
+
const url = new URL(`${this.api}${path}`);
|
|
213
|
+
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
214
|
+
for (const [key, value] of Object.entries(query)) {
|
|
215
|
+
if (value !== undefined)
|
|
216
|
+
url.searchParams.set(key, String(value));
|
|
217
|
+
}
|
|
218
|
+
if (this.secret)
|
|
219
|
+
url.searchParams.set('token', this.secret);
|
|
220
|
+
return url.toString();
|
|
221
|
+
}
|
|
222
|
+
logsUrl(level = 'info') {
|
|
223
|
+
return this.wsUrl('/logs', { level });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function safeJsonParse(text) {
|
|
227
|
+
try {
|
|
228
|
+
return JSON.parse(text);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function isErrorBody(value) {
|
|
235
|
+
return (typeof value === 'object' &&
|
|
236
|
+
value !== null &&
|
|
237
|
+
typeof value.message === 'string');
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 取节点最近一次延迟。
|
|
3
|
+
* 优先用 extra[testUrl] 下的记录(同一节点在不同测速地址下有独立历史),
|
|
4
|
+
* 无对应记录时回退到顶层 history。
|
|
5
|
+
*/
|
|
6
|
+
export function latestDelay(proxy, testUrl) {
|
|
7
|
+
const scoped = testUrl ? proxy.extra?.[testUrl]?.history : undefined;
|
|
8
|
+
const history = scoped && scoped.length > 0 ? scoped : proxy.history;
|
|
9
|
+
return history?.at(-1)?.delay;
|
|
10
|
+
}
|
|
11
|
+
export function classify(proxy, thresholds, testUrl) {
|
|
12
|
+
const delay = latestDelay(proxy, testUrl);
|
|
13
|
+
// 从未测过:history 为空,与「测过但失败」是两件事
|
|
14
|
+
if (delay === undefined)
|
|
15
|
+
return { status: 'untested', delay: undefined };
|
|
16
|
+
// 内核用 delay=0 表示该次测试失败
|
|
17
|
+
if (delay <= 0)
|
|
18
|
+
return { status: 'dead', delay: undefined };
|
|
19
|
+
if (delay < thresholds.good)
|
|
20
|
+
return { status: 'good', delay };
|
|
21
|
+
if (delay < thresholds.fair)
|
|
22
|
+
return { status: 'fair', delay };
|
|
23
|
+
return { status: 'slow', delay };
|
|
24
|
+
}
|
|
25
|
+
/** 纯文本渲染(CLI 用)。TUI 里由 DelayBadge 负责着色 */
|
|
26
|
+
export function formatDelay(result) {
|
|
27
|
+
switch (result.status) {
|
|
28
|
+
case 'untested':
|
|
29
|
+
return '---';
|
|
30
|
+
case 'dead':
|
|
31
|
+
return '错误';
|
|
32
|
+
default:
|
|
33
|
+
return `${result.delay}ms`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export const STATUS_LABEL = {
|
|
37
|
+
good: '正常',
|
|
38
|
+
fair: '一般',
|
|
39
|
+
slow: '缓慢',
|
|
40
|
+
untested: '未测试',
|
|
41
|
+
dead: '错误',
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/api/status.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,OAAgB;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;IACpE,MAAM,OAAO,GAAG,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAA;IACpE,OAAO,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,KAAgB,EAChB,UAA2B,EAC3B,OAAgB;IAEhB,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;IACzC,+BAA+B;IAC/B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IACxE,uBAAuB;IACvB,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IAC3D,IAAI,KAAK,GAAG,UAAU,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC7D,IAAI,KAAK,GAAG,UAAU,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAClC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,UAAU;YACb,OAAO,KAAK,CAAA;QACd,KAAK,MAAM;YACT,OAAO,IAAI,CAAA;QACb;YACE,OAAO,GAAG,MAAM,CAAC,KAAK,IAAI,CAAA;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAA+B;IACtD,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,KAAK;IACf,IAAI,EAAE,IAAI;CACX,CAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket 封装。Node 24 内置全局 WebSocket,不引入 ws 依赖。
|
|
3
|
+
* 三个端点:/traffic、/memory、/logs、/connections(均实测返回 101)。
|
|
4
|
+
*
|
|
5
|
+
* 要点:
|
|
6
|
+
* - secret 通过 query 参数 token 传递(WebSocket 无法自定义请求头);
|
|
7
|
+
* - 断线指数退避重连 1s → 2s → 4s,上限 30s;
|
|
8
|
+
* - 重连期间保留已有数据,由调用方决定如何展示。
|
|
9
|
+
*/
|
|
10
|
+
const INITIAL_DELAY = 1000;
|
|
11
|
+
const MAX_DELAY = 30_000;
|
|
12
|
+
/**
|
|
13
|
+
* 一条自动重连的 WebSocket 订阅。
|
|
14
|
+
* 返回的 close() 是幂等的,调用后不再重连。
|
|
15
|
+
*/
|
|
16
|
+
export class Stream {
|
|
17
|
+
url;
|
|
18
|
+
handlers;
|
|
19
|
+
options;
|
|
20
|
+
socket;
|
|
21
|
+
timer;
|
|
22
|
+
delay;
|
|
23
|
+
closed = false;
|
|
24
|
+
state = 'connecting';
|
|
25
|
+
constructor(url, handlers, options = {}) {
|
|
26
|
+
this.url = url;
|
|
27
|
+
this.handlers = handlers;
|
|
28
|
+
this.options = options;
|
|
29
|
+
this.delay = options.initialDelay ?? INITIAL_DELAY;
|
|
30
|
+
this.connect();
|
|
31
|
+
}
|
|
32
|
+
setState(state, detail) {
|
|
33
|
+
if (this.state === state)
|
|
34
|
+
return;
|
|
35
|
+
this.state = state;
|
|
36
|
+
this.handlers.onState?.(state, detail);
|
|
37
|
+
}
|
|
38
|
+
connect() {
|
|
39
|
+
if (this.closed)
|
|
40
|
+
return;
|
|
41
|
+
this.setState(this.delay === (this.options.initialDelay ?? INITIAL_DELAY) ? 'connecting' : 'reconnecting');
|
|
42
|
+
let socket;
|
|
43
|
+
try {
|
|
44
|
+
socket = new WebSocket(this.url);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
// 非法 URL 之类的同步失败,也走退避重试,避免调用方拿到异常直接崩
|
|
48
|
+
this.scheduleReconnect(err instanceof Error ? err.message : String(err));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
this.socket = socket;
|
|
52
|
+
socket.addEventListener('open', () => {
|
|
53
|
+
// 连上后重置退避,下次断线仍从 1s 起
|
|
54
|
+
this.delay = this.options.initialDelay ?? INITIAL_DELAY;
|
|
55
|
+
this.setState('open');
|
|
56
|
+
});
|
|
57
|
+
socket.addEventListener('message', (event) => {
|
|
58
|
+
const text = typeof event.data === 'string'
|
|
59
|
+
? event.data
|
|
60
|
+
: Buffer.from(event.data).toString('utf8');
|
|
61
|
+
// 内核偶发发送空帧或非 JSON 心跳,静默丢弃,不能让流断掉
|
|
62
|
+
let frame;
|
|
63
|
+
try {
|
|
64
|
+
frame = JSON.parse(text);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.handlers.onMessage(frame);
|
|
70
|
+
});
|
|
71
|
+
socket.addEventListener('error', () => {
|
|
72
|
+
// error 之后必然跟一个 close,重连逻辑统一放 close 里,避免重复排程
|
|
73
|
+
});
|
|
74
|
+
socket.addEventListener('close', (event) => {
|
|
75
|
+
this.socket = undefined;
|
|
76
|
+
if (this.closed)
|
|
77
|
+
return;
|
|
78
|
+
this.scheduleReconnect(event.reason || `code ${event.code}`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
scheduleReconnect(detail) {
|
|
82
|
+
if (this.closed)
|
|
83
|
+
return;
|
|
84
|
+
this.setState('reconnecting', detail);
|
|
85
|
+
const wait = this.delay;
|
|
86
|
+
this.delay = Math.min(this.delay * 2, this.options.maxDelay ?? MAX_DELAY);
|
|
87
|
+
this.timer = setTimeout(() => {
|
|
88
|
+
this.timer = undefined;
|
|
89
|
+
this.connect();
|
|
90
|
+
}, wait);
|
|
91
|
+
// 重连定时器不应吊住进程退出
|
|
92
|
+
this.timer.unref?.();
|
|
93
|
+
}
|
|
94
|
+
close() {
|
|
95
|
+
if (this.closed)
|
|
96
|
+
return;
|
|
97
|
+
this.closed = true;
|
|
98
|
+
if (this.timer)
|
|
99
|
+
clearTimeout(this.timer);
|
|
100
|
+
this.timer = undefined;
|
|
101
|
+
// 1000 = 正常关闭。
|
|
102
|
+
// 注意:mihomo v1.19.24 实测**从不回应 close 帧**,仍继续推送数据帧与 ping,
|
|
103
|
+
// 因此 undici 的 close() 握手永不完成、底层 TCP 句柄不会释放,事件循环也就不会空。
|
|
104
|
+
// 本类只保证「关闭后不再回调、不再重连」;进程退出必须由调用方显式执行
|
|
105
|
+
// (CLI 用 exitAfterFlush,TUI 在 unmount 后同样需要显式退出)。
|
|
106
|
+
this.socket?.close(1000);
|
|
107
|
+
this.socket = undefined;
|
|
108
|
+
this.setState('closed');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** 定长环形缓冲,用于日志与连接历史,防止长时间运行内存膨胀 */
|
|
112
|
+
export class RingBuffer {
|
|
113
|
+
capacity;
|
|
114
|
+
items = [];
|
|
115
|
+
constructor(capacity) {
|
|
116
|
+
this.capacity = capacity;
|
|
117
|
+
}
|
|
118
|
+
push(item) {
|
|
119
|
+
this.items.push(item);
|
|
120
|
+
if (this.items.length > this.capacity) {
|
|
121
|
+
this.items.splice(0, this.items.length - this.capacity);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
get size() {
|
|
125
|
+
return this.items.length;
|
|
126
|
+
}
|
|
127
|
+
/** 返回副本,调用方可安全持有 */
|
|
128
|
+
toArray() {
|
|
129
|
+
return [...this.items];
|
|
130
|
+
}
|
|
131
|
+
/** 取最后 n 条 */
|
|
132
|
+
tail(n) {
|
|
133
|
+
return this.items.slice(Math.max(0, this.items.length - n));
|
|
134
|
+
}
|
|
135
|
+
clear() {
|
|
136
|
+
this.items = [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/api/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/api/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|