Files
pangolin/client/lib/bridge/vpn_bridge_mock.dart
T
wangjia c84b6ea7da 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 分支补全
2026-06-13 14:58:38 +08:00

138 lines
3.8 KiB
Dart
Raw 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_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();
}
}