import Foundation /// 已保存的主机(连接目标)。 /// 注:`password` 暂存本地 JSON 仅供 demo;产品化后应移入 Keychain(未签名时 SecItem 不可用,见 HANDOFF)。 struct SavedHost: Identifiable, Codable, Equatable { var id: UUID = UUID() var name: String var host: String var port: Int = 22 var username: String var password: String = "" /// 登录后引导 mosh 承载会话。 var useMosh: Bool = false var addressLine: String { "\(username)@\(host):\(port)" } } /// 主机列表持久化(Application Support 下 JSON)。凭据不入仓库;此文件只在设备本地。 @MainActor final class HostStore: ObservableObject { @Published private(set) var hosts: [SavedHost] = [] private let url: URL init() { let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] try? FileManager.default.createDirectory(at: base, withIntermediateDirectories: true) url = base.appendingPathComponent("hosts.json") load() } func add(_ host: SavedHost) { hosts.append(host) save() } func update(_ host: SavedHost) { guard let i = hosts.firstIndex(where: { $0.id == host.id }) else { return } hosts[i] = host save() } func remove(_ host: SavedHost) { hosts.removeAll { $0.id == host.id } save() } private func load() { guard let data = try? Data(contentsOf: url), let decoded = try? JSONDecoder().decode([SavedHost].self, from: data) else { return } hosts = decoded } private func save() { guard let data = try? JSONEncoder().encode(hosts) else { return } try? data.write(to: url, options: .atomic) } }