Files
VibeCoding/webrtc_controller_ios/web_rtc_controller_ios/Utils/ApiClient.swift
TongTongStudio 1bd0318d35 feat(ios): add online device list and pairing code redemption
- Add API methods for fetching online devices and redeeming pairing codes
- Add OnlineDevice model and corresponding ViewModel state management
- Add UI sections for online device list and pairing code input in SetupPanel
- Ensure access token is verified before connecting to signaling server
2026-08-03 21:42:31 +08:00

115 lines
5.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// HTTP
///
/// - login(username,password) accessToken + refreshTokenses_
/// - refresh(refreshToken) accessToken refreshToken
/// - verify() accessToken
/// - bindings()
/// - turnCredentials() TURN iceServers
struct ApiClient {
/// HTTP Build Setting / Info.plist
static var baseURL: String {
// Info.plist APIBase退 HTTPS
if let v = Bundle.main.object(forInfoDictionaryKey: "APIBase") as? String, !v.isEmpty {
return v
}
return "https://www.ttstd.com"
}
/// accessToken / refreshToken / expireAt
static func login(username: String, password: String) async throws -> [String: Any] {
let body: [String: Any] = ["username": username, "password": password]
return try await post("/api/auth/login", body: body)
}
/// body refreshToken accessToken refreshToken
static func refresh(refreshToken: String) async throws -> [String: Any] {
let body: [String: Any] = ["refreshToken": refreshToken]
return try await post("/api/auth/refresh", body: body)
}
/// accessToken
static func verify(accessToken: String) async throws -> [String: Any] {
return try await get("/api/client/verify", accessToken: accessToken)
}
///
static func bindings(accessToken: String) async throws -> [String: Any] {
return try await get("/api/client/bindings", accessToken: accessToken)
}
/// TURN
static func turnCredentials(accessToken: String) async throws -> [String: Any] {
return try await get("/api/client/turn-credentials", accessToken: accessToken)
}
/// 线线
/// Flutter `devices_online`GET /api/client/devices/online
static func onlineDevices(accessToken: String) async throws -> [String: Any] {
return try await get("/api/client/devices/online", accessToken: accessToken)
}
///
/// Flutter `redeemPairingCode`POST /api/client/pairing/redeem
static func redeemPairingCode(accessToken: String, code: String) async throws -> [String: Any] {
let body: [String: Any] = ["code": code]
return try await post("/api/client/pairing/redeem", body: body, accessToken: accessToken)
}
// MARK: - Private
private static func post(_ path: String, body: [String: Any]) async throws -> [String: Any] {
return try await post(path, body: body, accessToken: nil)
}
private static func post(_ path: String, body: [String: Any], accessToken: String?) async throws -> [String: Any] {
var req = URLRequest(url: URL(string: baseURL + path)!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let accessToken, !accessToken.isEmpty {
req.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
}
req.httpBody = try JSONSerialization.data(withJSONObject: body)
return try await perform(req)
}
private static func get(_ path: String, accessToken: String) async throws -> [String: Any] {
var req = URLRequest(url: URL(string: baseURL + path)!)
req.httpMethod = "GET"
req.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
return try await perform(req)
}
private static func perform(_ request: URLRequest) async throws -> [String: Any] {
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse else {
throw ApiError(message: "无效响应")
}
if http.statusCode == 401 {
throw ApiError(code: "UNAUTHORIZED", message: "令牌失效", httpCode: 401)
}
guard (200..<300).contains(http.statusCode) else {
let msg = (try? JSONSerialization.jsonObject(with: data) as? [String: Any])?["error"] as? String
?? "HTTP \(http.statusCode)"
throw ApiError(message: msg)
}
return (try? JSONSerialization.jsonObject(with: data) as? [String: Any]) ?? [:]
}
}
struct ApiError: Error {
let code: String
let message: String
let httpCode: Int
init(code: String = "ERROR", message: String, httpCode: Int = -1) {
self.code = code
self.message = message
self.httpCode = httpCode
}
var localizedDescription: String { message }
}