feat(workout-observer): bypass @capgo via requestAllHealthPermissions
Le plugin tiers @capgo/capacitor-health v8.4.9 a un bug iOS 26 : Health.requestAuthorization pend indéfiniment (Promise jamais résolue, confirmé empiriquement 2026-05-18 — timeout 8s même avec 1 seul type 'steps' + désinstall/réinstall complet inefficace). Workaround : nouvelle méthode requestAllHealthPermissions exposée sur notre plugin custom CoachWorkoutObserver (qui fonctionne empiriquement — fireCount=10 confirme que HealthKit native marche). Appelle directement HKHealthStore.requestAuthorization avec un Set<HKObjectType> construit en Swift (14 quantity + 1 category + workout). Pas de string→enum mapping intermédiaire, pas de parsing TypeScript→Swift, donc pas de chaîne où ça pourrait silently fail comme dans @capgo. Bump build requis côté Mac avant rebuild TestFlight build 22.
This commit is contained in:
@@ -36,6 +36,7 @@ public class CoachWorkoutObserverPlugin: CAPPlugin, CAPBridgedPlugin {
|
|||||||
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
|
CAPPluginMethod(name: "getStatus", returnType: CAPPluginReturnPromise),
|
||||||
CAPPluginMethod(name: "testNotification", returnType: CAPPluginReturnPromise),
|
CAPPluginMethod(name: "testNotification", returnType: CAPPluginReturnPromise),
|
||||||
CAPPluginMethod(name: "resetAnchor", returnType: CAPPluginReturnPromise),
|
CAPPluginMethod(name: "resetAnchor", returnType: CAPPluginReturnPromise),
|
||||||
|
CAPPluginMethod(name: "requestAllHealthPermissions", returnType: CAPPluginReturnPromise),
|
||||||
]
|
]
|
||||||
|
|
||||||
// Clés UserDefaults pour debug visible côté JS via getStatus()
|
// 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<HKObjectType>()
|
||||||
|
|
||||||
|
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
|
// Debug — reset l'anchor stocké. Au prochain observer fire, on
|
||||||
// récupèrera TOUS les workouts (initial sync). Utile pour tester
|
// récupèrera TOUS les workouts (initial sync). Utile pour tester
|
||||||
// le pipeline avec un workout pré-existant.
|
// le pipeline avec un workout pré-existant.
|
||||||
|
|||||||
Reference in New Issue
Block a user