Files
hope-voice-api/Sources/Networking/TokenClient.swift
T
snowspeeder 378b8cb36f 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.
2026-08-29 17:18:38 -04:00

70 lines
2.1 KiB
Swift

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