Files
pangolin/client/lib/services/vpn_bridge.dart
wangjia bd8f974a25 feat(M6): 控制面联调 — mock connect server + 客户端 ConnectApi 透传
tsk_nuoKSM4Vt-zK

## 服务端(server/cmd/mockserver)
- `main.go`(79 行):独立 mock HTTP server,实现 POST /v1/nodes/:id/connect
  - 路径/请求体/响应体字段名与 §3.1 契约逐字一致
  - Bearer token 鉴权(-token 参数,空值跳过,不入库)
  - 缺少 device_id → 400;未授权 → 401;非 POST → 405
  - 返回完整 sing-box config JSON(tun + REALITY/Hy2 outbound + urltest + route/dns)
  - 注:mock 联调;待 #5/#6 真实 connect 接口就绪后替换
- `main_test.go`:6 项单测,覆盖 §3.1 结构校验、auth、method、path

## 客户端(client/)
- `lib/services/connect_api.dart`:ConnectApi 类
  - fetchConfig(nodeId, deviceId) → 原始响应体字符串(不做任何修改)
  - 错误路径:HTTP 非 200 / 超时 / 非法 JSON → ConnectApiException(含双语 message)
- `lib/services/vpn_bridge.dart`:VpnBridge stub(M6 联调,libbox 绑定待 11C)
  - start(configJson) 原样存储,不修改;stop() 清空
- `lib/widgets/home_shell.dart`:_toggle/_pick 替换为真实 API 流程
  - ConnectApi.fetchConfig → VpnBridge.start(透传,无中间变换)
  - 错误路径:ConnectApiException → SnackBar,status 回 off,无残留半开隧道
  - API URL/token/deviceId 由 --dart-define 注入(不入库)
- `lib/widgets/server_tile.dart`:ServerInfo 新增 nodeId 字段
- `pubspec.yaml`:新增 http: ^1.2.1 依赖
- `test/connect_passthrough_test.dart`:4 项透传断言单测
  - 核心:fetchConfig 返回字符串 === VpnBridge.start 接收字符串(逐字节相等)
  - 空格/格式原样保留(不经 jsonEncode 重序列化)
  - HTTP 错误、非法 JSON 错误路径覆盖

## 运行方式(M6 联调)
```
# 启动 mock server(无 auth)
go run ./server/cmd/mockserver -addr :8081

# 启动客户端(指向 mock server)
flutter run \
  --dart-define=PANGOLIN_API_URL=http://localhost:8081 \
  --dart-define=PANGOLIN_API_TOKEN=dev-mock-token \
  --dart-define=PANGOLIN_DEVICE_ID=demo-device-001
```

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 14:16:29 +08:00

39 lines
1.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// vpn_bridge.dart — sing-box libbox 的 Dart 侧封装
//
// 关键约束(§3.1):[start] 接收的 [configJson] 必须原样透传,
// 禁止在 Dart 层对任何字段做增删改——组装逻辑全在服务端。
//
// M6 联调状态:stub 实现。
// TODO(11C): 替换为真实 gomobile libbox 绑定。
// tsk_nuoKSM4Vt-zK
// ignore_for_file: avoid_print
/// [VpnBridge] 封装 sing-box libbox 的启动与停止。
///
/// 每次创建新实例(非单例),便于单测注入与隔离。
class VpnBridge {
String? _lastConfigJson;
/// 以原样 [configJson] 启动 sing-box 隧道。
///
/// **禁止在此修改 configJson**(§3.1 透传约束:客户端零组装)。
/// 当前为 stub11C 完成后替换为 `libbox.start(configJson)` 原生调用。
Future<void> start(String configJson) async {
_lastConfigJson = configJson;
// TODO(11C): await _libbox.start(configJson);
print('[VpnBridge] start — length=${configJson.length} (stub, libbox pending 11C)');
}
/// 停止隧道。当前为 stub。
Future<void> stop() async {
_lastConfigJson = null;
// TODO(11C): await _libbox.stop();
print('[VpnBridge] stop (stub)');
}
/// 最近一次传入 [start] 的原始 JSON 字符串。
/// 仅供测试验证透传约束,生产代码不应依赖此字段。
// @visibleForTesting
String? get lastConfigJson => _lastConfigJson;
}