Files
hope-voice-api/Sources/Networking/TokenClient.swift
T
snowspeeder 4986b6d7dc feat: LiveKit voice session, control channel, transcripts (Phase 2)
- VoiceSession: token->connect with fresh-room retry on timeout, agent
  discovery, agent_ready wait, mic enable/mute, text send over lk.chat
- ControlChannel: outbound set_voice/set_model/get_models/set_mode/
  media_state; inbound agent_ready/models/model_changed/task_event
- TranscriptHandler: transcription segments + transcript topic finals,
  partial/final dedupe per role
- ConversationView/OrbView: status orb, mic/text-mode toggles, composer
- ServerConfig: plain ws:// signaling for private IPs (self-signed TLS
  not injectable into SDK WebSocket); Info.plist local networking
2026-08-30 07:26:58 -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) {
self.config = config
self.session = TLSPinning.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
}
}
}