Add iPad support, auto-pinning, and comprehensive logging
- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import SwiftUI
|
||||
import NetworkExtension
|
||||
@preconcurrency import NetworkExtension
|
||||
import LocalAuthentication
|
||||
import ProxyCore
|
||||
import GRDB
|
||||
|
||||
@Observable
|
||||
@MainActor
|
||||
@@ -8,14 +10,44 @@ final class AppState {
|
||||
var vpnStatus: NEVPNStatus = .disconnected
|
||||
var isCertificateInstalled: Bool = false
|
||||
var isCertificateTrusted: Bool = false
|
||||
var isLocked: Bool = false
|
||||
var runtimeStatus = ProxyRuntimeStatus()
|
||||
|
||||
var isAppLockEnabled: Bool {
|
||||
get { UserDefaults.standard.bool(forKey: "appLockEnabled") }
|
||||
set { UserDefaults.standard.set(newValue, forKey: "appLockEnabled") }
|
||||
}
|
||||
|
||||
private var vpnManager: NETunnelProviderManager?
|
||||
private var statusObservation: NSObjectProtocol?
|
||||
@ObservationIgnored private var runtimeObservation: AnyDatabaseCancellable?
|
||||
private let runtimeStatusRepo = RuntimeStatusRepository()
|
||||
|
||||
init() {
|
||||
if UserDefaults.standard.bool(forKey: "appLockEnabled") {
|
||||
isLocked = true
|
||||
}
|
||||
isCertificateInstalled = CertificateManager.shared.hasCA
|
||||
Task {
|
||||
await loadVPNManager()
|
||||
}
|
||||
observeRuntimeStatus()
|
||||
}
|
||||
|
||||
func authenticate() {
|
||||
let context = LAContext()
|
||||
var error: NSError?
|
||||
guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {
|
||||
isLocked = false
|
||||
return
|
||||
}
|
||||
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: "Unlock Proxy") { [weak self] success, _ in
|
||||
Task { @MainActor in
|
||||
if success {
|
||||
self?.isLocked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadVPNManager() async {
|
||||
@@ -70,6 +102,19 @@ final class AppState {
|
||||
vpnStatus == .connected
|
||||
}
|
||||
|
||||
var hasSharedCertificate: Bool {
|
||||
guard let localFingerprint = CertificateManager.shared.caFingerprint else { return false }
|
||||
return localFingerprint == runtimeStatus.caFingerprint
|
||||
}
|
||||
|
||||
var isHTTPSInspectionVerified: Bool {
|
||||
runtimeStatus.lastSuccessfulMITMAt != nil
|
||||
}
|
||||
|
||||
var lastRuntimeError: String? {
|
||||
runtimeStatus.lastMITMError ?? runtimeStatus.lastConnectError ?? runtimeStatus.lastProxyError
|
||||
}
|
||||
|
||||
var vpnStatusText: String {
|
||||
switch vpnStatus {
|
||||
case .connected: "Connected"
|
||||
@@ -100,4 +145,17 @@ final class AppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func observeRuntimeStatus() {
|
||||
runtimeObservation = runtimeStatusRepo.observeStatus()
|
||||
.start(in: DatabaseManager.shared.dbPool) { error in
|
||||
ProxyLogger.ui.error("AppState runtime observation error: \(error.localizedDescription)")
|
||||
} onChange: { [weak self] status in
|
||||
Task { @MainActor in
|
||||
self?.runtimeStatus = status
|
||||
self?.isCertificateInstalled = CertificateManager.shared.hasCA
|
||||
self?.isCertificateTrusted = status.lastSuccessfulMITMAt != nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user