// // LoginState.swift // punchnet // // Created by 安礼成 on 2026/1/16. // import Foundation import Observation @Observable class UserContext { var isLogined: Bool = false var loginCredit: Credit? var networkSession: NetworkSession? enum Credit { case token(token: String) case accountAndPasword(account: String, password: String) } // 登陆后的网络会话信息 struct NetworkSession: Codable { struct ExitNode: Codable { let uuid = UUID().uuidString let nnid: Int let nodeName: String enum CodingKeys: String, CodingKey { case nnid case nodeName = "node_name" } } let accessToken: String let username: String let userType: String let audit: Int let networkId: Int let networkName: String let networkDomain: String let exitNodes: [ExitNode] var networkUrl: String { return "https://www.test.cn/id=\(self.networkId)" } enum CodingKeys: String, CodingKey { case accessToken = "access_token" case username case userType = "user_type" case audit case networkId = "network_id" case networkName = "network_name" case networkDomain = "network_domain" case exitNodes = "exit_node" } } private let baseParams: [String: Any] = [ "client_id": SystemConfig.getClientId(), "mac": SystemConfig.macAddressString(mac: SystemConfig.getMacAddress()) ] @MainActor func loginWithAccountAndPassword(username: String, password: String) async throws { var params: [String: Any] = [ "username": username, "password": password, "system": SystemConfig.systemInfo, "version": SystemConfig.version_name ] params.merge(baseParams) {$1} self.networkSession = try await SDLAPIClient.doPost(path: "/auth/login", params: params, as: NetworkSession.self) self.loginCredit = .accountAndPasword(account: username, password: password) self.isLogined = true // 将数据缓存到keychain if let data = "\(username):\(password)".data(using: .utf8) { try KeychainStore.shared.save(data, account: "accountAndPasword") } } @MainActor func loginWithToken(token: String) async throws { var params: [String: Any] = [ "token": token, "system": SystemConfig.systemInfo, "version": SystemConfig.version_name ] params.merge(baseParams) {$1} self.networkSession = try await SDLAPIClient.doPost(path: "/auth/token", params: params, as: NetworkSession.self) self.loginCredit = .token(token: token) self.isLogined = true // 将数据缓存到keychain if let data = token.data(using: .utf8) { try KeychainStore.shared.save(data, account: "token") } } func sendVerifyCode(username: String) async throws -> String { var params: [String: Any] = [ "username": username ] params.merge(baseParams) {$1} return try await SDLAPIClient.doPost(path: "/auth/sendVerifyCode", params: params, as: String.self) } func submitVerifyCode(username: String, verifyCode: String) async throws -> String { var params: [String: Any] = [ "username": username, "verify_code": verifyCode, ] params.merge(baseParams) {$1} return try await SDLAPIClient.doPost(path: "/auth/submitVerifyCode", params: params, as: String.self) } func resetPassword(username: String, password: String) async throws -> String { var params: [String: Any] = [ "username": username, "password": password, ] params.merge(baseParams) {$1} return try await SDLAPIClient.doPost(path: "/auth/resetPassword", params: params, as: String.self) } 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 } // 退出登陆 func logout() async throws { try await VPNManager.shared.disableVpn() self.isLogined = false } }