mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-09-20 03:10:57 +00:00
.
This commit is contained in:
parent
f3a1850d27
commit
eaef1e801b
@ -1,55 +0,0 @@
|
||||
import XPC
|
||||
import SecretAgentKit
|
||||
import OSLog
|
||||
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent.AgentRequestParser", category: "Parser")
|
||||
|
||||
func handleRequest(_ request: XPCListener.IncomingSessionRequest) -> XPCListener.IncomingSessionRequest.Decision {
|
||||
logger.log("Parser received inbound request")
|
||||
return request.accept { xpcMessage in
|
||||
xpcMessage.handoffReply(to: .global(qos: .userInteractive)) {
|
||||
logger.log("Parser accepted inbound request")
|
||||
handle(with: xpcMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handle(with xpcMessage: XPCReceivedMessage) {
|
||||
do {
|
||||
let parser = SSHAgentInputParser()
|
||||
let result = try parser.parse(data: xpcMessage.wrappedDecode())
|
||||
logger.log("Parser parsed message as type \(result.debugDescription)")
|
||||
xpcMessage.reply(result)
|
||||
} catch {
|
||||
logger.error("Parser failed with error \(error)")
|
||||
xpcMessage.reply(error)
|
||||
}
|
||||
}
|
||||
|
||||
extension XPCReceivedMessage {
|
||||
|
||||
func wrappedDecode() throws(SSHAgentInputParser.AgentParsingError) -> Data {
|
||||
do {
|
||||
return try decode(as: Data.self)
|
||||
} catch {
|
||||
throw SSHAgentInputParser.AgentParsingError.invalidData
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
do {
|
||||
if #available(macOS 26.0, *) {
|
||||
_ = try XPCListener(
|
||||
service: "com.maxgoedjen.Secretive.AgentRequestParser",
|
||||
requirement: .isFromSameTeam(),
|
||||
incomingSessionHandler: handleRequest(_:)
|
||||
)
|
||||
} else {
|
||||
_ = try XPCListener(service: "com.maxgoedjen.Secretive.AgentRequestParser", incomingSessionHandler: handleRequest(_:))
|
||||
}
|
||||
logger.log("Parser initialized")
|
||||
dispatchMain()
|
||||
} catch {
|
||||
logger.error("Failed to create parser, error: \(error)")
|
||||
}
|
@ -47,7 +47,7 @@ import XPCWrappers
|
||||
|
||||
/// Manually trigger an update check.
|
||||
public func checkForUpdates() async throws {
|
||||
let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.ReleasesDownloader")
|
||||
let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.SecretiveUpdater")
|
||||
await evaluate(releases: try await session.send())
|
||||
session.complete()
|
||||
}
|
||||
|
14
Sources/Packages/Sources/XPCWrappers/XPCProtocol.swift
Normal file
14
Sources/Packages/Sources/XPCWrappers/XPCProtocol.swift
Normal file
@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
@objc protocol _XPCProtocol: Sendable {
|
||||
func process(_ data: Data, with reply: @Sendable @escaping (Data?, Error?) -> Void)
|
||||
}
|
||||
|
||||
public protocol XPCProtocol<Input, Output>: Sendable {
|
||||
|
||||
associatedtype Input: Codable
|
||||
associatedtype Output: Codable
|
||||
|
||||
func process(_ data: Input) async throws -> Output
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import Foundation
|
||||
|
||||
public final class XPCServiceDelegate: NSObject, NSXPCListenerDelegate {
|
||||
|
||||
private let exportedObject: ErasedXPCProtocol
|
||||
|
||||
public init<XPCProtocolType: XPCProtocol>(exportedObject: XPCProtocolType) {
|
||||
self.exportedObject = ErasedXPCProtocol(exportedObject)
|
||||
}
|
||||
|
||||
public func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||
newConnection.exportedInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||
let exportedObject = exportedObject
|
||||
newConnection.exportedObject = exportedObject
|
||||
newConnection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||
newConnection.resume()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@objc private final class ErasedXPCProtocol: NSObject, _XPCProtocol {
|
||||
|
||||
let _process: @Sendable (Data, @Sendable @escaping (Data?, (any Error)?) -> Void) -> Void
|
||||
|
||||
public init<XPCProtocolType: XPCProtocol>(_ exportedObject: XPCProtocolType) {
|
||||
_process = { data, reply in
|
||||
Task { [reply] in
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(XPCProtocolType.Input.self, from: data)
|
||||
let result = try await exportedObject.process(decoded)
|
||||
let encoded = try JSONEncoder().encode(result)
|
||||
reply(encoded, nil)
|
||||
} catch {
|
||||
reply(nil, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func process(_ data: Data, with reply: @Sendable @escaping (Data?, (any Error)?) -> Void) {
|
||||
_process(data, reply)
|
||||
}
|
||||
|
||||
|
||||
}
|
52
Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift
Normal file
52
Sources/Packages/Sources/XPCWrappers/XPCTypedSession.swift
Normal file
@ -0,0 +1,52 @@
|
||||
import Foundation
|
||||
|
||||
public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error & Codable>: Sendable {
|
||||
|
||||
private nonisolated(unsafe) let connection: NSXPCConnection
|
||||
private var proxy: _XPCProtocol
|
||||
|
||||
public init(serviceName: String, warmup: Bool = false) throws {
|
||||
connection = NSXPCConnection(serviceName: serviceName)
|
||||
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
|
||||
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
|
||||
connection.resume()
|
||||
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
|
||||
self.proxy = proxy
|
||||
if warmup {
|
||||
Task { [self] in
|
||||
_ = try? await send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func send(_ message: some Encodable = Data()) async throws -> ResponseType {
|
||||
let encoded = try JSONEncoder().encode(message)
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
proxy.process(encoded) { data, error in
|
||||
do {
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
guard let data else {
|
||||
throw NoDataError()
|
||||
}
|
||||
let decoded = try JSONDecoder().decode(ResponseType.self, from: data)
|
||||
continuation.resume(returning: decoded)
|
||||
} catch let error as ErrorType {
|
||||
continuation.resume(throwing: error)
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func complete() {
|
||||
connection.invalidate()
|
||||
}
|
||||
|
||||
public struct NoDataError: Error {}
|
||||
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
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 {}
|
@ -1,44 +0,0 @@
|
||||
import XPC
|
||||
import OSLog
|
||||
import Brief
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
@ -9,15 +9,11 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
|
||||
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
|
||||
|
||||
public init() throws {
|
||||
session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.AgentRequestParser", warmup: true)
|
||||
session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
|
||||
}
|
||||
|
||||
public func parse(data: Data) async throws -> SSHAgent.Request {
|
||||
try await session.send(data)
|
||||
}
|
||||
|
||||
deinit {
|
||||
session.complete()
|
||||
}
|
||||
|
||||
}
|
||||
|
17
Sources/SecretAgentInputParser/SecretAgentInputParser.swift
Normal file
17
Sources/SecretAgentInputParser/SecretAgentInputParser.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
import XPCWrappers
|
||||
import SecretAgentKit
|
||||
|
||||
final class SecretAgentInputParser: NSObject, XPCProtocol {
|
||||
|
||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.SecretAgentInputParser", category: "SecretAgentInputParser")
|
||||
|
||||
func process(_ data: Data) async throws -> SSHAgent.Request {
|
||||
let parser = SSHAgentInputParser()
|
||||
let result = try parser.parse(data: data)
|
||||
logger.log("Parser parsed message as type \(result.debugDescription)")
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
7
Sources/SecretAgentInputParser/main.swift
Normal file
7
Sources/SecretAgentInputParser/main.swift
Normal file
@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import XPCWrappers
|
||||
|
||||
let delegate = XPCServiceDelegate(exportedObject: SecretAgentInputParser())
|
||||
let listener = NSXPCListener.service()
|
||||
listener.delegate = delegate
|
||||
listener.resume()
|
File diff suppressed because it is too large
Load Diff
19
Sources/SecretiveUpdater/SecretiveUpdater.swift
Normal file
19
Sources/SecretiveUpdater/SecretiveUpdater.swift
Normal file
@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
import OSLog
|
||||
import XPCWrappers
|
||||
import Brief
|
||||
|
||||
final class SecretiveUpdater: NSObject, XPCProtocol {
|
||||
|
||||
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 process(_: Data) async throws -> [Release] {
|
||||
let (data, _) = try await URLSession.shared.data(from: Constants.updateURL)
|
||||
return try JSONDecoder().decode([Release].self, from: data)
|
||||
}
|
||||
|
||||
}
|
7
Sources/SecretiveUpdater/main.swift
Normal file
7
Sources/SecretiveUpdater/main.swift
Normal file
@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import XPCWrappers
|
||||
|
||||
let delegate = XPCServiceDelegate(exportedObject: SecretiveUpdater())
|
||||
let listener = NSXPCListener.service()
|
||||
listener.delegate = delegate
|
||||
listener.resume()
|
Loading…
Reference in New Issue
Block a user