104 lines
3.1 KiB
Swift
104 lines
3.1 KiB
Swift
import SwiftUI
|
|
import NetworkExtension
|
|
import ProxyCore
|
|
|
|
@Observable
|
|
@MainActor
|
|
final class AppState {
|
|
var vpnStatus: NEVPNStatus = .disconnected
|
|
var isCertificateInstalled: Bool = false
|
|
var isCertificateTrusted: Bool = false
|
|
|
|
private var vpnManager: NETunnelProviderManager?
|
|
private var statusObservation: NSObjectProtocol?
|
|
|
|
init() {
|
|
Task {
|
|
await loadVPNManager()
|
|
}
|
|
}
|
|
|
|
func loadVPNManager() async {
|
|
do {
|
|
let managers = try await NETunnelProviderManager.loadAllFromPreferences()
|
|
if let existing = managers.first {
|
|
vpnManager = existing
|
|
} else {
|
|
let manager = NETunnelProviderManager()
|
|
let proto = NETunnelProviderProtocol()
|
|
proto.providerBundleIdentifier = ProxyConstants.extensionBundleIdentifier
|
|
proto.serverAddress = ProxyConstants.proxyHost
|
|
manager.protocolConfiguration = proto
|
|
manager.localizedDescription = "Proxy"
|
|
manager.isEnabled = true
|
|
try await manager.saveToPreferences()
|
|
try await manager.loadFromPreferences()
|
|
vpnManager = manager
|
|
}
|
|
observeVPNStatus()
|
|
vpnStatus = vpnManager?.connection.status ?? .disconnected
|
|
} catch {
|
|
print("[AppState] Failed to load VPN manager: \(error)")
|
|
}
|
|
}
|
|
|
|
func toggleVPN() async {
|
|
guard let manager = vpnManager else {
|
|
await loadVPNManager()
|
|
return
|
|
}
|
|
|
|
switch manager.connection.status {
|
|
case .connected, .connecting:
|
|
manager.connection.stopVPNTunnel()
|
|
case .disconnected, .invalid:
|
|
do {
|
|
// Ensure saved and fresh before starting
|
|
manager.isEnabled = true
|
|
try await manager.saveToPreferences()
|
|
try await manager.loadFromPreferences()
|
|
try manager.connection.startVPNTunnel()
|
|
} catch {
|
|
print("[AppState] Failed to start VPN: \(error)")
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
var isVPNConnected: Bool {
|
|
vpnStatus == .connected
|
|
}
|
|
|
|
var vpnStatusText: String {
|
|
switch vpnStatus {
|
|
case .connected: "Connected"
|
|
case .connecting: "Connecting..."
|
|
case .disconnecting: "Disconnecting..."
|
|
case .disconnected: "Disconnected"
|
|
case .invalid: "Not Configured"
|
|
case .reasserting: "Reconnecting..."
|
|
@unknown default: "Unknown"
|
|
}
|
|
}
|
|
|
|
private func observeVPNStatus() {
|
|
guard let manager = vpnManager else { return }
|
|
|
|
// Remove existing observer
|
|
if let existing = statusObservation {
|
|
NotificationCenter.default.removeObserver(existing)
|
|
}
|
|
|
|
statusObservation = NotificationCenter.default.addObserver(
|
|
forName: .NEVPNStatusDidChange,
|
|
object: manager.connection,
|
|
queue: .main
|
|
) { [weak self] _ in
|
|
Task { @MainActor in
|
|
self?.vpnStatus = manager.connection.status
|
|
}
|
|
}
|
|
}
|
|
}
|