import Foundation import CryptoKit import PushAPI import SwiftUI final class API { @AppStorage("server") var server: String = "" var url: URL? { URL(string: server) } init() { } init(server: URL) { self.server = server.path } init(server: URL, application: ApplicationId) { self.server = server.path self.application = application } @AppStorage("application") var application: ApplicationId = "" private static let encoder = JSONEncoder() private static let decoder = JSONDecoder() func register(token: PushToken, name: String) async -> AuthenticationToken? { let device = DeviceRegistration( pushToken: token, application: application, name: name) guard let token = await post(.registerNewDevice, body: device) else { print("Failed to register") return nil } guard token.count == 16 else { print("Failed to register: Unexpected token length: \(token.count)") return nil } return token } func getDeviceList(pushToken: PushToken, authToken: AuthenticationToken) async -> [DeviceRegistration] { let device = DeviceAuthentication(pushToken: pushToken, authentication: authToken) guard let data = await post(.listDevicesInApplication, body: device) else { print("Devices: Failed") return [] } do { return try API.decoder.decode([DeviceRegistration].self, from: data) } catch { print("Devices: Failed to decode response") return [] } } func getUnconfirmedDevices(masterKey: String) async -> [DeviceRegistration] { let hash = hash(masterKey) guard let data = await post(.listUnapprovedDevices, bodyData: hash) else { print("Devices: Failed") return [] } do { return try API.decoder.decode([DeviceRegistration].self, from: data) } catch { print("Devices: Failed to decode response") return [] } } private func hash(_ masterKey: String) -> Data { Data(SHA256.hash(data: masterKey.data(using: .utf8)!)) } func confirm(pushToken: PushToken, with masterKey: String) async -> Bool { let hash = hash(masterKey) let device = DeviceDecision(pushToken: pushToken, masterKeyHash: hash) return await post(.approveDevice, body: device) != nil } func reject(pushToken: PushToken, with masterKey: String) async -> Bool { let hash = hash(masterKey) let device = DeviceDecision(pushToken: pushToken, masterKeyHash: hash) return await post(.rejectDevice, body: device) != nil } func isConfirmed(token: PushToken, authentication: AuthenticationToken) async -> Bool { let device = DeviceAuthentication(pushToken: token, authentication: authentication) return await post(.isDeviceApproved, body: device) != nil } func send(push: AuthenticatedPushMessage) async -> Bool { await post(.sendPushNotification, body: push) != nil } private func post(_ route: Route, body: T) async -> Data? where T: Encodable { let bodyData = try! API.encoder.encode(body) return await post(route, bodyData: bodyData) } private func post(_ route: Route, bodyData: Data) async -> Data? { guard let url = url else { return nil } var request = URLRequest(url: url.appendingPathComponent(route.rawValue)) request.httpBody = bodyData request.httpMethod = "POST" do { let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse else { return nil } guard httpResponse.statusCode == 200 else { print("Failed with code: \(httpResponse.statusCode)") return nil } return data } catch { print("Failed with error: \(error)") return nil } } }