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,12 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
/// Lightweight IPC between the main app and the packet tunnel extension
|
||||
/// using Darwin notifications (fire-and-forget signals) and shared UserDefaults.
|
||||
public final class IPCManager: Sendable {
|
||||
/// using Darwin notifications and a shared database-backed configuration model.
|
||||
public final class IPCManager: @unchecked Sendable {
|
||||
public static let shared = IPCManager()
|
||||
|
||||
private let suiteName = "group.com.treyt.proxyapp"
|
||||
|
||||
public enum Notification: String, Sendable {
|
||||
case newTrafficCaptured = "com.treyt.proxyapp.newTraffic"
|
||||
case configurationChanged = "com.treyt.proxyapp.configChanged"
|
||||
@@ -14,11 +12,16 @@ public final class IPCManager: Sendable {
|
||||
case extensionStopped = "com.treyt.proxyapp.extensionStopped"
|
||||
}
|
||||
|
||||
private init() {}
|
||||
private let configurationRepo = ConfigurationRepository()
|
||||
|
||||
private init() {
|
||||
ProxyLogger.ipc.info("IPCManager using shared database-backed configuration")
|
||||
}
|
||||
|
||||
// MARK: - Darwin Notifications
|
||||
|
||||
public func post(_ notification: Notification) {
|
||||
ProxyLogger.ipc.debug("POST Darwin: \(notification.rawValue)")
|
||||
let name = CFNotificationName(notification.rawValue as CFString)
|
||||
CFNotificationCenterPostNotification(
|
||||
CFNotificationCenterGetDarwinNotifyCenter(),
|
||||
@@ -27,12 +30,16 @@ public final class IPCManager: Sendable {
|
||||
}
|
||||
|
||||
public func observe(_ notification: Notification, callback: @escaping @Sendable () -> Void) {
|
||||
let didInstall = DarwinCallbackStore.shared.register(name: notification.rawValue, callback: callback)
|
||||
if !didInstall {
|
||||
ProxyLogger.ipc.info("OBSERVE Darwin reuse: \(notification.rawValue)")
|
||||
return
|
||||
}
|
||||
|
||||
ProxyLogger.ipc.info("OBSERVE Darwin install: \(notification.rawValue)")
|
||||
let name = notification.rawValue as CFString
|
||||
let center = CFNotificationCenterGetDarwinNotifyCenter()
|
||||
|
||||
// Store callback in a static dictionary keyed by notification name
|
||||
DarwinCallbackStore.shared.register(name: notification.rawValue, callback: callback)
|
||||
|
||||
CFNotificationCenterAddObserver(
|
||||
center, nil,
|
||||
{ _, _, name, _, _ in
|
||||
@@ -44,40 +51,58 @@ public final class IPCManager: Sendable {
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Shared UserDefaults
|
||||
|
||||
public var sharedDefaults: UserDefaults? {
|
||||
UserDefaults(suiteName: suiteName)
|
||||
}
|
||||
// MARK: - Shared Config (file-based, reliable cross-process)
|
||||
|
||||
public var isSSLProxyingEnabled: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "sslProxyingEnabled") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "sslProxyingEnabled") }
|
||||
get { get(\.sslProxyingEnabled) }
|
||||
set { set(\.sslProxyingEnabled, newValue) }
|
||||
}
|
||||
|
||||
public var isBlockListEnabled: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "blockListEnabled") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "blockListEnabled") }
|
||||
get { get(\.blockListEnabled) }
|
||||
set { set(\.blockListEnabled, newValue) }
|
||||
}
|
||||
|
||||
public var isBreakpointEnabled: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "breakpointEnabled") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "breakpointEnabled") }
|
||||
get { get(\.breakpointEnabled) }
|
||||
set { set(\.breakpointEnabled, newValue) }
|
||||
}
|
||||
|
||||
public var isNoCachingEnabled: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "noCachingEnabled") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "noCachingEnabled") }
|
||||
get { get(\.noCachingEnabled) }
|
||||
set { set(\.noCachingEnabled, newValue) }
|
||||
}
|
||||
|
||||
public var isDNSSpoofingEnabled: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "dnsSpoofingEnabled") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "dnsSpoofingEnabled") }
|
||||
get { get(\.dnsSpoofingEnabled) }
|
||||
set { set(\.dnsSpoofingEnabled, newValue) }
|
||||
}
|
||||
|
||||
public var hideSystemTraffic: Bool {
|
||||
get { sharedDefaults?.bool(forKey: "hideSystemTraffic") ?? false }
|
||||
set { sharedDefaults?.set(newValue, forKey: "hideSystemTraffic") }
|
||||
get { get(\.hideSystemTraffic) }
|
||||
set { set(\.hideSystemTraffic, newValue) }
|
||||
}
|
||||
|
||||
// MARK: - Configuration
|
||||
|
||||
private func get(_ keyPath: KeyPath<ProxyConfiguration, Bool>) -> Bool {
|
||||
do {
|
||||
return try configurationRepo.current()[keyPath: keyPath]
|
||||
} catch {
|
||||
ProxyLogger.ipc.error("Config READ failed: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func set(_ keyPath: WritableKeyPath<ProxyConfiguration, Bool>, _ value: Bool) {
|
||||
do {
|
||||
try configurationRepo.update {
|
||||
$0[keyPath: keyPath] = value
|
||||
}
|
||||
ProxyLogger.ipc.info("Config SET value=\(value)")
|
||||
} catch {
|
||||
ProxyLogger.ipc.error("Config WRITE failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,18 +111,25 @@ public final class IPCManager: Sendable {
|
||||
private final class DarwinCallbackStore: @unchecked Sendable {
|
||||
static let shared = DarwinCallbackStore()
|
||||
private var callbacks: [String: @Sendable () -> Void] = [:]
|
||||
private var installedObserverNames: Set<String> = []
|
||||
private var fireCounts: [String: Int] = [:]
|
||||
private let lock = NSLock()
|
||||
|
||||
func register(name: String, callback: @escaping @Sendable () -> Void) {
|
||||
func register(name: String, callback: @escaping @Sendable () -> Void) -> Bool {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
callbacks[name] = callback
|
||||
lock.unlock()
|
||||
let isNew = installedObserverNames.insert(name).inserted
|
||||
return isNew
|
||||
}
|
||||
|
||||
func fire(name: String) {
|
||||
lock.lock()
|
||||
let cb = callbacks[name]
|
||||
fireCounts[name, default: 0] += 1
|
||||
let count = fireCounts[name] ?? 0
|
||||
lock.unlock()
|
||||
ProxyLogger.ipc.debug("FIRE Darwin callback: \(name) count=\(count)")
|
||||
cb?()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user