This commit is contained in:
Max Goedjen 2025-09-06 18:01:57 -07:00
parent 2efba1bc21
commit be28d59873
No known key found for this signature in database
5 changed files with 76 additions and 58 deletions

View File

@ -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<ServiceProtocol, Result>(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<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

@ -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")!
}
}

View File

@ -1,18 +1,44 @@
import Foundation
import XPC
import OSLog
import Brief
class ServiceDelegate: NSObject, NSXPCListenerDelegate {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.ReleasesDownloader", category: "ReleasesDownloader")
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
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)")
}

View File

@ -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())
}
}

View File

@ -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 = "<group>"; };
501577D62E6BC5F3004A37D0 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleasesDownloader.swift; sourceTree = "<group>"; };
501578122E6C0479004A37D0 /* XPCInputParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCInputParser.swift; sourceTree = "<group>"; };
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
504788EB2E680DC400B4556F /* URLs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLs.swift; sourceTree = "<group>"; };
@ -293,7 +291,6 @@
children = (
501577D52E6BC5F3004A37D0 /* Info.plist */,
501577D62E6BC5F3004A37D0 /* main.swift */,
501577D72E6BC5F3004A37D0 /* ReleasesDownloader.swift */,
);
path = ReleasesDownloader;
sourceTree = "<group>";
@ -674,7 +671,6 @@
buildActionMask = 2147483647;
files = (
501577DA2E6BC5F3004A37D0 /* main.swift in Sources */,
501577DB2E6BC5F3004A37D0 /* ReleasesDownloader.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};