Files
ProxyIOS/ProxyCore/Sources/Shared/IPCManager.swift
Trey t 148bc3887c 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
2026-04-11 12:52:18 -05:00

136 lines
4.5 KiB
Swift

import Foundation
/// Lightweight IPC between the main app and the packet tunnel extension
/// using Darwin notifications and a shared database-backed configuration model.
public final class IPCManager: @unchecked Sendable {
public static let shared = IPCManager()
public enum Notification: String, Sendable {
case newTrafficCaptured = "com.treyt.proxyapp.newTraffic"
case configurationChanged = "com.treyt.proxyapp.configChanged"
case extensionStarted = "com.treyt.proxyapp.extensionStarted"
case extensionStopped = "com.treyt.proxyapp.extensionStopped"
}
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(),
name, nil, nil, true
)
}
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()
CFNotificationCenterAddObserver(
center, nil,
{ _, _, name, _, _ in
guard let cfName = name?.rawValue as? String else { return }
DarwinCallbackStore.shared.fire(name: cfName)
},
name, nil,
.deliverImmediately
)
}
// MARK: - Shared Config (file-based, reliable cross-process)
public var isSSLProxyingEnabled: Bool {
get { get(\.sslProxyingEnabled) }
set { set(\.sslProxyingEnabled, newValue) }
}
public var isBlockListEnabled: Bool {
get { get(\.blockListEnabled) }
set { set(\.blockListEnabled, newValue) }
}
public var isBreakpointEnabled: Bool {
get { get(\.breakpointEnabled) }
set { set(\.breakpointEnabled, newValue) }
}
public var isNoCachingEnabled: Bool {
get { get(\.noCachingEnabled) }
set { set(\.noCachingEnabled, newValue) }
}
public var isDNSSpoofingEnabled: Bool {
get { get(\.dnsSpoofingEnabled) }
set { set(\.dnsSpoofingEnabled, newValue) }
}
public var hideSystemTraffic: Bool {
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)")
}
}
}
// MARK: - Darwin Callback Storage
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) -> Bool {
lock.lock()
defer { lock.unlock() }
callbacks[name] = callback
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?()
}
}