@flomentumsolutions/capacitor-health-extended 0.0.11 → 0.0.13

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 CHANGED
@@ -112,6 +112,11 @@ This setup ensures your WebView will load HTTPS content securely and complies wi
112
112
  * [`showHealthConnectInPlayStore()`](#showhealthconnectinplaystore)
113
113
  * [`queryAggregated(...)`](#queryaggregated)
114
114
  * [`queryWorkouts(...)`](#queryworkouts)
115
+ * [`queryLatestSample(...)`](#querylatestsample)
116
+ * [`queryWeight()`](#queryweight)
117
+ * [`queryHeight()`](#queryheight)
118
+ * [`queryHeartRate()`](#queryheartrate)
119
+ * [`querySteps()`](#querysteps)
115
120
  * [Interfaces](#interfaces)
116
121
  * [Type Aliases](#type-aliases)
117
122
 
@@ -245,6 +250,75 @@ Query workouts
245
250
  --------------------
246
251
 
247
252
 
253
+ ### queryLatestSample(...)
254
+
255
+ ```typescript
256
+ queryLatestSample(request: { dataType: string; }) => Promise<QueryLatestSampleResponse>
257
+ ```
258
+
259
+ Query latest sample for a specific data type
260
+
261
+ | Param | Type |
262
+ | ------------- | ---------------------------------- |
263
+ | **`request`** | <code>{ dataType: string; }</code> |
264
+
265
+ **Returns:** <code>Promise&lt;<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>&gt;</code>
266
+
267
+ --------------------
268
+
269
+
270
+ ### queryWeight()
271
+
272
+ ```typescript
273
+ queryWeight() => Promise<QueryLatestSampleResponse>
274
+ ```
275
+
276
+ Query latest weight sample
277
+
278
+ **Returns:** <code>Promise&lt;<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>&gt;</code>
279
+
280
+ --------------------
281
+
282
+
283
+ ### queryHeight()
284
+
285
+ ```typescript
286
+ queryHeight() => Promise<QueryLatestSampleResponse>
287
+ ```
288
+
289
+ Query latest height sample
290
+
291
+ **Returns:** <code>Promise&lt;<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>&gt;</code>
292
+
293
+ --------------------
294
+
295
+
296
+ ### queryHeartRate()
297
+
298
+ ```typescript
299
+ queryHeartRate() => Promise<QueryLatestSampleResponse>
300
+ ```
301
+
302
+ Query latest heart rate sample
303
+
304
+ **Returns:** <code>Promise&lt;<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>&gt;</code>
305
+
306
+ --------------------
307
+
308
+
309
+ ### querySteps()
310
+
311
+ ```typescript
312
+ querySteps() => Promise<QueryLatestSampleResponse>
313
+ ```
314
+
315
+ Query latest steps sample
316
+
317
+ **Returns:** <code>Promise&lt;<a href="#querylatestsampleresponse">QueryLatestSampleResponse</a>&gt;</code>
318
+
319
+ --------------------
320
+
321
+
248
322
  ### Interfaces
249
323
 
250
324
 
@@ -342,6 +416,17 @@ Query workouts
342
416
  | **`includeSteps`** | <code>boolean</code> |
343
417
 
344
418
 
419
+ #### QueryLatestSampleResponse
420
+
421
+ | Prop | Type |
422
+ | --------------- | ------------------- |
423
+ | **`value`** | <code>number</code> |
424
+ | **`systolic`** | <code>number</code> |
425
+ | **`diastolic`** | <code>number</code> |
426
+ | **`timestamp`** | <code>number</code> |
427
+ | **`unit`** | <code>string</code> |
428
+
429
+
345
430
  ### Type Aliases
346
431
 
347
432
 
@@ -33,7 +33,7 @@ import androidx.core.net.toUri
33
33
 
34
34
  enum class CapHealthPermission {
35
35
  READ_STEPS, READ_WORKOUTS, READ_HEART_RATE, READ_ACTIVE_CALORIES, READ_TOTAL_CALORIES, READ_DISTANCE, READ_WEIGHT
36
- , READ_HRV, READ_BLOOD_PRESSURE;
36
+ , READ_HRV, READ_BLOOD_PRESSURE, READ_HEIGHT, READ_ROUTE, READ_MINDFULNESS;
37
37
 
38
38
  companion object {
39
39
  fun from(s: String): CapHealthPermission? {
@@ -58,7 +58,9 @@ enum class CapHealthPermission {
58
58
  Permission(alias = "READ_TOTAL_CALORIES", strings = ["android.permission.health.READ_TOTAL_CALORIES_BURNED"]),
59
59
  Permission(alias = "READ_HEART_RATE", strings = ["android.permission.health.READ_HEART_RATE"]),
60
60
  Permission(alias = "READ_HRV", strings = ["android.permission.health.READ_HEART_RATE_VARIABILITY"]),
61
- Permission(alias = "READ_BLOOD_PRESSURE", strings = ["android.permission.health.READ_BLOOD_PRESSURE"])
61
+ Permission(alias = "READ_BLOOD_PRESSURE", strings = ["android.permission.health.READ_BLOOD_PRESSURE"]),
62
+ Permission(alias = "READ_ROUTE", strings = ["android.permission.health.READ_EXERCISE"]),
63
+ Permission(alias = "READ_MINDFULNESS", strings = ["android.permission.health.READ_SLEEP"])
62
64
  ]
63
65
  )
64
66
 
@@ -76,12 +78,15 @@ class HealthPlugin : Plugin() {
76
78
  CapHealthPermission.READ_STEPS to HealthPermission.getReadPermission(StepsRecord::class),
77
79
  CapHealthPermission.READ_HEART_RATE to HealthPermission.getReadPermission(HeartRateRecord::class),
78
80
  CapHealthPermission.READ_WEIGHT to HealthPermission.getReadPermission(WeightRecord::class),
81
+ CapHealthPermission.READ_HEIGHT to HealthPermission.getReadPermission(HeightRecord::class),
79
82
  CapHealthPermission.READ_ACTIVE_CALORIES to HealthPermission.getReadPermission(ActiveCaloriesBurnedRecord::class),
80
83
  CapHealthPermission.READ_TOTAL_CALORIES to HealthPermission.getReadPermission(TotalCaloriesBurnedRecord::class),
81
84
  CapHealthPermission.READ_DISTANCE to HealthPermission.getReadPermission(DistanceRecord::class),
82
85
  CapHealthPermission.READ_WORKOUTS to HealthPermission.getReadPermission(ExerciseSessionRecord::class),
83
86
  CapHealthPermission.READ_HRV to HealthPermission.getReadPermission(HeartRateVariabilitySdnnRecord::class),
84
- CapHealthPermission.READ_BLOOD_PRESSURE to HealthPermission.getReadPermission(BloodPressureRecord::class)
87
+ CapHealthPermission.READ_BLOOD_PRESSURE to HealthPermission.getReadPermission(BloodPressureRecord::class),
88
+ CapHealthPermission.READ_ROUTE to HealthPermission.getReadPermission(ExerciseSessionRecord::class),
89
+ CapHealthPermission.READ_MINDFULNESS to HealthPermission.getReadPermission(SleepSessionRecord::class)
85
90
  )
86
91
 
87
92
  override fun load() {
@@ -239,6 +244,12 @@ class HealthPlugin : Plugin() {
239
244
  }
240
245
  }
241
246
 
247
+ // Alias for iOS compatibility
248
+ @PluginMethod
249
+ fun openAppleHealthSettings(call: PluginCall) {
250
+ openHealthConnectSettings(call)
251
+ }
252
+
242
253
  // Open the Google Play Store to install Health Connect
243
254
  @PluginMethod
244
255
  fun showHealthConnectInPlayStore(call: PluginCall) {
@@ -283,9 +294,13 @@ class HealthPlugin : Plugin() {
283
294
  val result = when (dataType) {
284
295
  "heart-rate" -> readLatestHeartRate()
285
296
  "weight" -> readLatestWeight()
297
+ "height" -> readLatestHeight()
286
298
  "steps" -> readLatestSteps()
287
299
  "hrv" -> readLatestHrv()
288
300
  "blood-pressure" -> readLatestBloodPressure()
301
+ "distance" -> readLatestDistance()
302
+ "active-calories" -> readLatestActiveCalories()
303
+ "total-calories" -> readLatestTotalCalories()
289
304
  else -> {
290
305
  call.reject("Unsupported data type: $dataType")
291
306
  return@launch
@@ -299,6 +314,31 @@ class HealthPlugin : Plugin() {
299
314
  }
300
315
  }
301
316
 
317
+ // Convenience methods for specific data types
318
+ @PluginMethod
319
+ fun queryWeight(call: PluginCall) {
320
+ call.put("dataType", "weight")
321
+ queryLatestSample(call)
322
+ }
323
+
324
+ @PluginMethod
325
+ fun queryHeight(call: PluginCall) {
326
+ call.put("dataType", "height")
327
+ queryLatestSample(call)
328
+ }
329
+
330
+ @PluginMethod
331
+ fun queryHeartRate(call: PluginCall) {
332
+ call.put("dataType", "heart-rate")
333
+ queryLatestSample(call)
334
+ }
335
+
336
+ @PluginMethod
337
+ fun querySteps(call: PluginCall) {
338
+ call.put("dataType", "steps")
339
+ queryLatestSample(call)
340
+ }
341
+
302
342
  private suspend fun readLatestHeartRate(): JSObject {
303
343
  if (!hasPermission(CapHealthPermission.READ_HEART_RATE)) {
304
344
  throw Exception("Permission for heart rate not granted")
@@ -313,8 +353,9 @@ class HealthPlugin : Plugin() {
313
353
 
314
354
  val lastSample = record.samples.lastOrNull()
315
355
  return JSObject().apply {
316
- put("timestamp", lastSample?.time?.toString() ?: "")
317
356
  put("value", lastSample?.beatsPerMinute ?: 0)
357
+ put("timestamp", (lastSample?.time?.epochSecond ?: 0) * 1000) // Convert to milliseconds like iOS
358
+ put("unit", "count/min")
318
359
  }
319
360
  }
320
361
 
@@ -330,8 +371,9 @@ class HealthPlugin : Plugin() {
330
371
  val result = healthConnectClient.readRecords(request)
331
372
  val record = result.records.firstOrNull() ?: throw Exception("No weight data found")
332
373
  return JSObject().apply {
333
- put("timestamp", record.time.toString())
334
374
  put("value", record.weight.inKilograms)
375
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
376
+ put("unit", "kg")
335
377
  }
336
378
  }
337
379
 
@@ -347,9 +389,9 @@ class HealthPlugin : Plugin() {
347
389
  val result = healthConnectClient.readRecords(request)
348
390
  val record = result.records.firstOrNull() ?: throw Exception("No step data found")
349
391
  return JSObject().apply {
350
- put("startDate", record.startTime.toString())
351
- put("endDate", record.endTime.toString())
352
392
  put("value", record.count)
393
+ put("timestamp", record.endTime.epochSecond * 1000) // Convert to milliseconds like iOS
394
+ put("unit", "count")
353
395
  }
354
396
  }
355
397
 
@@ -365,8 +407,9 @@ class HealthPlugin : Plugin() {
365
407
  val result = healthConnectClient.readRecords(request)
366
408
  val record = result.records.firstOrNull() ?: throw Exception("No HRV data found")
367
409
  return JSObject().apply {
368
- put("timestamp", record.time.toString())
369
410
  put("value", record.sdnnMillis)
411
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
412
+ put("unit", "ms")
370
413
  }
371
414
  }
372
415
 
@@ -382,9 +425,82 @@ class HealthPlugin : Plugin() {
382
425
  val result = healthConnectClient.readRecords(request)
383
426
  val record = result.records.firstOrNull() ?: throw Exception("No blood pressure data found")
384
427
  return JSObject().apply {
385
- put("timestamp", record.time.toString())
386
428
  put("systolic", record.systolic.inMillimetersOfMercury)
387
429
  put("diastolic", record.diastolic.inMillimetersOfMercury)
430
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
431
+ put("unit", "mmHg")
432
+ }
433
+ }
434
+
435
+ private suspend fun readLatestHeight(): JSObject {
436
+ if (!hasPermission(CapHealthPermission.READ_HEIGHT)) {
437
+ throw Exception("Permission for height not granted")
438
+ }
439
+ val request = ReadRecordsRequest(
440
+ recordType = HeightRecord::class,
441
+ timeRangeFilter = TimeRangeFilter.after(Instant.EPOCH),
442
+ pageSize = 1
443
+ )
444
+ val result = healthConnectClient.readRecords(request)
445
+ val record = result.records.firstOrNull() ?: throw Exception("No height data found")
446
+ return JSObject().apply {
447
+ put("value", record.height.inMeters)
448
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
449
+ put("unit", "m")
450
+ }
451
+ }
452
+
453
+ private suspend fun readLatestDistance(): JSObject {
454
+ if (!hasPermission(CapHealthPermission.READ_DISTANCE)) {
455
+ throw Exception("Permission for distance not granted")
456
+ }
457
+ val request = ReadRecordsRequest(
458
+ recordType = DistanceRecord::class,
459
+ timeRangeFilter = TimeRangeFilter.after(Instant.EPOCH),
460
+ pageSize = 1
461
+ )
462
+ val result = healthConnectClient.readRecords(request)
463
+ val record = result.records.firstOrNull() ?: throw Exception("No distance data found")
464
+ return JSObject().apply {
465
+ put("value", record.distance.inMeters)
466
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
467
+ put("unit", "m")
468
+ }
469
+ }
470
+
471
+ private suspend fun readLatestActiveCalories(): JSObject {
472
+ if (!hasPermission(CapHealthPermission.READ_ACTIVE_CALORIES)) {
473
+ throw Exception("Permission for active calories not granted")
474
+ }
475
+ val request = ReadRecordsRequest(
476
+ recordType = ActiveCaloriesBurnedRecord::class,
477
+ timeRangeFilter = TimeRangeFilter.after(Instant.EPOCH),
478
+ pageSize = 1
479
+ )
480
+ val result = healthConnectClient.readRecords(request)
481
+ val record = result.records.firstOrNull() ?: throw Exception("No active calories data found")
482
+ return JSObject().apply {
483
+ put("value", record.activeCalories.inKilocalories)
484
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
485
+ put("unit", "kcal")
486
+ }
487
+ }
488
+
489
+ private suspend fun readLatestTotalCalories(): JSObject {
490
+ if (!hasPermission(CapHealthPermission.READ_TOTAL_CALORIES)) {
491
+ throw Exception("Permission for total calories not granted")
492
+ }
493
+ val request = ReadRecordsRequest(
494
+ recordType = TotalCaloriesBurnedRecord::class,
495
+ timeRangeFilter = TimeRangeFilter.after(Instant.EPOCH),
496
+ pageSize = 1
497
+ )
498
+ val result = healthConnectClient.readRecords(request)
499
+ val record = result.records.firstOrNull() ?: throw Exception("No total calories data found")
500
+ return JSObject().apply {
501
+ put("value", record.energy.inKilocalories)
502
+ put("timestamp", record.time.epochSecond * 1000) // Convert to milliseconds like iOS
503
+ put("unit", "kcal")
388
504
  }
389
505
  }
390
506
 
@@ -50,6 +50,29 @@ export interface HealthPlugin {
50
50
  * @param request
51
51
  */
52
52
  queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;
53
+ /**
54
+ * Query latest sample for a specific data type
55
+ * @param request
56
+ */
57
+ queryLatestSample(request: {
58
+ dataType: string;
59
+ }): Promise<QueryLatestSampleResponse>;
60
+ /**
61
+ * Query latest weight sample
62
+ */
63
+ queryWeight(): Promise<QueryLatestSampleResponse>;
64
+ /**
65
+ * Query latest height sample
66
+ */
67
+ queryHeight(): Promise<QueryLatestSampleResponse>;
68
+ /**
69
+ * Query latest heart rate sample
70
+ */
71
+ queryHeartRate(): Promise<QueryLatestSampleResponse>;
72
+ /**
73
+ * Query latest steps sample
74
+ */
75
+ querySteps(): Promise<QueryLatestSampleResponse>;
53
76
  }
54
77
  export declare type HealthPermission = 'READ_STEPS' | 'READ_WORKOUTS' | 'READ_ACTIVE_CALORIES' | 'READ_TOTAL_CALORIES' | 'READ_DISTANCE' | 'READ_WEIGHT' | 'READ_HEIGHT' | 'READ_HEART_RATE' | 'READ_ROUTE' | 'READ_MINDFULNESS' | 'READ_HRV' | 'READ_BLOOD_PRESSURE';
55
78
  export interface PermissionsRequest {
@@ -108,3 +131,10 @@ export interface AggregatedSample {
108
131
  endDate: string;
109
132
  value: number;
110
133
  }
134
+ export interface QueryLatestSampleResponse {
135
+ value?: number;
136
+ systolic?: number;
137
+ diastolic?: number;
138
+ timestamp: number;
139
+ unit: string;
140
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface HealthPlugin {\n /**\n * Checks if health API is available.\n * Android: If false is returned, the Google Health Connect app is probably not installed.\n * See showHealthConnectInPlayStore()\n *\n */\n isHealthAvailable(): Promise<{ available: boolean }>;\n\n /**\n * Android only: Returns for each given permission, if it was granted by the underlying health API\n * @param permissions permissions to query\n */\n checkHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Requests the permissions from the user.\n *\n * Android: Apps can ask only a few times for permissions, after that the user has to grant them manually in\n * the Health Connect app. See openHealthConnectSettings()\n *\n * iOS: If the permissions are already granted or denied, this method will just return without asking the user. In iOS\n * we can't really detect if a user granted or denied a permission. The return value reflects the assumption that all\n * permissions were granted.\n *\n * @param permissions permissions to request\n */\n requestHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Opens the apps settings, which is kind of wrong, because health permissions are configured under:\n * Settings > Apps > (Apple) Health > Access and Devices > [app-name]\n * But we can't go there directly.\n */\n openAppleHealthSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app in PlayStore\n */\n showHealthConnectInPlayStore(): Promise<void>;\n\n /**\n * Query aggregated data\n * @param request\n */\n queryAggregated(request: QueryAggregatedRequest): Promise<QueryAggregatedResponse>;\n\n /**\n * Query workouts\n * @param request\n */\n queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;\n}\n\nexport declare type HealthPermission =\n | 'READ_STEPS'\n | 'READ_WORKOUTS'\n | 'READ_ACTIVE_CALORIES'\n | 'READ_TOTAL_CALORIES'\n | 'READ_DISTANCE'\n | 'READ_WEIGHT'\n | 'READ_HEIGHT'\n | 'READ_HEART_RATE'\n | 'READ_ROUTE'\n | 'READ_MINDFULNESS'\n | 'READ_HRV'\n | 'READ_BLOOD_PRESSURE';\n\nexport interface PermissionsRequest {\n permissions: HealthPermission[];\n}\n\nexport interface PermissionResponse {\n permissions: { [key: string]: boolean }[];\n}\n\nexport interface QueryWorkoutRequest {\n startDate: string;\n endDate: string;\n includeHeartRate: boolean;\n includeRoute: boolean;\n includeSteps: boolean;\n}\n\nexport interface HeartRateSample {\n timestamp: string;\n bpm: number;\n}\n\nexport interface RouteSample {\n timestamp: string;\n lat: number;\n lng: number;\n alt?: number;\n}\n\nexport interface QueryWorkoutResponse {\n workouts: Workout[];\n}\n\nexport interface Workout {\n startDate: string;\n endDate: string;\n workoutType: string;\n sourceName: string;\n id?: string;\n duration: number;\n distance?: number;\n steps?: number;\n calories: number;\n sourceBundleId: string;\n route?: RouteSample[];\n heartRate?: HeartRateSample[];\n}\n\nexport interface QueryAggregatedRequest {\n startDate: string;\n endDate: string;\n dataType: 'steps' | 'active-calories' | 'mindfulness' | 'hrv' | 'blood-pressure';\n bucket: string;\n}\n\nexport interface QueryAggregatedResponse {\n aggregatedData: AggregatedSample[];\n}\n\nexport interface AggregatedSample {\n startDate: string;\n endDate: string;\n value: number;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface HealthPlugin {\n /**\n * Checks if health API is available.\n * Android: If false is returned, the Google Health Connect app is probably not installed.\n * See showHealthConnectInPlayStore()\n *\n */\n isHealthAvailable(): Promise<{ available: boolean }>;\n\n /**\n * Android only: Returns for each given permission, if it was granted by the underlying health API\n * @param permissions permissions to query\n */\n checkHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Requests the permissions from the user.\n *\n * Android: Apps can ask only a few times for permissions, after that the user has to grant them manually in\n * the Health Connect app. See openHealthConnectSettings()\n *\n * iOS: If the permissions are already granted or denied, this method will just return without asking the user. In iOS\n * we can't really detect if a user granted or denied a permission. The return value reflects the assumption that all\n * permissions were granted.\n *\n * @param permissions permissions to request\n */\n requestHealthPermissions(permissions: PermissionsRequest): Promise<PermissionResponse>;\n\n /**\n * Opens the apps settings, which is kind of wrong, because health permissions are configured under:\n * Settings > Apps > (Apple) Health > Access and Devices > [app-name]\n * But we can't go there directly.\n */\n openAppleHealthSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Opens the Google Health Connect app in PlayStore\n */\n showHealthConnectInPlayStore(): Promise<void>;\n\n /**\n * Query aggregated data\n * @param request\n */\n queryAggregated(request: QueryAggregatedRequest): Promise<QueryAggregatedResponse>;\n\n /**\n * Query workouts\n * @param request\n */\n queryWorkouts(request: QueryWorkoutRequest): Promise<QueryWorkoutResponse>;\n\n /**\n * Query latest sample for a specific data type\n * @param request\n */\n queryLatestSample(request: { dataType: string }): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest weight sample\n */\n queryWeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest height sample\n */\n queryHeight(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest heart rate sample\n */\n queryHeartRate(): Promise<QueryLatestSampleResponse>;\n\n /**\n * Query latest steps sample\n */\n querySteps(): Promise<QueryLatestSampleResponse>;\n}\n\nexport declare type HealthPermission =\n | 'READ_STEPS'\n | 'READ_WORKOUTS'\n | 'READ_ACTIVE_CALORIES'\n | 'READ_TOTAL_CALORIES'\n | 'READ_DISTANCE'\n | 'READ_WEIGHT'\n | 'READ_HEIGHT'\n | 'READ_HEART_RATE'\n | 'READ_ROUTE'\n | 'READ_MINDFULNESS'\n | 'READ_HRV'\n | 'READ_BLOOD_PRESSURE';\n\nexport interface PermissionsRequest {\n permissions: HealthPermission[];\n}\n\nexport interface PermissionResponse {\n permissions: { [key: string]: boolean }[];\n}\n\nexport interface QueryWorkoutRequest {\n startDate: string;\n endDate: string;\n includeHeartRate: boolean;\n includeRoute: boolean;\n includeSteps: boolean;\n}\n\nexport interface HeartRateSample {\n timestamp: string;\n bpm: number;\n}\n\nexport interface RouteSample {\n timestamp: string;\n lat: number;\n lng: number;\n alt?: number;\n}\n\nexport interface QueryWorkoutResponse {\n workouts: Workout[];\n}\n\nexport interface Workout {\n startDate: string;\n endDate: string;\n workoutType: string;\n sourceName: string;\n id?: string;\n duration: number;\n distance?: number;\n steps?: number;\n calories: number;\n sourceBundleId: string;\n route?: RouteSample[];\n heartRate?: HeartRateSample[];\n}\n\nexport interface QueryAggregatedRequest {\n startDate: string;\n endDate: string;\n dataType: 'steps' | 'active-calories' | 'mindfulness' | 'hrv' | 'blood-pressure';\n bucket: string;\n}\n\nexport interface QueryAggregatedResponse {\n aggregatedData: AggregatedSample[];\n}\n\nexport interface AggregatedSample {\n startDate: string;\n endDate: string;\n value: number;\n}\n\nexport interface QueryLatestSampleResponse {\n value?: number;\n systolic?: number;\n diastolic?: number;\n timestamp: number;\n unit: string;\n}\n"]}
@@ -17,7 +17,11 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
17
17
  CAPPluginMethod(name: "openAppleHealthSettings", returnType: CAPPluginReturnPromise),
18
18
  CAPPluginMethod(name: "queryAggregated", returnType: CAPPluginReturnPromise),
19
19
  CAPPluginMethod(name: "queryWorkouts", returnType: CAPPluginReturnPromise),
20
- CAPPluginMethod(name: "queryLatestSample", returnType: CAPPluginReturnPromise)
20
+ CAPPluginMethod(name: "queryLatestSample", returnType: CAPPluginReturnPromise),
21
+ CAPPluginMethod(name: "queryWeight", returnType: CAPPluginReturnPromise),
22
+ CAPPluginMethod(name: "queryHeight", returnType: CAPPluginReturnPromise),
23
+ CAPPluginMethod(name: "queryHeartRate", returnType: CAPPluginReturnPromise),
24
+ CAPPluginMethod(name: "querySteps", returnType: CAPPluginReturnPromise)
21
25
  ]
22
26
 
23
27
  let healthStore = HKHealthStore()
@@ -100,6 +104,8 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
100
104
  call.reject("Missing data type")
101
105
  return
102
106
  }
107
+
108
+ print("⚡️ [HealthPlugin] Querying latest sample for data type: \(dataTypeString)")
103
109
  // ---- Special handling for blood‑pressure correlation ----
104
110
  if dataTypeString == "blood-pressure" {
105
111
  guard let bpType = HKObjectType.correlationType(forIdentifier: .bloodPressure) else {
@@ -187,12 +193,14 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
187
193
 
188
194
  let query = HKSampleQuery(sampleType: type, predicate: predicate, limit: 1, sortDescriptors: [sortDescriptor]) { _, samples, error in
189
195
 
190
- print("Samples count for \(dataTypeString):", samples?.count ?? 0)
196
+ print("⚡️ [HealthPlugin] Query completed for \(dataTypeString): \(samples?.count ?? 0) samples, error: \(error?.localizedDescription ?? "none")")
191
197
 
192
198
  guard let quantitySample = samples?.first as? HKQuantitySample else {
193
199
  if let error = error {
200
+ print("⚡️ [HealthPlugin] Error fetching \(dataTypeString): \(error.localizedDescription)")
194
201
  call.reject("Error fetching latest sample", "NO_SAMPLE", error)
195
202
  } else {
203
+ print("⚡️ [HealthPlugin] No sample found for \(dataTypeString)")
196
204
  call.reject("No sample found", "NO_SAMPLE")
197
205
  }
198
206
  return
@@ -215,6 +223,8 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
215
223
  let value = quantitySample.quantity.doubleValue(for: unit)
216
224
  let timestamp = quantitySample.startDate.timeIntervalSince1970 * 1000
217
225
 
226
+ print("⚡️ [HealthPlugin] Successfully fetched \(dataTypeString): value=\(value), unit=\(unit.unitString)")
227
+
218
228
  call.resolve([
219
229
  "value": value,
220
230
  "timestamp": timestamp,
@@ -225,6 +235,30 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
225
235
  healthStore.execute(query)
226
236
  }
227
237
 
238
+ // Convenience methods for specific data types
239
+ @objc func queryWeight(_ call: CAPPluginCall) {
240
+ queryLatestSampleWithType(call, dataType: "weight")
241
+ }
242
+
243
+ @objc func queryHeight(_ call: CAPPluginCall) {
244
+ queryLatestSampleWithType(call, dataType: "height")
245
+ }
246
+
247
+ @objc func queryHeartRate(_ call: CAPPluginCall) {
248
+ queryLatestSampleWithType(call, dataType: "heart-rate")
249
+ }
250
+
251
+ @objc func querySteps(_ call: CAPPluginCall) {
252
+ queryLatestSampleWithType(call, dataType: "steps")
253
+ }
254
+
255
+ private func queryLatestSampleWithType(_ call: CAPPluginCall, dataType: String) {
256
+ let params = NSMutableDictionary(dictionary: call.options ?? [:])
257
+ params["dataType"] = dataType
258
+ let proxyCall = CAPPluginCall(callbackId: call.callbackId, options: params as? [String: Any], success: call.success, error: call.error)
259
+ queryLatestSample(proxyCall)
260
+ }
261
+
228
262
  @objc func openAppleHealthSettings(_ call: CAPPluginCall) {
229
263
  if let url = URL(string: UIApplication.openSettingsURLString) {
230
264
  DispatchQueue.main.async {
@@ -290,7 +324,7 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
290
324
  return [HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)].compactMap{$0}
291
325
  case "workouts":
292
326
  return [HKObjectType.workoutType()].compactMap{$0}
293
- case "heart-rate", "heartrate":
327
+ case "heart-rate", "heartrate", "heart_rate":
294
328
  return [HKObjectType.quantityType(forIdentifier: .heartRate)].compactMap{$0}
295
329
  case "route":
296
330
  return [HKSeriesType.workoutRoute()].compactMap{$0}
@@ -303,9 +337,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
303
337
  ].compactMap{$0}
304
338
  case "mindfulness":
305
339
  return [HKObjectType.categoryType(forIdentifier: .mindfulSession)!].compactMap{$0}
306
- case "hrv":
340
+ case "hrv", "heart_rate_variability_sdnn":
307
341
  return [HKObjectType.quantityType(forIdentifier: .heartRateVariabilitySDNN)].compactMap { $0 }
308
- case "blood-pressure", "bloodpressure":
342
+ case "blood-pressure", "bloodpressure", "blood_pressure_systolic", "blood_pressure_diastolic":
309
343
  return [
310
344
  HKObjectType.quantityType(forIdentifier: .bloodPressureSystolic),
311
345
  HKObjectType.quantityType(forIdentifier: .bloodPressureDiastolic)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flomentumsolutions/capacitor-health-extended",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
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",