diff --git a/client/macos/Runner/VpnChannel.swift b/client/macos/Runner/VpnChannel.swift index 4057510..55f8b29 100644 --- a/client/macos/Runner/VpnChannel.swift +++ b/client/macos/Runner/VpnChannel.swift @@ -12,6 +12,7 @@ import FlutterMacOS import NetworkExtension +import SystemExtensions // NSLog 兼容老部署目标(Runner < macOS 11,os.Logger 不可用)。 private func vpnLog(_ message: String) { NSLog("[pangolin/vpn] %@", message) } @@ -24,6 +25,7 @@ final class VpnChannel: NSObject { private var statusObserver: NSObjectProtocol? private var statsTimer: Timer? private var manager: NETunnelProviderManager? + private var sysextDelegate: SysExtActivationDelegate? static func register(with registrar: FlutterPluginRegistrar) { let instance = VpnChannel() @@ -100,11 +102,17 @@ final class VpnChannel: NSObject { return mgr } - // 站外 sysex 首次需用户授权激活。 + // 请求系统加载/更新 PacketTunnel System Extension。首启系统会弹「隐私与安全性」 + // 让用户允许;允许后 didFinishWithResult 回来。已是最新则快速完成。 private func activateSystemExtensionIfNeeded() async throws { - // TODO(P1): OSSystemExtensionRequest.activationRequest( - // forExtensionWithIdentifier: tunnelBundleId, queue: .main) + delegate 等待结果。 - // 已激活则直接返回。详见安装文档步骤 4。 + try await withCheckedThrowingContinuation { (cont: CheckedContinuation) in + let req = OSSystemExtensionRequest.activationRequest( + forExtensionWithIdentifier: Self.tunnelBundleId, queue: .main) + let delegate = SysExtActivationDelegate(continuation: cont) + self.sysextDelegate = delegate // 保活到回调结束 + req.delegate = delegate + OSSystemExtensionManager.shared.submitRequest(req) + } } // ── 状态 / 速率回传 ───────────────────────────────────────────── @@ -161,3 +169,32 @@ private final class StatsStreamHandler: NSObject, FlutterStreamHandler { } func onCancel(withArguments _: Any?) -> FlutterError? { owner?.onStatsCancel(); return nil } } + +// System Extension 激活请求回调。首启需用户在「系统设置→隐私与安全性」允许, +// 允许后收到 didFinishWithResult(故 await 会阻塞到用户批准 —— 这是对的, +// 扩展没加载前隧道起不来)。 +private final class SysExtActivationDelegate: NSObject, OSSystemExtensionRequestDelegate { + private let continuation: CheckedContinuation + private var resumed = false + init(continuation: CheckedContinuation) { self.continuation = continuation } + + func request(_ request: OSSystemExtensionRequest, + didFinishWithResult result: OSSystemExtensionRequest.Result) { + guard !resumed else { return } + resumed = true + continuation.resume() + } + func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { + guard !resumed else { return } + resumed = true + continuation.resume(throwing: error) + } + func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { + vpnLog("system extension 待用户允许(系统设置 → 隐私与安全性)") + } + func request(_ request: OSSystemExtensionRequest, + actionForReplacingExtension existing: OSSystemExtensionProperties, + withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction { + .replace + } +}