#if os(macOS) import CoreWLAN import Dispatch import Foundation import os import UserNotifications private let logger = Logger(category: "UserService") public extension Notification.Name { static let extensionRequiresWIFIState = Notification.Name("extensionRequiresWIFIState") static let extensionRequiresHelperService = Notification.Name("extensionRequiresHelperService") static let navigateToSettingsPage = Notification.Name("navigateToSettingsPage") } public final class UserServiceEndpointPublisher: NSObject, NSXPCListenerDelegate { public static let shared = UserServiceEndpointPublisher() private var listener: NSXPCListener? private let exportedObject = UserServiceHandler() public func start() { guard listener == nil else { return } let listener = NSXPCListener.anonymous() listener.delegate = self listener.resume() self.listener = listener registerEndpoint(listener.endpoint) } public func stop() { if let listener { listener.invalidate() self.listener = nil } registerEndpoint(nil) } public func refreshEndpointRegistration() { guard let listener else { return } registerEndpoint(listener.endpoint) } public func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { let allowedBundleIDs = [AppConfiguration.systemExtensionBundleID] guard XPCConnectionValidator.validateConnection( newConnection, teamID: AppConfiguration.teamID, allowedBundleIDs: allowedBundleIDs ) else { let info = XPCConnectionValidator.getConnectionInfo(newConnection) logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")") return false } newConnection.exportedInterface = NSXPCInterface(with: UserServiceProtocol.self) newConnection.exportedObject = exportedObject newConnection.resume() return true } public func checkExtensionRequirements() { Task.detached { let machServiceName = AppConfiguration.appGroupID + ".system" let connection = NSXPCConnection(machServiceName: machServiceName) let remoteInterface = NSXPCInterface(with: CommandXPCProtocol.self) CommandXPC.configureInterface(remoteInterface) connection.remoteObjectInterface = remoteInterface connection.resume() guard let proxy = connection.remoteObjectProxyWithErrorHandler({ error in logger.error("Extension requirements check error: \(error.localizedDescription)") connection.invalidate() }) as? CommandXPCProtocol else { connection.invalidate() return } proxy.extensionRequirements { needWIFI, needProcess, error in if let error { logger.error("Extension requirements error: \(error.localizedDescription)") connection.invalidate() return } if needWIFI { Task { @MainActor in NotificationCenter.default.post(name: .extensionRequiresWIFIState, object: nil) } } if needProcess { Task { @MainActor in NotificationCenter.default.post(name: .extensionRequiresHelperService, object: nil) } } connection.invalidate() } } } private func registerEndpoint(_ endpoint: NSXPCListenerEndpoint?) { let machServiceName = AppConfiguration.appGroupID + ".system" let connection = NSXPCConnection(machServiceName: machServiceName) let remoteInterface = NSXPCInterface(with: CommandXPCProtocol.self) CommandXPC.configureInterface(remoteInterface) connection.remoteObjectInterface = remoteInterface connection.resume() guard let proxy = connection.remoteObjectProxyWithErrorHandler({ error in logger.error("UserService registration error: \(error.localizedDescription)") connection.invalidate() }) as? CommandXPCProtocol else { connection.invalidate() return } proxy.registerUserServiceEndpoint(endpoint) { error in if let error { logger.error("UserService register failed: \(error.localizedDescription)") } connection.invalidate() } } } private final class UserServiceHandler: NSObject, UserServiceProtocol { func getWIFIState(reply: @escaping (String?, String?, NSError?) -> Void) { let client = CWWiFiClient.shared() guard let interface = client.interface() else { reply(nil, nil, nil) return } let ssid = interface.ssid() let bssid = interface.bssid() reply(ssid, bssid, nil) } func sendNotification( identifier: String, typeName _: String, typeID _: Int32, title: String, subtitle: String, body: String, openURL: String, reply: @escaping (NSError?) -> Void ) { Task { do { let center = UNUserNotificationCenter.current() let settings = await center.notificationSettings() if settings.authorizationStatus == .notDetermined { let granted = try await center.requestAuthorization(options: [.alert, .sound]) if !granted { let error = NSError(domain: "UserService", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Notification permission denied", ]) logger.error("sendNotification error: \(error.localizedDescription)") reply(error) return } } else if settings.authorizationStatus == .denied { let error = NSError(domain: "UserService", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Notification permission denied", ]) logger.error("sendNotification error: \(error.localizedDescription)") reply(error) return } let content = UNMutableNotificationContent() content.title = title if !subtitle.isEmpty { content.subtitle = subtitle } content.body = body content.sound = .default if !openURL.isEmpty { content.userInfo["openURL"] = openURL } let request = UNNotificationRequest( identifier: identifier, content: content, trigger: nil ) try await center.add(request) reply(nil) } catch { let nsError = error as NSError logger.error("sendNotification error: \(nsError.localizedDescription)") reply(nsError) } } } } #endif