import Foundation public protocol SimAdminClientProtocol: Sendable { func send(_ route: UpstreamRoute, baseURL: URL, cookie: String?) async throws -> UpstreamResponse } extension SimAdminClient: SimAdminClientProtocol {} public enum ConnectionStatus: String, Equatable, Sendable { case online case offline case authRequired case degraded case unknown public var label: String { switch self { case .online: return "在线" case .offline: return "离线" case .authRequired: return "需登录" case .degraded: return "降级" case .unknown: return "未知" } } } public struct ConnectionResult: Equatable, Sendable { public let status: ConnectionStatus public let requestLatencyMilliseconds: Int? public let checkedAt: Date public init( status: ConnectionStatus, requestLatencyMilliseconds: Int? = nil, checkedAt: Date ) { self.status = status self.requestLatencyMilliseconds = requestLatencyMilliseconds self.checkedAt = checkedAt } } public struct ConnectionProbe: Sendable { private let client: any SimAdminClientProtocol public init(client: any SimAdminClientProtocol) { self.client = client } public func test(_ instance: Instance) async throws -> ConnectionResult { let startedAt = DispatchTime.now() do { let response = try await client.send(.health, baseURL: instance.origin, cookie: nil) let latency = Self.elapsedMilliseconds(since: startedAt) if response.status == 401 || response.status == 403 { return ConnectionResult( status: .authRequired, requestLatencyMilliseconds: latency, checkedAt: Date() ) } return ConnectionResult( status: .online, requestLatencyMilliseconds: latency, checkedAt: Date() ) } catch { let latency = Self.elapsedMilliseconds(since: startedAt) switch error { case UpstreamError.unauthorized: return ConnectionResult( status: .authRequired, requestLatencyMilliseconds: latency, checkedAt: Date() ) case UpstreamError.timeout, is URLError: return ConnectionResult( status: .offline, requestLatencyMilliseconds: latency, checkedAt: Date() ) default: return ConnectionResult( status: .degraded, requestLatencyMilliseconds: latency, checkedAt: Date() ) } } } private static func elapsedMilliseconds(since startedAt: DispatchTime) -> Int? { let nanoseconds = DispatchTime.now().uptimeNanoseconds &- startedAt.uptimeNanoseconds let milliseconds = Double(nanoseconds) / 1_000_000 return milliseconds.isFinite ? Int(milliseconds.rounded()) : nil } }