@flomentumsolutions/capacitor-health-extended 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,6 @@ import HealthKit
6
6
  * Please read the Capacitor iOS Plugin Development Guide
7
7
  * here: https://capacitorjs.com/docs/plugins/ios
8
8
  */
9
- @MainActor
10
9
  @objc(HealthPlugin)
11
10
  public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
12
11
  public let identifier = "HealthPlugin"
@@ -66,8 +65,19 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
66
65
  return
67
66
  }
68
67
 
68
+ print("⚡️ [HealthPlugin] Requesting permissions: \(permissions)")
69
+
69
70
  let types: [HKObjectType] = permissions.flatMap { permissionToHKObjectType($0) }
70
71
 
72
+ print("⚡️ [HealthPlugin] Mapped to \(types.count) HKObjectTypes")
73
+
74
+ // Validate that we have at least one valid permission type
75
+ guard !types.isEmpty else {
76
+ let invalidPermissions = permissions.filter { permissionToHKObjectType($0).isEmpty }
77
+ call.reject("No valid permission types found. Invalid permissions: \(invalidPermissions)")
78
+ return
79
+ }
80
+
71
81
  healthStore.requestAuthorization(toShare: nil, read: Set(types)) { success, error in
72
82
  if success {
73
83
  //we don't know which actual permissions were granted, so we assume all
@@ -264,7 +274,44 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
264
274
  HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
265
275
  HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic)
266
276
  ].compactMap { $0 }
277
+ // Add common alternative permission names
278
+ case "steps":
279
+ return [HKObjectType.quantityType(forIdentifier: .stepCount)].compactMap{$0}
280
+ case "weight":
281
+ return [HKObjectType.quantityType(forIdentifier: .bodyMass)].compactMap{$0}
282
+ case "height":
283
+ return [HKObjectType.quantityType(forIdentifier: .height)].compactMap { $0 }
284
+ case "calories", "total-calories":
285
+ return [
286
+ HKObjectType.quantityType(forIdentifier: .activeEnergyBurned),
287
+ HKObjectType.quantityType(forIdentifier: .basalEnergyBurned) // iOS 16+
288
+ ].compactMap { $0 }
289
+ case "active-calories":
290
+ return [HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)].compactMap{$0}
291
+ case "workouts":
292
+ return [HKObjectType.workoutType()].compactMap{$0}
293
+ case "heart-rate", "heartrate":
294
+ return [HKObjectType.quantityType(forIdentifier: .heartRate)].compactMap{$0}
295
+ case "route":
296
+ return [HKSeriesType.workoutRoute()].compactMap{$0}
297
+ case "distance":
298
+ return [
299
+ HKObjectType.quantityType(forIdentifier: .distanceCycling),
300
+ HKObjectType.quantityType(forIdentifier: .distanceSwimming),
301
+ HKObjectType.quantityType(forIdentifier: .distanceWalkingRunning),
302
+ HKObjectType.quantityType(forIdentifier: .distanceDownhillSnowSports)
303
+ ].compactMap{$0}
304
+ case "mindfulness":
305
+ return [HKObjectType.categoryType(forIdentifier: .mindfulSession)!].compactMap{$0}
306
+ case "hrv":
307
+ return [HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN)].compactMap { $0 }
308
+ case "blood-pressure", "bloodpressure":
309
+ return [
310
+ HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
311
+ HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic)
312
+ ].compactMap { $0 }
267
313
  default:
314
+ print("⚡️ [HealthPlugin] Unknown permission: \(permission)")
268
315
  return []
269
316
  }
270
317
  }
@@ -333,17 +380,6 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
333
380
  switch dataType.aggregationStyle {
334
381
  case .cumulative:
335
382
  return .cumulativeSum
336
- case .discreteAverage:
337
- return .discreteAverage
338
- @available(iOS 17.0, *)
339
- case .discreteTemporallyWeighted:
340
- return .discreteAverage
341
- @available(iOS 17.0, *)
342
- case .discreteEquivalentContinuousLevel:
343
- return .discreteAverage
344
- @available(iOS 17.0, *)
345
- case .discreteArithmetic:
346
- return .discreteAverage
347
383
  case .discrete:
348
384
  return .discreteAverage
349
385
  @unknown default:
@@ -512,8 +548,10 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
512
548
  return
513
549
  }
514
550
  let outerGroup = DispatchGroup()
551
+ let resultsQueue = DispatchQueue(label: "com.flomentum.healthplugin.workoutResults")
515
552
  var workoutResults: [[String: Any]] = []
516
553
  var errors: [String: String] = [:]
554
+
517
555
  for workout in workouts {
518
556
  outerGroup.enter()
519
557
  var localDict: [String: Any] = [
@@ -530,11 +568,16 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
530
568
  let innerGroup = DispatchGroup()
531
569
  var localHeartRates: [[String: Any]] = []
532
570
  var localRoutes: [[String: Any]] = []
571
+
533
572
  if includeHeartRate {
534
573
  innerGroup.enter()
535
574
  self.queryHeartRate(for: workout) { rates, error in
536
575
  localHeartRates = rates
537
- if let error = error { errors["heart-rate"] = error }
576
+ if let error = error {
577
+ resultsQueue.async {
578
+ errors["heart-rate"] = error
579
+ }
580
+ }
538
581
  innerGroup.leave()
539
582
  }
540
583
  }
@@ -542,7 +585,11 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
542
585
  innerGroup.enter()
543
586
  self.queryRoute(for: workout) { routes, error in
544
587
  localRoutes = routes
545
- if let error = error { errors["route"] = error }
588
+ if let error = error {
589
+ resultsQueue.async {
590
+ errors["route"] = error
591
+ }
592
+ }
546
593
  innerGroup.leave()
547
594
  }
548
595
  }
@@ -558,7 +605,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
558
605
  innerGroup.notify(queue: .main) {
559
606
  localDict["heartRate"] = localHeartRates
560
607
  localDict["route"] = localRoutes
561
- workoutResults.append(localDict)
608
+ resultsQueue.async {
609
+ workoutResults.append(localDict)
610
+ }
562
611
  outerGroup.leave()
563
612
  }
564
613
  }
@@ -611,11 +660,15 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
611
660
  guard let self = self else { return }
612
661
  if let routes = samples as? [HKWorkoutRoute], error == nil {
613
662
  let routeDispatchGroup = DispatchGroup()
663
+ let allLocationsQueue = DispatchQueue(label: "com.flomentum.healthplugin.allLocations")
614
664
  var allLocations: [[String: Any]] = []
665
+
615
666
  for route in routes {
616
667
  routeDispatchGroup.enter()
617
668
  self.queryLocations(for: route) { locations in
618
- allLocations.append(contentsOf: locations)
669
+ allLocationsQueue.async {
670
+ allLocations.append(contentsOf: locations)
671
+ }
619
672
  routeDispatchGroup.leave()
620
673
  }
621
674
  }
@@ -634,8 +687,6 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
634
687
 
635
688
  // MARK: - Query Route Locations
636
689
  private func queryLocations(for route: HKWorkoutRoute, completion: @escaping @Sendable ([[String: Any]]) -> Void) {
637
- var routeLocations: [[String: Any]] = []
638
-
639
690
  let locationQuery = HKWorkoutRouteQuery(route: route) { [weak self] _, locations, done, error in
640
691
  guard let self = self else { return }
641
692
  guard let locations = locations, error == nil else {
@@ -645,8 +696,10 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
645
696
  return
646
697
  }
647
698
 
648
- // Append on a dedicated serial queue so we’re race‑free without NSLock
699
+ // Process locations on the serial queue to avoid race conditions
649
700
  self.routeSyncQueue.async {
701
+ var routeLocations: [[String: Any]] = []
702
+
650
703
  for location in locations {
651
704
  let locationDict: [String: Any] = [
652
705
  "timestamp": location.timestamp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flomentumsolutions/capacitor-health-extended",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Capacitor plugin for Apple HealthKit and Google Health Connect Platform",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",