// // LoginState.swift // punchnet // // Created by 安礼成 on 2026/1/16. // import Foundation import Observation struct AppContextError: Error { let message: String } @Observable class AppContext { private var vpnManager = VPNManager.shared var noticePort: Int // 调用 "/connect" 之后的网络信息 var networkContext: SDLAPIClient.NetworkContext = .default() // 保存当前登陆vpn使用的配置项 var vpnOptions: [String: NSObject]? = nil // 当前app所处的场景 var appScene: AppScene = .login(username: nil) // 当前的场景 enum AppScene: Equatable { case login(username: String?) case logined case register case resetPassword } // 登陆凭证 var loginCredit: Credit? // 判断用户是否登陆 var isLogined: Bool { return loginCredit != nil } enum Credit { case token(token: String, session: SDLAPIClient.NetworkSession) case accountAndPasword(account: String, password: String, session: SDLAPIClient.NetworkSession) } @ObservationIgnored var networkSession: SDLAPIClient.NetworkSession? { guard let loginCredit = self.loginCredit else { return nil } switch loginCredit { case .token(_, let session): return session case .accountAndPasword(_, _, let session): return session } } init(noticePort: Int) { self.noticePort = noticePort } func loginWith(token: String) async throws { let networkSession = try await SDLAPIClient.loginWithToken(token: token) self.loginCredit = .token(token: token, session: networkSession) // 将数据缓存到keychain if let data = token.data(using: .utf8) { try KeychainStore.shared.save(data, account: "token") } } func loginWith(username: String, password: String) async throws { let networkSession = try await SDLAPIClient.loginWithAccountAndPassword(username: username, password: password) self.loginCredit = .accountAndPasword(account: username, password: password, session: networkSession) // 将数据缓存到keychain if let data = "\(username):\(password)".data(using: .utf8) { try KeychainStore.shared.save(data, account: "accountAndPasword") } } // 连接到对应的网络 func connectNetwork() async throws { guard let session = self.networkSession else { throw AppContextError(message: "未登陆") } // 避免重复连接 guard !vpnManager.isConnected else { throw AppContextError(message: "网络已经连接") } let context = try await SDLAPIClient.connectNetwork(accesToken: session.accessToken) let options = SystemConfig.getOptions( networkId: UInt32(session.networkId), networkDomain: session.networkDomain, ip: context.ip, maskLen: context.maskLen, accessToken: session.accessToken, identityId: context.identityId, hostname: context.hostname, noticePort: noticePort ) try await self.vpnManager.enableVpn(options: options) self.networkContext = context self.vpnOptions = options } // 断开网络连接 func disconnectNetwork() async throws { try await self.vpnManager.disableVpn() } // 退出登陆 func logout() async throws { try await self.vpnManager.disableVpn() self.networkContext = .default() self.loginCredit = nil } func loadCacheToken() -> String? { if let data = try? KeychainStore.shared.load(account: "token") { return String(data: data, encoding: .utf8) } return nil } func loadCacheUsernameAndPassword() -> (String, String)? { if let data = try? KeychainStore.shared.load(account: "accountAndPasword"), let str = String(data: data, encoding: .utf8) { let parts = str.split(separator: ":") if parts.count == 2 { return (String(parts[0]), String(parts[1])) } } return nil } }