fix(client/macos): 实时统计改走 sendProviderMessage,根治容器路径死局

上一版试图让 root 扩展把 command.sock 建到用户容器,反而让 startTunnel 抛错、隧道连不上
(root 在用户 home 建 socket、属主/路径与无 root 的 app 对不上,且方向本就错)。回退该路子。

正解:用 NetworkExtension 官方跨进程通道,绕开「root 扩展容器 vs 用户 app 容器」死局——
- 扩展内新增 StatsCollector:连本进程自己的 command.sock(同容器、root 可达),订阅
  status/group 缓存最新上下行 + urltest;handleAppMessage 收到 "stats" 即回最新 JSON。
- 主 app StatsClient 改为每秒 NETunnelProviderSession.sendProviderMessage("stats") 拉取、
  解析后推 onStats(不再直连 socket);VpnChannel 三处启动点改传 session。
- 扩展代码变更 → CURRENT_PROJECT_VERSION 44→45,让 sysextd 重装。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-30 12:05:43 +08:00
parent 71132df669
commit 6c19b1bd92
4 changed files with 208 additions and 204 deletions
+12 -7
View File
@@ -171,10 +171,12 @@ final class VpnChannel: NSObject {
let s = Self.statusString(conn.status)
vpnLog("NEVPNStatus 变化 → \(s) (raw=\(conn.status.rawValue))")
self.statusSink?(s)
// CommandClient
// sendProviderMessage
switch conn.status {
case .connected:
self.statsClient.start()
if let s = conn as? NETunnelProviderSession {
self.statsClient.start(session: s)
}
case .disconnected, .invalid:
self.statsClient.stop()
default:
@@ -193,10 +195,10 @@ final class VpnChannel: NSObject {
let mgr = all.first else { return }
await MainActor.run {
self.manager = mgr
if mgr.connection.status == .connected {
if mgr.connection.status == .connected, let s = mgr.connection as? NETunnelProviderSession {
vpnLog("启动时检测到隧道已连接(raw=\(mgr.connection.status.rawValue)),主动启动实时统计")
self.statusSink?(Self.statusString(mgr.connection.status))
self.statsClient.start()
self.statsClient.start(session: s)
}
}
}
@@ -209,9 +211,12 @@ final class VpnChannel: NSObject {
fileprivate func onStatsListen(_ sink: @escaping FlutterEventSink) {
statsSink = sink
// StatsClient(libbox CommandClient) connected
// onStats ; sink
if manager?.connection.status == .connected { statsClient.start() }
// StatsClient( sendProviderMessage ) connected
// onStats ; sink
if manager?.connection.status == .connected,
let s = manager?.connection as? NETunnelProviderSession {
statsClient.start(session: s)
}
}
fileprivate func onStatsCancel() { statsSink = nil }