Stabilize iOS/watchOS/tvOS apps and add cross-platform audit remediation
This commit is contained in:
@@ -9,27 +9,63 @@ import Foundation
|
||||
import WatchConnectivity
|
||||
import AVFoundation
|
||||
import HealthKit
|
||||
import os
|
||||
import SharedCore
|
||||
|
||||
private let watchBridgeLogger = Logger(subsystem: "com.werkout.ios", category: "watch-bridge")
|
||||
|
||||
extension BridgeModule: WCSessionDelegate {
|
||||
private func send<Action: Encodable>(action: Action) {
|
||||
do {
|
||||
let data = try JSONEncoder().encode(action)
|
||||
send(data)
|
||||
} catch {
|
||||
watchBridgeLogger.error("Failed to encode watch action: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
private func sendInExerciseAction(_ model: WatchPackageModel) {
|
||||
do {
|
||||
let action = PhoneToWatchActions.inExercise(model)
|
||||
let payload = try JSONEncoder().encode(action)
|
||||
guard payload != lastSentInExercisePayload else {
|
||||
return
|
||||
}
|
||||
lastSentInExercisePayload = payload
|
||||
send(payload)
|
||||
} catch {
|
||||
watchBridgeLogger.error("Failed to encode in-exercise watch action: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
private func flushQueuedWatchMessages() {
|
||||
let queuedMessages = queuedWatchMessages.dequeueAll()
|
||||
guard queuedMessages.isEmpty == false else {
|
||||
return
|
||||
}
|
||||
queuedMessages.forEach { send($0) }
|
||||
}
|
||||
|
||||
private func enqueueWatchMessage(_ data: Data) {
|
||||
let droppedCount = queuedWatchMessages.enqueue(data)
|
||||
if droppedCount > 0 {
|
||||
watchBridgeLogger.warning("Dropping oldest queued watch message to enforce queue cap")
|
||||
}
|
||||
}
|
||||
|
||||
func sendResetToWatch() {
|
||||
let watchModel = PhoneToWatchActions.reset
|
||||
let data = try! JSONEncoder().encode(watchModel)
|
||||
send(data)
|
||||
// self.session.transferUserInfo(["package": data])
|
||||
lastSentInExercisePayload = nil
|
||||
send(action: PhoneToWatchActions.reset)
|
||||
}
|
||||
|
||||
func sendStartWorkoutToWatch() {
|
||||
let model = PhoneToWatchActions.startWorkout
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
send(data)
|
||||
// self.session.transferUserInfo(["package": data])
|
||||
lastSentInExercisePayload = nil
|
||||
send(action: PhoneToWatchActions.startWorkout)
|
||||
}
|
||||
|
||||
func sendWorkoutCompleteToWatch() {
|
||||
let model = PhoneToWatchActions.endWorkout
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
send(data)
|
||||
// self.session.transferUserInfo(["package": data])
|
||||
lastSentInExercisePayload = nil
|
||||
send(action: PhoneToWatchActions.endWorkout)
|
||||
}
|
||||
|
||||
func sendCurrentExerciseToWatch() {
|
||||
@@ -40,9 +76,7 @@ extension BridgeModule: WCSessionDelegate {
|
||||
currentExerciseID: currentExercise.id ?? -1,
|
||||
currentTimeLeft: currentExerciseTimeLeft,
|
||||
workoutStartDate: workoutStartDate ?? Date())
|
||||
let model = PhoneToWatchActions.inExercise(watchModel)
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
send(data)
|
||||
sendInExerciseAction(watchModel)
|
||||
} else {
|
||||
if let currentExercise = currentWorkoutInfo.currentExercise,
|
||||
let reps = currentExercise.reps,
|
||||
@@ -51,33 +85,38 @@ extension BridgeModule: WCSessionDelegate {
|
||||
// if not a timer we need to set the watch display with number of reps
|
||||
// if timer it will set when timer updates
|
||||
let watchModel = WatchPackageModel(currentExerciseName: currentExercise.exercise.name, currentExerciseID: currentExercise.id ?? -1, currentTimeLeft: reps, workoutStartDate: self.workoutStartDate ?? Date())
|
||||
let model = PhoneToWatchActions.inExercise(watchModel)
|
||||
let data = try! JSONEncoder().encode(model)
|
||||
self.send(data)
|
||||
self.sendInExerciseAction(watchModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
||||
if let model = try? JSONDecoder().decode(WatchActions.self, from: messageData) {
|
||||
switch model {
|
||||
case .nextExercise:
|
||||
nextExercise()
|
||||
AudioEngine.shared.playFinished()
|
||||
case .workoutComplete(let data):
|
||||
DispatchQueue.main.async {
|
||||
let model = try! JSONDecoder().decode(WatchFinishWorkoutModel.self, from: data)
|
||||
self.healthKitUUID = model.healthKitUUID
|
||||
do {
|
||||
let model = try WatchPayloadValidation.decode(WatchActions.self, from: messageData)
|
||||
DispatchQueue.main.async {
|
||||
switch model {
|
||||
case .nextExercise:
|
||||
self.nextExercise()
|
||||
AudioEngine.shared.playFinished()
|
||||
case .workoutComplete(let data):
|
||||
do {
|
||||
let finishModel = try WatchPayloadValidation.decode(WatchFinishWorkoutModel.self, from: data)
|
||||
self.healthKitUUID = finishModel.healthKitUUID
|
||||
} catch {
|
||||
watchBridgeLogger.error("Rejected watch completion payload: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
case .restartExercise:
|
||||
self.restartExercise()
|
||||
case .previousExercise:
|
||||
self.previousExercise()
|
||||
case .stopWorkout:
|
||||
self.completeWorkout()
|
||||
case .pauseWorkout:
|
||||
self.pauseWorkout()
|
||||
}
|
||||
case .restartExercise:
|
||||
restartExercise()
|
||||
case .previousExercise:
|
||||
previousExercise()
|
||||
case .stopWorkout:
|
||||
completeWorkout()
|
||||
case .pauseWorkout:
|
||||
pauseWorkout()
|
||||
}
|
||||
} catch {
|
||||
watchBridgeLogger.error("Rejected WatchActions payload: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,25 +124,30 @@ extension BridgeModule: WCSessionDelegate {
|
||||
func session(_ session: WCSession,
|
||||
activationDidCompleteWith activationState: WCSessionActivationState,
|
||||
error: Error?) {
|
||||
switch activationState {
|
||||
case .notActivated:
|
||||
print("notActivated")
|
||||
case .inactive:
|
||||
print("inactive")
|
||||
case .activated:
|
||||
print("activated")
|
||||
DispatchQueue.main.async {
|
||||
switch activationState {
|
||||
case .notActivated:
|
||||
watchBridgeLogger.info("Watch session notActivated")
|
||||
case .inactive:
|
||||
watchBridgeLogger.info("Watch session inactive")
|
||||
case .activated:
|
||||
watchBridgeLogger.info("Watch session activated")
|
||||
self.flushQueuedWatchMessages()
|
||||
#if os(iOS)
|
||||
let workoutConfiguration = HKWorkoutConfiguration()
|
||||
workoutConfiguration.activityType = .functionalStrengthTraining
|
||||
workoutConfiguration.locationType = .indoor
|
||||
if WCSession.isSupported(), session.activationState == .activated, session.isWatchAppInstalled {
|
||||
HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in
|
||||
print(error.debugDescription)
|
||||
})
|
||||
}
|
||||
let workoutConfiguration = HKWorkoutConfiguration()
|
||||
workoutConfiguration.activityType = .functionalStrengthTraining
|
||||
workoutConfiguration.locationType = .indoor
|
||||
if WCSession.isSupported(), session.activationState == .activated, session.isWatchAppInstalled {
|
||||
HKHealthStore().startWatchApp(with: workoutConfiguration, completion: { (success, error) in
|
||||
if let error = error {
|
||||
watchBridgeLogger.error("Failed to start watch app: \(error.localizedDescription, privacy: .public)")
|
||||
}
|
||||
})
|
||||
}
|
||||
#endif
|
||||
@unknown default:
|
||||
print("default")
|
||||
@unknown default:
|
||||
watchBridgeLogger.error("Unknown WCSession activation state")
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
@@ -116,7 +160,13 @@ extension BridgeModule: WCSessionDelegate {
|
||||
}
|
||||
#endif
|
||||
func send(_ data: Data) {
|
||||
guard WCSession.isSupported() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard session.activationState == .activated else {
|
||||
enqueueWatchMessage(data)
|
||||
session.activate()
|
||||
return
|
||||
}
|
||||
#if os(iOS)
|
||||
@@ -128,8 +178,15 @@ extension BridgeModule: WCSessionDelegate {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
session.sendMessageData(data, replyHandler: nil) { error in
|
||||
print("Cannot send message: \(String(describing: error))")
|
||||
if session.isReachable {
|
||||
session.sendMessageData(data, replyHandler: nil) { error in
|
||||
watchBridgeLogger.error("Cannot send watch message: \(error.localizedDescription, privacy: .public)")
|
||||
DispatchQueue.main.async {
|
||||
self.enqueueWatchMessage(data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
session.transferUserInfo(["package": data])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user