- 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
58 lines
2.1 KiB
Swift
58 lines
2.1 KiB
Swift
import Foundation
|
|
|
|
struct ServerConfig: Equatable {
|
|
let baseURL: URL
|
|
let livekitURL: URL
|
|
|
|
enum ConfigError: LocalizedError, Equatable {
|
|
case notHTTPS(String)
|
|
case invalidURL(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .notHTTPS(let raw):
|
|
return "Server URL must use https: \(raw)"
|
|
case .invalidURL(let raw):
|
|
return "Invalid server URL: \(raw)"
|
|
}
|
|
}
|
|
}
|
|
|
|
static func parse(_ raw: String) -> Result<ServerConfig, ConfigError> {
|
|
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return .failure(.invalidURL(raw)) }
|
|
var candidate = trimmed
|
|
if !candidate.contains("://") { candidate = "https://" + candidate }
|
|
guard let components = URLComponents(string: candidate),
|
|
let scheme = components.scheme?.lowercased(),
|
|
let host = components.host, !host.isEmpty else {
|
|
return .failure(.invalidURL(raw))
|
|
}
|
|
if scheme != "https" { return .failure(.notHTTPS(raw)) }
|
|
var c = components
|
|
c.path = ""
|
|
c.fragment = nil
|
|
guard let base = c.url else { return .failure(.invalidURL(raw)) }
|
|
// LAN deployment: LiveKit is reachable directly on :7880 (plain ws).
|
|
// Real domains use wss via same origin. The SDK derives /rtc from the base.
|
|
let livekit: URL
|
|
if Self.isPrivateIP(host) {
|
|
livekit = URL(string: "ws://\(host):7880")!
|
|
} else {
|
|
let portSuffix = components.port.map { ":\($0)" } ?? ""
|
|
livekit = URL(string: "wss://\(host)\(portSuffix)/livekit")!
|
|
}
|
|
return .success(ServerConfig(baseURL: base, livekitURL: livekit))
|
|
}
|
|
|
|
static func isPrivateIP(_ host: String) -> Bool {
|
|
let parts = host.split(separator: ".").compactMap { UInt8($0) }
|
|
guard parts.count == 4 else { return false }
|
|
return parts[0] == 10
|
|
|| (parts[0] == 172 && (16...31).contains(parts[1]))
|
|
|| (parts[0] == 192 && parts[1] == 168)
|
|
}
|
|
|
|
var host: String { baseURL.host ?? "" }
|
|
}
|