import Foundation import CryptoKit struct Client { let server: URL private let delegate = NeverCacheDelegate() init(server: URL) { self.server = server } private enum RequestReponse: Error { case requestFailed case unknownResponseData(Data) case unknownResponseString(String) case success(UInt8) } func deviceStatus() async -> ClientState { let url = server.appendingPathComponent(RouteAPI.getDeviceStatus.rawValue) let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData) let response = await integerReponse(to: request) switch response { case .requestFailed: return .deviceNotAvailable(.serverNotReached) case .unknownResponseData(let data): return .internalError("Unknown status (\(data.count) bytes)") case .unknownResponseString(let string): return .internalError("Unknown status (\(string.prefix(15)))") case .success(let int): switch int { case 0: return .deviceNotAvailable(.deviceDisconnected) case 1: return .ready default: return .internalError("Invalid status: \(int)") } } } func send(_ message: Message) async throws -> (state: ClientState, response: Message?) { let url = server.appendingPathComponent(RouteAPI.postMessage.rawValue) var request = URLRequest(url: url) request.httpBody = message.encoded request.httpMethod = "POST" guard let data = await fulfill(request) else { return (.deviceNotAvailable(.serverNotReached), nil) } guard let byte = data.first else { return (.internalError("Empty response"), nil) } guard let status = MessageResult(rawValue: byte) else { return (.internalError("Invalid message response: \(byte)"), nil) } let result = ClientState(keyResult: status) guard data.count == Message.length + 1 else { return (result, nil) } let messageData = Array(data.advanced(by: 1)) let message = Message(decodeFrom: messageData) return (result, message) } private func fulfill(_ request: URLRequest) async -> Data? { do { let (data, response) = try await URLSession.shared.data(for: request) guard let code = (response as? HTTPURLResponse)?.statusCode else { print("No response from server") return nil } guard code == 200 else { print("Invalid server response \(code)") return nil } return data } catch { print("Request failed: \(error)") return nil } } private func integerReponse(to request: URLRequest) async -> RequestReponse { guard let data = await fulfill(request) else { return .requestFailed } guard let string = String(data: data, encoding: .utf8) else { print("Unexpected device status data: \([UInt8](data))") return .unknownResponseData(data) } guard let int = UInt8(string) else { print("Unexpected device status '\(string)'") return .unknownResponseString(string) } return .success(int) } } class NeverCacheDelegate: NSObject, NSURLConnectionDataDelegate { func connection(_ connection: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? { return nil } }