feat(bridge): 桥接 API 面 Channel 契约 + 三端原生骨架 [tsk_iP5-_Ztq5THx]

MethodChannel pangolin/vpn (start/stop/getStatus/selectOutbound/
getActiveOutbound/setKillSwitch) + 双 EventChannel (status/stats)
契约定义在 lib/bridge/vpn_bridge.dart 头部注释冻结。

- Dart: VpnBridge 抽象接口 + VpnNativeBridge 原生实现 + VpnBridgeMock
  假内核(含 connectDelay/statsInterval 可配参数)
- Dart 桌面: kernel_process.dart KernelProcess/ClashApiClient 接口骨架
- Android: PangolinVpnService (VpnService 骨架 + 前台通知占位) +
  VpnEventBus 解耦总线 + MainActivity MethodChannel/EventChannel 注册
- iOS: PacketTunnelProvider (NEPacketTunnelProvider 骨架) + VpnManager
  (NETunnelProviderManager + NEVPNStatus 订阅) + AppDelegate 通道注册
  + Xcode project.pbxproj 添加 PacketTunnel extension target
- Widget: VpnStatus enum 迁移至 bridge/vpn_bridge.dart (+error 态),
  connect_button/home_shell 更新穷举匹配
- Test: test/bridge/vpn_bridge_mock_test.dart 覆盖 start→connecting→on
  →stop→off 序列、stats 周期推帧、selectOutbound 重连序列、schema 回测

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 12:06:03 +08:00
parent 787151245e
commit 4992d916d0
16 changed files with 1677 additions and 11 deletions
+112
View File
@@ -0,0 +1,112 @@
// 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 实现');
}