import Foundation import WebSocketKit import Vapor final class DeviceManager { /// The connection to the device private var connection: WebSocket? /// The authentication token of the device for the socket connection private let deviceKey: String /// The authentication token of the remote private let remoteKey: Data /// Indicate that the socket is fully initialized with an authorized device var deviceIsAuthenticated = false private var isOpeningNewConnection = false /// Indicator for device availability var deviceIsConnected: Bool { deviceIsAuthenticated && !(connection?.isClosed ?? true) } /// A promise to finish the request once the device responds or times out private var requestInProgress: EventLoopPromise? init(deviceKey: String, remoteKey: Data) { self.deviceKey = deviceKey self.remoteKey = remoteKey } // MARK: API var deviceStatus: String { deviceIsConnected ? "1" : "0" } func sendMessageToDevice(_ message: Message, on eventLoop: EventLoop) -> EventLoopFuture { guard let socket = connection, !socket.isClosed else { connection = nil return eventLoop.makeSucceededFuture(.deviceNotConnected) } guard requestInProgress == nil else { return eventLoop.makeSucceededFuture(.operationInProgress) } requestInProgress = eventLoop.makePromise(of: DeviceResponse.self) socket.send(message.bytes, promise: nil) eventLoop.scheduleTask(in: .seconds(Config.deviceTimeout)) { [weak self] in guard let promise = self?.requestInProgress else { return } self?.requestInProgress = nil promise.succeed(.deviceTimedOut) } return requestInProgress!.futureResult } func authenticateDevice(psk: String) { guard psk == self.deviceKey else { print("Invalid device key") _ = connection?.close() deviceIsAuthenticated = false return } print("Device authenticated") deviceIsAuthenticated = true } func authenticateRemote(_ token: Data) -> Bool { let hash = SHA256.hash(data: token) return hash == remoteKey } func processDeviceResponse(_ data: ByteBuffer) { guard let promise = requestInProgress else { return } defer { requestInProgress = nil } promise.succeed(DeviceResponse(data) ?? .unexpectedSocketEvent) } func didCloseDeviceSocket() { guard !isOpeningNewConnection else { return } deviceIsAuthenticated = false guard connection != nil else { print("Socket closed, but no connection anyway") return } connection = nil print("Socket closed") } func removeDeviceConnection() { deviceIsAuthenticated = false guard let socket = connection else { return } try? socket.close().wait() connection = nil print("Removed device connection") } func createNewDeviceConnection(_ socket: WebSocket) { isOpeningNewConnection = true removeDeviceConnection() connection = socket print("Socket connected") isOpeningNewConnection = false } }