import Foundation struct HistoryItem { /// The sent/received date (local time, not including compensation offset) let requestDate: Date let request: Message.Content let usedLocalConnection: Bool var response: ClientState let responseMessage: Message.Content? let responseDate: Date init(sent message: Message.Content, sentDate: Date, local: Bool, response: ClientState, responseDate: Date, responseMessage: Message.Content?) { self.requestDate = sentDate self.request = message self.responseMessage = responseMessage self.response = response self.responseDate = responseDate self.usedLocalConnection = local } // MARK: Statistics var roundTripTime: TimeInterval { responseDate.timeIntervalSince(requestDate) } var deviceTime: Date? { guard let timestamp = responseMessage?.time else { return nil } return Date(timestamp: timestamp) } var requestLatency: TimeInterval? { deviceTime?.timeIntervalSince(requestDate) } var responseLatency: TimeInterval? { guard let deviceTime = deviceTime else { return nil } return responseDate.timeIntervalSince(deviceTime) } var clockOffset: Int? { guard let deviceTime = deviceTime else { return nil } let estimatedArrival = requestDate.advanced(by: roundTripTime / 2) return Int(deviceTime.timeIntervalSince(estimatedArrival)) } } extension HistoryItem: Codable { enum CodingKeys: Int, CodingKey { case requestDate = 1 case request = 2 case usedLocalConnection = 3 case response = 4 case responseMessage = 5 case responseDate = 6 } } extension ClientState: Codable { init(from decoder: Decoder) throws { let code = try decoder.singleValueContainer().decode(UInt8.self) self.init(code: code) } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() try container.encode(code) } } extension HistoryItem: Identifiable { var id: UInt32 { requestDate.timestamp } } extension HistoryItem: Comparable { static func < (lhs: HistoryItem, rhs: HistoryItem) -> Bool { lhs.requestDate < rhs.requestDate } } extension HistoryItem { static var mock: HistoryItem { let content = Message.Content(time: Date.now.timestamp, id: 123, device: 0) let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124, device: 0) return .init( sent: content, sentDate: .now, local: false, response: .openSesame, responseDate: .now + 2, responseMessage: content2) } }