import Cocoa import OSLog import SecretKit import SecureEnclaveSecretKit import SmartCardSecretKit import SecretAgentKit import Brief import Observation import Common import SwiftUI import CertificateKit @main struct SecretAgent: App { @MainActor private let storeList: SecretStoreList = { let list = SecretStoreList() let cryptoKit = SecureEnclave.Store() let migrator = SecureEnclave.CryptoKitMigrator() try? migrator.migrate(to: cryptoKit) list.add(store: cryptoKit) list.add(store: SmartCard.Store()) return list }() @MainActor private let certificateStore: CertificateStore = CertificateStore() private let updater = Updater(checkOnLaunch: true) private let notifier = Notifier() private let authenticationHandler = AuthenticationHandler() private let publicKeyFileStoreController = PublicKeyFileStoreController(publicKeysURL: URL.publicKeyDirectory, certificatesURL: URL.certificatesDirectory) @Environment(\.openWindow) var openWindow private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "App") @SceneBuilder var body: some Scene { MenuBarExtra(isInserted: .constant(false)) { EmptyView() } label: { Image(systemName: "lock") .task { await notifier.registerPersistenceHandler { try await authenticationHandler.persistAuthentication(secret: $0, forDuration: $1) } } .task { let socketController = SocketController(path: URL.socketPath) let agent = Agent( storeList: storeList, certificateStore: certificateStore, authenticationHandler: authenticationHandler, witness: notifier ) for await session in socketController.sessions { Task { do { let inputParser = try await XPCAgentInputParser() let hosts = try? await XPCHostsfileReader().read() for await message in session.messages { let request = try await inputParser.parse(data: message) let agentResponse = await agent.handle(request: request, provenance: session.provenance, hosts: hosts) try session.write(agentResponse) } } catch { try? session.close() } } } } .task { try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) { try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true) } } .task { let certsMigrator = CertificateMigrator(homeDirectory: URL.homeDirectory, certificateStore: certificateStore) try? certsMigrator.migrate() try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true) for await _ in NotificationCenter.default.notifications(named: .certificateStoreReloaded) { try? publicKeyFileStoreController.generateCertificates(for: certificateStore.certificates, clear: true) } } .task { authenticationHandler.setBatchAuthHandler { @MainActor in openWindow(value: BatchedRequestsViewIdentifier()) } } .task { notifier.prompt() _ = withObservationTracking { updater.update } onChange: { [updater, notifier] in Task { guard !updater.currentVersion.isTestBuild else { return } await notifier.notify(update: updater.update!) { release in await updater.ignore(release: release) } } } } } WindowGroup(for: BatchedRequestsViewIdentifier.self) { _ in pendingView } .windowStyle(.hiddenTitleBar) .windowResizability(.contentSize) } @ViewBuilder var pendingView: some View { if !authenticationHandler.batchableRequests.isEmpty { BatchedRequestsView(authenticationHandler: authenticationHandler) } } } struct BatchedRequestsViewIdentifier: Codable, Hashable {}