解决登陆问题

This commit is contained in:
anlicheng 2026-02-26 11:13:24 +08:00
parent 616ba21662
commit 27d0d11508
8 changed files with 208 additions and 491 deletions

View File

@ -1,120 +0,0 @@
//
// SDLApi.swift
// sdlan
//
// Created by on 2024/6/5.
//
import Foundation
struct JSONRPCResponse<T: Decodable>: 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<Upgrade> {
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<Upgrade>.self, from: data)
}
static func getUserNetworks(clientId: String) async throws -> JSONRPCResponse<NetworkProfile> {
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<NetworkProfile>.self, from: data)
}
static func loginWithAccountAndPassword<T: Decodable>(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<T: Decodable>(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<T>.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) ?? "")"
)
)
}
}
}

View File

@ -0,0 +1,49 @@
//
// SDLApi.swift
// sdlan
//
// Created by on 2024/6/5.
//
import Foundation
struct SDLAPIResponse<T: Decodable>: 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<T: Decodable>(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<T>.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) ?? "")"
)
)
}
}
}

View File

@ -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 // mac
private static func generateMacAddress() -> Data { private static func generateMacAddress() -> Data {
var macAddress = [UInt8](repeating: 0, count: 6) var macAddress = [UInt8](repeating: 0, count: 6)

View File

@ -30,34 +30,34 @@ struct AbortView: View {
Text("Version1.1") Text("Version1.1")
Button { Button {
Task { // Task {
guard let response = try? await SDLAPI.checkVersion(clientId: "test", version: 1, channel: "macos") else { // guard let response = try? await SDLAPI.checkVersion(clientId: "test", version: 1, channel: "macos") else {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.alertShow = AlertShow(id: "network_error", content: .error("Network Error")) // self.alertShow = AlertShow(id: "network_error", content: .error("Network Error"))
} // }
return // return
} // }
//
if let result = response.result { // if let result = response.result {
if result.upgrade_type == 0 { // if result.upgrade_type == 0 {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.alertShow = AlertShow(id: "upgrade_0", content: .upgrade(result.upgrade_prompt, "")) // self.alertShow = AlertShow(id: "upgrade_0", content: .upgrade(result.upgrade_prompt, ""))
} // }
} else if result.upgrade_type == 1 { // } else if result.upgrade_type == 1 {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) // self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address))
} // }
} else if result.upgrade_type == 2 { // } else if result.upgrade_type == 2 {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address)) // self.alertShow = AlertShow(id: "upgrade_1", content: .upgrade(result.upgrade_prompt, result.upgrade_address))
} // }
} // }
} else if let error = response.error { // } else if let error = response.error {
DispatchQueue.main.async { // DispatchQueue.main.async {
self.alertShow = AlertShow(id: "response_error", content: .error(error.message)) // self.alertShow = AlertShow(id: "response_error", content: .error(error.message))
} // }
} // }
} // }
} label: { } label: {
Text("版本检测") Text("版本检测")

View File

@ -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)
}

View File

@ -12,10 +12,74 @@ import Observation
class UserContext { class UserContext {
var isLogined: Bool = false var isLogined: Bool = false
var loginCredit: LoginCredit? var loginCredit: LoginCredit?
var networkSession: NetworkSession?
enum LoginCredit { enum LoginCredit {
case token(token: String, networkdId: Int) case token(token: String)
case accountAndPasword(account: String, password: String, networkId: Int) 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
} }
} }

View File

@ -10,9 +10,10 @@ import Observation
// //
struct LoginView: View { 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 token
case account case account
} }
@ -24,7 +25,7 @@ struct LoginView: View {
HStack(alignment: .center, spacing: 30) { HStack(alignment: .center, spacing: 30) {
Button { Button {
self.loginMode = .token self.authMethod = .token
} label: { } label: {
HStack { HStack {
Image("logo") Image("logo")
@ -33,14 +34,14 @@ struct LoginView: View {
.frame(width: 25, height: 25) .frame(width: 25, height: 25)
Text("密钥登陆") Text("密钥登陆")
.foregroundColor(self.loginMode == .token ? .blue : .black) .foregroundColor(self.authMethod == .token ? .blue : .black)
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
.buttonStyle(.plain) .buttonStyle(.plain)
Button { Button {
self.loginMode = .account self.authMethod = .account
} label: { } label: {
HStack { HStack {
Image("logo") Image("logo")
@ -48,7 +49,7 @@ struct LoginView: View {
.clipped() .clipped()
.frame(width: 25, height: 25) .frame(width: 25, height: 25)
Text("账户登陆") Text("账户登陆")
.foregroundColor(self.loginMode == .account ? .blue : .black) .foregroundColor(self.authMethod == .account ? .blue : .black)
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
@ -56,7 +57,7 @@ struct LoginView: View {
} }
Group { Group {
switch loginMode { switch self.authMethod {
case .token: case .token:
LoginTokenView() LoginTokenView()
case .account: case .account:
@ -75,6 +76,9 @@ struct LoginTokenView: View {
@Environment(UserContext.self) var userContext: UserContext @Environment(UserContext.self) var userContext: UserContext
@State private var token: String = "" @State private var token: String = ""
@State private var showAlert = false
@State private var errorMessage = ""
var body: some View { var body: some View {
TextField("认证密钥", text: $token) TextField("认证密钥", text: $token)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
@ -99,17 +103,44 @@ struct LoginTokenView: View {
.foregroundColor(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255)) .foregroundColor(Color(red: 74 / 255, green: 207 / 255, blue: 154 / 255))
.cornerRadius(5.0) .cornerRadius(5.0)
.onTapGesture { .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 { struct LoginAccountView: View {
@Environment(UserContext.self) var userContext: UserContext @Environment(UserContext.self) var userContext: UserContext
@State private var username: String = "" @State private var username: String = "test3"
@State private var password: String = "" @State private var password: String = "111111"
@State private var showAlert = false @State private var showAlert = false
@State private var errorMessage = "" @State private var errorMessage = ""
@ -181,34 +212,26 @@ struct LoginAccountView: View {
// //
private func doLogin() async { private func doLogin() async {
// let clientId = SystemConfig.getClientId() do {
// do { try await self.userContext.loginWithAccountAndPassword(username: self.username, password: self.password)
// let loginResult = try await SDLAPI.loginWithAccountAndPassword(clientId: clientId, username: self.username, password: self.password, as: LoginResult.self)
// // KeychainStore
// print(loginResult.accessToken)
//
// // KeychainStore
// let store = KeychainStore.shared // let store = KeychainStore.shared
// if let tokenData = loginResult.accessToken.data(using: .utf8) { // if let tokenData = loginResult.accessToken.data(using: .utf8) {
// try store.save(tokenData, account: self.username) // try store.save(tokenData, account: self.username)
// } // }
//
// await MainActor.run { } catch let err as SDLAPIError {
// self.userContext.isLogined = true await MainActor.run {
// self.userContext.loginCredit = .accountAndPasword(account: username, password: password, networkId: loginResult.networkId) self.showAlert = true
// } self.errorMessage = err.message
// }
// } catch let err as JSONRPCError { } catch {
// await MainActor.run { await MainActor.run {
// self.showAlert = true self.showAlert = true
// self.errorMessage = err.message self.errorMessage = "内部错误,请稍后重试"
// } }
// } catch { }
// await MainActor.run {
// self.showAlert = true
// self.errorMessage = ""
// }
// }
} }
} }

View File

@ -44,9 +44,8 @@ struct punchnetApp: App {
var body: some Scene { var body: some Scene {
WindowGroup(id: "mainWindow") { WindowGroup(id: "mainWindow") {
//IndexView(noticeServer: self.noticeServer) RootView()
//RootView() //NetworkDisconnctedView(state: NetworkState())
NetworkDisconnctedView(state: NetworkState())
.onAppear { .onAppear {
// //
guard let screenFrame = NSScreen.main?.frame else { return } guard let screenFrame = NSScreen.main?.frame else { return }