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:
snowspeeder committed 2026-08-29 17:18:38 -04:00
1 parent 297fbaa19c
commit 378b8cb36f
7 files changed
+263

No files matched your search

@@ -0,0 +1,86 @@
import SwiftUI
struct ConnectionTestView: View {
@ObservedObject var settings: SettingsStore
@State private var isBusy = false
@State private var result: Result<TokenResponse, Error>?
@State private var urlError: String?
var body: some View {
Form {
Section("Server") {
LabeledContent("URL") {
Text(settings.trimmedServerURL)
.font(.footnote.monospaced())
.foregroundStyle(.secondary)
}
if let urlError {
Label(urlError, systemImage: "xmark.octagon")
.foregroundStyle(.red)
}
}
Section {
Button(action: runTest) {
HStack {
Spacer()
if isBusy {
ProgressView()
} else {
Text("Get token")
}
Spacer()
}
}
.disabled(isBusy || settings.trimmedServerURL.isEmpty)
}
if let result {
switch result {
case .success(let response):
Section("Success") {
LabeledContent("Room") {
Text(response.room)
.font(.footnote.monospaced())
}
LabeledContent("User") {
Text(response.user.displayName ?? response.user.id)
}
Label("Token endpoint and TLS are working.", systemImage: "checkmark.circle")
.foregroundStyle(.green)
}
case .failure(let error):
Section("Error") {
Label(error.localizedDescription, systemImage: "xmark.octagon")
.foregroundStyle(.red)
.font(.footnote)
}
}
}
}
.navigationTitle("Connection test")
}
private func runTest() {
result = nil
urlError = nil
switch ServerConfig.parse(settings.trimmedServerURL) {
case .success(let config):
isBusy = true
Task {
let client = TokenClient(config: config)
do {
let response = try await client.fetchToken()
result = .success(response)
} catch {
result = .failure(error)
}
isBusy = false
}
case .failure(let error):
urlError = error.localizedDescription
}
}
}
#Preview {
ConnectionTestView(settings: SettingsStore())
}