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 { 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" case extensionStarted = "com.treyt.proxyapp.extensionStarted" case extensionStopped = "com.treyt.proxyapp.extensionStopped" } private init() {} // MARK: - Darwin Notifications public func post(_ notification: Notification) { let name = CFNotificationName(notification.rawValue as CFString) CFNotificationCenterPostNotification( CFNotificationCenterGetDarwinNotifyCenter(), name, nil, nil, true ) } public func observe(_ notification: Notification, callback: @escaping @Sendable () -> Void) { 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 guard let cfName = name?.rawValue as? String else { return } DarwinCallbackStore.shared.fire(name: cfName) }, name, nil, .deliverImmediately ) } // MARK: - Shared UserDefaults public var sharedDefaults: UserDefaults? { UserDefaults(suiteName: suiteName) } public var isSSLProxyingEnabled: Bool { get { sharedDefaults?.bool(forKey: "sslProxyingEnabled") ?? false } set { sharedDefaults?.set(newValue, forKey: "sslProxyingEnabled") } } public var isBlockListEnabled: Bool { get { sharedDefaults?.bool(forKey: "blockListEnabled") ?? false } set { sharedDefaults?.set(newValue, forKey: "blockListEnabled") } } public var isBreakpointEnabled: Bool { get { sharedDefaults?.bool(forKey: "breakpointEnabled") ?? false } set { sharedDefaults?.set(newValue, forKey: "breakpointEnabled") } } public var isNoCachingEnabled: Bool { get { sharedDefaults?.bool(forKey: "noCachingEnabled") ?? false } set { sharedDefaults?.set(newValue, forKey: "noCachingEnabled") } } public var isDNSSpoofingEnabled: Bool { get { sharedDefaults?.bool(forKey: "dnsSpoofingEnabled") ?? false } set { sharedDefaults?.set(newValue, forKey: "dnsSpoofingEnabled") } } public var hideSystemTraffic: Bool { get { sharedDefaults?.bool(forKey: "hideSystemTraffic") ?? false } set { sharedDefaults?.set(newValue, forKey: "hideSystemTraffic") } } } // MARK: - Darwin Callback Storage private final class DarwinCallbackStore: @unchecked Sendable { static let shared = DarwinCallbackStore() private var callbacks: [String: @Sendable () -> Void] = [:] private let lock = NSLock() func register(name: String, callback: @escaping @Sendable () -> Void) { lock.lock() callbacks[name] = callback lock.unlock() } func fire(name: String) { lock.lock() let cb = callbacks[name] lock.unlock() cb?() } }