beatmatchr/Desktop/YabaiPro/Sources/RemoteAuthManager.swift
BusyBee3333 7694d965c9 feat: Add structured signal editor with app dropdown and action builder
- Add AppDiscovery provider for running app enumeration
- Implement AppDropdownView with auto-launch functionality
- Create SignalAction models for 40+ yabai commands
- Build ActionBuilderView with nested parameter controls
- Add LiveShellPreview for real-time shell command generation
- Implement ActionValidator for conflict detection
- Add migration parser for existing raw action strings
- Include feature flag for safe rollout
- Maintain full backward compatibility
2025-12-31 01:44:13 -05:00

103 lines
2.7 KiB
Swift

//
// RemoteAuthManager.swift
// YabaiPro
//
// Lightweight pairing manager for local network devices
//
import Foundation
final class RemoteAuthManager {
static let shared = RemoteAuthManager()
struct PairedDevice: Codable {
var name: String
var token: String
var pairedAt: Date
}
private let storageURL: URL
private var pairedDevices: [PairedDevice] = []
// ephemeral PIN -> token mapping (in-memory)
private var pendingPins: [String: String] = [:]
private let pinLifetimeSeconds: TimeInterval = 120
private init() {
let fm = FileManager.default
let appSupport = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let dir = appSupport ?? URL(fileURLWithPath: NSTemporaryDirectory())
storageURL = dir.appendingPathComponent("yabaipro_paired_devices.json")
load()
}
private func load() {
do {
let data = try Data(contentsOf: storageURL)
pairedDevices = try JSONDecoder().decode([PairedDevice].self, from: data)
} catch {
pairedDevices = []
}
}
private func save() {
do {
let data = try JSONEncoder().encode(pairedDevices)
try FileManager.default.createDirectory(at: storageURL.deletingLastPathComponent(), withIntermediateDirectories: true)
try data.write(to: storageURL, options: [.atomic])
} catch {
print("RemoteAuthManager: failed to save paired devices: \(error)")
}
}
func generatePIN() -> String {
// generate 6-digit pin
let pin = String(format: "%06d", Int.random(in: 0...999999))
let token = UUID().uuidString
pendingPins[pin] = token
// schedule expiration
DispatchQueue.global().asyncAfter(deadline: .now() + pinLifetimeSeconds) { [weak self] in
self?.pendingPins[pin] = nil
}
return pin
}
func confirmPIN(_ pin: String, deviceName: String?) -> String? {
guard let token = pendingPins[pin] else { return nil }
pendingPins[pin] = nil
let name = deviceName ?? "iPhone-\(Int.random(in: 1000...9999))"
let device = PairedDevice(name: name, token: token, pairedAt: Date())
pairedDevices.append(device)
save()
return token
}
func revokeDevice(named name: String) {
pairedDevices.removeAll { $0.name == name }
save()
}
func isTokenPaired(_ token: String) -> Bool {
pairedDevices.contains { $0.token == token }
}
func listPairedDevices() -> [PairedDevice] {
pairedDevices
}
}