From 27d0d115087a97ed0c13f45ed8a31162fb86f0d4 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Thu, 26 Feb 2026 11:13:24 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E7=99=BB=E9=99=86=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- punchnet/Core/SDLAPI.swift | 120 ---------- punchnet/Core/SDLAPIClient.swift | 49 +++++ punchnet/Core/SystemConfig.swift | 5 + punchnet/Views/AbortView.swift | 56 ++--- punchnet/Views/IndexView.swift | 303 -------------------------- punchnet/Views/Login/LoginState.swift | 70 +++++- punchnet/Views/Login/LoginView.swift | 91 +++++--- punchnet/punchnetApp.swift | 5 +- 8 files changed, 208 insertions(+), 491 deletions(-) delete mode 100644 punchnet/Core/SDLAPI.swift create mode 100644 punchnet/Core/SDLAPIClient.swift delete mode 100644 punchnet/Views/IndexView.swift diff --git a/punchnet/Core/SDLAPI.swift b/punchnet/Core/SDLAPI.swift deleted file mode 100644 index 3c72a67..0000000 --- a/punchnet/Core/SDLAPI.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// SDLApi.swift -// sdlan -// -// Created by 安礼成 on 2024/6/5. -// - -import Foundation - -struct JSONRPCResponse: Decodable { - let result: T? - let error: JSONRPCError? -} - -struct JSONRPCError: Error, Decodable { - let code: Int - let message: String - let data: String? -} - -struct SDLAPI { - - enum Mode { - case debug - case prod - } - - static let mode: Mode = .debug - - static var baseUrl: String { - switch mode { - case .debug: - return "http://127.0.0.1:18082/test" - case .prod: - return "https://punchnet.s5s8.com/api" - } - } - - struct Upgrade: Decodable { - let upgrade_type: Int - let upgrade_prompt: String - let upgrade_address: String - } - - struct NetworkProfile: Decodable { - struct NetworkItem: Decodable { - let name: String - let code: String - } - let network: [NetworkItem] - } - - static func checkVersion(clientId: String, version: Int, channel: String) async throws -> JSONRPCResponse { - let params: [String:Any] = [ - "client_id": clientId, - "version": version, - "channel": channel - ] - - let postData = try! JSONSerialization.data(withJSONObject: params) - var request = URLRequest(url: URL(string: baseUrl + "/upgrade")!) - request.httpMethod = "POST" - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = postData - - let (data, _) = try await URLSession.shared.data(for: request) - - return try JSONDecoder().decode(JSONRPCResponse.self, from: data) - } - - static func getUserNetworks(clientId: String) async throws -> JSONRPCResponse { - let params: [String:Any] = [ - "client_id": clientId - ] - - let postData = try! JSONSerialization.data(withJSONObject: params) - var request = URLRequest(url: URL(string: baseUrl + "/get_user_network")!) - request.httpMethod = "POST" - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = postData - - let (data, _) = try await URLSession.shared.data(for: request) - - return try JSONDecoder().decode(JSONRPCResponse.self, from: data) - } - - static func loginWithAccountAndPassword(clientId: String, username: String, password: String, as: T.Type) async throws -> T { - let params: [String:Any] = [ - "client_id": clientId, - "username": username, - "password": password - ] - - return try await doPost(path: "/login_with_account", params: params, as: T.self) - } - - private static func doPost(path: String, params: [String: Any], as: T.Type) async throws -> T { - let postData = try! JSONSerialization.data(withJSONObject: params) - var request = URLRequest(url: URL(string: baseUrl + path)!) - request.httpMethod = "POST" - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = postData - - let (data, _) = try await URLSession.shared.data(for: request) - let rpcResponse = try JSONDecoder().decode(JSONRPCResponse.self, from: data) - if let result = rpcResponse.result { - return result - } else if let error = rpcResponse.error { - throw error - } else { - throw DecodingError.dataCorrupted( - .init( - codingPath: [], - debugDescription: "Invalid JSON-RPC response: \(String(data: data, encoding: .utf8) ?? "")" - ) - ) - } - } - -} diff --git a/punchnet/Core/SDLAPIClient.swift b/punchnet/Core/SDLAPIClient.swift new file mode 100644 index 0000000..1500bdb --- /dev/null +++ b/punchnet/Core/SDLAPIClient.swift @@ -0,0 +1,49 @@ +// +// SDLApi.swift +// sdlan +// +// Created by 安礼成 on 2024/6/5. +// + +import Foundation + +struct SDLAPIResponse: Decodable { + let code: Int + let message: String? + let data: T? +} + +struct SDLAPIError: Error, Decodable { + let code: Int + let message: String +} + +struct SDLAPIClient { + + static var baseUrl: String = "https://punchnet.s5s8.com/api" + + static func doPost(path: String, params: [String: Any], as: T.Type) async throws -> T { + let postData = try! JSONSerialization.data(withJSONObject: params) + var request = URLRequest(url: URL(string: baseUrl + path)!) + request.httpMethod = "POST" + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = postData + + let (data, _) = try await URLSession.shared.data(for: request) + let apiResponse = try JSONDecoder().decode(SDLAPIResponse.self, from: data) + + if apiResponse.code == 0, let data = apiResponse.data { + return data + } else if let message = apiResponse.message { + throw SDLAPIError(code: apiResponse.code, message: message) + } else { + throw DecodingError.dataCorrupted( + .init( + codingPath: [], + debugDescription: "Invalid JSON-RPC response: \(String(data: data, encoding: .utf8) ?? "")" + ) + ) + } + } + +} diff --git a/punchnet/Core/SystemConfig.swift b/punchnet/Core/SystemConfig.swift index 27ca869..64d5f16 100644 --- a/punchnet/Core/SystemConfig.swift +++ b/punchnet/Core/SystemConfig.swift @@ -75,6 +75,11 @@ struct SystemConfig { } } + public static func macAddressString(mac: Data, separator: String = ":") -> String { + return mac.map { String(format: "%02X", $0) } + .joined(separator: separator) + } + // 随机生成mac地址 private static func generateMacAddress() -> Data { var macAddress = [UInt8](repeating: 0, count: 6) diff --git a/punchnet/Views/AbortView.swift b/punchnet/Views/AbortView.swift index 36ba170..e552c5b 100644 --- a/punchnet/Views/AbortView.swift +++ b/punchnet/Views/AbortView.swift @@ -30,34 +30,34 @@ struct AbortView: View { Text("Version1.1") Button { - Task { - guard let response = try? await SDLAPI.checkVersion(clientId: "test", version: 1, channel: "macos") else { - DispatchQueue.main.async { - self.alertShow = AlertShow(id: "network_error", content: .error("Network Error")) - } - return - } - - if let result = response.result { - if result.upgrade_type == 0 { - DispatchQueue.main.async { - self.alertShow = AlertShow(id: "upgrade_0", content: .upgrade(result.upgrade_prompt, "")) - } - } else if result.upgrade_type == 1 { - DispatchQueue.main.async { - self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) - } - } else if result.upgrade_type == 2 { - DispatchQueue.main.async { - self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) - } - } - } else if let error = response.error { - DispatchQueue.main.async { - self.alertShow = AlertShow(id: "response_error", content: .error(error.message)) - } - } - } +// Task { +// guard let response = try? await SDLAPI.checkVersion(clientId: "test", version: 1, channel: "macos") else { +// DispatchQueue.main.async { +// self.alertShow = AlertShow(id: "network_error", content: .error("Network Error")) +// } +// return +// } +// +// if let result = response.result { +// if result.upgrade_type == 0 { +// DispatchQueue.main.async { +// self.alertShow = AlertShow(id: "upgrade_0", content: .upgrade(result.upgrade_prompt, "")) +// } +// } else if result.upgrade_type == 1 { +// DispatchQueue.main.async { +// self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) +// } +// } else if result.upgrade_type == 2 { +// DispatchQueue.main.async { +// self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) +// } +// } +// } else if let error = response.error { +// DispatchQueue.main.async { +// self.alertShow = AlertShow(id: "response_error", content: .error(error.message)) +// } +// } +// } } label: { Text("版本检测") diff --git a/punchnet/Views/IndexView.swift b/punchnet/Views/IndexView.swift deleted file mode 100644 index 20c1d71..0000000 --- a/punchnet/Views/IndexView.swift +++ /dev/null @@ -1,303 +0,0 @@ -// -// ContentView.swift -// sdlan -// -// Created by 安礼成 on 2024/1/17. -// - -import SwiftUI -import SwiftData -import Combine - -struct IndexView: View { - @AppStorage("token") private var token: String = "" - @AppStorage("hostname") private var hostname: String = "" - @AppStorage("network_code") private var networkCode: String = "" - - @State private var showToken: Bool = false - - @ObservedObject private var vpnManager = VPNManager.shared - @State private var showAlert = false - @State private var showStunAlert = false - @State private var message: NoticeMessage.InboundMessage = .none - @State private var cancel: AnyCancellable? - - @State private var showMenu: Bool = false - - @State private var networkProfile: SDLAPI.NetworkProfile = .init(network: []) - @State private var selectedIdx: Int = 0 - - // 显示ip信息 - @State private var showIpAdress: Bool = false - @State private var ipAddress: String = "" - - public var noticeServer: UDPNoticeCenterServer - - var body: some View { - - VStack(alignment: .center, spacing: 10) { - VStack(alignment: .center, spacing: 10) { - Spacer() - .frame(height: 100) - - Image("logo") - .resizable() - .frame(width: 150, height: 150) - - Text("Connecting the Infinite") - .font(.system(size: 24, weight: .bold)) - .foregroundColor(.white) - .cornerRadius(5.0) - - Text("Welcome to PunchNet") - .font(.system(size: 14, weight: .regular)) - .foregroundColor(.white) - .cornerRadius(5.0) - } - .contentShape(Rectangle()) - .onTapGesture { - self.showMenu = false - } - TextField("主机名", text: $hostname) - .multilineTextAlignment(.leading) - .textFieldStyle(PlainTextFieldStyle()) - .frame(width: 200, height: 25) - .background(Color.white) - .foregroundColor(Color.black) - .cornerRadius(5.0) - - if showIpAdress { - HStack { - Spacer() - - Text("ip: ") - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - .cornerRadius(5.0) - - Text(ipAddress) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - .cornerRadius(5.0) - - Spacer() - } - } - - Spacer() - .frame(width: 1, height: 10) - - VStack(spacing: 0) { - ForEach(Array(networkProfile.network.enumerated()), id: \.offset) { idx, network in - NetworkItemView(idx: idx, item: network) - .padding(.horizontal, 8) - .padding(.vertical, 6) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(selectedIdx == idx ? Color.blue.opacity(0.3) : Color.clear) - ) - .contentShape(Rectangle()) - .onTapGesture { - withAnimation(.easeInOut(duration: 0.2)) { - selectedIdx = idx - self.networkCode = network.code - } - } - } - } - - TextField("邀请码", text: $token) - .multilineTextAlignment(.leading) - .textFieldStyle(PlainTextFieldStyle()) - .frame(width: 200, height: 25) - .background(Color.white) - .foregroundColor(Color.black) - .cornerRadius(5.0) - .opacity(showToken ? 1 : 0) - - Spacer() - .frame(width: 1, height: 10) - - Rectangle() - .overlay { - Text(vpnManager.title) - .font(.system(size: 14, weight: .regular)) - .foregroundColor(vpnManager.color) - } - .frame(width: 120, height: 35) - .foregroundColor(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255)) - .cornerRadius(5.0) - .onTapGesture { - Task { - do { - try await self.clickSwitchButton() - } catch let err { - NSLog("start vpn get error: \(err)") - } - } - } - } - .overlay(alignment: .top) { - - HStack(spacing: 200) { - HStack { - Button(action: { - NSApplication.shared.terminate(nil) - }) { - - Image("close") - .resizable() - .frame(width: 15, height: 15) - } - .buttonStyle(PlainButtonStyle()) - - Button(action: { - NSApplication.shared.keyWindow?.miniaturize(nil) - }) { - Image("line") - .resizable() - .frame(width: 15, height: 15) - } - .buttonStyle(PlainButtonStyle()) - } - - Button(action: { - showMenu.toggle() - }) { - Image("IosSettings") - .resizable() - .frame(width: 20, height: 20) - } - .buttonStyle(PlainButtonStyle()) - .overlay(alignment: .leading) { - showMenu ? - - GeometryReader { geometry in - - VStack(alignment: .leading, spacing: 8) { - Button(action: { - self.showMenu = false - }) { - Text("主页") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .buttonStyle(PlainButtonStyle()) - - Button(action: { - self.showToken.toggle() - }) { - Text("邀请码") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .buttonStyle(PlainButtonStyle()) - - Button(action: { - NSApplication.shared.terminate(nil) - }) { - Text("退出") - .font(.system(size: 14)) - .foregroundColor(.white) - } - .buttonStyle(PlainButtonStyle()) - } - .frame(width: 90, height: 80) - .background(Color(red: 50 / 255, green: 55 / 255, blue: 52 / 255)) - .offset(x: -55, y: 20) - } - - : nil - } - - } - .offset(x: 0, y: 10) - } - .padding([.leading, .trailing, .top], 10) - .padding([.bottom], 20) - .background(Color(red: 36 / 255, green: 38 / 255, blue: 51 / 255)) - .frame(width: 320) - .alert(isPresented: $showAlert) { - Alert(title: Text("请输入正确的邀请码")) - } - .alert(isPresented: $showStunAlert) { - switch self.message { - case .alertMessage(let alert): - Alert(title: Text(alert)) - default: - Alert(title: Text("")) - } - } - .task { - do { - let response = try await SDLAPI.getUserNetworks(clientId: SystemConfig.getClientId()) - print("get user networks: \(response)") - if let result = response.result { - self.networkProfile = result - if self.networkProfile.network.count > 0 { - self.networkCode = self.networkProfile.network[0].code - } - } - } catch let err { - NSLog("get user networks get error: \(err)") - } - } - .onAppear { - self.cancel = self.noticeServer.messageFlow.sink{ message in - DispatchQueue.main.async { - switch message { - case .none: - () - default: - self.message = message - self.showStunAlert = true - } - } - } - } - } - - private func clickSwitchButton() async throws { - switch self.vpnManager.vpnStatus { - case .connected: - self.showIpAdress = false - self.ipAddress = "" - try await vpnManager.disableVpn() - case .disconnected: -// let clientId = SystemConfig.getClientId() -// NSLog("[IndexView] use token: \(self.token), network_code: \(networkCode)") -// // token存在则优先使用token -// try await vpnManager.enableVpn(options: SystemConfig.getOptions(networkCode: self.networkCode, token: self.token, clientId: clientId, hostname: self.hostname, noticePort: self.noticeServer.port)!) - () - } - } - -} - -extension IndexView { - struct NetworkItemView: View { - let idx: Int - let item: SDLAPI.NetworkProfile.NetworkItem - - var body: some View { - HStack { - Text(item.name) - .font(.system(size: 14)) - .foregroundColor(.white) - .frame(width: 80, alignment: .leading) - - Text(item.code) - .font(.system(size: 14)) - .foregroundColor(.white) - - Spacer() - } - } - } -} - -#Preview { - let server = UDPNoticeCenterServer() - IndexView(noticeServer: server) - //.modelContainer(for: Item.self, inMemory: true) -} diff --git a/punchnet/Views/Login/LoginState.swift b/punchnet/Views/Login/LoginState.swift index a928255..abb8fb0 100644 --- a/punchnet/Views/Login/LoginState.swift +++ b/punchnet/Views/Login/LoginState.swift @@ -12,10 +12,74 @@ import Observation class UserContext { var isLogined: Bool = false var loginCredit: LoginCredit? - + var networkSession: NetworkSession? + enum LoginCredit { - case token(token: String, networkdId: Int) - case accountAndPasword(account: String, password: String, networkId: Int) + case token(token: String) + case accountAndPasword(account: String, password: String) + } + + // 登陆后的网络会话信息 + struct NetworkSession: Codable { + struct ExitNode: Codable { + 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] + + 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, + ] + 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 + } + + @MainActor + func loginWithToken(token: String) async throws { + var params: [String: Any] = [ + "token": token, + ] + 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 } } diff --git a/punchnet/Views/Login/LoginView.swift b/punchnet/Views/Login/LoginView.swift index 0f14fec..d73acaa 100644 --- a/punchnet/Views/Login/LoginView.swift +++ b/punchnet/Views/Login/LoginView.swift @@ -10,9 +10,10 @@ import Observation // 登陆页面 struct LoginView: View { - @State private var loginMode: LoginMode = .account + @Environment(UserContext.self) var userContext: UserContext + @State private var authMethod: AuthMethod = .account - enum LoginMode { + enum AuthMethod { case token case account } @@ -24,7 +25,7 @@ struct LoginView: View { HStack(alignment: .center, spacing: 30) { Button { - self.loginMode = .token + self.authMethod = .token } label: { HStack { Image("logo") @@ -33,14 +34,14 @@ struct LoginView: View { .frame(width: 25, height: 25) Text("密钥登陆") - .foregroundColor(self.loginMode == .token ? .blue : .black) + .foregroundColor(self.authMethod == .token ? .blue : .black) } .contentShape(Rectangle()) } .buttonStyle(.plain) Button { - self.loginMode = .account + self.authMethod = .account } label: { HStack { Image("logo") @@ -48,7 +49,7 @@ struct LoginView: View { .clipped() .frame(width: 25, height: 25) Text("账户登陆") - .foregroundColor(self.loginMode == .account ? .blue : .black) + .foregroundColor(self.authMethod == .account ? .blue : .black) } .contentShape(Rectangle()) } @@ -56,7 +57,7 @@ struct LoginView: View { } Group { - switch loginMode { + switch self.authMethod { case .token: LoginTokenView() case .account: @@ -75,6 +76,9 @@ struct LoginTokenView: View { @Environment(UserContext.self) var userContext: UserContext @State private var token: String = "" + @State private var showAlert = false + @State private var errorMessage = "" + var body: some View { TextField("认证密钥", text: $token) .multilineTextAlignment(.leading) @@ -99,17 +103,44 @@ struct LoginTokenView: View { .foregroundColor(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255)) .cornerRadius(5.0) .onTapGesture { - print("call me here") + Task { + await self.doLogin() + } } } + // 执行登陆操作 + private func doLogin() async { + do { + try await self.userContext.loginWithToken(token: self.token) + print(self.userContext.networkSession?.accessToken) + + // 保存信息到KeychainStore +// let store = KeychainStore.shared +// if let tokenData = loginResult.accessToken.data(using: .utf8) { +// try store.save(tokenData, account: self.username) +// } + + } catch let err as SDLAPIError { + await MainActor.run { + self.showAlert = true + self.errorMessage = err.message + } + } catch { + await MainActor.run { + self.showAlert = true + self.errorMessage = "内部错误,请稍后重试" + } + } + } + } struct LoginAccountView: View { @Environment(UserContext.self) var userContext: UserContext - @State private var username: String = "" - @State private var password: String = "" + @State private var username: String = "test3" + @State private var password: String = "111111" @State private var showAlert = false @State private var errorMessage = "" @@ -181,34 +212,26 @@ struct LoginAccountView: View { // 执行登陆操作 private func doLogin() async { -// let clientId = SystemConfig.getClientId() -// do { -// let loginResult = try await SDLAPI.loginWithAccountAndPassword(clientId: clientId, username: self.username, password: self.password, as: LoginResult.self) -// -// print(loginResult.accessToken) -// -// // 保存信息到KeychainStore + do { + try await self.userContext.loginWithAccountAndPassword(username: self.username, password: self.password) + + // 保存信息到KeychainStore // let store = KeychainStore.shared // if let tokenData = loginResult.accessToken.data(using: .utf8) { // try store.save(tokenData, account: self.username) // } -// -// await MainActor.run { -// self.userContext.isLogined = true -// self.userContext.loginCredit = .accountAndPasword(account: username, password: password, networkId: loginResult.networkId) -// } -// -// } catch let err as JSONRPCError { -// await MainActor.run { -// self.showAlert = true -// self.errorMessage = err.message -// } -// } catch { -// await MainActor.run { -// self.showAlert = true -// self.errorMessage = "内部错误,请稍后重试" -// } -// } + + } catch let err as SDLAPIError { + await MainActor.run { + self.showAlert = true + self.errorMessage = err.message + } + } catch { + await MainActor.run { + self.showAlert = true + self.errorMessage = "内部错误,请稍后重试" + } + } } } diff --git a/punchnet/punchnetApp.swift b/punchnet/punchnetApp.swift index 6e370be..6727af6 100644 --- a/punchnet/punchnetApp.swift +++ b/punchnet/punchnetApp.swift @@ -44,9 +44,8 @@ struct punchnetApp: App { var body: some Scene { WindowGroup(id: "mainWindow") { - //IndexView(noticeServer: self.noticeServer) - //RootView() - NetworkDisconnctedView(state: NetworkState()) + RootView() + //NetworkDisconnctedView(state: NetworkState()) .onAppear { // 获取主屏幕的尺寸 guard let screenFrame = NSScreen.main?.frame else { return }