Archived
feat: server config, token client, TLS pinning, connection test (Phase 1)
ServerConfig parses https base URL and derives wss livekit URL; TokenClient POSTs /token with clean 503/offline error states; TLSPinning holds optional cert SHA-384 pins (empty = system trust); Settings gains a Connection test screen proving the endpoint works.
This commit is contained in:
1 parent
297fbaa19c
commit
378b8cb36f
7 files changed
+263
No files matched your search
@@ -0,0 +1,69 @@
|
||||
import Foundation
|
||||
|
||||
struct TokenResponse: Decodable, Equatable {
|
||||
let token: String
|
||||
let room: String
|
||||
let user: User
|
||||
|
||||
struct User: Decodable, Equatable {
|
||||
let id: String
|
||||
let displayName: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case displayName = "display_name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TokenError: LocalizedError {
|
||||
case invalidServerURL(String)
|
||||
case httpStatus(Int, String)
|
||||
case decodeFailed
|
||||
case network(Error)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidServerURL(let raw):
|
||||
return "Invalid server URL: \(raw)"
|
||||
case .httpStatus(let code, let detail):
|
||||
if code == 503 {
|
||||
return "Server is starting up or the user file is missing (HTTP 503). Check the deployment on the server. \(detail)"
|
||||
}
|
||||
return "Token request failed with HTTP \(code). \(detail)"
|
||||
case .decodeFailed:
|
||||
return "Unexpected response from the token endpoint."
|
||||
case .network(let error):
|
||||
return "Could not reach the server: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class TokenClient {
|
||||
let config: ServerConfig
|
||||
private let session: URLSession
|
||||
|
||||
init(config: ServerConfig, session: URLSession = .shared) {
|
||||
self.config = config
|
||||
self.session = session
|
||||
}
|
||||
|
||||
func fetchToken() async throws -> TokenResponse {
|
||||
var request = URLRequest(url: config.baseURL.appendingPathComponent("token"))
|
||||
request.httpMethod = "POST"
|
||||
request.timeoutInterval = 10
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse else {
|
||||
throw TokenError.decodeFailed
|
||||
}
|
||||
guard (200..<300).contains(http.statusCode) else {
|
||||
let detail = String(data: data, encoding: .utf8) ?? ""
|
||||
throw TokenError.httpStatus(http.statusCode, detail)
|
||||
}
|
||||
do {
|
||||
return try JSONDecoder().decode(TokenResponse.self, from: data)
|
||||
} catch {
|
||||
throw TokenError.decodeFailed
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user