mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-01-08 12:37:07 +00:00
More
This commit is contained in:
parent
304741e019
commit
576e625b8f
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import Synchronization
|
import Synchronization
|
||||||
|
|
||||||
/// A protocol for retreiving the latest available version of an app.
|
/// A protocol for retreiving the latest available version of an app.
|
||||||
public protocol UpdaterProtocol: ObservableObject {
|
public protocol UpdaterProtocol: Observable {
|
||||||
|
|
||||||
/// The latest update
|
/// The latest update
|
||||||
var update: Release? { get }
|
var update: Release? { get }
|
||||||
|
@ -5,7 +5,7 @@ import SecretKit
|
|||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
|
/// The `Agent` is an implementation of an SSH agent. It manages coordination and access between a socket, traces requests, notifies witnesses and passes requests to stores.
|
||||||
public final class Agent {
|
public final class Agent: Sendable {
|
||||||
|
|
||||||
private let storeList: SecretStoreList
|
private let storeList: SecretStoreList
|
||||||
private let witness: SigningWitness?
|
private let witness: SigningWitness?
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
/// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access.
|
/// A protocol that allows conformers to be notified of access to secrets, and optionally prevent access.
|
||||||
public protocol SigningWitness {
|
public protocol SigningWitness: Sendable {
|
||||||
|
|
||||||
/// A ridiculously named method that notifies the callee that a signing operation is about to be performed using a secret. The callee may `throw` an `Error` to prevent access from occurring.
|
/// A ridiculously named method that notifies the callee that a signing operation is about to be performed using a secret. The callee may `throw` an `Error` to prevent access from occurring.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import Synchronization
|
||||||
|
|
||||||
/// Manages storage and lookup for OpenSSH certificates.
|
/// Manages storage and lookup for OpenSSH certificates.
|
||||||
public final class OpenSSHCertificateHandler {
|
public final class OpenSSHCertificateHandler: Sendable {
|
||||||
|
|
||||||
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory())
|
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory())
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
|
||||||
private let writer = OpenSSHKeyWriter()
|
private let writer = OpenSSHKeyWriter()
|
||||||
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]
|
private let keyBlobsAndNames: Mutex<[AnySecret: (Data, Data)]> = .init([:])
|
||||||
|
|
||||||
/// Initializes an OpenSSHCertificateHandler.
|
/// Initializes an OpenSSHCertificateHandler.
|
||||||
public init() {
|
public init() {
|
||||||
@ -20,8 +21,10 @@ public final class OpenSSHCertificateHandler {
|
|||||||
logger.log("No certificates, short circuiting")
|
logger.log("No certificates, short circuiting")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyBlobsAndNames = secrets.reduce(into: [:]) { partialResult, next in
|
keyBlobsAndNames.withLock {
|
||||||
partialResult[next] = try? loadKeyblobAndName(for: next)
|
$0 = secrets.reduce(into: [:]) { partialResult, next in
|
||||||
|
partialResult[next] = try? loadKeyblobAndName(for: next)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +32,10 @@ public final class OpenSSHCertificateHandler {
|
|||||||
/// - Parameter secret: The secret to check for a certificate.
|
/// - Parameter secret: The secret to check for a certificate.
|
||||||
/// - Returns: A boolean describing whether or not the certificate handler has a certifiicate associated with a given secret
|
/// - Returns: A boolean describing whether or not the certificate handler has a certifiicate associated with a given secret
|
||||||
public func hasCertificate<SecretType: Secret>(for secret: SecretType) -> Bool {
|
public func hasCertificate<SecretType: Secret>(for secret: SecretType) -> Bool {
|
||||||
keyBlobsAndNames[AnySecret(secret)] != nil
|
keyBlobsAndNames.withLock {
|
||||||
|
$0[AnySecret(secret)] != nil
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +67,9 @@ public final class OpenSSHCertificateHandler {
|
|||||||
/// - Parameter secret: The secret to search for a certificate with
|
/// - Parameter secret: The secret to search for a certificate with
|
||||||
/// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively.
|
/// - Returns: A (``Data``, ``Data``) tuple containing the certificate and certificate name, respectively.
|
||||||
public func keyBlobAndName<SecretType: Secret>(for secret: SecretType) throws -> (Data, Data)? {
|
public func keyBlobAndName<SecretType: Secret>(for secret: SecretType) throws -> (Data, Data)? {
|
||||||
keyBlobsAndNames[AnySecret(secret)]
|
keyBlobsAndNames.withLock {
|
||||||
|
$0[AnySecret(secret)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
/// Attempts to find an OpenSSH Certificate that corresponds to a ``Secret``
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import CryptoKit
|
import CryptoKit
|
||||||
|
|
||||||
/// Generates OpenSSH representations of Secrets.
|
/// Generates OpenSSH representations of Secrets.
|
||||||
public struct OpenSSHKeyWriter {
|
public struct OpenSSHKeyWriter: Sendable {
|
||||||
|
|
||||||
/// Initializes the writer.
|
/// Initializes the writer.
|
||||||
public init() {
|
public init() {
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
|
/// Controller responsible for writing public keys to disk, so that they're easily accessible by scripts.
|
||||||
public final class PublicKeyFileStoreController {
|
public final class PublicKeyFileStoreController: Sendable {
|
||||||
|
|
||||||
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
|
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
|
||||||
private let directory: String
|
private let directory: String
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
|
import Synchronization
|
||||||
|
|
||||||
/// A "Store Store," which holds a list of type-erased stores.
|
/// A "Store Store," which holds a list of type-erased stores.
|
||||||
@Observable public final class SecretStoreList: ObservableObject {
|
@Observable public final class SecretStoreList: Sendable {
|
||||||
|
|
||||||
/// The Stores managed by the SecretStoreList.
|
/// The Stores managed by the SecretStoreList.
|
||||||
public var stores: [AnySecretStore] = []
|
public var stores: [AnySecretStore] {
|
||||||
|
__stores.withLock { $0 }
|
||||||
|
}
|
||||||
|
private let __stores: Mutex<[AnySecretStore]> = .init([])
|
||||||
|
|
||||||
/// A modifiable store, if one is available.
|
/// A modifiable store, if one is available.
|
||||||
public var modifiableStore: AnySecretStoreModifiable?
|
public var modifiableStore: AnySecretStoreModifiable? {
|
||||||
|
__modifiableStore.withLock { $0 }
|
||||||
|
}
|
||||||
|
private let __modifiableStore: Mutex<AnySecretStoreModifiable?> = .init(nil)
|
||||||
|
|
||||||
/// Initializes a SecretStoreList.
|
/// Initializes a SecretStoreList.
|
||||||
public init() {
|
public init() {
|
||||||
@ -15,23 +23,33 @@ import Observation
|
|||||||
|
|
||||||
/// Adds a non-type-erased SecretStore to the list.
|
/// Adds a non-type-erased SecretStore to the list.
|
||||||
public func add<SecretStoreType: SecretStore>(store: SecretStoreType) {
|
public func add<SecretStoreType: SecretStore>(store: SecretStoreType) {
|
||||||
stores.append(AnySecretStore(store))
|
__stores.withLock {
|
||||||
|
$0.append(AnySecretStore(store))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a non-type-erased modifiable SecretStore.
|
/// Adds a non-type-erased modifiable SecretStore.
|
||||||
public func add<SecretStoreType: SecretStoreModifiable>(store: SecretStoreType) {
|
public func add<SecretStoreType: SecretStoreModifiable>(store: SecretStoreType) {
|
||||||
let modifiable = AnySecretStoreModifiable(modifiable: store)
|
let modifiable = AnySecretStoreModifiable(modifiable: store)
|
||||||
modifiableStore = modifiable
|
__modifiableStore.withLock {
|
||||||
stores.append(modifiable)
|
$0 = modifiable
|
||||||
|
}
|
||||||
|
__stores.withLock {
|
||||||
|
$0.append(modifiable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A boolean describing whether there are any Stores available.
|
/// A boolean describing whether there are any Stores available.
|
||||||
public var anyAvailable: Bool {
|
public var anyAvailable: Bool {
|
||||||
stores.reduce(false, { $0 || $1.isAvailable })
|
__stores.withLock {
|
||||||
|
$0.reduce(false, { $0 || $1.isAvailable })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var allSecrets: [AnySecret] {
|
public var allSecrets: [AnySecret] {
|
||||||
stores.flatMap(\.secrets)
|
__stores.withLock {
|
||||||
|
$0.flatMap(\.secrets)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
|
import Observation
|
||||||
|
|
||||||
@main
|
@main
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
@ -31,19 +32,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||||
logger.debug("SecretAgent finished launching")
|
logger.debug("SecretAgent finished launching")
|
||||||
// DispatchQueue.main.async {
|
Task { @MainActor in
|
||||||
// self.socketController.handler = self.agent.handle(reader:writer:)
|
socketController.handler = { [agent] reader, writer in
|
||||||
// }
|
await agent.handle(reader: reader, writer: writer)
|
||||||
// NotificationCenter.default.addObserver(forName: .secretStoreReloaded, object: nil, queue: .main) { [self] _ in
|
}
|
||||||
// try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
}
|
||||||
// }
|
Task {
|
||||||
|
for await _ in NotificationCenter.default.notifications(named: .secretStoreReloaded) {
|
||||||
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
try? publicKeyFileStoreController.generatePublicKeys(for: storeList.allSecrets, clear: true)
|
||||||
notifier.prompt()
|
notifier.prompt()
|
||||||
// updateSink = updater.$update.sink { update in
|
_ = withObservationTracking {
|
||||||
// guard let update = update else { return }
|
updater.update
|
||||||
// self.notifier.notify(update: update, ignore: self.updater.ignore(release:))
|
} onChange: { [updater, notifier] in
|
||||||
// }
|
notifier.notify(update: updater.update!) { release in
|
||||||
|
Task {
|
||||||
|
await updater.ignore(release: release)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import AppKit
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
import SecretAgentKit
|
import SecretAgentKit
|
||||||
import Brief
|
import Brief
|
||||||
|
import Synchronization
|
||||||
|
|
||||||
class Notifier {
|
final class Notifier: Sendable {
|
||||||
|
|
||||||
private let notificationDelegate = NotificationDelegate()
|
private let notificationDelegate = NotificationDelegate()
|
||||||
|
|
||||||
@ -34,7 +35,9 @@ class Notifier {
|
|||||||
guard let string = formatter.string(from: seconds)?.capitalized else { continue }
|
guard let string = formatter.string(from: seconds)?.capitalized else { continue }
|
||||||
let identifier = Constants.persistAuthenticationCategoryIdentitifier.appending("\(seconds)")
|
let identifier = Constants.persistAuthenticationCategoryIdentitifier.appending("\(seconds)")
|
||||||
let action = UNNotificationAction(identifier: identifier, title: string, options: [])
|
let action = UNNotificationAction(identifier: identifier, title: string, options: [])
|
||||||
notificationDelegate.persistOptions[identifier] = seconds
|
notificationDelegate.state.withLock { state in
|
||||||
|
state.persistOptions[identifier] = seconds
|
||||||
|
}
|
||||||
allPersistenceActions.append(action)
|
allPersistenceActions.append(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,9 +48,11 @@ class Notifier {
|
|||||||
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory, persistAuthenticationCategory])
|
UNUserNotificationCenter.current().setNotificationCategories([updateCategory, criticalUpdateCategory, persistAuthenticationCategory])
|
||||||
UNUserNotificationCenter.current().delegate = notificationDelegate
|
UNUserNotificationCenter.current().delegate = notificationDelegate
|
||||||
|
|
||||||
notificationDelegate.persistAuthentication = { secret, store, duration in
|
notificationDelegate.state.withLock { state in
|
||||||
guard let duration = duration else { return }
|
state.persistAuthentication = { secret, store, duration in
|
||||||
try? await store.persistAuthentication(secret: secret, forDuration: duration)
|
guard let duration = duration else { return }
|
||||||
|
try? await store.persistAuthentication(secret: secret, forDuration: duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -58,8 +63,10 @@ class Notifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async {
|
func notify(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance) async {
|
||||||
notificationDelegate.pendingPersistableSecrets[secret.id.description] = secret
|
notificationDelegate.state.withLock { state in
|
||||||
notificationDelegate.pendingPersistableStores[store.id.description] = store
|
state.pendingPersistableSecrets[secret.id.description] = secret
|
||||||
|
state.pendingPersistableStores[store.id.description] = store
|
||||||
|
}
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
notificationContent.title = String(localized: "signed_notification_title_\(provenance.origin.displayName)")
|
notificationContent.title = String(localized: "signed_notification_title_\(provenance.origin.displayName)")
|
||||||
@ -74,12 +81,14 @@ class Notifier {
|
|||||||
notificationContent.attachments = [attachment]
|
notificationContent.attachments = [attachment]
|
||||||
}
|
}
|
||||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notificationContent, trigger: nil)
|
||||||
notificationCenter.add(request, withCompletionHandler: nil)
|
try? await notificationCenter.add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notify(update: Release, ignore: ((Release) -> Void)?) {
|
func notify(update: Release, ignore: ((Release) -> Void)?) {
|
||||||
notificationDelegate.release = update
|
notificationDelegate.state.withLock { [update] state in
|
||||||
notificationDelegate.ignore = ignore
|
state.release = update
|
||||||
|
// state.ignore = ignore
|
||||||
|
}
|
||||||
let notificationCenter = UNUserNotificationCenter.current()
|
let notificationCenter = UNUserNotificationCenter.current()
|
||||||
let notificationContent = UNMutableNotificationContent()
|
let notificationContent = UNMutableNotificationContent()
|
||||||
if update.critical {
|
if update.critical {
|
||||||
@ -129,15 +138,21 @@ extension Notifier {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate, Sendable {
|
||||||
|
|
||||||
fileprivate var release: Release?
|
|
||||||
fileprivate var ignore: ((Release) -> Void)?
|
|
||||||
fileprivate var persistAuthentication: ((AnySecret, AnySecretStore, TimeInterval?) async -> Void)?
|
|
||||||
fileprivate var persistOptions: [String: TimeInterval] = [:]
|
|
||||||
fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:]
|
|
||||||
fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:]
|
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
typealias PersistAuthentication = ((AnySecret, AnySecretStore, TimeInterval?) async -> Void)
|
||||||
|
typealias Ignore = ((Release) -> Void)
|
||||||
|
fileprivate var release: Release?
|
||||||
|
fileprivate var ignore: Ignore?
|
||||||
|
fileprivate var persistAuthentication: PersistAuthentication?
|
||||||
|
fileprivate var persistOptions: [String: TimeInterval] = [:]
|
||||||
|
fileprivate var pendingPersistableStores: [String: AnySecretStore] = [:]
|
||||||
|
fileprivate var pendingPersistableSecrets: [String: AnySecret] = [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate let state: Mutex<State> = .init(.init())
|
||||||
|
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
|
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -155,27 +170,34 @@ class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleUpdateResponse(response: UNNotificationResponse) {
|
func handleUpdateResponse(response: UNNotificationResponse) {
|
||||||
guard let update = release else { return }
|
state.withLock { state in
|
||||||
switch response.actionIdentifier {
|
guard let update = state.release else { return }
|
||||||
case Notifier.Constants.updateActionIdentitifier, UNNotificationDefaultActionIdentifier:
|
switch response.actionIdentifier {
|
||||||
NSWorkspace.shared.open(update.html_url)
|
case Notifier.Constants.updateActionIdentitifier, UNNotificationDefaultActionIdentifier:
|
||||||
case Notifier.Constants.ignoreActionIdentitifier:
|
NSWorkspace.shared.open(update.html_url)
|
||||||
ignore?(update)
|
case Notifier.Constants.ignoreActionIdentitifier:
|
||||||
default:
|
state.ignore?(update)
|
||||||
fatalError()
|
default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePersistAuthenticationResponse(response: UNNotificationResponse) async {
|
func handlePersistAuthenticationResponse(response: UNNotificationResponse) async {
|
||||||
guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String, let secret = pendingPersistableSecrets[secretID],
|
// let (secret, store, persistOptions, callback): (AnySecret?, AnySecretStore?, TimeInterval?, State.PersistAuthentication?) = state.withLock { state in
|
||||||
let storeID = response.notification.request.content.userInfo[Notifier.Constants.persistStoreIDKey] as? String, let store = pendingPersistableStores[storeID]
|
// guard let secretID = response.notification.request.content.userInfo[Notifier.Constants.persistSecretIDKey] as? String, let secret = state.pendingPersistableSecrets[secretID],
|
||||||
else { return }
|
// let storeID = response.notification.request.content.userInfo[Notifier.Constants.persistStoreIDKey] as? String, let store = state.pendingPersistableStores[storeID]
|
||||||
pendingPersistableSecrets[secretID] = nil
|
// else { return (nil, nil, nil, nil) }
|
||||||
await persistAuthentication?(secret, store, persistOptions[response.actionIdentifier])
|
// state.pendingPersistableSecrets[secretID] = nil
|
||||||
|
// return (secret, store, state.persistOptions[response.actionIdentifier], state.persistAuthentication)
|
||||||
|
// }
|
||||||
|
// guard let secret, let store, let persistOptions else { return }
|
||||||
|
// await callback?(secret, store, persistOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
||||||
completionHandler([.list, .banner])
|
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||||
|
[.list, .banner]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,20 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
@Entry var secretStoreList: SecretStoreList = {
|
||||||
|
let list = SecretStoreList()
|
||||||
|
list.add(store: SecureEnclave.Store())
|
||||||
|
list.add(store: SmartCard.Store())
|
||||||
|
return list
|
||||||
|
}()
|
||||||
|
@Entry var agentStatusChecker: any AgentStatusCheckerProtocol = AgentStatusChecker()
|
||||||
|
@Entry var updater: any UpdaterProtocol = Updater(checkOnLaunch: false)
|
||||||
|
}
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct Secretive: App {
|
struct Secretive: App {
|
||||||
|
|
||||||
private let storeList: SecretStoreList = {
|
private let storeList: SecretStoreList = {
|
||||||
let list = SecretStoreList()
|
let list = SecretStoreList()
|
||||||
list.add(store: SecureEnclave.Store())
|
list.add(store: SecureEnclave.Store())
|
||||||
@ -23,10 +34,10 @@ struct Secretive: App {
|
|||||||
|
|
||||||
@SceneBuilder var body: some Scene {
|
@SceneBuilder var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView<Updater, AgentStatusChecker>(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
ContentView(showingCreation: $showingCreation, runningSetup: $showingSetup, hasRunSetup: $hasRunSetup)
|
||||||
.environmentObject(storeList)
|
.environment(storeList)
|
||||||
.environmentObject(Updater(checkOnLaunch: hasRunSetup))
|
.environment(Updater(checkOnLaunch: hasRunSetup))
|
||||||
.environmentObject(agentStatusChecker)
|
.environment(agentStatusChecker)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if !hasRunSetup {
|
if !hasRunSetup {
|
||||||
showingSetup = true
|
showingSetup = true
|
||||||
|
@ -2,15 +2,16 @@ import Foundation
|
|||||||
import Combine
|
import Combine
|
||||||
import AppKit
|
import AppKit
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
import Observation
|
||||||
|
|
||||||
protocol AgentStatusCheckerProtocol: ObservableObject {
|
protocol AgentStatusCheckerProtocol: Observable {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
var developmentBuild: Bool { get }
|
var developmentBuild: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
@Observable class AgentStatusChecker: AgentStatusCheckerProtocol {
|
||||||
|
|
||||||
@Published var running: Bool = false
|
var running: Bool = false
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
check()
|
check()
|
||||||
|
@ -4,7 +4,7 @@ import SecureEnclaveSecretKit
|
|||||||
import SmartCardSecretKit
|
import SmartCardSecretKit
|
||||||
import Brief
|
import Brief
|
||||||
|
|
||||||
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
|
struct ContentView: View {
|
||||||
|
|
||||||
@Binding var showingCreation: Bool
|
@Binding var showingCreation: Bool
|
||||||
@Binding var runningSetup: Bool
|
@Binding var runningSetup: Bool
|
||||||
@ -13,9 +13,9 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
|
|||||||
@State var activeSecret: AnySecret.ID?
|
@State var activeSecret: AnySecret.ID?
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
@EnvironmentObject private var storeList: SecretStoreList
|
@Environment(\.secretStoreList) private var storeList: SecretStoreList
|
||||||
@EnvironmentObject private var updater: UpdaterType
|
@Environment(\.updater) private var updater: any UpdaterProtocol
|
||||||
@EnvironmentObject private var agentStatusChecker: AgentStatusCheckerType
|
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
|
||||||
|
|
||||||
@State private var selectedUpdate: Release?
|
@State private var selectedUpdate: Release?
|
||||||
@State private var showingAppPathNotice = false
|
@State private var showingAppPathNotice = false
|
||||||
|
@ -6,7 +6,7 @@ struct StoreListView: View {
|
|||||||
|
|
||||||
@Binding var activeSecret: AnySecret.ID?
|
@Binding var activeSecret: AnySecret.ID?
|
||||||
|
|
||||||
@EnvironmentObject private var storeList: SecretStoreList
|
@Environment(SecretStoreList.self) private var storeList: SecretStoreList
|
||||||
|
|
||||||
private func secretDeleted(secret: AnySecret) {
|
private func secretDeleted(secret: AnySecret) {
|
||||||
activeSecret = nextDefaultSecret
|
activeSecret = nextDefaultSecret
|
||||||
|
@ -3,7 +3,7 @@ import Brief
|
|||||||
|
|
||||||
struct UpdateDetailView<UpdaterType: Updater>: View {
|
struct UpdateDetailView<UpdaterType: Updater>: View {
|
||||||
|
|
||||||
@EnvironmentObject var updater: UpdaterType
|
@Environment(UpdaterType.self) var updater: UpdaterType
|
||||||
|
|
||||||
let update: Release
|
let update: Release
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user