// AppState.swift — global app state, credential lifecycle import SwiftUI @MainActor final class AppState: ObservableObject { static let shared = AppState() @Published var credential: SidecarCredential? = nil @Published var isLocked = false @Published var lastForegroundedAt: Date = Date() private init() { // Test-only overrides let args = ProcessInfo.processInfo.arguments if args.contains("--reset-state") { Keychain.shared.delete(key: "piremote.credential") UserDefaults.standard.removeObject(forKey: "faceid.enabled") } if args.contains("--enable-faceid") { UserDefaults.standard.set(true, forKey: "faceid.enabled") } if args.contains("--force-lock") { isLocked = true } // Try loading persisted credential on launch credential = try? Keychain.shared.load(key: "piremote.credential") } func didPair(credential: SidecarCredential) { self.credential = credential try? Keychain.shared.save(credential, key: "piremote.credential") } func unpair() { credential = nil Keychain.shared.delete(key: "piremote.credential") } // MARK: - Face ID gate func appDidBackground() { lastForegroundedAt = Date() } func appWillForeground() async { let elapsed = Date().timeIntervalSince(lastForegroundedAt) guard elapsed > 60 else { return } // within 60s → no re-auth isLocked = true let ok = await FaceIDGate.authenticate() isLocked = !ok } }