35ba6ecf05
The NSAppleEventsUsageDescription usage string was dead — the app has no AppleEvents/AppleScript code path (Meet detection reads window titles), so the permission prompt never fired; remove it. Rephrase the leftover "Phase N" build-plan references in source comments (one of which falsely claimed "no audio, capture, or call detection yet"), and complete the AGENTS.md Audio/Detection layout listings.
69 lines
2.4 KiB
Swift
69 lines
2.4 KiB
Swift
import Foundation
|
|
import Combine
|
|
|
|
/// Performs the backend reachability check: `GET {baseURL}/api/status`.
|
|
///
|
|
/// This is a thin slice; the full upload path (label-merge, multipart, sequential
|
|
/// queueing, retries) lives in `SparkControlClient`.
|
|
@MainActor
|
|
final class SparkControlHealth: ObservableObject {
|
|
|
|
enum Status: Equatable {
|
|
case unknown
|
|
case checking
|
|
case online(String)
|
|
case offline(String)
|
|
}
|
|
|
|
@Published private(set) var status: Status = .unknown
|
|
@Published private(set) var lastChecked: Date?
|
|
|
|
func check(baseURL: String, skipTLS: Bool) async {
|
|
status = .checking
|
|
|
|
let trimmed = baseURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let base = trimmed.hasSuffix("/") ? String(trimmed.dropLast()) : trimmed
|
|
guard !base.isEmpty, let url = URL(string: base + "/api/status") else {
|
|
status = .offline("Invalid host URL")
|
|
return
|
|
}
|
|
|
|
let config = URLSessionConfiguration.ephemeral
|
|
config.timeoutIntervalForRequest = 8
|
|
config.waitsForConnectivity = false
|
|
|
|
let delegate: URLSessionDelegate? = skipTLS
|
|
? InsecureTrustDelegate(allowedHost: url.host)
|
|
: nil
|
|
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
|
|
defer { session.finishTasksAndInvalidate() }
|
|
|
|
do {
|
|
let (data, response) = try await session.data(from: url)
|
|
lastChecked = Date()
|
|
guard let http = response as? HTTPURLResponse else {
|
|
status = .offline("No HTTP response")
|
|
return
|
|
}
|
|
if (200..<300).contains(http.statusCode) {
|
|
status = .online(Self.summarize(data) ?? "Reachable")
|
|
} else {
|
|
status = .offline("HTTP \(http.statusCode)")
|
|
}
|
|
} catch {
|
|
lastChecked = Date()
|
|
status = .offline(error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
/// Best-effort one-line summary of the `/api/status` body, if it's JSON.
|
|
private static func summarize(_ data: Data) -> String? {
|
|
guard let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
return nil
|
|
}
|
|
if let s = object["status"] as? String { return s }
|
|
if let s = object["state"] as? String { return s }
|
|
return "Reachable"
|
|
}
|
|
}
|