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
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>穿山甲 Tunnel</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>PacketTunnel</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</dict>
</plist>
@@ -0,0 +1,78 @@
// PacketTunnelProvider.swift Packet Tunnel Extension
//
// Target: PacketTunnel (NEPacketTunnelProvider extension)
// Bundle: com.pangolin.pangolinVpn.PacketTunnel
//
// TUN libbox 线 11F
//
// App
// App extension: NETunnelProviderSession.sendProviderMessage(_:responseHandler:)
// extension App: NEProvider.cancelTunnelWithError NETunnelProviderSession
//
// TODO(11F): startTunnel sing-box/libbox TUN
import NetworkExtension
import os.log
class PacketTunnelProvider: NEPacketTunnelProvider {
private let log = OSLog(
subsystem: "com.pangolin.pangolinVpn.PacketTunnel",
category: "VPN"
)
// NEPacketTunnelProvider
override func startTunnel(
options: [String: NSObject]?,
completionHandler: @escaping (Error?) -> Void
) {
os_log(
"PacketTunnelProvider: startTunnel — options keys: %{public}@",
log: log, type: .info,
(options?.keys.joined(separator: ", ") ?? "(none)")
)
// TODO(11F): options["config"]
// libbox/sing-box TUN API
// packetFlow / setTunnelNetworkSettings
completionHandler(nil)
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: @escaping () -> Void
) {
os_log(
"PacketTunnelProvider: stopTunnel — reason=%d",
log: log, type: .info,
reason.rawValue
)
// TODO(11F): sing-box TUN fd
completionHandler()
}
override func handleAppMessage(
_ messageData: Data,
completionHandler: ((Data?) -> Void)?
) {
os_log(
"PacketTunnelProvider: handleAppMessage — %d bytes",
log: log, type: .debug,
messageData.count
)
// TODO(11F): messageDataJSON protobuf
// selectOutbound / getStats
completionHandler?(nil)
}
override func sleep(completionHandler: @escaping () -> Void) {
os_log("PacketTunnelProvider: sleep", log: log, type: .debug)
// TODO(11F):
completionHandler()
}
override func wake() {
os_log("PacketTunnelProvider: wake", log: log, type: .debug)
// TODO(11F):
}
}