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