c84b6ea7da
合并冲突补救:原分支从 merge base 7871512 分叉,仅修改 client/ 目录,
与 main 新增的 server/(apierr/idgen/CONVENTIONS.md) 完全不重叠,
无真实文件冲突,手动应用 feature branch 的全部 client/ 改动。
client 侧新增:
- lib/bridge/vpn_bridge.dart — Dart↔原生通道契约(冻结)
- lib/bridge/vpn_bridge_mock.dart — 假内核,UI 联调用
- lib/bridge/kernel_process.dart — 桌面子进程管理接口
- ios/PacketTunnel/{Info.plist,PacketTunnelProvider.swift} — NEPacketTunnel 骨架
- ios/Runner/VpnManager.swift — NETunnelProviderManager 封装
- android/.../PangolinVpnService.kt — VpnService 骨架
- android/.../VpnEventBus.kt — Application 级状态总线
- test/bridge/vpn_bridge_mock_test.dart — 桥接层单元测试
client 侧修改:
- android/.../MainActivity.kt — 注册三通道(MethodChannel + 2×EventChannel)
- android/.../AndroidManifest.xml — 声明 FOREGROUND_SERVICE + PangolinVpnService
- ios/Runner/AppDelegate.swift — 注册通道 + VpnManager 初始化
- ios/Runner/Info.plist — 新增 NSVPNUsageDescription
- ios/Runner.xcodeproj/project.pbxproj — 添加 PacketTunnel extension target
- lib/widgets/connect_button.dart — VpnStatus 迁移到 bridge/vpn_bridge.dart
- lib/widgets/home_shell.dart — error 状态 UI 分支补全
113 lines
3.9 KiB
Dart
113 lines
3.9 KiB
Dart
// kernel_process.dart — 桌面端内核子进程管理接口
|
||
//
|
||
// 桌面平台(macOS / Windows / Linux)直接将 sing-box 二进制作为
|
||
// 子进程运行,通过其 Clash 兼容 REST API 进行控制。
|
||
//
|
||
// 本文件仅定义接口契约;具体实现由任务 11D(桌面 PoC)完成。
|
||
//
|
||
// 进程生命周期:
|
||
// spawn(configPath) → 子进程启动,REST API 开始监听
|
||
// kill() → 优雅停止(SIGTERM),超时后 SIGKILL
|
||
//
|
||
// Clash API 通信由 [ClashApiClient] 封装,基地址固定为
|
||
// http://127.0.0.1:9090(可覆盖)。
|
||
|
||
import 'dart:async';
|
||
|
||
// ── Clash API 客户端 ─────────────────────────────────────────────
|
||
|
||
/// sing-box Clash 兼容 REST API 客户端。
|
||
/// 实现由 11D(桌面 PoC)填充;本骨架仅定义调用约定。
|
||
class ClashApiClient {
|
||
ClashApiClient({this.baseUrl = 'http://127.0.0.1:9090'});
|
||
|
||
final String baseUrl;
|
||
|
||
/// GET /traffic — 流量统计
|
||
Future<Map<String, dynamic>> getTraffic() {
|
||
throw UnimplementedError('ClashApiClient.getTraffic — 11D 实现');
|
||
}
|
||
|
||
/// GET /proxies — 节点列表
|
||
Future<Map<String, dynamic>> getProxies() {
|
||
throw UnimplementedError('ClashApiClient.getProxies — 11D 实现');
|
||
}
|
||
|
||
/// PUT /proxies/{group} — 切换出口
|
||
Future<void> selectProxy(String group, String proxy) {
|
||
throw UnimplementedError('ClashApiClient.selectProxy — 11D 实现');
|
||
}
|
||
|
||
/// GET /connections — 实时连接
|
||
Future<Map<String, dynamic>> getConnections() {
|
||
throw UnimplementedError('ClashApiClient.getConnections — 11D 实现');
|
||
}
|
||
|
||
/// DELETE /connections — 关闭所有连接
|
||
Future<void> closeAllConnections() {
|
||
throw UnimplementedError('ClashApiClient.closeAllConnections — 11D 实现');
|
||
}
|
||
}
|
||
|
||
// ── 进程管理接口 ─────────────────────────────────────────────────
|
||
|
||
/// 桌面端内核子进程管理接口。
|
||
///
|
||
/// 使用方式:
|
||
/// ```dart
|
||
/// final kernel = DesktopKernelProcess();
|
||
/// await kernel.spawn('/path/to/config.json');
|
||
/// final traffic = await kernel.clashApiClient.getTraffic();
|
||
/// await kernel.kill();
|
||
/// ```
|
||
abstract class KernelProcess {
|
||
/// 启动内核子进程。
|
||
///
|
||
/// [configPath] 为 sing-box JSON 配置文件的绝对路径。
|
||
/// 返回的 [Future] 在进程就绪(REST API 可用)后 complete。
|
||
Future<void> spawn(String configPath);
|
||
|
||
/// 停止内核子进程。
|
||
///
|
||
/// 先发 SIGTERM,[gracePeriod] 内未退出则 SIGKILL。
|
||
Future<void> kill({
|
||
Duration gracePeriod = const Duration(seconds: 3),
|
||
});
|
||
|
||
/// 进程是否正在运行。
|
||
bool get isRunning;
|
||
|
||
/// Clash 兼容 REST API 客户端(进程运行时可用)。
|
||
ClashApiClient get clashApiClient;
|
||
|
||
/// 进程标准输出/标准错误日志流(用于调试面板)。
|
||
Stream<String> get logStream;
|
||
}
|
||
|
||
// ── 占位实现(防 analyze 报错)────────────────────────────────────
|
||
|
||
/// 占位实现,抛出 [UnimplementedError]。
|
||
/// 11D 替换为真实子进程实现时删除本类。
|
||
class DesktopKernelProcess implements KernelProcess {
|
||
@override
|
||
Future<void> spawn(String configPath) {
|
||
throw UnimplementedError('DesktopKernelProcess.spawn — 11D 实现');
|
||
}
|
||
|
||
@override
|
||
Future<void> kill({Duration gracePeriod = const Duration(seconds: 3)}) {
|
||
throw UnimplementedError('DesktopKernelProcess.kill — 11D 实现');
|
||
}
|
||
|
||
@override
|
||
bool get isRunning => false;
|
||
|
||
@override
|
||
ClashApiClient get clashApiClient =>
|
||
throw UnimplementedError('DesktopKernelProcess.clashApiClient — 11D 实现');
|
||
|
||
@override
|
||
Stream<String> get logStream =>
|
||
throw UnimplementedError('DesktopKernelProcess.logStream — 11D 实现');
|
||
}
|