@kingstinct/react-native-healthkit 14.1.0 → 16.0.0

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.
Files changed (123) hide show
  1. package/README.md +37 -3
  2. package/ReactNativeHealthkit.podspec +11 -7
  3. package/app.plugin.js +33 -94
  4. package/app.plugin.ts +29 -104
  5. package/ios/CategoryTypeModule.swift +1 -0
  6. package/ios/CharacteristicTypeModule.swift +7 -6
  7. package/ios/Constants.swift +9 -10
  8. package/ios/CoreModule.swift +77 -42
  9. package/ios/CorrelationTypeModule.swift +3 -2
  10. package/ios/ElectrocardiogramModule.swift +4 -3
  11. package/ios/HeartbeatSeriesModule.swift +4 -3
  12. package/ios/Helpers.swift +11 -216
  13. package/ios/MedicationModule.swift +9 -8
  14. package/ios/MemoryEstimation.swift +84 -0
  15. package/ios/PredicateHelpers.swift +16 -86
  16. package/ios/QuantityTypeModule.swift +37 -28
  17. package/ios/Serializers.swift +34 -70
  18. package/ios/SourceProxy.swift +15 -5
  19. package/ios/StateOfMindModule.swift +3 -2
  20. package/ios/WorkoutProxy.swift +31 -13
  21. package/ios/WorkoutsModule.swift +4 -3
  22. package/lib/commonjs/generated/healthkit.generated.js +15 -3
  23. package/lib/commonjs/healthkit.ios.js +6 -2
  24. package/lib/commonjs/healthkit.js +24 -79
  25. package/lib/commonjs/types/Auth.js +3 -18
  26. package/lib/commonjs/types/Background.js +2 -10
  27. package/lib/commonjs/types/QueryOptions.js +2 -17
  28. package/lib/module/generated/healthkit.generated.js +14 -2
  29. package/lib/module/healthkit.ios.js +4 -0
  30. package/lib/module/healthkit.js +22 -77
  31. package/lib/module/types/Auth.js +1 -18
  32. package/lib/module/types/Background.js +1 -10
  33. package/lib/module/types/QueryOptions.js +1 -17
  34. package/lib/typescript/generated/healthkit.generated.d.ts +20 -7
  35. package/lib/typescript/healthkit.d.ts +4 -2
  36. package/lib/typescript/healthkit.ios.d.ts +13 -9
  37. package/lib/typescript/specs/CoreModule.nitro.d.ts +22 -4
  38. package/lib/typescript/specs/SourceProxy.nitro.d.ts +2 -5
  39. package/lib/typescript/types/Auth.d.ts +1 -16
  40. package/lib/typescript/types/Background.d.ts +1 -9
  41. package/lib/typescript/types/Device.d.ts +1 -13
  42. package/lib/typescript/types/QuantityType.d.ts +3 -3
  43. package/lib/typescript/types/QueryOptions.d.ts +3 -37
  44. package/lib/typescript/types/Shared.d.ts +2 -25
  45. package/lib/typescript/types/Source.d.ts +1 -10
  46. package/lib/typescript/types/Subscriptions.d.ts +2 -3
  47. package/lib/typescript/types/Units.d.ts +1 -1
  48. package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.cpp +24 -16
  49. package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.hpp +91 -12
  50. package/nitrogen/generated/ios/c++/HybridCategoryTypeModuleSpecSwift.hpp +8 -5
  51. package/nitrogen/generated/ios/c++/HybridCoreModuleSpecSwift.hpp +8 -0
  52. package/nitrogen/generated/ios/c++/HybridCorrelationTypeModuleSpecSwift.hpp +8 -5
  53. package/nitrogen/generated/ios/c++/HybridElectrocardiogramModuleSpecSwift.hpp +8 -5
  54. package/nitrogen/generated/ios/c++/HybridHeartbeatSeriesModuleSpecSwift.hpp +8 -5
  55. package/nitrogen/generated/ios/c++/HybridMedicationModuleSpecSwift.hpp +8 -5
  56. package/nitrogen/generated/ios/c++/HybridQuantityTypeModuleSpecSwift.hpp +8 -5
  57. package/nitrogen/generated/ios/c++/HybridStateOfMindModuleSpecSwift.hpp +8 -5
  58. package/nitrogen/generated/ios/c++/HybridWorkoutProxySpecSwift.hpp +3 -4
  59. package/nitrogen/generated/ios/c++/HybridWorkoutsModuleSpecSwift.hpp +6 -3
  60. package/nitrogen/generated/ios/swift/CategorySample.swift +8 -8
  61. package/nitrogen/generated/ios/swift/CategoryTypeIdentifier.swift +8 -0
  62. package/nitrogen/generated/ios/swift/CategoryTypeIdentifierWriteable.swift +8 -0
  63. package/nitrogen/generated/ios/swift/CorrelationSample.swift +8 -8
  64. package/nitrogen/generated/ios/swift/ElectrocardiogramSample.swift +8 -8
  65. package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__chrono__system_clock__time_point_.swift +54 -0
  66. package/nitrogen/generated/ios/swift/HeartbeatSeriesSample.swift +8 -8
  67. package/nitrogen/generated/ios/swift/HybridCoreModuleSpec.swift +1 -0
  68. package/nitrogen/generated/ios/swift/HybridCoreModuleSpec_cxx.swift +25 -0
  69. package/nitrogen/generated/ios/swift/MedicationDoseEvent.swift +8 -8
  70. package/nitrogen/generated/ios/swift/ObjectTypeIdentifier.swift +12 -4
  71. package/nitrogen/generated/ios/swift/QuantitySample.swift +8 -8
  72. package/nitrogen/generated/ios/swift/QuantityTypeIdentifier.swift +4 -4
  73. package/nitrogen/generated/ios/swift/QuantityTypeIdentifierWriteable.swift +4 -4
  74. package/nitrogen/generated/ios/swift/QueryStatisticsResponse.swift +6 -13
  75. package/nitrogen/generated/ios/swift/QueryStatisticsResponseFromSingleSource.swift +4 -11
  76. package/nitrogen/generated/ios/swift/SampleTypeIdentifier.swift +12 -4
  77. package/nitrogen/generated/ios/swift/SampleTypeIdentifierWriteable.swift +12 -4
  78. package/nitrogen/generated/ios/swift/SourceRevision.swift +4 -11
  79. package/nitrogen/generated/ios/swift/StateOfMindSample.swift +8 -8
  80. package/nitrogen/generated/ios/swift/WorkoutActivityType.swift +8 -0
  81. package/nitrogen/generated/shared/c++/CategorySample.hpp +7 -7
  82. package/nitrogen/generated/shared/c++/CategoryTypeIdentifier.hpp +67 -59
  83. package/nitrogen/generated/shared/c++/CategoryTypeIdentifierWriteable.hpp +67 -59
  84. package/nitrogen/generated/shared/c++/CorrelationSample.hpp +7 -7
  85. package/nitrogen/generated/shared/c++/ElectrocardiogramSample.hpp +7 -7
  86. package/nitrogen/generated/shared/c++/HeartbeatSeriesSample.hpp +7 -7
  87. package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.cpp +1 -0
  88. package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.hpp +3 -1
  89. package/nitrogen/generated/shared/c++/MedicationDoseEvent.hpp +7 -7
  90. package/nitrogen/generated/shared/c++/ObjectTypeIdentifier.hpp +206 -198
  91. package/nitrogen/generated/shared/c++/QuantitySample.hpp +7 -7
  92. package/nitrogen/generated/shared/c++/QuantityTypeIdentifier.hpp +73 -73
  93. package/nitrogen/generated/shared/c++/QuantityTypeIdentifierWriteable.hpp +73 -73
  94. package/nitrogen/generated/shared/c++/QueryStatisticsResponse.hpp +8 -9
  95. package/nitrogen/generated/shared/c++/QueryStatisticsResponseFromSingleSource.hpp +8 -9
  96. package/nitrogen/generated/shared/c++/SampleTypeIdentifier.hpp +199 -191
  97. package/nitrogen/generated/shared/c++/SampleTypeIdentifierWriteable.hpp +193 -185
  98. package/nitrogen/generated/shared/c++/SourceRevision.hpp +8 -9
  99. package/nitrogen/generated/shared/c++/StateOfMindSample.hpp +7 -7
  100. package/nitrogen/generated/shared/c++/WorkoutActivityType.hpp +4 -0
  101. package/package.json +8 -3
  102. package/src/generated/healthkit-schema.json +53 -8
  103. package/src/generated/healthkit.generated.ts +22 -6
  104. package/src/healthkit.ios.ts +6 -0
  105. package/src/healthkit.ts +32 -84
  106. package/src/specs/CoreModule.nitro.ts +25 -4
  107. package/src/specs/SourceProxy.nitro.ts +2 -4
  108. package/src/test-setup.ts +1 -0
  109. package/src/type-tests/generated-typing.ts +30 -8
  110. package/src/types/Auth.ts +4 -17
  111. package/src/types/Background.ts +1 -9
  112. package/src/types/Device.ts +1 -13
  113. package/src/types/QuantityType.ts +3 -3
  114. package/src/types/QueryOptions.ts +7 -65
  115. package/src/types/Shared.ts +7 -28
  116. package/src/types/Source.ts +1 -11
  117. package/src/types/Subscriptions.ts +2 -3
  118. package/src/types/Units.ts +1 -1
  119. package/ios/BackgroundDeliveryManager.swift +0 -277
  120. package/ios/Bridge.h +0 -10
  121. package/ios/ExceptionCatcher.h +0 -5
  122. package/ios/ExceptionCatcher.mm +0 -32
  123. package/ios/module.modulemap +0 -5
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  //
5
6
  // Core.swift
@@ -8,8 +9,6 @@ import NitroModules
8
9
  // Created by Robert Herber on 2025-05-28.
9
10
  //
10
11
 
11
- var store = HKHealthStore.init()
12
-
13
12
  // Thread-safe cache with concurrent read/exclusive write access
14
13
  private let quantityTypeCacheQueue = DispatchQueue(
15
14
  label: "com.kingstinct.healthkit.cache",
@@ -57,7 +56,7 @@ async throws -> [HKQuantityType: HKUnit] {
57
56
  }
58
57
 
59
58
  return try await withCheckedThrowingContinuation { continuation in
60
- store.preferredUnits(for: Set(quantityTypes)) {
59
+ healthStore.preferredUnits(for: Set(quantityTypes)) {
61
60
  (typePerUnits: [HKQuantityType: HKUnit], error: Error?) in
62
61
  DispatchQueue.main.async {
63
62
  if let error = error {
@@ -82,7 +81,7 @@ class CoreModule: HybridCoreModuleSpec {
82
81
  return Promise.async {
83
82
  let objectType = try perObjectTypeFrom(objectTypeIdentifier: typeIdentifier)
84
83
  if #available(iOS 16.0, *) {
85
- try await store.requestPerObjectReadAuthorization(
84
+ try await healthStore.requestPerObjectReadAuthorization(
86
85
  for: objectType,
87
86
  predicate: nil
88
87
  )
@@ -92,6 +91,37 @@ class CoreModule: HybridCoreModuleSpec {
92
91
  }
93
92
  }
94
93
 
94
+ func getEarliestAuthorizedSampleDates(objectTypeIdentifiers: [ObjectTypeIdentifier]) -> Promise<
95
+ [String: Date]
96
+ > {
97
+ return Promise.async {
98
+ #if compiler(>=6.4)
99
+ if #available(iOS 27.0, *) {
100
+ let objectTypes = objectTypesFromArray(typeIdentifiers: objectTypeIdentifiers)
101
+
102
+ if objectTypes.isEmpty {
103
+ return [String: Date]()
104
+ }
105
+
106
+ let earliestDates = try await healthStore.earliestAuthorizedSampleDate(for: objectTypes)
107
+
108
+ return earliestDates.reduce(into: [String: Date]()) { result, entry in
109
+ result[entry.key.identifier] = entry.value
110
+ }
111
+ }
112
+ #else
113
+ if #available(iOS 27.0, *) {
114
+ warnWithPrefix(
115
+ "getEarliestAuthorizedSampleDates needs to be built with Xcode 27.0 or later to report limited-access dates"
116
+ )
117
+ }
118
+ #endif
119
+
120
+ // Before iOS 27 there is no limited-access earliest date, so no type has one.
121
+ return [String: Date]()
122
+ }
123
+ }
124
+
95
125
  func currentAppSource() -> any HybridSourceProxySpec {
96
126
  return SourceProxy(source: HKSource.default())
97
127
  }
@@ -133,7 +163,7 @@ class CoreModule: HybridCoreModuleSpec {
133
163
  ) throws -> AuthorizationStatus {
134
164
  let objectType = try objectTypeFrom(objectTypeIdentifier: type)
135
165
 
136
- let authStatus = store.authorizationStatus(for: objectType)
166
+ let authStatus = healthStore.authorizationStatus(for: objectType)
137
167
 
138
168
  if let authStatus = AuthorizationStatus(rawValue: Int32(authStatus.rawValue)) {
139
169
  return authStatus
@@ -157,27 +187,27 @@ class CoreModule: HybridCoreModuleSpec {
157
187
  returning: .unnecessary
158
188
  )
159
189
  }
160
- var caughtError: NSError?
161
- let started = RunBlockCatchingObjCExceptions({
162
- store.getRequestStatusForAuthorization(toShare: toShare, read: toRead) {
163
- status, error in
164
- DispatchQueue.main.async {
165
- if let error = error {
166
- continuation.resume(throwing: error)
167
- } else {
168
- if let authStatus = AuthorizationRequestStatus(rawValue: Int32(status.rawValue)) {
169
- continuation.resume(returning: authStatus)
190
+ do {
191
+ try runCatchingObjCExceptions {
192
+ healthStore.getRequestStatusForAuthorization(toShare: toShare, read: toRead) {
193
+ status, error in
194
+ DispatchQueue.main.async {
195
+ if let error = error {
196
+ continuation.resume(throwing: error)
170
197
  } else {
171
- continuation.resume(
172
- throwing: runtimeErrorWithPrefix(
173
- "Unrecognized authStatus returned: \(status.rawValue)"))
198
+ if let authStatus = AuthorizationRequestStatus(rawValue: Int32(status.rawValue)) {
199
+ continuation.resume(returning: authStatus)
200
+ } else {
201
+ continuation.resume(
202
+ throwing: runtimeErrorWithPrefix(
203
+ "Unrecognized authStatus returned: \(status.rawValue)"))
204
+ }
174
205
  }
175
206
  }
176
207
  }
177
208
  }
178
- }, &caughtError)
179
- if !started, let caughtError {
180
- continuation.resume(throwing: caughtError)
209
+ } catch {
210
+ continuation.resume(throwing: error)
181
211
  }
182
212
  }
183
213
  }
@@ -189,20 +219,20 @@ class CoreModule: HybridCoreModuleSpec {
189
219
  let toRead = objectTypesFromArray(typeIdentifiers: toRequest.toRead ?? [])
190
220
 
191
221
  return try await withCheckedThrowingContinuation { continuation in
192
- var caughtError: NSError?
193
- let started = RunBlockCatchingObjCExceptions({
194
- store.requestAuthorization(toShare: share, read: toRead) { status, error in
195
- DispatchQueue.main.async {
196
- if let error = error {
197
- continuation.resume(throwing: error)
198
- } else {
199
- continuation.resume(returning: status)
222
+ do {
223
+ try runCatchingObjCExceptions {
224
+ healthStore.requestAuthorization(toShare: share, read: toRead) { status, error in
225
+ DispatchQueue.main.async {
226
+ if let error = error {
227
+ continuation.resume(throwing: error)
228
+ } else {
229
+ continuation.resume(returning: status)
230
+ }
200
231
  }
201
232
  }
202
233
  }
203
- }, &caughtError)
204
- if !started, let caughtError {
205
- continuation.resume(throwing: caughtError)
234
+ } catch {
235
+ continuation.resume(throwing: error)
206
236
  }
207
237
  }
208
238
  }
@@ -243,7 +273,7 @@ class CoreModule: HybridCoreModuleSpec {
243
273
  }
244
274
  }
245
275
 
246
- store.execute(query)
276
+ healthStore.execute(query)
247
277
  }
248
278
  }
249
279
  }
@@ -255,7 +285,7 @@ class CoreModule: HybridCoreModuleSpec {
255
285
  if let frequency = HKUpdateFrequency(rawValue: Int(updateFrequency.rawValue)) {
256
286
  let type = try objectTypeFrom(objectTypeIdentifier: typeIdentifier)
257
287
  return try await withCheckedThrowingContinuation { continuation in
258
- store.enableBackgroundDelivery(
288
+ healthStore.enableBackgroundDelivery(
259
289
  for: type,
260
290
  frequency: frequency
261
291
  ) { (success, error) in
@@ -280,7 +310,7 @@ class CoreModule: HybridCoreModuleSpec {
280
310
  return Promise.async {
281
311
  let type = try objectTypeFrom(objectTypeIdentifier: typeIdentifier)
282
312
  return try await withCheckedThrowingContinuation { continuation in
283
- store.disableBackgroundDelivery(
313
+ healthStore.disableBackgroundDelivery(
284
314
  for: type
285
315
  ) { (success, error) in
286
316
  DispatchQueue.main.async {
@@ -297,7 +327,7 @@ class CoreModule: HybridCoreModuleSpec {
297
327
  func disableAllBackgroundDelivery() -> Promise<Bool> {
298
328
  return Promise.async {
299
329
  try await withCheckedThrowingContinuation { continuation in
300
- store.disableAllBackgroundDelivery(completion: { (success, error) in
330
+ healthStore.disableAllBackgroundDelivery(completion: { (success, error) in
301
331
  DispatchQueue.main.async {
302
332
  guard let err = error else {
303
333
  return continuation.resume(returning: success)
@@ -320,7 +350,11 @@ class CoreModule: HybridCoreModuleSpec {
320
350
  }
321
351
 
322
352
  func isProtectedDataAvailableAsync() -> Promise<Bool> {
323
- return Promise.resolved(withResult: UIApplication.shared.isProtectedDataAvailable)
353
+ return Promise.async {
354
+ return await MainActor.run {
355
+ UIApplication.shared.isProtectedDataAvailable
356
+ }
357
+ }
324
358
  }
325
359
 
326
360
  func isHealthDataAvailable() -> Bool {
@@ -373,7 +407,7 @@ class CoreModule: HybridCoreModuleSpec {
373
407
 
374
408
  let of = try sampleTypeFrom(sampleTypeIdentifierWriteable: objectTypeIdentifier)
375
409
  return try await withCheckedThrowingContinuation { continuation in
376
- store.deleteObjects(of: of, predicate: predicate) { (_, count, error) in
410
+ healthStore.deleteObjects(of: of, predicate: predicate) { (_, count, error) in
377
411
  DispatchQueue.main.async {
378
412
  if let error = error {
379
413
  continuation.resume(throwing: error)
@@ -438,7 +472,7 @@ class CoreModule: HybridCoreModuleSpec {
438
472
 
439
473
  }
440
474
 
441
- store.execute(query)
475
+ healthStore.execute(query)
442
476
 
443
477
  self._runningQueries.updateValue(query, forKey: queryId)
444
478
 
@@ -459,7 +493,7 @@ class CoreModule: HybridCoreModuleSpec {
459
493
  return false
460
494
  }
461
495
 
462
- store.stop(query)
496
+ healthStore.stop(query)
463
497
 
464
498
  self._runningQueries.removeValue(forKey: queryId)
465
499
 
@@ -481,6 +515,7 @@ class CoreModule: HybridCoreModuleSpec {
481
515
  }
482
516
 
483
517
  BackgroundDeliveryManager.shared.configure(
518
+ scope: backgroundDeliveryScope,
484
519
  typeIdentifiers: typeIdentifiers,
485
520
  frequency: frequency
486
521
  )
@@ -491,7 +526,7 @@ class CoreModule: HybridCoreModuleSpec {
491
526
 
492
527
  func clearBackgroundTypes() -> Promise<Bool> {
493
528
  return Promise.async {
494
- BackgroundDeliveryManager.shared.clearConfiguration()
529
+ BackgroundDeliveryManager.shared.clearConfiguration(scope: backgroundDeliveryScope)
495
530
  return true
496
531
  }
497
532
  }
@@ -499,7 +534,7 @@ class CoreModule: HybridCoreModuleSpec {
499
534
  func unsubscribeQueries(queryIds: [String]) -> Double {
500
535
  let successCounts = queryIds.map { queryId in
501
536
  if let query = self._runningQueries[queryId] {
502
- store.stop(query)
537
+ healthStore.stop(query)
503
538
 
504
539
  self._runningQueries.removeValue(forKey: queryId)
505
540
 
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  func serializeCorrelationSample(correlation: HKCorrelation, unitMap: [HKQuantityType: HKUnit])
5
6
  -> CorrelationSample {
@@ -28,10 +29,10 @@ func serializeCorrelationSample(correlation: HKCorrelation, unitMap: [HKQuantity
28
29
  startDate: correlation.startDate,
29
30
  endDate: correlation.endDate,
30
31
  hasUndeterminedDuration: correlation.hasUndeterminedDuration,
31
- metadata: serializeMetadata(correlation.metadata),
32
32
  uuid: correlation.uuid.uuidString,
33
33
  sourceRevision: serializeSourceRevision(correlation.sourceRevision),
34
- device: serializeDevice(hkDevice: correlation.device)
34
+ device: serializeDevice(hkDevice: correlation.device),
35
+ metadata: serializeMetadata(correlation.metadata)
35
36
  )
36
37
  }
37
38
 
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  // MARK: - Helpers
5
6
 
@@ -38,10 +39,10 @@ private func serializeECGSample(sample: HKElectrocardiogram, includeVoltages: Bo
38
39
  startDate: sample.startDate,
39
40
  endDate: sample.endDate,
40
41
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
41
- metadata: serializeMetadata(sample.metadata),
42
42
  uuid: sample.uuid.uuidString,
43
43
  sourceRevision: serializeSourceRevision(sample.sourceRevision),
44
- device: serializeDevice(hkDevice: sample.device)
44
+ device: serializeDevice(hkDevice: sample.device),
45
+ metadata: serializeMetadata(sample.metadata)
45
46
  )
46
47
  }
47
48
 
@@ -76,7 +77,7 @@ func getECGVoltages(sample: HKElectrocardiogram) async throws -> [Electrocardiog
76
77
  }
77
78
  }
78
79
  }
79
- store.execute(q)
80
+ healthStore.execute(q)
80
81
  }
81
82
  }
82
83
 
@@ -1,5 +1,6 @@
1
1
  import HealthKit
2
2
  import NitroModules
3
+ import ReactNativeHealthkitCore
3
4
 
4
5
  func serializeHeartbeatSeriesSample(sample: HKHeartbeatSeriesSample) async throws
5
6
  -> HeartbeatSeriesSample {
@@ -11,10 +12,10 @@ func serializeHeartbeatSeriesSample(sample: HKHeartbeatSeriesSample) async throw
11
12
  startDate: sample.startDate,
12
13
  endDate: sample.endDate,
13
14
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
14
- metadata: serializeMetadata(sample.metadata),
15
15
  uuid: sample.uuid.uuidString,
16
16
  sourceRevision: serializeSourceRevision(sample.sourceRevision),
17
- device: serializeDevice(hkDevice: sample.device)
17
+ device: serializeDevice(hkDevice: sample.device),
18
+ metadata: serializeMetadata(sample.metadata)
18
19
  )
19
20
  }
20
21
 
@@ -47,7 +48,7 @@ func getHeartbeatSeriesHeartbeats(sample: HKHeartbeatSeriesSample) async throws
47
48
  }
48
49
  }
49
50
 
50
- store.execute(query)
51
+ healthStore.execute(query)
51
52
  }
52
53
  }
53
54
 
package/ios/Helpers.swift CHANGED
@@ -8,171 +8,18 @@
8
8
  import Foundation
9
9
  import HealthKit
10
10
  import NitroModules
11
+ import ReactNativeHealthkitCore
11
12
 
12
- func parseUnitStringSafe(_ unitString: String) throws -> HKUnit {
13
- var err: NSError?
14
- let unitOut = HKUnitFromStringCatchingExceptions(unitString, &err)
15
-
16
- if let hkUnit = unitOut {
17
- return hkUnit
18
- }
19
-
20
- throw runtimeErrorWithPrefix("Supplied invalid '\(unitString)' as HKUnit")
21
- }
22
-
23
- func getQueryLimit(_ limit: Double) -> Int {
24
- if limit == .infinity || limit <= 0 || limit == .nan || limit == .signalingNaN {
25
- return HKObjectQueryNoLimit
26
- }
27
-
28
- return Int(limit)
29
- }
30
-
31
- struct AnchoredQueryResponse {
32
- var samples: [HKSample]
33
- var deletedSamples: [DeletedSample]
34
- var newAnchor: String
35
- }
36
-
37
- func sampleAnchoredQueryAsync(
38
- sampleType: HKSampleType,
39
- limit: Double,
40
- queryAnchor: String?,
41
- predicate: NSPredicate?
42
- ) async throws -> AnchoredQueryResponse {
43
- let queryAnchor = try deserializeHKQueryAnchor(base64String: queryAnchor)
44
-
45
- return try await withCheckedThrowingContinuation { continuation in
46
- let query = HKAnchoredObjectQuery(
47
- type: sampleType,
48
- predicate: predicate,
49
- anchor: queryAnchor,
50
- limit: getQueryLimit(limit)
51
- ) {
52
- (
53
- _: HKAnchoredObjectQuery, samples: [HKSample]?, deletedSamples: [HKDeletedObject]?,
54
- newAnchor:
55
- HKQueryAnchor?, error: Error?
56
- ) in
57
- DispatchQueue.main.async {
58
- if let error = error {
59
- return continuation.resume(throwing: error)
60
- }
61
-
62
- if let samples = samples, let deletedSamples = deletedSamples,
63
- let newAnchor = serializeAnchor(anchor: newAnchor) {
64
- return continuation.resume(
65
- returning: AnchoredQueryResponse(
66
- samples: samples,
67
- deletedSamples: deletedSamples.map({ deletedSample in
68
- return serializeDeletedSample(sample: deletedSample)
69
- }),
70
- newAnchor: newAnchor
71
- )
72
- )
73
- }
74
-
75
- return continuation.resume(
76
- throwing: runtimeErrorWithPrefix("Unexpected empty response"))
77
- }
78
- }
79
-
80
- store.execute(query)
81
- }
82
- }
83
-
84
- func serializeAnchor(anchor: HKQueryAnchor?) -> String? {
85
- return toBase64(anchor)
86
- }
87
-
88
- func toBase64(_ data: Any?) -> String? {
89
- guard let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true) else {
90
- return nil
91
- }
92
-
93
- return archivedData.base64EncodedString()
94
- }
95
-
96
- func sampleQueryAsync(
97
- sampleType: HKSampleType,
98
- limit: Double,
99
- predicate: NSPredicate?,
100
- sortDescriptors: [NSSortDescriptor]?
101
- ) async throws -> [HKSample] {
102
- let limit = getQueryLimit(limit)
103
- return try await withCheckedThrowingContinuation { continuation in
104
- let q = HKSampleQuery(
105
- sampleType: sampleType,
106
- predicate: predicate,
107
- limit: limit,
108
- sortDescriptors: sortDescriptors,
109
- ) { (_: HKSampleQuery, samples: [HKSample]?, error: Error?) in
110
- DispatchQueue.main.async {
111
- if let error = error {
112
- return continuation.resume(throwing: error)
113
- }
114
-
115
- if let samples = samples {
116
- return continuation.resume(returning: samples)
117
- }
118
-
119
- return continuation.resume(
120
- throwing: runtimeErrorWithPrefix("Unexpected empty response"))
121
- }
122
- }
123
-
124
- store.execute(q)
125
- }
126
- }
127
-
128
- func saveAsync(sample: HKObject) async throws -> Bool {
129
- return try await withCheckedThrowingContinuation { continuation in
130
- store.save(sample) { (success: Bool, error: Error?) in
131
- DispatchQueue.main.async {
132
- if let error = error {
133
- continuation.resume(throwing: error)
134
- } else {
135
- continuation.resume(returning: success)
136
- }
137
- }
138
- }
139
- }
140
- }
141
-
142
- func getSortDescriptors(ascending: Bool?) -> [NSSortDescriptor] {
143
- return [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: ascending ?? false)]
144
- }
145
-
146
- func fromBase64(base64String: String?) throws -> Any? {
147
- if let base64String = base64String {
148
- if base64String.isEmpty {
149
- return nil
150
- }
151
-
152
- // Step 1: Decode the base64 string to a Data object
153
- guard let data = Data(base64Encoded: base64String) else {
154
- throw runtimeErrorWithPrefix("Invalid base64 string: \(base64String)")
155
- }
156
-
157
- // Step 2: Use NSKeyedUnarchiver to unarchive the data and create an HKQueryAnchor object
158
- do {
159
- let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
160
- unarchiver.requiresSecureCoding = true
161
- return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
162
- } catch {
163
- throw runtimeErrorWithPrefix(
164
- "Error recreating HKQueryAnchor object: \(error.localizedDescription)")
165
- }
13
+ /// Anchored query results come back with raw HKDeletedObjects; this package
14
+ /// serializes them into its own DeletedSample struct.
15
+ extension AnchoredQueryResponse {
16
+ var deletedSamples: [DeletedSample] {
17
+ return deletedObjects.map { serializeDeletedSample(sample: $0) }
166
18
  }
167
- return nil
168
- }
169
-
170
- func deserializeHKQueryAnchor(base64String: String?) throws -> HKQueryAnchor? {
171
- return try fromBase64(base64String: base64String) as? HKQueryAnchor
172
19
  }
173
20
 
174
21
  func initializeCategoryType(_ identifier: String) throws -> HKCategoryType {
175
- let identifier = HKCategoryTypeIdentifier(rawValue: identifier)
22
+ let identifier = hkCategoryTypeIdentifier(fromIdentifierName: identifier)
176
23
  if let sampleType = HKSampleType.categoryType(forIdentifier: identifier) {
177
24
  return sampleType
178
25
  }
@@ -245,51 +92,7 @@ func sampleTypeFrom(sampleTypeIdentifierWriteable: SampleTypeIdentifierWriteable
245
92
  }
246
93
 
247
94
  private func sampleTypeFromStringNullable(typeIdentifier: String) throws -> HKSampleType? {
248
- if typeIdentifier.starts(with: HKQuantityTypeIdentifier_PREFIX) {
249
- return try initializeQuantityType(typeIdentifier)
250
- }
251
-
252
- if typeIdentifier.starts(with: HKCategoryTypeIdentifier_PREFIX) {
253
- return try initializeCategoryType(typeIdentifier)
254
- }
255
-
256
- if typeIdentifier.starts(with: HKCorrelationTypeIdentifier_PREFIX) {
257
- return try initializeCorrelationType(typeIdentifier)
258
- }
259
-
260
- if typeIdentifier == HKWorkoutTypeIdentifier {
261
- return HKSampleType.workoutType()
262
- }
263
-
264
- if typeIdentifier == HKWorkoutRouteTypeIdentifier {
265
- return try initializeSeriesType(typeIdentifier)
266
- }
267
-
268
- if typeIdentifier == HKAudiogramTypeIdentifier {
269
- return HKObjectType.audiogramSampleType()
270
- }
271
-
272
- if typeIdentifier == HKDataTypeIdentifierHeartbeatSeries {
273
- return try initializeSeriesType(typeIdentifier)
274
- }
275
-
276
- if typeIdentifier == HKAudiogramTypeIdentifier {
277
- return HKSampleType.audiogramSampleType()
278
- }
279
-
280
- if typeIdentifier == HKElectrocardiogramType {
281
- return HKSampleType.electrocardiogramType()
282
- }
283
-
284
- #if compiler(>=6)
285
- if #available(iOS 18, *) {
286
- if typeIdentifier == HKStateOfMindTypeIdentifier {
287
- return HKObjectType.stateOfMindType()
288
- }
289
- }
290
- #endif
291
-
292
- return nil
95
+ return sampleType(fromIdentifier: typeIdentifier)
293
96
  }
294
97
 
295
98
  func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObjectType> {
@@ -305,14 +108,6 @@ func objectTypesFromArray(typeIdentifiers: [ObjectTypeIdentifier]) -> Set<HKObje
305
108
  return share
306
109
  }
307
110
 
308
- func initializeUUID(_ uuidString: String) throws -> UUID {
309
- if let uuid = UUID(uuidString: uuidString) {
310
- return uuid
311
- }
312
-
313
- throw runtimeErrorWithPrefix("Got invalid UUID: \(uuidString)")
314
- }
315
-
316
111
  func sampleTypesFromArray(typeIdentifiers: [SampleTypeIdentifier]) -> Set<HKSampleType> {
317
112
  return Set(
318
113
  typeIdentifiers.compactMap { typeIdentifier in
@@ -348,7 +143,7 @@ func objectTypeFrom(objectTypeIdentifier: ObjectTypeIdentifier) throws -> HKObje
348
143
  return sampleType
349
144
  }
350
145
 
351
- if typeIdentifier.starts(with: HKCharacteristicTypeIdentifier_PREFIX) {
146
+ if typeIdentifier.starts(with: characteristicTypeIdentifierPrefix) {
352
147
  let identifier = HKCharacteristicTypeIdentifier.init(rawValue: typeIdentifier)
353
148
  if let type = HKObjectType.characteristicType(forIdentifier: identifier) as HKObjectType? {
354
149
  return type
@@ -434,11 +229,11 @@ func anyMapToDictionary(_ anyMap: AnyMap) -> [String: Any] {
434
229
  }
435
230
 
436
231
  func runtimeErrorWithPrefix(_ withMessage: String) -> Error {
437
- return RuntimeError.error(withMessage: "[react-native-healthkit] \(withMessage)")
232
+ return makeRuntimeError(withMessage, prefix: logPrefix)
438
233
  }
439
234
 
440
235
  func warnWithPrefix(_ withMessage: String) {
441
- print("[react-native-healthkit] \(withMessage)")
236
+ logWarning(withMessage, prefix: logPrefix)
442
237
  }
443
238
 
444
239
  func buildStatisticsOptions(statistics: [StatisticsOptions], quantityType: HKQuantityType) -> HKStatisticsOptions {
@@ -7,12 +7,13 @@
7
7
 
8
8
  import HealthKit
9
9
  import NitroModules
10
+ import ReactNativeHealthkitCore
10
11
 
11
12
  #if compiler(>=6.2)
12
13
  @available(iOS 26.0, *)
13
14
  func deserializeHKMedicationConceptIdentifier(base64String: String?) throws
14
15
  -> HKHealthConceptIdentifier? {
15
- return try fromBase64(base64String: base64String) as? HKHealthConceptIdentifier
16
+ return try fromBase64(base64String: base64String, as: HKHealthConceptIdentifier.self)
16
17
  }
17
18
 
18
19
  @available(iOS 26.0, *)
@@ -50,7 +51,7 @@ import NitroModules
50
51
  var lookup: [String: (name: String, nickname: String?)] = [:]
51
52
  do {
52
53
  let q = HKUserAnnotatedMedicationQueryDescriptor()
53
- let medications = try await q.result(for: store)
54
+ let medications = try await q.result(for: healthStore)
54
55
  for med in medications {
55
56
  let medPredicate = HKQuery.predicateForMedicationDoseEvent(
56
57
  medicationConceptIdentifier: med.medication.identifier)
@@ -97,16 +98,16 @@ import NitroModules
97
98
  startDate: sample.startDate,
98
99
  endDate: sample.endDate,
99
100
  hasUndeterminedDuration: sample.hasUndeterminedDuration,
101
+ uuid: sample.uuid.uuidString,
102
+ sourceRevision: serializeSourceRevision(sample.sourceRevision),
103
+ device: serializeDevice(hkDevice: sample.device),
100
104
  metadata: {
101
105
  var meta = serializeMetadata(sample.metadata)
102
106
  if let nick = info?.nickname {
103
107
  meta.setString(key: "HKMedicationNickname", value: nick)
104
108
  }
105
109
  return meta
106
- }(),
107
- uuid: sample.uuid.uuidString,
108
- sourceRevision: serializeSourceRevision(sample.sourceRevision),
109
- device: serializeDevice(hkDevice: sample.device)
110
+ }()
110
111
  )
111
112
  }
112
113
 
@@ -152,7 +153,7 @@ import NitroModules
152
153
  if #available(iOS 26.0, *) {
153
154
  let q = HKUserAnnotatedMedicationQueryDescriptor()
154
155
 
155
- let medications = try await q.result(for: store)
156
+ let medications = try await q.result(for: healthStore)
156
157
 
157
158
  return try medications.compactMap({ medication in
158
159
  return try serializeMedication(medication: medication)
@@ -193,7 +194,7 @@ import NitroModules
193
194
  func requestMedicationsAuthorization() -> Promise<Bool> {
194
195
  return Promise.async {
195
196
  if #available(iOS 26.0, *) {
196
- try await store.requestPerObjectReadAuthorization(
197
+ try await healthStore.requestPerObjectReadAuthorization(
197
198
  for: .userAnnotatedMedicationType(), predicate: nil)
198
199
  return true
199
200
  } else {