merge: maestro/tsk_iP5-_Ztq5THx [桥接 API 面 Channel 契约 + 三端原生骨架] [tsk_rcNKc3GQHBUd]
合并冲突补救:原分支从 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 分支补全
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// vpn_bridge_mock.dart — 假内核实现,供 UI 层先行联调
|
||||
//
|
||||
// 行为:
|
||||
// start() → 立即 emit connecting,[connectDelay] 后 emit on,启动统计计时器
|
||||
// stop() → 立即 emit off,停止统计计时器
|
||||
// stats → on 状态下按 [statsInterval] 周期推假数据帧
|
||||
//
|
||||
// 用法示例(UI 联调):
|
||||
// final bridge = VpnBridgeMock();
|
||||
//
|
||||
// 用法示例(测试):
|
||||
// final bridge = VpnBridgeMock(
|
||||
// connectDelay: Duration(milliseconds: 50),
|
||||
// statsInterval: Duration(milliseconds: 50),
|
||||
// );
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'vpn_bridge.dart';
|
||||
|
||||
class VpnBridgeMock implements VpnBridge {
|
||||
VpnBridgeMock({
|
||||
this.connectDelay = const Duration(milliseconds: 1500),
|
||||
this.statsInterval = const Duration(seconds: 1),
|
||||
});
|
||||
|
||||
final Duration connectDelay;
|
||||
final Duration statsInterval;
|
||||
|
||||
// 内部状态
|
||||
VpnStatus _status = VpnStatus.off;
|
||||
String _activeOutbound = 'auto';
|
||||
bool _killSwitch = false;
|
||||
|
||||
final _statusCtrl = StreamController<VpnStatus>.broadcast();
|
||||
final _statsCtrl = StreamController<VpnStatsEvent>.broadcast();
|
||||
|
||||
Timer? _connectTimer;
|
||||
Timer? _statsTimer;
|
||||
|
||||
int _tickCount = 0;
|
||||
final _rng = Random();
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────
|
||||
|
||||
void _emitStatus(VpnStatus s) {
|
||||
_status = s;
|
||||
if (!_statusCtrl.isClosed) _statusCtrl.add(s);
|
||||
}
|
||||
|
||||
void _startStats() {
|
||||
_tickCount = 0;
|
||||
_statsTimer?.cancel();
|
||||
_statsTimer = Timer.periodic(statsInterval, (_) {
|
||||
_tickCount++;
|
||||
if (!_statsCtrl.isClosed) _statsCtrl.add(_fakeTick(_tickCount));
|
||||
});
|
||||
}
|
||||
|
||||
VpnStatsEvent _fakeTick(int t) {
|
||||
// 模拟稳定带宽 + 微抖动
|
||||
final upSpeed = 10240.0 + _rng.nextDouble() * 2048;
|
||||
final downSpeed = 51200.0 + _rng.nextDouble() * 8192;
|
||||
return VpnStatsEvent(
|
||||
uploadBytes: (upSpeed * t * statsInterval.inMilliseconds / 1000).round(),
|
||||
downloadBytes: (downSpeed * t * statsInterval.inMilliseconds / 1000).round(),
|
||||
uploadSpeed: upSpeed,
|
||||
downloadSpeed: downSpeed,
|
||||
urltestResults: const [
|
||||
UrltestResult(tag: 'hk-01', delayMs: 18),
|
||||
UrltestResult(tag: 'jp-01', delayMs: 32),
|
||||
UrltestResult(tag: 'sg-01', delayMs: 54),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// ── VpnBridge methods ─────────────────────────────────────────
|
||||
|
||||
@override
|
||||
Future<void> start(String configJson) async {
|
||||
_connectTimer?.cancel();
|
||||
_statsTimer?.cancel();
|
||||
_emitStatus(VpnStatus.connecting);
|
||||
_connectTimer = Timer(connectDelay, () {
|
||||
_emitStatus(VpnStatus.on);
|
||||
_startStats();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
_connectTimer?.cancel();
|
||||
_statsTimer?.cancel();
|
||||
_connectTimer = null;
|
||||
_statsTimer = null;
|
||||
_emitStatus(VpnStatus.off);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<VpnStatus> getStatus() async => _status;
|
||||
|
||||
@override
|
||||
Future<void> selectOutbound(String tag) async {
|
||||
_activeOutbound = tag;
|
||||
// 切换出口时模拟短暂重连
|
||||
if (_status == VpnStatus.on) {
|
||||
_statsTimer?.cancel();
|
||||
_emitStatus(VpnStatus.connecting);
|
||||
_connectTimer = Timer(connectDelay ~/ 2, () {
|
||||
_emitStatus(VpnStatus.on);
|
||||
_startStats();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getActiveOutbound() async => _activeOutbound;
|
||||
|
||||
@override
|
||||
Future<void> setKillSwitch({required bool on}) async {
|
||||
_killSwitch = on;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<VpnStatus> get statusStream => _statusCtrl.stream;
|
||||
|
||||
@override
|
||||
Stream<VpnStatsEvent> get statsStream => _statsCtrl.stream;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_connectTimer?.cancel();
|
||||
_statsTimer?.cancel();
|
||||
_statusCtrl.close();
|
||||
_statsCtrl.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user