a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
78 lines
2.7 KiB
Swift
78 lines
2.7 KiB
Swift
import Foundation
|
|
|
|
/// Exchanges a pairing code for a long-lived auth token via `POST /pair`.
|
|
public struct PairingClient: Sendable {
|
|
public struct Response: Equatable, Sendable {
|
|
public var token: String
|
|
public var serverName: String
|
|
public var serverVersion: String
|
|
|
|
public init(token: String, serverName: String, serverVersion: String) {
|
|
self.token = token
|
|
self.serverName = serverName
|
|
self.serverVersion = serverVersion
|
|
}
|
|
}
|
|
|
|
public enum PairingError: Error, Equatable {
|
|
case invalidCode(String)
|
|
case serverError(statusCode: Int, message: String)
|
|
case invalidResponse
|
|
}
|
|
|
|
private let session: URLSession
|
|
|
|
public init(session: URLSession = .shared) {
|
|
self.session = session
|
|
}
|
|
|
|
public func pair(
|
|
gateway: Gateway,
|
|
code: String,
|
|
deviceID: String,
|
|
deviceName: String
|
|
) async throws -> Response {
|
|
var request = URLRequest(url: gateway.pairURL)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.timeoutInterval = 15
|
|
let body: [String: String] = [
|
|
"code": code,
|
|
"device_id": deviceID,
|
|
"device_name": deviceName,
|
|
]
|
|
request.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
|
|
let (data, response) = try await session.data(for: request)
|
|
guard let http = response as? HTTPURLResponse else {
|
|
throw PairingError.invalidResponse
|
|
}
|
|
let object = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] ?? [:]
|
|
guard http.statusCode == 200 else {
|
|
let message = object["error"] as? String ?? "HTTP \(http.statusCode)"
|
|
if http.statusCode == 401 {
|
|
throw PairingError.invalidCode(message)
|
|
}
|
|
throw PairingError.serverError(statusCode: http.statusCode, message: message)
|
|
}
|
|
guard let token = object["token"] as? String, !token.isEmpty else {
|
|
throw PairingError.invalidResponse
|
|
}
|
|
return Response(
|
|
token: token,
|
|
serverName: object["server_name"] as? String ?? "jcode",
|
|
serverVersion: object["server_version"] as? String ?? "unknown"
|
|
)
|
|
}
|
|
|
|
/// Probes `GET /health`. Returns true when the gateway is reachable.
|
|
public func checkHealth(gateway: Gateway) async -> Bool {
|
|
var request = URLRequest(url: gateway.healthURL)
|
|
request.timeoutInterval = 5
|
|
guard let (_, response) = try? await session.data(for: request),
|
|
let http = response as? HTTPURLResponse
|
|
else { return false }
|
|
return http.statusCode == 200
|
|
}
|
|
}
|