// // WCManager.swift // meterios // // (c) 2025 Martin "maride" Dessauer // import SwiftUI import WatchConnectivity enum WCMeteriosMessageTypes: String { case ConnectionInfo case DrinkList case BookDrink } class WCManager: NSObject, WCSessionDelegate { @AppStorage("meteHostAddr") private var meteHostAddr = "" @AppStorage("meteUserID") private var meteUserID = -1 private let session: WCSession = WCSession.default override init() { super.init() if (WCSession.isSupported()) { session.delegate = self session.activate() log("Activated session") } } // Functions required by WCSessionDelegate protocol, but not of interest for Meterios #if os(iOS) func sessionDidBecomeInactive(_ session: WCSession) {} func sessionDidDeactivate(_ session: WCSession) {} #endif // os(iOS) func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) {} // session(...) gets called for every incoming message through the WatchConnectivity channel // As Meterios currently only needs messages from watch to device and responses back to the watch, this function currently only implements device-related responses. func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) { // Check for the different possible message types if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.ConnectionInfo.rawValue }) { // Request for ConnectionInfo replyHandler([ "userID": meteUserID, "hostAddr": meteHostAddr ]) } else if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.DrinkList.rawValue }) { // Request for a list of available drinks Task { let drinks = try await BackendConnector().GetDrinks(baseAddr: meteHostAddr) let availableDrinks = drinks.filter({ $0.active }) // Create a plist-compatible variant of the Drink object containing the required information var drinkArray: Array = [] for d in availableDrinks { drinkArray.append( try! JSONEncoder().encode(d).base64EncodedString() ) } replyHandler([ "drinks": drinkArray ]) } } else if message.contains(where: { $0.key == "request" && $0.value as! String == WCMeteriosMessageTypes.BookDrink.rawValue }) { // Book the given drink at the backend Task { do { // Book let drinkID = message["drinkID"] as! Int try await BackendConnector().BookDrink(baseAddr: meteHostAddr, userID: meteUserID, drinkID: drinkID) replyHandler([ "salut": SalutManager.getSalute() ]) } catch { // Error booking against the backend replyHandler([ "error": error, "salut": "" ]) } } } else { log("Error: received unknown request. \(message)") } } // requestConnectionInfo requests User ID and Host address from the paired device func requestConnectionInfo(replyHandler: @escaping (Int, String) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ "request": WCMeteriosMessageTypes.ConnectionInfo.rawValue ], replyHandler: { reply in replyHandler(reply["userID"] as! Int, reply["hostAddr"] as! String) }, errorHandler: { error in errorHandler(error) }) } // requestDrinksList requests the list of drinks available at the backend from the paired device func requestDrinksList(replyHandler: @escaping (Array) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ "request": WCMeteriosMessageTypes.DrinkList.rawValue ], replyHandler: { reply in let drinks = reply["drinks"] as! Array // Create Drink objects from response var drinkArray: Array = [] for d in drinks { let jsonDecoded = Data(base64Encoded: d) let drink = try! JSONDecoder().decode(Drink.self, from: jsonDecoded!) drinkArray.append(drink) } // enjoy your drinks, replyHandler! replyHandler(drinkArray) }, errorHandler: { error in errorHandler(error) }) } // bookDrink books the drink given by drinkID func bookDrink(drinkID: Int, replyHandler: @escaping (String) -> Void, errorHandler: @escaping ((any Error)?) -> Void) { session.sendMessage([ "request": WCMeteriosMessageTypes.BookDrink.rawValue, "drinkID": drinkID, ], replyHandler: { reply in let salut = reply["salut"] as! String replyHandler(salut) }, errorHandler: { error in errorHandler(error) }) } private func log(_ line: String) { #if os(iOS) let prefix = "[WCM@iOS]" #elseif os(watchOS) let prefix = "[WCM@wOS]" #else let prefix = "[WCM@?OS]" #endif // os(...) print("\(prefix) \(line)") } }