From be28d59873ec1672c141f56e1baf31bb2fd70851 Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sat, 6 Sep 2025 18:01:57 -0700 Subject: [PATCH] . --- Sources/Packages/Sources/Brief/Updater.swift | 49 ++++++++++++----- .../ReleasesDownloader.swift | 27 ---------- Sources/ReleasesDownloader/main.swift | 52 ++++++++++++++----- Sources/SecretAgent/XPCInputParser.swift | 2 +- Sources/Secretive.xcodeproj/project.pbxproj | 4 -- 5 files changed, 76 insertions(+), 58 deletions(-) delete mode 100644 Sources/ReleasesDownloader/ReleasesDownloader.swift diff --git a/Sources/Packages/Sources/Brief/Updater.swift b/Sources/Packages/Sources/Brief/Updater.swift index 33ccce5..9e2cf9d 100644 --- a/Sources/Packages/Sources/Brief/Updater.swift +++ b/Sources/Packages/Sources/Brief/Updater.swift @@ -46,22 +46,16 @@ import Observation /// Manually trigger an update check. public func checkForUpdates() async throws { - let releaseData = try await withXPCCall(to: "com.maxgoedjen.Secretive.ReleasesDownloader", ReleasesDownloaderProtocol.self) { - try await $0.downloadReleases() + 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 releases = try JSONDecoder().decode([Release].self, from: releaseData) - await evaluate(releases: releases) + await evaluate(releases: try await session.send()) + session.cancel(reason: "Done") } - func withXPCCall(to service: String, _: ServiceProtocol.Type, closure: (ServiceProtocol) async throws -> Result) async rethrows -> Result { - let connectionToService = NSXPCConnection(serviceName: "com.maxgoedjen.Secretive.ReleasesDownloader") - connectionToService.remoteObjectInterface = NSXPCInterface(with: (any ReleasesDownloaderProtocol).self)// fixme - connectionToService.resume() - let service = connectionToService.remoteObjectProxy as! ServiceProtocol - let result = try await closure(service) - connectionToService.invalidate() - return result - } /// Ignores a specified release. `update` will be nil if the user has ignored the latest available release. /// - Parameter release: The release to ignore. @@ -110,3 +104,32 @@ 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/ReleasesDownloader/ReleasesDownloader.swift b/Sources/ReleasesDownloader/ReleasesDownloader.swift deleted file mode 100644 index 655c69b..0000000 --- a/Sources/ReleasesDownloader/ReleasesDownloader.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation -import Brief - -final class ReleasesDownloader: NSObject, ReleasesDownloaderProtocol { - - @objc func downloadReleases(with reply: @escaping (Data?, (any Error)?) -> Void) { - Task { - do { - let (data, _) = try await URLSession.shared.data(from: Constants.updateURL) - let releases = try JSONDecoder().decode([Release].self, from: data) - print(releases) - let jsonOut = try JSONEncoder().encode(releases) - reply(jsonOut, nil) - } catch { - reply(nil, error) - } - } - } -} - -extension ReleasesDownloader { - - enum Constants { - static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")! - } - -} diff --git a/Sources/ReleasesDownloader/main.swift b/Sources/ReleasesDownloader/main.swift index 324b839..622e0bf 100644 --- a/Sources/ReleasesDownloader/main.swift +++ b/Sources/ReleasesDownloader/main.swift @@ -1,18 +1,44 @@ -import Foundation +import XPC +import OSLog import Brief -class ServiceDelegate: NSObject, NSXPCListenerDelegate { - - func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool { - newConnection.exportedInterface = NSXPCInterface(with: (any ReleasesDownloaderProtocol).self) - let exportedObject = ReleasesDownloader() - newConnection.exportedObject = exportedObject - newConnection.resume() - return true +private let logger = Logger(subsystem: "com.maxgoedjen.secretive.ReleasesDownloader", category: "ReleasesDownloader") + +enum Constants { + static let updateURL = URL(string: "https://api.github.com/repos/maxgoedjen/secretive/releases")! +} + +func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision { + logger.log("ReleasesDownloader received inbound request") + return request.accept { xpcDictionary in + xpcDictionary.handoffReply(to: .global(qos: .userInteractive)) { + logger.log("ReleasesDownloader accepted inbound request") + Task { + do { + let (data, _) = try await URLSession.shared.data(from: Constants.updateURL) + let releases = try JSONDecoder().decode([Release].self, from: data) + xpcDictionary.reply(releases) + } catch { + logger.error("ReleasesDownloader failed with unknown error \(error)") + xpcDictionary.reply([] as [Release]) + } + } + } } } -let delegate = ServiceDelegate() -let listener = NSXPCListener.service() -listener.delegate = delegate -listener.resume() +do { + if #available(macOS 26.0, *) { + _ = try XPCListener( + service: "com.maxgoedjen.Secretive.ReleasesDownloader", + requirement: .isFromSameTeam(), + incomingSessionHandler: handleRequest(_:) + ) + } else { + _ = try XPCListener(service: "com.maxgoedjen.Secretive.ReleasesDownloader", incomingSessionHandler: handleRequest(_:)) + } + logger.log("ReleasesDownloader initialized") + dispatchMain() +} catch { + logger.error("Failed to create ReleasesDownloader, error: \(error)") +} diff --git a/Sources/SecretAgent/XPCInputParser.swift b/Sources/SecretAgent/XPCInputParser.swift index 089a390..1026bea 100644 --- a/Sources/SecretAgent/XPCInputParser.swift +++ b/Sources/SecretAgent/XPCInputParser.swift @@ -1,5 +1,6 @@ import Foundation import SecretAgentKit +import Brief /// Delegates all agent input parsing to an XPC service which wraps OpenSSH public final class XPCAgentInputParser: SSHAgentInputParserProtocol { @@ -16,7 +17,6 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol { Task { // Warm up the XPC endpoint. _ = try? await parse(data: Data()) - } } diff --git a/Sources/Secretive.xcodeproj/project.pbxproj b/Sources/Secretive.xcodeproj/project.pbxproj index e988daa..c9fce11 100644 --- a/Sources/Secretive.xcodeproj/project.pbxproj +++ b/Sources/Secretive.xcodeproj/project.pbxproj @@ -31,7 +31,6 @@ 501577C82E6BC5B4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 501577CF2E6BC5D4004A37D0 /* ReleasesDownloader.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 501577DA2E6BC5F3004A37D0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501577D62E6BC5F3004A37D0 /* main.swift */; }; - 501577DB2E6BC5F3004A37D0 /* ReleasesDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */; }; 501577DF2E6BC647004A37D0 /* Brief in Frameworks */ = {isa = PBXBuildFile; productRef = 501577DE2E6BC647004A37D0 /* Brief */; }; 501578132E6C0479004A37D0 /* XPCInputParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501578122E6C0479004A37D0 /* XPCInputParser.swift */; }; 5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; }; @@ -180,7 +179,6 @@ 501577BD2E6BC5B4004A37D0 /* ReleasesDownloader.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = ReleasesDownloader.xpc; sourceTree = BUILT_PRODUCTS_DIR; }; 501577D52E6BC5F3004A37D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 501577D62E6BC5F3004A37D0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - 501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleasesDownloader.swift; sourceTree = ""; }; 501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = ""; }; 5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = ""; }; 504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = ""; }; @@ -293,7 +291,6 @@ children = ( 501577D52E6BC5F3004A37D0 /* Info.plist */, 501577D62E6BC5F3004A37D0 /* main.swift */, - 501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */, ); path = ReleasesDownloader; sourceTree = ""; @@ -674,7 +671,6 @@ buildActionMask = 2147483647; files = ( 501577DA2E6BC5F3004A37D0 /* main.swift in Sources */, - 501577DB2E6BC5F3004A37D0 /* ReleasesDownloader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; };