diff --git a/ios/App/App/CoachWorkoutObserver.swift b/ios/App/App/CoachWorkoutObserver.swift index a166af7..279b3c8 100644 --- a/ios/App/App/CoachWorkoutObserver.swift +++ b/ios/App/App/CoachWorkoutObserver.swift @@ -36,6 +36,7 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin { CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "testNotification", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "resetAnchor", returnType: CAPPluginReturnPromise), + CAPPluginMethod(name: "requestAllHealthPermissions", returnType: CAPPluginReturnPromise), ] // Clés UserDefaults pour debug visible côté JS via getStatus() @@ -103,6 +104,73 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin { ]) } + // Bypass complet de @capgo/capacitor-health pour requestAuthorization. + // Le plugin tiers semble pendre indéfiniment sur iOS 26 (Promise jamais + // résolue, confirmé empiriquement 2026-05-18 avec timeout 8s même + // avec 1 seul type 'steps'). On appelle directement HKHealthStore + // depuis notre plugin custom qui fonctionne (CoachWorkoutObserver + // a fireCount=10 = preuve que HealthKit native marche pour cette app). + @objc func requestAllHealthPermissions(_ call: CAPPluginCall) { + guard HKHealthStore.isHealthDataAvailable() else { + call.reject("HealthKit not available on this device") + return + } + + // Types lus par coach.hypnotruck.ch — alignés sur READ_SCOPES JS + var readTypes = Set() + + let quantityIds: [HKQuantityTypeIdentifier] = [ + .stepCount, + .heartRate, + .restingHeartRate, + .heartRateVariabilitySDNN, + .bodyMass, + .height, + .bodyFatPercentage, + .oxygenSaturation, + .distanceWalkingRunning, + .distanceCycling, + .activeEnergyBurned, + .basalEnergyBurned, + .appleExerciseTime, + .flightsClimbed, + ] + for id in quantityIds { + if let t = HKObjectType.quantityType(forIdentifier: id) { + readTypes.insert(t) + } + } + + let categoryIds: [HKCategoryTypeIdentifier] = [ + .sleepAnalysis, + ] + for id in categoryIds { + if let t = HKObjectType.categoryType(forIdentifier: id) { + readTypes.insert(t) + } + } + + readTypes.insert(HKObjectType.workoutType()) + + let totalTypes = readTypes.count + NSLog("[CoachWorkoutObserver] requestAllHealthPermissions: requesting %d types", totalTypes) + + healthStore.requestAuthorization(toShare: nil, read: readTypes) { success, error in + DispatchQueue.main.async { + if let error = error { + NSLog("[CoachWorkoutObserver] requestAuthorization error: %@", error.localizedDescription) + call.reject("requestAuthorization failed: \(error.localizedDescription)") + } else { + NSLog("[CoachWorkoutObserver] requestAuthorization OK success=%@", success ? "true" : "false") + call.resolve([ + "success": success, + "typesRequested": totalTypes, + ]) + } + } + } + } + // Debug — reset l'anchor stocké. Au prochain observer fire, on // récupèrera TOUS les workouts (initial sync). Utile pour tester // le pipeline avec un workout pré-existant.