tv-anarchy/Sources/TVAnarchyiOS/BridgeClient.swift
Natalie f0669f1ca8 feat(ios): TVAnarchyiOS app target + UI tests
(cherry picked from commit 7f8f4b0dd92358ba687f8230a922d8f316cb06e9)
2026-06-09 05:50:26 -07:00

65 lines
2.3 KiB
Swift

// HTTP client for the plum-control-bridge. Phase-1 surface: list shows, and
// build a raw-file stream URL for VLCKit. Auth (when the bridge has a token set)
// rides as a Bearer header for JSON and a ?token= query for the stream URL,
// because VLCKit is handed a bare URL with no header hook.
import Foundation
enum BridgeError: LocalizedError {
case badStatus(Int)
case transport(String)
var errorDescription: String? {
switch self {
case .badStatus(let code): return "Bridge returned HTTP \(code)."
case .transport(let msg): return msg
}
}
}
struct BridgeClient {
let baseURL: URL
let token: String?
func fetchShows(refresh: Bool = false) async throws -> [BridgeShow] {
var comps = URLComponents(
url: baseURL.appendingPathComponent("library").appendingPathComponent("shows"),
resolvingAgainstBaseURL: false
)!
if refresh { comps.queryItems = [URLQueryItem(name: "refresh", value: "1")] }
var request = URLRequest(url: comps.url!)
request.timeoutInterval = 30
if let token { request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") }
let data: Data
let response: URLResponse
do {
(data, response) = try await URLSession.shared.data(for: request)
} catch {
throw BridgeError.transport(error.localizedDescription)
}
guard let http = response as? HTTPURLResponse else {
throw BridgeError.transport("No HTTP response.")
}
guard http.statusCode == 200 else {
throw BridgeError.badStatus(http.statusCode)
}
return try JSONDecoder().decode(ShowsResponse.self, from: data).shows
}
/// URL VLCKit (or a download task) reads the raw video from. The episode id
/// is base64url, hence already path-safe no further escaping needed.
func streamURL(episodeId: String) -> URL {
var comps = URLComponents(
url: baseURL.appendingPathComponent("stream").appendingPathComponent(episodeId),
resolvingAgainstBaseURL: false
)!
if let token { comps.queryItems = [URLQueryItem(name: "token", value: token)] }
return comps.url!
}
func healthURL() -> URL {
baseURL.appendingPathComponent("healthz")
}
}