160 lines
5.3 KiB
Swift
160 lines
5.3 KiB
Swift
#if os(macOS)
|
|
import Foundation
|
|
import Libbox
|
|
import os
|
|
|
|
private let logger = Logger(category: "UserServiceXPC")
|
|
|
|
@objc public protocol UserServiceProtocol {
|
|
func getWIFIState(reply: @escaping (String?, String?, NSError?) -> Void)
|
|
func sendNotification(
|
|
identifier: String,
|
|
typeName: String,
|
|
typeID: Int32,
|
|
title: String,
|
|
subtitle: String,
|
|
body: String,
|
|
openURL: String,
|
|
reply: @escaping (NSError?) -> Void
|
|
)
|
|
}
|
|
|
|
public class UserServiceClient {
|
|
public static let shared = UserServiceClient()
|
|
|
|
private var connection: NSXPCConnection?
|
|
private let connectionLock = NSLock()
|
|
|
|
private init() {}
|
|
|
|
private func getConnection() -> NSXPCConnection? {
|
|
connectionLock.lock()
|
|
defer { connectionLock.unlock() }
|
|
|
|
if let existing = connection {
|
|
return existing
|
|
}
|
|
|
|
guard let endpoint = UserServiceEndpointRegistry.shared.get() else {
|
|
logger.error("UserService endpoint unavailable")
|
|
return nil
|
|
}
|
|
|
|
let newConnection = NSXPCConnection(listenerEndpoint: endpoint)
|
|
newConnection.remoteObjectInterface = NSXPCInterface(with: UserServiceProtocol.self)
|
|
|
|
newConnection.invalidationHandler = { [weak self] in
|
|
guard let self else { return }
|
|
connectionLock.lock()
|
|
connection = nil
|
|
connectionLock.unlock()
|
|
}
|
|
|
|
newConnection.resume()
|
|
connection = newConnection
|
|
return newConnection
|
|
}
|
|
|
|
private func getProxy() -> UserServiceProtocol? {
|
|
guard let conn = getConnection() else {
|
|
return nil
|
|
}
|
|
guard let proxy = conn.remoteObjectProxyWithErrorHandler { [weak self] error in
|
|
guard let self else { return }
|
|
logger.error("UserService XPC error: \(error.localizedDescription)")
|
|
connectionLock.lock()
|
|
connection = nil
|
|
connectionLock.unlock()
|
|
} as? UserServiceProtocol else {
|
|
connectionLock.lock()
|
|
connection = nil
|
|
connectionLock.unlock()
|
|
conn.invalidate()
|
|
return nil
|
|
}
|
|
return proxy
|
|
}
|
|
|
|
private func performXPCCallVoid(
|
|
_ operation: String,
|
|
call: (UserServiceProtocol, @escaping (NSError?) -> Void) -> Void
|
|
) throws {
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
var resultError: NSError?
|
|
|
|
guard let proxy = getProxy() else {
|
|
throw NSError(domain: "UserService", code: -1, userInfo: [
|
|
NSLocalizedDescriptionKey: "UserService connection unavailable",
|
|
])
|
|
}
|
|
|
|
call(proxy) { error in
|
|
resultError = error
|
|
semaphore.signal()
|
|
}
|
|
|
|
let deadline = DispatchTime.now() + .seconds(5)
|
|
if semaphore.wait(timeout: deadline) == .timedOut {
|
|
let error = NSError(domain: "UserService", code: -1, userInfo: [
|
|
NSLocalizedDescriptionKey: "\(operation) request timeout",
|
|
])
|
|
logger.error("\(operation): timeout")
|
|
throw error
|
|
}
|
|
|
|
if let error = resultError {
|
|
logger.error("\(operation) error: \(error.localizedDescription)")
|
|
throw error
|
|
}
|
|
}
|
|
|
|
public func readWIFIState() -> LibboxWIFIState? {
|
|
let semaphore = DispatchSemaphore(value: 0)
|
|
var resultSSID: String?
|
|
var resultBSSID: String?
|
|
|
|
guard let proxy = getProxy() else {
|
|
logger.error("readWIFIState: no UserService connection")
|
|
return nil
|
|
}
|
|
|
|
proxy.getWIFIState { ssid, bssid, error in
|
|
if let error {
|
|
logger.error("readWIFIState error: \(error.localizedDescription)")
|
|
} else {
|
|
resultSSID = ssid
|
|
resultBSSID = bssid
|
|
}
|
|
semaphore.signal()
|
|
}
|
|
|
|
let timeout = DispatchTime.now() + .seconds(5)
|
|
if semaphore.wait(timeout: timeout) == .timedOut {
|
|
logger.error("readWIFIState: timeout")
|
|
return nil
|
|
}
|
|
|
|
guard let ssid = resultSSID, let bssid = resultBSSID else {
|
|
return nil
|
|
}
|
|
|
|
return LibboxWIFIState(ssid, wifiBSSID: bssid)
|
|
}
|
|
|
|
public func sendNotification(_ notification: LibboxNotification) throws {
|
|
try performXPCCallVoid("sendNotification") { proxy, reply in
|
|
proxy.sendNotification(
|
|
identifier: notification.identifier,
|
|
typeName: notification.typeName,
|
|
typeID: notification.typeID,
|
|
title: notification.title,
|
|
subtitle: notification.subtitle,
|
|
body: notification.body,
|
|
openURL: notification.openURL,
|
|
reply: reply
|
|
)
|
|
}
|
|
}
|
|
}
|
|
#endif
|