diff --git a/Sources/Packages/Package.swift b/Sources/Packages/Package.swift index 8acfb24..dec30d6 100644 --- a/Sources/Packages/Package.swift +++ b/Sources/Packages/Package.swift @@ -21,13 +21,16 @@ let package = Package( targets: ["SmartCardSecretKit"]), .library( name: "SecretAgentKit", - targets: ["SecretAgentKit"]), + targets: ["SecretAgentKit", "XPCWrappers"]), .library( name: "SecretAgentKitHeaders", targets: ["SecretAgentKitHeaders"]), .library( name: "Brief", targets: ["Brief"]), + .library( + name: "XPCWrappers", + targets: ["XPCWrappers"]), ], dependencies: [ ], @@ -70,7 +73,7 @@ let package = Package( ), .target( name: "Brief", - dependencies: [], + dependencies: ["XPCWrappers"], resources: [localization], swiftSettings: swiftSettings, ), @@ -78,6 +81,10 @@ let package = Package( name: "BriefTests", dependencies: ["Brief"], ), + .target( + name: "XPCWrappers", + swiftSettings: swiftSettings, + ), ] ) diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 9e2cf9d..7491378 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -1,5 +1,6 @@ import Foundation import Observation +import XPCWrappers /// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version. @Observable public final class Updater: UpdaterProtocol, Sendable { @@ -46,14 +47,9 @@ import Observation /// Manually trigger an update check. public func checkForUpdates() async throws { - let session: XPCSession - if #available(macOS 26.0, *) { - session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.ReleasesDownloader", requirement: .isFromSameTeam()) - } else { - session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.ReleasesDownloader") - } + let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.ReleasesDownloader") await evaluate(releases: try await session.send()) - session.cancel(reason: "Done") + session.complete() } @@ -103,33 +99,3 @@ extension Updater { } } - -private extension XPCSession { - - func send(_ message: some Encodable = XPCSession.emptyMessage) async throws -> Response { - try await withCheckedThrowingContinuation { continuation in - do { - try send(message) { result in - switch result { - case .success(let message): - do { - let decoded = try message.decode(as: Response.self) - continuation.resume(returning: decoded) - } catch { - continuation.resume(throwing: error) - } - case .failure(let error): - continuation.resume(throwing: error) - } - } - } catch { - continuation.resume(throwing: error) - } - } - } - - static var emptyMessage: some Encodable { - Data() - } - -} diff --git a/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift b/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift new file mode 100644 index 0000000..21515eb --- /dev/null +++ b/Sources/Packages/Sources/XPCWrappers/XPCWrappers.swift @@ -0,0 +1,49 @@ +import Foundation + +public struct XPCTypedSession: Sendable { + + private let session: XPCSession + + public init(serviceName: String, warmup: Bool = false) throws { + if #available(macOS 26.0, *) { + session = try XPCSession(xpcService: serviceName, requirement: .isFromSameTeam()) + } else { + session = try XPCSession(xpcService: serviceName) + } + if warmup { + Task { [self] in + _ = try? await send() + } + } + } + + public func send(_ message: some Encodable = Data()) async throws -> ResponseType { + try await withCheckedThrowingContinuation { continuation in + do { + try session.send(message) { result in + switch result { + case .success(let message): + if let result = try? message.decode(as: ResponseType.self) { + continuation.resume(returning: result) + } else if let error = try? message.decode(as: ErrorType.self) { + continuation.resume(throwing: error) + } else { + continuation.resume(throwing: UnknownMessageError()) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } catch { + continuation.resume(throwing: error) + } + } + } + + public func complete() { + session.cancel(reason: "Done") + } + +} + +public struct UnknownMessageError: Error, Codable {} diff --git a/Sources/SecretAgent/XPCInputParser.swift b/Sources/SecretAgent/XPCInputParser.swift index 1026bea..40ff327 100644 --- a/Sources/SecretAgent/XPCInputParser.swift +++ b/Sources/SecretAgent/XPCInputParser.swift @@ -1,52 +1,23 @@ import Foundation import SecretAgentKit import Brief +import XPCWrappers /// Delegates all agent input parsing to an XPC service which wraps OpenSSH public final class XPCAgentInputParser: SSHAgentInputParserProtocol { - private let session: XPCSession - private let queue = DispatchQueue(label: "com.maxgoedjen.Secretive.AgentRequestParser", qos: .userInteractive) + private let session: XPCTypedSession public init() throws { - if #available(macOS 26.0, *) { - session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue, requirement: .isFromSameTeam()) - } else { - session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.AgentRequestParser", targetQueue: queue) - } - Task { - // Warm up the XPC endpoint. - _ = try? await parse(data: Data()) - } + session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.AgentRequestParser", warmup: true) } public func parse(data: Data) async throws -> SSHAgent.Request { - try await withCheckedThrowingContinuation { continuation in - do { - try session.send(data) { result in - switch result { - case .success(let result): - if let result = try? result.decode(as: SSHAgent.Request.self) { - continuation.resume(returning: result) - } else if let error = try? result.decode(as: SSHAgentInputParser.AgentParsingError.self) { - continuation.resume(throwing: error) - } else { - continuation.resume(throwing: UnknownMessageError()) - } - case .failure(let error): - continuation.resume(throwing: error) - } - } - } catch { - continuation.resume(throwing: error) - } - } + try await session.send(data) } deinit { - session.cancel(reason: "Done") + session.complete() } - struct UnknownMessageError: Error {} - }