@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.
- package/README.md +37 -3
- package/ReactNativeHealthkit.podspec +11 -7
- package/app.plugin.js +33 -94
- package/app.plugin.ts +29 -104
- package/ios/CategoryTypeModule.swift +1 -0
- package/ios/CharacteristicTypeModule.swift +7 -6
- package/ios/Constants.swift +9 -10
- package/ios/CoreModule.swift +77 -42
- package/ios/CorrelationTypeModule.swift +3 -2
- package/ios/ElectrocardiogramModule.swift +4 -3
- package/ios/HeartbeatSeriesModule.swift +4 -3
- package/ios/Helpers.swift +11 -216
- package/ios/MedicationModule.swift +9 -8
- package/ios/MemoryEstimation.swift +84 -0
- package/ios/PredicateHelpers.swift +16 -86
- package/ios/QuantityTypeModule.swift +37 -28
- package/ios/Serializers.swift +34 -70
- package/ios/SourceProxy.swift +15 -5
- package/ios/StateOfMindModule.swift +3 -2
- package/ios/WorkoutProxy.swift +31 -13
- package/ios/WorkoutsModule.swift +4 -3
- package/lib/commonjs/generated/healthkit.generated.js +15 -3
- package/lib/commonjs/healthkit.ios.js +6 -2
- package/lib/commonjs/healthkit.js +24 -79
- package/lib/commonjs/types/Auth.js +3 -18
- package/lib/commonjs/types/Background.js +2 -10
- package/lib/commonjs/types/QueryOptions.js +2 -17
- package/lib/module/generated/healthkit.generated.js +14 -2
- package/lib/module/healthkit.ios.js +4 -0
- package/lib/module/healthkit.js +22 -77
- package/lib/module/types/Auth.js +1 -18
- package/lib/module/types/Background.js +1 -10
- package/lib/module/types/QueryOptions.js +1 -17
- package/lib/typescript/generated/healthkit.generated.d.ts +20 -7
- package/lib/typescript/healthkit.d.ts +4 -2
- package/lib/typescript/healthkit.ios.d.ts +13 -9
- package/lib/typescript/specs/CoreModule.nitro.d.ts +22 -4
- package/lib/typescript/specs/SourceProxy.nitro.d.ts +2 -5
- package/lib/typescript/types/Auth.d.ts +1 -16
- package/lib/typescript/types/Background.d.ts +1 -9
- package/lib/typescript/types/Device.d.ts +1 -13
- package/lib/typescript/types/QuantityType.d.ts +3 -3
- package/lib/typescript/types/QueryOptions.d.ts +3 -37
- package/lib/typescript/types/Shared.d.ts +2 -25
- package/lib/typescript/types/Source.d.ts +1 -10
- package/lib/typescript/types/Subscriptions.d.ts +2 -3
- package/lib/typescript/types/Units.d.ts +1 -1
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.cpp +24 -16
- package/nitrogen/generated/ios/ReactNativeHealthkit-Swift-Cxx-Bridge.hpp +91 -12
- package/nitrogen/generated/ios/c++/HybridCategoryTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridCoreModuleSpecSwift.hpp +8 -0
- package/nitrogen/generated/ios/c++/HybridCorrelationTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridElectrocardiogramModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridHeartbeatSeriesModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridMedicationModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridQuantityTypeModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridStateOfMindModuleSpecSwift.hpp +8 -5
- package/nitrogen/generated/ios/c++/HybridWorkoutProxySpecSwift.hpp +3 -4
- package/nitrogen/generated/ios/c++/HybridWorkoutsModuleSpecSwift.hpp +6 -3
- package/nitrogen/generated/ios/swift/CategorySample.swift +8 -8
- package/nitrogen/generated/ios/swift/CategoryTypeIdentifier.swift +8 -0
- package/nitrogen/generated/ios/swift/CategoryTypeIdentifierWriteable.swift +8 -0
- package/nitrogen/generated/ios/swift/CorrelationSample.swift +8 -8
- package/nitrogen/generated/ios/swift/ElectrocardiogramSample.swift +8 -8
- package/nitrogen/generated/ios/swift/Func_void_std__unordered_map_std__string__std__chrono__system_clock__time_point_.swift +54 -0
- package/nitrogen/generated/ios/swift/HeartbeatSeriesSample.swift +8 -8
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridCoreModuleSpec_cxx.swift +25 -0
- package/nitrogen/generated/ios/swift/MedicationDoseEvent.swift +8 -8
- package/nitrogen/generated/ios/swift/ObjectTypeIdentifier.swift +12 -4
- package/nitrogen/generated/ios/swift/QuantitySample.swift +8 -8
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifier.swift +4 -4
- package/nitrogen/generated/ios/swift/QuantityTypeIdentifierWriteable.swift +4 -4
- package/nitrogen/generated/ios/swift/QueryStatisticsResponse.swift +6 -13
- package/nitrogen/generated/ios/swift/QueryStatisticsResponseFromSingleSource.swift +4 -11
- package/nitrogen/generated/ios/swift/SampleTypeIdentifier.swift +12 -4
- package/nitrogen/generated/ios/swift/SampleTypeIdentifierWriteable.swift +12 -4
- package/nitrogen/generated/ios/swift/SourceRevision.swift +4 -11
- package/nitrogen/generated/ios/swift/StateOfMindSample.swift +8 -8
- package/nitrogen/generated/ios/swift/WorkoutActivityType.swift +8 -0
- package/nitrogen/generated/shared/c++/CategorySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/CategoryTypeIdentifier.hpp +67 -59
- package/nitrogen/generated/shared/c++/CategoryTypeIdentifierWriteable.hpp +67 -59
- package/nitrogen/generated/shared/c++/CorrelationSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/ElectrocardiogramSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HeartbeatSeriesSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridCoreModuleSpec.hpp +3 -1
- package/nitrogen/generated/shared/c++/MedicationDoseEvent.hpp +7 -7
- package/nitrogen/generated/shared/c++/ObjectTypeIdentifier.hpp +206 -198
- package/nitrogen/generated/shared/c++/QuantitySample.hpp +7 -7
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifier.hpp +73 -73
- package/nitrogen/generated/shared/c++/QuantityTypeIdentifierWriteable.hpp +73 -73
- package/nitrogen/generated/shared/c++/QueryStatisticsResponse.hpp +8 -9
- package/nitrogen/generated/shared/c++/QueryStatisticsResponseFromSingleSource.hpp +8 -9
- package/nitrogen/generated/shared/c++/SampleTypeIdentifier.hpp +199 -191
- package/nitrogen/generated/shared/c++/SampleTypeIdentifierWriteable.hpp +193 -185
- package/nitrogen/generated/shared/c++/SourceRevision.hpp +8 -9
- package/nitrogen/generated/shared/c++/StateOfMindSample.hpp +7 -7
- package/nitrogen/generated/shared/c++/WorkoutActivityType.hpp +4 -0
- package/package.json +8 -3
- package/src/generated/healthkit-schema.json +53 -8
- package/src/generated/healthkit.generated.ts +22 -6
- package/src/healthkit.ios.ts +6 -0
- package/src/healthkit.ts +32 -84
- package/src/specs/CoreModule.nitro.ts +25 -4
- package/src/specs/SourceProxy.nitro.ts +2 -4
- package/src/test-setup.ts +1 -0
- package/src/type-tests/generated-typing.ts +30 -8
- package/src/types/Auth.ts +4 -17
- package/src/types/Background.ts +1 -9
- package/src/types/Device.ts +1 -13
- package/src/types/QuantityType.ts +3 -3
- package/src/types/QueryOptions.ts +7 -65
- package/src/types/Shared.ts +7 -28
- package/src/types/Source.ts +1 -11
- package/src/types/Subscriptions.ts +2 -3
- package/src/types/Units.ts +1 -1
- package/ios/BackgroundDeliveryManager.swift +0 -277
- package/ios/Bridge.h +0 -10
- package/ios/ExceptionCatcher.h +0 -5
- package/ios/ExceptionCatcher.mm +0 -32
- package/ios/module.modulemap +0 -5
package/ios/CoreModule.swift
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
}
|
|
179
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
}
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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
|
|
232
|
+
return makeRuntimeError(withMessage, prefix: logPrefix)
|
|
438
233
|
}
|
|
439
234
|
|
|
440
235
|
func warnWithPrefix(_ withMessage: String) {
|
|
441
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
197
|
+
try await healthStore.requestPerObjectReadAuthorization(
|
|
197
198
|
for: .userAnnotatedMedicationType(), predicate: nil)
|
|
198
199
|
return true
|
|
199
200
|
} else {
|