import Foundation final class NoRedirect: NSObject, URLSessionTaskDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { completionHandler(nil) } } enum ServerProbe { static func check(_ server: ServerAddress) async throws { let configuration = URLSessionConfiguration.ephemeral configuration.httpCookieStorage = nil configuration.urlCache = nil configuration.timeoutIntervalForRequest = 10 configuration.timeoutIntervalForResource = 20 let session = URLSession(configuration: configuration, delegate: NoRedirect(), delegateQueue: nil) defer { session.invalidateAndCancel() } let version = try await json(session, server.url.appendingPathComponent("api/v1/version")) guard version["app_name"] is String, version["setup_required"] is Bool, version["password_reset_enabled"] is Bool else { throw ConnectionError.incompatible } let auth = try await json(session, server.url.appendingPathComponent("api/v1/auth/oidc/config")) guard auth["enabled"] is Bool, auth["password_login_enabled"] is Bool else { throw ConnectionError.incompatible } } private static func json(_ session: URLSession, _ url: URL) async throws -> [String: Any] { var request = URLRequest(url: url) request.setValue("application/json", forHTTPHeaderField: "Accept") let (stream, response) = try await session.bytes(for: request) guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw ConnectionError.unavailable } var data = Data() for try await byte in stream { guard data.count < 65_536 else { throw ConnectionError.incompatible } data.append(byte) } guard let result = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { throw ConnectionError.incompatible } return result } }