refactor: Fix macOS standalone application
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user