This commit is contained in:
Max Goedjen 2025-09-06 18:32:19 -07:00
parent be28d59873
commit e966203312
No known key found for this signature in database
4 changed files with 66 additions and 73 deletions

View File

@ -21,13 +21,16 @@ let package = Package(
targets: ["SmartCardSecretKit"]), targets: ["SmartCardSecretKit"]),
.library( .library(
name: "SecretAgentKit", name: "SecretAgentKit",
targets: ["SecretAgentKit"]), targets: ["SecretAgentKit", "XPCWrappers"]),
.library( .library(
name: "SecretAgentKitHeaders", name: "SecretAgentKitHeaders",
targets: ["SecretAgentKitHeaders"]), targets: ["SecretAgentKitHeaders"]),
.library( .library(
name: "Brief", name: "Brief",
targets: ["Brief"]), targets: ["Brief"]),
.library(
name: "XPCWrappers",
targets: ["XPCWrappers"]),
], ],
dependencies: [ dependencies: [
], ],
@ -70,7 +73,7 @@ let package = Package(
), ),
.target( .target(
name: "Brief", name: "Brief",
dependencies: [], dependencies: ["XPCWrappers"],
resources: [localization], resources: [localization],
swiftSettings: swiftSettings, swiftSettings: swiftSettings,
), ),
@ -78,6 +81,10 @@ let package = Package(
name: "BriefTests", name: "BriefTests",
dependencies: ["Brief"], dependencies: ["Brief"],
), ),
.target(
name: "XPCWrappers",
swiftSettings: swiftSettings,
),
] ]
) )

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
import Observation import Observation
import XPCWrappers
/// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version. /// A concrete implementation of ``UpdaterProtocol`` which considers the current release and OS version.
@Observable public final class Updater: UpdaterProtocol, Sendable { @Observable public final class Updater: UpdaterProtocol, Sendable {
@ -46,14 +47,9 @@ import Observation
/// Manually trigger an update check. /// Manually trigger an update check.
public func checkForUpdates() async throws { public func checkForUpdates() async throws {
let session: XPCSession let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.ReleasesDownloader")
if #available(macOS 26.0, *) {
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.ReleasesDownloader", requirement: .isFromSameTeam())
} else {
session = try XPCSession(xpcService: "com.maxgoedjen.Secretive.ReleasesDownloader")
}
await evaluate(releases: try await session.send()) await evaluate(releases: try await session.send())
session.cancel(reason: "Done") session.complete()
} }
@ -103,33 +99,3 @@ extension Updater {
} }
} }
private extension XPCSession {
func send<Response: Decodable & Sendable>(_ 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()
}
}

View File

@ -0,0 +1,49 @@
import Foundation
public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error & Codable>: 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 {}

View File

@ -1,52 +1,23 @@
import Foundation import Foundation
import SecretAgentKit import SecretAgentKit
import Brief import Brief
import XPCWrappers
/// Delegates all agent input parsing to an XPC service which wraps OpenSSH /// Delegates all agent input parsing to an XPC service which wraps OpenSSH
public final class XPCAgentInputParser: SSHAgentInputParserProtocol { public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
private let session: XPCSession private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
private let queue = DispatchQueue(label: "com.maxgoedjen.Secretive.AgentRequestParser", qos: .userInteractive)
public init() throws { public init() throws {
if #available(macOS 26.0, *) { session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.AgentRequestParser", warmup: true)
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())
}
} }
public func parse(data: Data) async throws -> SSHAgent.Request { public func parse(data: Data) async throws -> SSHAgent.Request {
try await withCheckedThrowingContinuation { continuation in try await session.send(data)
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)
}
}
} }
deinit { deinit {
session.cancel(reason: "Done") session.complete()
} }
struct UnknownMessageError: Error {}
} }