@capgo/capacitor-health 8.6.7 → 8.7.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 +78 -20
- package/android/src/main/java/app/capgo/plugin/health/HealthManager.kt +57 -4
- package/android/src/main/java/app/capgo/plugin/health/HealthPlugin.kt +21 -6
- package/dist/docs.json +29 -1
- package/dist/esm/definitions.d.ts +44 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/HealthPlugin/HealthPlugin.swift +5 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,6 +231,55 @@ await Health.requestAuthorization({
|
|
|
231
231
|
});
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
+
### Reading data older than 30 days (Android)
|
|
235
|
+
|
|
236
|
+
On Android, Health Connect caps reads to roughly the **last 30 days** unless your app holds the
|
|
237
|
+
`android.permission.health.READ_HEALTH_DATA_HISTORY` permission. To read older history, set
|
|
238
|
+
`requestHistoryAccess: true` on your normal `requestAuthorization()` call. The permission then
|
|
239
|
+
appears in the first-time Health Connect permission sheet alongside your data-type permissions,
|
|
240
|
+
instead of forcing users to grant it manually in Health Connect settings.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
const status = await Health.requestAuthorization({
|
|
244
|
+
read: ['steps', 'heartRate'],
|
|
245
|
+
write: [],
|
|
246
|
+
requestHistoryAccess: true,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// On Android, whether the history permission was granted:
|
|
250
|
+
console.log(status.historyAccessAuthorized); // true | false
|
|
251
|
+
// And whether the provider supports it at all:
|
|
252
|
+
console.log(status.historyAccessAvailable); // true | false
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The result is reported back as the top-level `historyAccessAuthorized` boolean (present only when
|
|
256
|
+
`requestHistoryAccess` was set). The same option works with `checkAuthorization()`.
|
|
257
|
+
|
|
258
|
+
The `READ_HEALTH_DATA_HISTORY` permission only exists on sufficiently new Health Connect providers
|
|
259
|
+
(Android 14 extension 13+ or Health Connect APK 171302+). On an older but otherwise supported
|
|
260
|
+
provider the permission can never be granted, so the plugin checks
|
|
261
|
+
[feature availability](https://developer.android.com/health-and-fitness/health-connect/features/availability)
|
|
262
|
+
and **silently skips** the history permission when it is unavailable — your normal read/write scopes
|
|
263
|
+
are still requested as usual. In that case the status reports `historyAccessAvailable: false` (and
|
|
264
|
+
`historyAccessAuthorized: false`), which lets you distinguish "the device can't do this" from "the
|
|
265
|
+
user denied it" and avoid re-prompting. `historyAccessAvailable` is omitted unless
|
|
266
|
+
`requestHistoryAccess` was set, and always omitted on iOS.
|
|
267
|
+
|
|
268
|
+
You must **also** declare the permission in your app's `AndroidManifest.xml`:
|
|
269
|
+
|
|
270
|
+
```xml
|
|
271
|
+
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" />
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Health Connect only shows permissions that are both declared in the manifest and passed to the
|
|
275
|
+
permission request, so this declaration is required for `requestHistoryAccess` to take effect. On
|
|
276
|
+
iOS, HealthKit has no equivalent permission and no 30-day read cap, so `requestHistoryAccess` is
|
|
277
|
+
ignored and `historyAccessAuthorized` is omitted from the returned status.
|
|
278
|
+
|
|
279
|
+
See Google's documentation on
|
|
280
|
+
[reading historical Health Connect data](https://developer.android.com/health-and-fitness/guides/health-connect/develop/read-data#read-restriction)
|
|
281
|
+
for more details.
|
|
282
|
+
|
|
234
283
|
**Pagination example:** Use the `anchor` parameter to paginate through workout results:
|
|
235
284
|
|
|
236
285
|
```ts
|
|
@@ -373,6 +422,11 @@ requestAuthorization(options: AuthorizationOptions) => Promise<AuthorizationStat
|
|
|
373
422
|
|
|
374
423
|
Requests read/write access to the provided data types.
|
|
375
424
|
|
|
425
|
+
Set `requestHistoryAccess: true` to additionally request Android's
|
|
426
|
+
`READ_HEALTH_DATA_HISTORY` permission in the same Health Connect permission sheet
|
|
427
|
+
(see {@link <a href="#authorizationoptions">AuthorizationOptions.requestHistoryAccess</a>}). The granted/denied status is
|
|
428
|
+
reported back as `historyAccessAuthorized` on the result.
|
|
429
|
+
|
|
376
430
|
| Param | Type |
|
|
377
431
|
| ------------- | --------------------------------------------------------------------- |
|
|
378
432
|
| **`options`** | <code><a href="#authorizationoptions">AuthorizationOptions</a></code> |
|
|
@@ -531,20 +585,23 @@ Supported on iOS (HealthKit) and Android (Health Connect).
|
|
|
531
585
|
|
|
532
586
|
#### AuthorizationStatus
|
|
533
587
|
|
|
534
|
-
| Prop
|
|
535
|
-
|
|
|
536
|
-
| **`readAuthorized`**
|
|
537
|
-
| **`readDenied`**
|
|
538
|
-
| **`writeAuthorized`**
|
|
539
|
-
| **`writeDenied`**
|
|
588
|
+
| Prop | Type | Description |
|
|
589
|
+
| ----------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
590
|
+
| **`readAuthorized`** | <code>HealthDataType[]</code> | |
|
|
591
|
+
| **`readDenied`** | <code>HealthDataType[]</code> | |
|
|
592
|
+
| **`writeAuthorized`** | <code>HealthDataType[]</code> | |
|
|
593
|
+
| **`writeDenied`** | <code>HealthDataType[]</code> | |
|
|
594
|
+
| **`historyAccessAuthorized`** | <code>boolean</code> | Android only: whether the `READ_HEALTH_DATA_HISTORY` permission is granted. Only present when `requestHistoryAccess` was set on the request; omitted otherwise and always omitted on iOS. Always `false` when `historyAccessAvailable` is `false`, since an unsupported provider can never grant the permission. |
|
|
595
|
+
| **`historyAccessAvailable`** | <code>boolean</code> | Android only: whether the connected Health Connect provider supports the `READ_HEALTH_DATA_HISTORY` permission at all. Only present when `requestHistoryAccess` was set on the request; omitted otherwise and always omitted on iOS. `false` means the provider is too old (pre Android 14 extension 13 / Health Connect APK 171302) to ever grant history access — distinct from the user simply denying it. Use it to avoid re-prompting and to message the user that history access is unavailable on their device. |
|
|
540
596
|
|
|
541
597
|
|
|
542
598
|
#### AuthorizationOptions
|
|
543
599
|
|
|
544
|
-
| Prop
|
|
545
|
-
|
|
|
546
|
-
| **`read`**
|
|
547
|
-
| **`write`**
|
|
600
|
+
| Prop | Type | Description |
|
|
601
|
+
| -------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
602
|
+
| **`read`** | <code>HealthDataType[]</code> | Data types that should be readable after authorization. |
|
|
603
|
+
| **`write`** | <code>HealthDataType[]</code> | Data types that should be writable after authorization. |
|
|
604
|
+
| **`requestHistoryAccess`** | <code>boolean</code> | Android only: also request the `READ_HEALTH_DATA_HISTORY` permission in the same Health Connect permission sheet. Without it, Health Connect caps reads to roughly the last 30 days; granting it lets you read older data. The consuming app must also declare the permission in its `AndroidManifest.xml`: `<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" />` The permission only exists on sufficiently new Health Connect providers (Android 14 extension 13+ or Health Connect APK 171302+). On older but otherwise supported providers it is silently skipped — the normal read/write scopes are still requested — and the returned status reports `historyAccessAvailable: false`. Ignored on iOS (HealthKit has no equivalent permission and no 30-day read cap). |
|
|
548
605
|
|
|
549
606
|
|
|
550
607
|
#### ReadSamplesResult
|
|
@@ -599,16 +656,17 @@ Stage-level sleep segment emitted for sleep samples when platform data is availa
|
|
|
599
656
|
|
|
600
657
|
#### WriteSampleOptions
|
|
601
658
|
|
|
602
|
-
| Prop
|
|
603
|
-
|
|
|
604
|
-
| **`dataType`**
|
|
605
|
-
| **`value`**
|
|
606
|
-
| **`unit`**
|
|
607
|
-
| **`startDate`**
|
|
608
|
-
| **`endDate`**
|
|
609
|
-
| **`metadata`**
|
|
610
|
-
| **`
|
|
611
|
-
| **`
|
|
659
|
+
| Prop | Type | Description |
|
|
660
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
661
|
+
| **`dataType`** | <code><a href="#healthdatatype">HealthDataType</a></code> | |
|
|
662
|
+
| **`value`** | <code>number</code> | |
|
|
663
|
+
| **`unit`** | <code><a href="#healthunit">HealthUnit</a></code> | Optional unit override. If omitted, the default unit for the data type is used (count for `steps`, meter for `distance`, kilocalorie for `calories`, bpm for `heartRate`, kilogram for `weight`). |
|
|
664
|
+
| **`startDate`** | <code>string</code> | ISO 8601 start date for the sample. Defaults to now. |
|
|
665
|
+
| **`endDate`** | <code>string</code> | ISO 8601 end date for the sample. Defaults to startDate. |
|
|
666
|
+
| **`metadata`** | <code><a href="#record">Record</a><string, string></code> | Metadata key-value pairs forwarded to the native APIs where supported. |
|
|
667
|
+
| **`mindfulnessSessionType`** | <code>'meditation' \| 'unknown' \| 'breathing' \| 'music' \| 'movement' \| 'unguided'</code> | Android mindfulness session type. Defaults to 'meditation' when dataType is 'mindfulness'. |
|
|
668
|
+
| **`systolic`** | <code>number</code> | For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. |
|
|
669
|
+
| **`diastolic`** | <code>number</code> | For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. |
|
|
612
670
|
|
|
613
671
|
|
|
614
672
|
#### QueryWorkoutsResult
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package app.capgo.plugin.health
|
|
2
2
|
|
|
3
3
|
import androidx.health.connect.client.HealthConnectClient
|
|
4
|
+
import androidx.health.connect.client.HealthConnectFeatures
|
|
4
5
|
import androidx.health.connect.client.aggregate.AggregationResult
|
|
5
6
|
import androidx.health.connect.client.feature.ExperimentalMindfulnessSessionApi
|
|
6
7
|
import androidx.health.connect.client.permission.HealthPermission
|
|
@@ -56,20 +57,49 @@ class HealthManager {
|
|
|
56
57
|
|
|
57
58
|
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Whether the connected Health Connect provider supports the
|
|
62
|
+
* READ_HEALTH_DATA_HISTORY permission. The permission only exists on sufficiently
|
|
63
|
+
* new providers (Android 14 extension 13+ or Health Connect APK 171302+); on older
|
|
64
|
+
* but otherwise supported providers it can never be granted, so it must not be added
|
|
65
|
+
* to a permission request (it would never appear in getGrantedPermissions() and would
|
|
66
|
+
* leave the request flow permanently unsatisfiable).
|
|
67
|
+
*
|
|
68
|
+
* See https://developer.android.com/health-and-fitness/health-connect/features/availability
|
|
69
|
+
*/
|
|
70
|
+
fun isHistoryAccessAvailable(client: HealthConnectClient): Boolean =
|
|
71
|
+
client.features.getFeatureStatus(
|
|
72
|
+
HealthConnectFeatures.FEATURE_READ_HEALTH_DATA_HISTORY
|
|
73
|
+
) == HealthConnectFeatures.FEATURE_STATUS_AVAILABLE
|
|
74
|
+
|
|
75
|
+
fun permissionsFor(
|
|
76
|
+
readTypes: Collection<HealthDataType>,
|
|
77
|
+
writeTypes: Collection<HealthDataType>,
|
|
78
|
+
includeWorkouts: Boolean = false,
|
|
79
|
+
includeHistoryAccess: Boolean = false
|
|
80
|
+
): Set<String> = buildSet {
|
|
60
81
|
readTypes.forEach { add(it.readPermission) }
|
|
61
82
|
writeTypes.forEach { add(it.writePermission) }
|
|
62
83
|
// Include workout read permission if explicitly requested
|
|
63
84
|
if (includeWorkouts) {
|
|
64
85
|
add(HealthPermission.getReadPermission(ExerciseSessionRecord::class))
|
|
65
86
|
}
|
|
87
|
+
// Include history-access permission if explicitly requested so reads can
|
|
88
|
+
// exceed Health Connect's default 30-day window. Requires the consumer to
|
|
89
|
+
// declare READ_HEALTH_DATA_HISTORY in their AndroidManifest. Callers must
|
|
90
|
+
// gate this on isHistoryAccessAvailable(): adding the permission on a provider
|
|
91
|
+
// that does not support it makes the request flow permanently unsatisfiable.
|
|
92
|
+
if (includeHistoryAccess) {
|
|
93
|
+
add(READ_HEALTH_DATA_HISTORY_PERMISSION)
|
|
94
|
+
}
|
|
66
95
|
}
|
|
67
96
|
|
|
68
97
|
suspend fun authorizationStatus(
|
|
69
98
|
client: HealthConnectClient,
|
|
70
99
|
readTypes: Collection<HealthDataType>,
|
|
71
100
|
writeTypes: Collection<HealthDataType>,
|
|
72
|
-
includeWorkouts: Boolean = false
|
|
101
|
+
includeWorkouts: Boolean = false,
|
|
102
|
+
includeHistoryAccess: Boolean = false
|
|
73
103
|
): JSObject {
|
|
74
104
|
val granted = client.permissionController.getGrantedPermissions()
|
|
75
105
|
|
|
@@ -108,6 +138,18 @@ class HealthManager {
|
|
|
108
138
|
put("readDenied", readDenied)
|
|
109
139
|
put("writeAuthorized", writeAuthorized)
|
|
110
140
|
put("writeDenied", writeDenied)
|
|
141
|
+
// Report history-access status as top-level flags, only when it was requested.
|
|
142
|
+
// historyAccessAvailable distinguishes "the provider is too old to ever grant
|
|
143
|
+
// this" from "the user denied it"; historyAccessAuthorized is necessarily false
|
|
144
|
+
// when the feature is unavailable.
|
|
145
|
+
if (includeHistoryAccess) {
|
|
146
|
+
val historyAccessAvailable = isHistoryAccessAvailable(client)
|
|
147
|
+
put("historyAccessAvailable", historyAccessAvailable)
|
|
148
|
+
put(
|
|
149
|
+
"historyAccessAuthorized",
|
|
150
|
+
historyAccessAvailable && granted.contains(READ_HEALTH_DATA_HISTORY_PERMISSION)
|
|
151
|
+
)
|
|
152
|
+
}
|
|
111
153
|
}
|
|
112
154
|
}
|
|
113
155
|
|
|
@@ -414,7 +456,8 @@ class HealthManager {
|
|
|
414
456
|
endTime: Instant,
|
|
415
457
|
metadata: Map<String, String>?,
|
|
416
458
|
systolic: Double?,
|
|
417
|
-
diastolic: Double
|
|
459
|
+
diastolic: Double?,
|
|
460
|
+
mindfulnessSessionType: String?
|
|
418
461
|
) {
|
|
419
462
|
val recordMetadata = Metadata.manualEntry()
|
|
420
463
|
|
|
@@ -635,7 +678,7 @@ class HealthManager {
|
|
|
635
678
|
endTime = endTime,
|
|
636
679
|
endZoneOffset = zoneOffset(endTime),
|
|
637
680
|
metadata = recordMetadata,
|
|
638
|
-
mindfulnessSessionType =
|
|
681
|
+
mindfulnessSessionType = mindfulnessSessionType.toMindfulnessSessionType()
|
|
639
682
|
)
|
|
640
683
|
client.insertRecords(listOf(record))
|
|
641
684
|
}
|
|
@@ -648,6 +691,14 @@ class HealthManager {
|
|
|
648
691
|
}
|
|
649
692
|
return Instant.parse(value)
|
|
650
693
|
}
|
|
694
|
+
private fun String?.toMindfulnessSessionType(): Int {
|
|
695
|
+
val normalized = this?.trim()
|
|
696
|
+
if (normalized.isNullOrEmpty()) {
|
|
697
|
+
return MindfulnessSessionRecord.MINDFULNESS_SESSION_TYPE_MEDITATION
|
|
698
|
+
}
|
|
699
|
+
return MindfulnessSessionRecord.MINDFULNESS_SESSION_TYPE_STRING_TO_INT_MAP[normalized]
|
|
700
|
+
?: throw IllegalArgumentException("Unsupported mindfulnessSessionType: $normalized")
|
|
701
|
+
}
|
|
651
702
|
private fun mapSleepStageToString(stage: Int): String? {
|
|
652
703
|
return when (stage) {
|
|
653
704
|
SleepSessionRecord.STAGE_TYPE_AWAKE -> "awake"
|
|
@@ -1035,5 +1086,7 @@ private fun createSamplePayload(
|
|
|
1035
1086
|
private const val DEFAULT_PAGE_SIZE = 100
|
|
1036
1087
|
private const val MAX_PAGE_SIZE = 500
|
|
1037
1088
|
private const val MAX_AGGREGATE_GROUP_BUCKETS = 5000
|
|
1089
|
+
// Permission that lifts Health Connect's default 30-day read window.
|
|
1090
|
+
private const val READ_HEALTH_DATA_HISTORY_PERMISSION = "android.permission.health.READ_HEALTH_DATA_HISTORY"
|
|
1038
1091
|
}
|
|
1039
1092
|
}
|
|
@@ -35,6 +35,7 @@ class HealthPlugin : Plugin() {
|
|
|
35
35
|
private var pendingReadTypes: List<HealthDataType> = emptyList()
|
|
36
36
|
private var pendingWriteTypes: List<HealthDataType> = emptyList()
|
|
37
37
|
private var pendingIncludeWorkouts: Boolean = false
|
|
38
|
+
private var pendingIncludeHistoryAccess: Boolean = false
|
|
38
39
|
|
|
39
40
|
override fun handleOnDestroy() {
|
|
40
41
|
super.handleOnDestroy()
|
|
@@ -63,19 +64,27 @@ class HealthPlugin : Plugin() {
|
|
|
63
64
|
return
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
val includeHistoryAccess = call.getBoolean("requestHistoryAccess") ?: false
|
|
68
|
+
|
|
66
69
|
pluginScope.launch {
|
|
67
70
|
val client = getClientOrReject(call) ?: return@launch
|
|
68
|
-
|
|
71
|
+
// Only request the history permission when the provider actually supports it.
|
|
72
|
+
// On an older-but-supported provider it can never be granted, which would leave
|
|
73
|
+
// granted.containsAll(permissions) permanently false and reopen the permission
|
|
74
|
+
// sheet on every call. The normal data scopes are still requested either way;
|
|
75
|
+
// authorizationStatus reports historyAccessAvailable so callers can react.
|
|
76
|
+
val requestHistoryAccess = includeHistoryAccess && manager.isHistoryAccessAvailable(client)
|
|
77
|
+
val permissions = manager.permissionsFor(readTypes, writeTypes, includeWorkouts, requestHistoryAccess)
|
|
69
78
|
|
|
70
79
|
if (permissions.isEmpty()) {
|
|
71
|
-
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
80
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts, includeHistoryAccess)
|
|
72
81
|
call.resolve(status)
|
|
73
82
|
return@launch
|
|
74
83
|
}
|
|
75
84
|
|
|
76
85
|
val granted = client.permissionController.getGrantedPermissions()
|
|
77
86
|
if (granted.containsAll(permissions)) {
|
|
78
|
-
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
87
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts, includeHistoryAccess)
|
|
79
88
|
call.resolve(status)
|
|
80
89
|
return@launch
|
|
81
90
|
}
|
|
@@ -84,6 +93,7 @@ class HealthPlugin : Plugin() {
|
|
|
84
93
|
pendingReadTypes = readTypes
|
|
85
94
|
pendingWriteTypes = writeTypes
|
|
86
95
|
pendingIncludeWorkouts = includeWorkouts
|
|
96
|
+
pendingIncludeHistoryAccess = includeHistoryAccess
|
|
87
97
|
|
|
88
98
|
// Create intent using the Health Connect permission contract
|
|
89
99
|
val intent = permissionContract.createIntent(context, permissions)
|
|
@@ -107,13 +117,15 @@ class HealthPlugin : Plugin() {
|
|
|
107
117
|
val readTypes = pendingReadTypes
|
|
108
118
|
val writeTypes = pendingWriteTypes
|
|
109
119
|
val includeWorkouts = pendingIncludeWorkouts
|
|
120
|
+
val includeHistoryAccess = pendingIncludeHistoryAccess
|
|
110
121
|
pendingReadTypes = emptyList()
|
|
111
122
|
pendingWriteTypes = emptyList()
|
|
112
123
|
pendingIncludeWorkouts = false
|
|
124
|
+
pendingIncludeHistoryAccess = false
|
|
113
125
|
|
|
114
126
|
pluginScope.launch {
|
|
115
127
|
val client = getClientOrReject(call) ?: return@launch
|
|
116
|
-
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
128
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts, includeHistoryAccess)
|
|
117
129
|
call.resolve(status)
|
|
118
130
|
}
|
|
119
131
|
}
|
|
@@ -134,9 +146,11 @@ class HealthPlugin : Plugin() {
|
|
|
134
146
|
return
|
|
135
147
|
}
|
|
136
148
|
|
|
149
|
+
val includeHistoryAccess = call.getBoolean("requestHistoryAccess") ?: false
|
|
150
|
+
|
|
137
151
|
pluginScope.launch {
|
|
138
152
|
val client = getClientOrReject(call) ?: return@launch
|
|
139
|
-
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts)
|
|
153
|
+
val status = manager.authorizationStatus(client, readTypes, writeTypes, includeWorkouts, includeHistoryAccess)
|
|
140
154
|
call.resolve(status)
|
|
141
155
|
}
|
|
142
156
|
}
|
|
@@ -250,11 +264,12 @@ class HealthPlugin : Plugin() {
|
|
|
250
264
|
|
|
251
265
|
val systolic = call.getDouble("systolic")
|
|
252
266
|
val diastolic = call.getDouble("diastolic")
|
|
267
|
+
val mindfulnessSessionType = call.getString("mindfulnessSessionType")
|
|
253
268
|
|
|
254
269
|
pluginScope.launch {
|
|
255
270
|
val client = getClientOrReject(call) ?: return@launch
|
|
256
271
|
try {
|
|
257
|
-
manager.saveSample(client, dataType, value, startInstant, endInstant, metadata, systolic, diastolic)
|
|
272
|
+
manager.saveSample(client, dataType, value, startInstant, endInstant, metadata, systolic, diastolic, mindfulnessSessionType)
|
|
258
273
|
call.resolve()
|
|
259
274
|
} catch (e: Exception) {
|
|
260
275
|
call.reject(e.message ?: "Failed to save sample.", null, e)
|
package/dist/docs.json
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
],
|
|
30
30
|
"returns": "Promise<AuthorizationStatus>",
|
|
31
31
|
"tags": [],
|
|
32
|
-
"docs": "Requests read/write access to the provided data types.",
|
|
32
|
+
"docs": "Requests read/write access to the provided data types.\n\nSet `requestHistoryAccess: true` to additionally request Android's\n`READ_HEALTH_DATA_HISTORY` permission in the same Health Connect permission sheet\n(see {@link AuthorizationOptions.requestHistoryAccess}). The granted/denied status is\nreported back as `historyAccessAuthorized` on the result.",
|
|
33
33
|
"complexTypes": [
|
|
34
34
|
"AuthorizationStatus",
|
|
35
35
|
"AuthorizationOptions"
|
|
@@ -281,6 +281,20 @@
|
|
|
281
281
|
"HealthDataType"
|
|
282
282
|
],
|
|
283
283
|
"type": "HealthDataType[]"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"name": "historyAccessAuthorized",
|
|
287
|
+
"tags": [],
|
|
288
|
+
"docs": "Android only: whether the `READ_HEALTH_DATA_HISTORY` permission is granted. Only\npresent when `requestHistoryAccess` was set on the request; omitted otherwise and\nalways omitted on iOS. Always `false` when `historyAccessAvailable` is `false`,\nsince an unsupported provider can never grant the permission.",
|
|
289
|
+
"complexTypes": [],
|
|
290
|
+
"type": "boolean | undefined"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "historyAccessAvailable",
|
|
294
|
+
"tags": [],
|
|
295
|
+
"docs": "Android only: whether the connected Health Connect provider supports the\n`READ_HEALTH_DATA_HISTORY` permission at all. Only present when `requestHistoryAccess`\nwas set on the request; omitted otherwise and always omitted on iOS.\n\n`false` means the provider is too old (pre Android 14 extension 13 / Health Connect APK\n171302) to ever grant history access — distinct from the user simply denying it. Use it\nto avoid re-prompting and to message the user that history access is unavailable on their\ndevice.",
|
|
296
|
+
"complexTypes": [],
|
|
297
|
+
"type": "boolean | undefined"
|
|
284
298
|
}
|
|
285
299
|
]
|
|
286
300
|
},
|
|
@@ -308,6 +322,13 @@
|
|
|
308
322
|
"HealthDataType"
|
|
309
323
|
],
|
|
310
324
|
"type": "HealthDataType[] | undefined"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"name": "requestHistoryAccess",
|
|
328
|
+
"tags": [],
|
|
329
|
+
"docs": "Android only: also request the `READ_HEALTH_DATA_HISTORY` permission in the same\nHealth Connect permission sheet. Without it, Health Connect caps reads to roughly\nthe last 30 days; granting it lets you read older data.\n\nThe consuming app must also declare the permission in its `AndroidManifest.xml`:\n`<uses-permission android:name=\"android.permission.health.READ_HEALTH_DATA_HISTORY\" />`\n\nThe permission only exists on sufficiently new Health Connect providers (Android 14\nextension 13+ or Health Connect APK 171302+). On older but otherwise supported\nproviders it is silently skipped — the normal read/write scopes are still requested —\nand the returned status reports `historyAccessAvailable: false`.\n\nIgnored on iOS (HealthKit has no equivalent permission and no 30-day read cap).",
|
|
330
|
+
"complexTypes": [],
|
|
331
|
+
"type": "boolean | undefined"
|
|
311
332
|
}
|
|
312
333
|
]
|
|
313
334
|
},
|
|
@@ -584,6 +605,13 @@
|
|
|
584
605
|
],
|
|
585
606
|
"type": "Record<string, string>"
|
|
586
607
|
},
|
|
608
|
+
{
|
|
609
|
+
"name": "mindfulnessSessionType",
|
|
610
|
+
"tags": [],
|
|
611
|
+
"docs": "Android mindfulness session type. Defaults to 'meditation' when dataType is 'mindfulness'.",
|
|
612
|
+
"complexTypes": [],
|
|
613
|
+
"type": "'meditation' | 'unknown' | 'breathing' | 'music' | 'movement' | 'unguided' | undefined"
|
|
614
|
+
},
|
|
587
615
|
{
|
|
588
616
|
"name": "systolic",
|
|
589
617
|
"tags": [],
|
|
@@ -5,12 +5,46 @@ export interface AuthorizationOptions {
|
|
|
5
5
|
read?: HealthDataType[];
|
|
6
6
|
/** Data types that should be writable after authorization. */
|
|
7
7
|
write?: HealthDataType[];
|
|
8
|
+
/**
|
|
9
|
+
* Android only: also request the `READ_HEALTH_DATA_HISTORY` permission in the same
|
|
10
|
+
* Health Connect permission sheet. Without it, Health Connect caps reads to roughly
|
|
11
|
+
* the last 30 days; granting it lets you read older data.
|
|
12
|
+
*
|
|
13
|
+
* The consuming app must also declare the permission in its `AndroidManifest.xml`:
|
|
14
|
+
* `<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" />`
|
|
15
|
+
*
|
|
16
|
+
* The permission only exists on sufficiently new Health Connect providers (Android 14
|
|
17
|
+
* extension 13+ or Health Connect APK 171302+). On older but otherwise supported
|
|
18
|
+
* providers it is silently skipped — the normal read/write scopes are still requested —
|
|
19
|
+
* and the returned status reports `historyAccessAvailable: false`.
|
|
20
|
+
*
|
|
21
|
+
* Ignored on iOS (HealthKit has no equivalent permission and no 30-day read cap).
|
|
22
|
+
*/
|
|
23
|
+
requestHistoryAccess?: boolean;
|
|
8
24
|
}
|
|
9
25
|
export interface AuthorizationStatus {
|
|
10
26
|
readAuthorized: HealthDataType[];
|
|
11
27
|
readDenied: HealthDataType[];
|
|
12
28
|
writeAuthorized: HealthDataType[];
|
|
13
29
|
writeDenied: HealthDataType[];
|
|
30
|
+
/**
|
|
31
|
+
* Android only: whether the `READ_HEALTH_DATA_HISTORY` permission is granted. Only
|
|
32
|
+
* present when `requestHistoryAccess` was set on the request; omitted otherwise and
|
|
33
|
+
* always omitted on iOS. Always `false` when `historyAccessAvailable` is `false`,
|
|
34
|
+
* since an unsupported provider can never grant the permission.
|
|
35
|
+
*/
|
|
36
|
+
historyAccessAuthorized?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Android only: whether the connected Health Connect provider supports the
|
|
39
|
+
* `READ_HEALTH_DATA_HISTORY` permission at all. Only present when `requestHistoryAccess`
|
|
40
|
+
* was set on the request; omitted otherwise and always omitted on iOS.
|
|
41
|
+
*
|
|
42
|
+
* `false` means the provider is too old (pre Android 14 extension 13 / Health Connect APK
|
|
43
|
+
* 171302) to ever grant history access — distinct from the user simply denying it. Use it
|
|
44
|
+
* to avoid re-prompting and to message the user that history access is unavailable on their
|
|
45
|
+
* device.
|
|
46
|
+
*/
|
|
47
|
+
historyAccessAvailable?: boolean;
|
|
14
48
|
}
|
|
15
49
|
export interface AvailabilityResult {
|
|
16
50
|
available: boolean;
|
|
@@ -132,6 +166,8 @@ export interface WriteSampleOptions {
|
|
|
132
166
|
endDate?: string;
|
|
133
167
|
/** Metadata key-value pairs forwarded to the native APIs where supported. */
|
|
134
168
|
metadata?: Record<string, string>;
|
|
169
|
+
/** Android mindfulness session type. Defaults to 'meditation' when dataType is 'mindfulness'. */
|
|
170
|
+
mindfulnessSessionType?: 'unknown' | 'meditation' | 'breathing' | 'music' | 'movement' | 'unguided';
|
|
135
171
|
/** For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. */
|
|
136
172
|
systolic?: number;
|
|
137
173
|
/** For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. */
|
|
@@ -167,7 +203,14 @@ export interface QueryAggregatedResult {
|
|
|
167
203
|
export interface HealthPlugin {
|
|
168
204
|
/** Returns whether the current platform supports the native health SDK. */
|
|
169
205
|
isAvailable(): Promise<AvailabilityResult>;
|
|
170
|
-
/**
|
|
206
|
+
/**
|
|
207
|
+
* Requests read/write access to the provided data types.
|
|
208
|
+
*
|
|
209
|
+
* Set `requestHistoryAccess: true` to additionally request Android's
|
|
210
|
+
* `READ_HEALTH_DATA_HISTORY` permission in the same Health Connect permission sheet
|
|
211
|
+
* (see {@link AuthorizationOptions.requestHistoryAccess}). The granted/denied status is
|
|
212
|
+
* reported back as `historyAccessAuthorized` on the result.
|
|
213
|
+
*/
|
|
171
214
|
requestAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;
|
|
172
215
|
/** Checks authorization status for the provided data types without prompting the user. */
|
|
173
216
|
checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type HealthDataType =\n | 'steps'\n | 'distance'\n | 'calories'\n | 'heartRate'\n | 'weight'\n | 'sleep'\n | 'respiratoryRate'\n | 'oxygenSaturation'\n | 'restingHeartRate'\n | 'heartRateVariability'\n | 'vo2Max'\n | 'bloodPressure'\n | 'bloodGlucose'\n | 'bodyTemperature'\n | 'height'\n | 'flightsClimbed'\n | 'exerciseTime'\n | 'distanceCycling'\n | 'bodyFat'\n | 'basalBodyTemperature'\n | 'basalCalories'\n | 'totalCalories'\n | 'mindfulness'\n | 'workouts';\n\nexport type HealthUnit =\n | 'count'\n | 'meter'\n | 'kilocalorie'\n | 'bpm'\n | 'kilogram'\n | 'minute'\n | 'percent'\n | 'millisecond'\n | 'mL/min/kg'\n | 'mmHg'\n | 'mg/dL'\n | 'celsius'\n | 'fahrenheit'\n | 'centimeter';\n\nexport interface AuthorizationOptions {\n /** Data types that should be readable after authorization. */\n read?: HealthDataType[];\n /** Data types that should be writable after authorization. */\n write?: HealthDataType[];\n}\n\nexport interface AuthorizationStatus {\n readAuthorized: HealthDataType[];\n readDenied: HealthDataType[];\n writeAuthorized: HealthDataType[];\n writeDenied: HealthDataType[];\n}\n\nexport interface AvailabilityResult {\n available: boolean;\n /** Platform specific details (for debugging/diagnostics). */\n platform?: 'ios' | 'android' | 'web';\n reason?: string;\n}\n\nexport interface QueryOptions {\n /** The type of data to retrieve from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of samples to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n}\n\nexport type SleepState = 'inBed' | 'asleep' | 'awake' | 'rem' | 'deep' | 'light';\n\n/** Stage-level sleep segment emitted for sleep samples when platform data is available. */\nexport interface SleepStage {\n /** Stage segment start date in ISO 8601 format. */\n startDate: string;\n /** Stage segment end date in ISO 8601 format. */\n endDate: string;\n /** Sleep stage label for this segment. */\n stage: SleepState;\n /** Duration of this stage segment in minutes. */\n durationMinutes: number;\n}\n\nexport interface HealthSample {\n dataType: HealthDataType;\n value: number;\n unit: HealthUnit;\n startDate: string;\n endDate: string;\n sourceName?: string;\n sourceId?: string;\n /** Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android). */\n platformId?: string;\n /** For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light'). */\n sleepState?: SleepState;\n /** For sleep data, individual sleep stages when the platform exposes stage-level data. */\n stages?: SleepStage[];\n /** For sleep data, indicates whether stage-level data was emitted. */\n hasStageData?: boolean;\n /** For blood pressure data, the systolic value in mmHg. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. */\n diastolic?: number;\n /** For VO2 max data on Android, Health Connect's measurement method enum value. */\n measurementMethod?: number;\n}\n\nexport interface ReadSamplesResult {\n samples: HealthSample[];\n}\n\nexport type WorkoutType =\n // Common types (supported on both platforms)\n | 'americanFootball'\n | 'australianFootball'\n | 'badminton'\n | 'baseball'\n | 'basketball'\n | 'bowling'\n | 'boxing'\n | 'climbing'\n | 'cricket'\n | 'crossTraining'\n | 'curling'\n | 'cycling'\n | 'dance'\n | 'elliptical'\n | 'fencing'\n | 'functionalStrengthTraining'\n | 'golf'\n | 'gymnastics'\n | 'handball'\n | 'hiking'\n | 'hockey'\n | 'jumpRope'\n | 'kickboxing'\n | 'lacrosse'\n | 'martialArts'\n | 'pilates'\n | 'racquetball'\n | 'rowing'\n | 'rugby'\n | 'running'\n | 'sailing'\n | 'skatingSports'\n | 'skiing'\n | 'snowboarding'\n | 'soccer'\n | 'softball'\n | 'squash'\n | 'stairClimbing'\n | 'strengthTraining'\n | 'surfing'\n | 'swimming'\n | 'swimmingPool'\n | 'swimmingOpenWater'\n | 'tableTennis'\n | 'tennis'\n | 'trackAndField'\n | 'traditionalStrengthTraining'\n | 'volleyball'\n | 'walking'\n | 'waterFitness'\n | 'waterPolo'\n | 'waterSports'\n | 'weightlifting'\n | 'wheelchair'\n | 'yoga'\n // iOS specific types\n | 'archery'\n | 'barre'\n | 'cooldown'\n | 'coreTraining'\n | 'crossCountrySkiing'\n | 'discSports'\n | 'downhillSkiing'\n | 'equestrianSports'\n | 'fishing'\n | 'fitnessGaming'\n | 'flexibility'\n | 'handCycling'\n | 'highIntensityIntervalTraining'\n | 'hunting'\n | 'mindAndBody'\n | 'mixedCardio'\n | 'paddleSports'\n | 'pickleball'\n | 'play'\n | 'preparationAndRecovery'\n | 'snowSports'\n | 'stairs'\n | 'stepTraining'\n | 'surfingSports'\n | 'taiChi'\n | 'transition'\n | 'underwaterDiving'\n | 'wheelchairRunPace'\n | 'wheelchairWalkPace'\n | 'wrestling'\n | 'cardioDance'\n | 'socialDance'\n // Android specific types\n | 'backExtension'\n | 'barbellShoulderPress'\n | 'benchPress'\n | 'benchSitUp'\n | 'bikingStationary'\n | 'bootCamp'\n | 'burpee'\n | 'calisthenics'\n | 'crunch'\n | 'dancing'\n | 'deadlift'\n | 'dumbbellCurlLeftArm'\n | 'dumbbellCurlRightArm'\n | 'dumbbellFrontRaise'\n | 'dumbbellLateralRaise'\n | 'dumbbellTricepsExtensionLeftArm'\n | 'dumbbellTricepsExtensionRightArm'\n | 'dumbbellTricepsExtensionTwoArm'\n | 'exerciseClass'\n | 'forwardTwist'\n | 'frisbeedisc'\n | 'guidedBreathing'\n | 'iceHockey'\n | 'iceSkating'\n | 'jumpingJack'\n | 'latPullDown'\n | 'lunge'\n | 'meditation'\n | 'paddling'\n | 'paraGliding'\n | 'plank'\n | 'rockClimbing'\n | 'rollerHockey'\n | 'rowingMachine'\n | 'runningTreadmill'\n | 'scubaDiving'\n | 'skating'\n | 'snowshoeing'\n | 'stairClimbingMachine'\n | 'stretching'\n | 'upperTwist'\n | 'other';\n\nexport interface QueryWorkoutsOptions {\n /** Optional workout type filter. If omitted, all workout types are returned. */\n workoutType?: WorkoutType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of workouts to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n /**\n * Anchor for pagination. Use the anchor returned from a previous query to continue from that point.\n * On iOS, this is the ISO 8601 cursor returned by the previous query. On Android, this uses\n * Health Connect's pageToken.\n * Omit this parameter to start from the beginning.\n */\n anchor?: string;\n}\n\nexport interface Workout {\n /** The type of workout. */\n workoutType: WorkoutType;\n /** Duration of the workout in seconds. */\n duration: number;\n /** Total energy burned in kilocalories (if available). */\n totalEnergyBurned?: number;\n /** Total distance in meters (if available). */\n totalDistance?: number;\n /** ISO 8601 start date of the workout. */\n startDate: string;\n /** ISO 8601 end date of the workout. */\n endDate: string;\n /** Source name that recorded the workout. */\n sourceName?: string;\n /** Source bundle identifier. */\n sourceId?: string;\n /** Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android). */\n platformId?: string;\n /** Additional metadata (if available). */\n metadata?: Record<string, string>;\n}\n\nexport interface QueryWorkoutsResult {\n workouts: Workout[];\n /**\n * Anchor for the next page of results. Pass this value as the anchor parameter in the next query\n * to continue pagination. If undefined or null, there are no more results.\n */\n anchor?: string;\n}\n\nexport interface WriteSampleOptions {\n dataType: HealthDataType;\n value: number;\n /**\n * Optional unit override. If omitted, the default unit for the data type is used\n * (count for `steps`, meter for `distance`, kilocalorie for `calories`, bpm for `heartRate`, kilogram for `weight`).\n */\n unit?: HealthUnit;\n /** ISO 8601 start date for the sample. Defaults to now. */\n startDate?: string;\n /** ISO 8601 end date for the sample. Defaults to startDate. */\n endDate?: string;\n /** Metadata key-value pairs forwarded to the native APIs where supported. */\n metadata?: Record<string, string>;\n /** For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. */\n diastolic?: number;\n}\n\nexport type BucketType = 'hour' | 'day' | 'week' | 'month';\n\nexport type AggregationType = 'sum' | 'average' | 'min' | 'max';\n\nexport interface QueryAggregatedOptions {\n /** The type of data to aggregate from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Time bucket for aggregation (defaults to 'day'). */\n bucket?: BucketType;\n /** Aggregation operation to perform (defaults to 'sum'). */\n aggregation?: AggregationType;\n}\n\nexport interface AggregatedSample {\n /** ISO 8601 start date of the bucket. */\n startDate: string;\n /** ISO 8601 end date of the bucket. */\n endDate: string;\n /** Aggregated value for the bucket. */\n value: number;\n /** Unit of the aggregated value. */\n unit: HealthUnit;\n}\n\nexport interface QueryAggregatedResult {\n samples: AggregatedSample[];\n}\n\nexport interface HealthPlugin {\n /** Returns whether the current platform supports the native health SDK. */\n isAvailable(): Promise<AvailabilityResult>;\n /** Requests read/write access to the provided data types. */\n requestAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Checks authorization status for the provided data types without prompting the user. */\n checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Reads samples for the given data type within the specified time frame. */\n readSamples(options: QueryOptions): Promise<ReadSamplesResult>;\n /** Writes a single sample to the native health store. */\n saveSample(options: WriteSampleOptions): Promise<void>;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ version: string }>} a Promise with version for this device\n * @throws An error if something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Opens the Health Connect settings screen (Android only).\n * On iOS, this method does nothing.\n *\n * Use this to direct users to manage their Health Connect permissions\n * or to install Health Connect if not available.\n *\n * @throws An error if Health Connect settings cannot be opened\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Shows the app's privacy policy for Health Connect (Android only).\n * On iOS, this method does nothing.\n *\n * This displays the same privacy policy screen that Health Connect shows\n * when the user taps \"Privacy policy\" in the permissions dialog.\n *\n * The privacy policy URL can be configured by adding a string resource\n * named \"health_connect_privacy_policy_url\" in your app's strings.xml,\n * or by placing an HTML file at www/privacypolicy.html in your assets.\n *\n * @throws An error if the privacy policy cannot be displayed\n */\n showPrivacyPolicy(): Promise<void>;\n\n /**\n * Queries workout sessions from the native health store.\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including optional workout type filter, date range, limit, and sort order\n * @returns A promise that resolves with the workout sessions\n * @throws An error if something went wrong\n */\n queryWorkouts(options: QueryWorkoutsOptions): Promise<QueryWorkoutsResult>;\n\n /**\n * Queries aggregated health data from the native health store.\n * Aggregates data into time buckets (hour, day, week, month) with operations like sum, average, min, or max.\n * This is more efficient than fetching individual samples for large date ranges.\n *\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including data type, date range, bucket size, and aggregation type\n * @returns A promise that resolves with the aggregated samples\n * @throws An error if something went wrong\n */\n queryAggregated(options: QueryAggregatedOptions): Promise<QueryAggregatedResult>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export type HealthDataType =\n | 'steps'\n | 'distance'\n | 'calories'\n | 'heartRate'\n | 'weight'\n | 'sleep'\n | 'respiratoryRate'\n | 'oxygenSaturation'\n | 'restingHeartRate'\n | 'heartRateVariability'\n | 'vo2Max'\n | 'bloodPressure'\n | 'bloodGlucose'\n | 'bodyTemperature'\n | 'height'\n | 'flightsClimbed'\n | 'exerciseTime'\n | 'distanceCycling'\n | 'bodyFat'\n | 'basalBodyTemperature'\n | 'basalCalories'\n | 'totalCalories'\n | 'mindfulness'\n | 'workouts';\n\nexport type HealthUnit =\n | 'count'\n | 'meter'\n | 'kilocalorie'\n | 'bpm'\n | 'kilogram'\n | 'minute'\n | 'percent'\n | 'millisecond'\n | 'mL/min/kg'\n | 'mmHg'\n | 'mg/dL'\n | 'celsius'\n | 'fahrenheit'\n | 'centimeter';\n\nexport interface AuthorizationOptions {\n /** Data types that should be readable after authorization. */\n read?: HealthDataType[];\n /** Data types that should be writable after authorization. */\n write?: HealthDataType[];\n /**\n * Android only: also request the `READ_HEALTH_DATA_HISTORY` permission in the same\n * Health Connect permission sheet. Without it, Health Connect caps reads to roughly\n * the last 30 days; granting it lets you read older data.\n *\n * The consuming app must also declare the permission in its `AndroidManifest.xml`:\n * `<uses-permission android:name=\"android.permission.health.READ_HEALTH_DATA_HISTORY\" />`\n *\n * The permission only exists on sufficiently new Health Connect providers (Android 14\n * extension 13+ or Health Connect APK 171302+). On older but otherwise supported\n * providers it is silently skipped — the normal read/write scopes are still requested —\n * and the returned status reports `historyAccessAvailable: false`.\n *\n * Ignored on iOS (HealthKit has no equivalent permission and no 30-day read cap).\n */\n requestHistoryAccess?: boolean;\n}\n\nexport interface AuthorizationStatus {\n readAuthorized: HealthDataType[];\n readDenied: HealthDataType[];\n writeAuthorized: HealthDataType[];\n writeDenied: HealthDataType[];\n /**\n * Android only: whether the `READ_HEALTH_DATA_HISTORY` permission is granted. Only\n * present when `requestHistoryAccess` was set on the request; omitted otherwise and\n * always omitted on iOS. Always `false` when `historyAccessAvailable` is `false`,\n * since an unsupported provider can never grant the permission.\n */\n historyAccessAuthorized?: boolean;\n /**\n * Android only: whether the connected Health Connect provider supports the\n * `READ_HEALTH_DATA_HISTORY` permission at all. Only present when `requestHistoryAccess`\n * was set on the request; omitted otherwise and always omitted on iOS.\n *\n * `false` means the provider is too old (pre Android 14 extension 13 / Health Connect APK\n * 171302) to ever grant history access — distinct from the user simply denying it. Use it\n * to avoid re-prompting and to message the user that history access is unavailable on their\n * device.\n */\n historyAccessAvailable?: boolean;\n}\n\nexport interface AvailabilityResult {\n available: boolean;\n /** Platform specific details (for debugging/diagnostics). */\n platform?: 'ios' | 'android' | 'web';\n reason?: string;\n}\n\nexport interface QueryOptions {\n /** The type of data to retrieve from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of samples to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n}\n\nexport type SleepState = 'inBed' | 'asleep' | 'awake' | 'rem' | 'deep' | 'light';\n\n/** Stage-level sleep segment emitted for sleep samples when platform data is available. */\nexport interface SleepStage {\n /** Stage segment start date in ISO 8601 format. */\n startDate: string;\n /** Stage segment end date in ISO 8601 format. */\n endDate: string;\n /** Sleep stage label for this segment. */\n stage: SleepState;\n /** Duration of this stage segment in minutes. */\n durationMinutes: number;\n}\n\nexport interface HealthSample {\n dataType: HealthDataType;\n value: number;\n unit: HealthUnit;\n startDate: string;\n endDate: string;\n sourceName?: string;\n sourceId?: string;\n /** Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android). */\n platformId?: string;\n /** For sleep data, indicates the sleep state (e.g., 'asleep', 'awake', 'rem', 'deep', 'light'). */\n sleepState?: SleepState;\n /** For sleep data, individual sleep stages when the platform exposes stage-level data. */\n stages?: SleepStage[];\n /** For sleep data, indicates whether stage-level data was emitted. */\n hasStageData?: boolean;\n /** For blood pressure data, the systolic value in mmHg. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. */\n diastolic?: number;\n /** For VO2 max data on Android, Health Connect's measurement method enum value. */\n measurementMethod?: number;\n}\n\nexport interface ReadSamplesResult {\n samples: HealthSample[];\n}\n\nexport type WorkoutType =\n // Common types (supported on both platforms)\n | 'americanFootball'\n | 'australianFootball'\n | 'badminton'\n | 'baseball'\n | 'basketball'\n | 'bowling'\n | 'boxing'\n | 'climbing'\n | 'cricket'\n | 'crossTraining'\n | 'curling'\n | 'cycling'\n | 'dance'\n | 'elliptical'\n | 'fencing'\n | 'functionalStrengthTraining'\n | 'golf'\n | 'gymnastics'\n | 'handball'\n | 'hiking'\n | 'hockey'\n | 'jumpRope'\n | 'kickboxing'\n | 'lacrosse'\n | 'martialArts'\n | 'pilates'\n | 'racquetball'\n | 'rowing'\n | 'rugby'\n | 'running'\n | 'sailing'\n | 'skatingSports'\n | 'skiing'\n | 'snowboarding'\n | 'soccer'\n | 'softball'\n | 'squash'\n | 'stairClimbing'\n | 'strengthTraining'\n | 'surfing'\n | 'swimming'\n | 'swimmingPool'\n | 'swimmingOpenWater'\n | 'tableTennis'\n | 'tennis'\n | 'trackAndField'\n | 'traditionalStrengthTraining'\n | 'volleyball'\n | 'walking'\n | 'waterFitness'\n | 'waterPolo'\n | 'waterSports'\n | 'weightlifting'\n | 'wheelchair'\n | 'yoga'\n // iOS specific types\n | 'archery'\n | 'barre'\n | 'cooldown'\n | 'coreTraining'\n | 'crossCountrySkiing'\n | 'discSports'\n | 'downhillSkiing'\n | 'equestrianSports'\n | 'fishing'\n | 'fitnessGaming'\n | 'flexibility'\n | 'handCycling'\n | 'highIntensityIntervalTraining'\n | 'hunting'\n | 'mindAndBody'\n | 'mixedCardio'\n | 'paddleSports'\n | 'pickleball'\n | 'play'\n | 'preparationAndRecovery'\n | 'snowSports'\n | 'stairs'\n | 'stepTraining'\n | 'surfingSports'\n | 'taiChi'\n | 'transition'\n | 'underwaterDiving'\n | 'wheelchairRunPace'\n | 'wheelchairWalkPace'\n | 'wrestling'\n | 'cardioDance'\n | 'socialDance'\n // Android specific types\n | 'backExtension'\n | 'barbellShoulderPress'\n | 'benchPress'\n | 'benchSitUp'\n | 'bikingStationary'\n | 'bootCamp'\n | 'burpee'\n | 'calisthenics'\n | 'crunch'\n | 'dancing'\n | 'deadlift'\n | 'dumbbellCurlLeftArm'\n | 'dumbbellCurlRightArm'\n | 'dumbbellFrontRaise'\n | 'dumbbellLateralRaise'\n | 'dumbbellTricepsExtensionLeftArm'\n | 'dumbbellTricepsExtensionRightArm'\n | 'dumbbellTricepsExtensionTwoArm'\n | 'exerciseClass'\n | 'forwardTwist'\n | 'frisbeedisc'\n | 'guidedBreathing'\n | 'iceHockey'\n | 'iceSkating'\n | 'jumpingJack'\n | 'latPullDown'\n | 'lunge'\n | 'meditation'\n | 'paddling'\n | 'paraGliding'\n | 'plank'\n | 'rockClimbing'\n | 'rollerHockey'\n | 'rowingMachine'\n | 'runningTreadmill'\n | 'scubaDiving'\n | 'skating'\n | 'snowshoeing'\n | 'stairClimbingMachine'\n | 'stretching'\n | 'upperTwist'\n | 'other';\n\nexport interface QueryWorkoutsOptions {\n /** Optional workout type filter. If omitted, all workout types are returned. */\n workoutType?: WorkoutType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Maximum number of workouts to return (defaults to 100). */\n limit?: number;\n /** Return results sorted ascending by start date (defaults to false). */\n ascending?: boolean;\n /**\n * Anchor for pagination. Use the anchor returned from a previous query to continue from that point.\n * On iOS, this is the ISO 8601 cursor returned by the previous query. On Android, this uses\n * Health Connect's pageToken.\n * Omit this parameter to start from the beginning.\n */\n anchor?: string;\n}\n\nexport interface Workout {\n /** The type of workout. */\n workoutType: WorkoutType;\n /** Duration of the workout in seconds. */\n duration: number;\n /** Total energy burned in kilocalories (if available). */\n totalEnergyBurned?: number;\n /** Total distance in meters (if available). */\n totalDistance?: number;\n /** ISO 8601 start date of the workout. */\n startDate: string;\n /** ISO 8601 end date of the workout. */\n endDate: string;\n /** Source name that recorded the workout. */\n sourceName?: string;\n /** Source bundle identifier. */\n sourceId?: string;\n /** Platform-specific unique identifier (HealthKit UUID on iOS, Health Connect metadata ID on Android). */\n platformId?: string;\n /** Additional metadata (if available). */\n metadata?: Record<string, string>;\n}\n\nexport interface QueryWorkoutsResult {\n workouts: Workout[];\n /**\n * Anchor for the next page of results. Pass this value as the anchor parameter in the next query\n * to continue pagination. If undefined or null, there are no more results.\n */\n anchor?: string;\n}\n\nexport interface WriteSampleOptions {\n dataType: HealthDataType;\n value: number;\n /**\n * Optional unit override. If omitted, the default unit for the data type is used\n * (count for `steps`, meter for `distance`, kilocalorie for `calories`, bpm for `heartRate`, kilogram for `weight`).\n */\n unit?: HealthUnit;\n /** ISO 8601 start date for the sample. Defaults to now. */\n startDate?: string;\n /** ISO 8601 end date for the sample. Defaults to startDate. */\n endDate?: string;\n /** Metadata key-value pairs forwarded to the native APIs where supported. */\n metadata?: Record<string, string>;\n /** Android mindfulness session type. Defaults to 'meditation' when dataType is 'mindfulness'. */\n mindfulnessSessionType?: 'unknown' | 'meditation' | 'breathing' | 'music' | 'movement' | 'unguided';\n /** For blood pressure data, the systolic value in mmHg. Required when dataType is 'bloodPressure'. */\n systolic?: number;\n /** For blood pressure data, the diastolic value in mmHg. Required when dataType is 'bloodPressure'. */\n diastolic?: number;\n}\n\nexport type BucketType = 'hour' | 'day' | 'week' | 'month';\n\nexport type AggregationType = 'sum' | 'average' | 'min' | 'max';\n\nexport interface QueryAggregatedOptions {\n /** The type of data to aggregate from the health store. */\n dataType: HealthDataType;\n /** Inclusive ISO 8601 start date (defaults to now - 1 day). */\n startDate?: string;\n /** Exclusive ISO 8601 end date (defaults to now). */\n endDate?: string;\n /** Time bucket for aggregation (defaults to 'day'). */\n bucket?: BucketType;\n /** Aggregation operation to perform (defaults to 'sum'). */\n aggregation?: AggregationType;\n}\n\nexport interface AggregatedSample {\n /** ISO 8601 start date of the bucket. */\n startDate: string;\n /** ISO 8601 end date of the bucket. */\n endDate: string;\n /** Aggregated value for the bucket. */\n value: number;\n /** Unit of the aggregated value. */\n unit: HealthUnit;\n}\n\nexport interface QueryAggregatedResult {\n samples: AggregatedSample[];\n}\n\nexport interface HealthPlugin {\n /** Returns whether the current platform supports the native health SDK. */\n isAvailable(): Promise<AvailabilityResult>;\n /**\n * Requests read/write access to the provided data types.\n *\n * Set `requestHistoryAccess: true` to additionally request Android's\n * `READ_HEALTH_DATA_HISTORY` permission in the same Health Connect permission sheet\n * (see {@link AuthorizationOptions.requestHistoryAccess}). The granted/denied status is\n * reported back as `historyAccessAuthorized` on the result.\n */\n requestAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Checks authorization status for the provided data types without prompting the user. */\n checkAuthorization(options: AuthorizationOptions): Promise<AuthorizationStatus>;\n /** Reads samples for the given data type within the specified time frame. */\n readSamples(options: QueryOptions): Promise<ReadSamplesResult>;\n /** Writes a single sample to the native health store. */\n saveSample(options: WriteSampleOptions): Promise<void>;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ version: string }>} a Promise with version for this device\n * @throws An error if something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n\n /**\n * Opens the Health Connect settings screen (Android only).\n * On iOS, this method does nothing.\n *\n * Use this to direct users to manage their Health Connect permissions\n * or to install Health Connect if not available.\n *\n * @throws An error if Health Connect settings cannot be opened\n */\n openHealthConnectSettings(): Promise<void>;\n\n /**\n * Shows the app's privacy policy for Health Connect (Android only).\n * On iOS, this method does nothing.\n *\n * This displays the same privacy policy screen that Health Connect shows\n * when the user taps \"Privacy policy\" in the permissions dialog.\n *\n * The privacy policy URL can be configured by adding a string resource\n * named \"health_connect_privacy_policy_url\" in your app's strings.xml,\n * or by placing an HTML file at www/privacypolicy.html in your assets.\n *\n * @throws An error if the privacy policy cannot be displayed\n */\n showPrivacyPolicy(): Promise<void>;\n\n /**\n * Queries workout sessions from the native health store.\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including optional workout type filter, date range, limit, and sort order\n * @returns A promise that resolves with the workout sessions\n * @throws An error if something went wrong\n */\n queryWorkouts(options: QueryWorkoutsOptions): Promise<QueryWorkoutsResult>;\n\n /**\n * Queries aggregated health data from the native health store.\n * Aggregates data into time buckets (hour, day, week, month) with operations like sum, average, min, or max.\n * This is more efficient than fetching individual samples for large date ranges.\n *\n * Supported on iOS (HealthKit) and Android (Health Connect).\n *\n * @param options Query options including data type, date range, bucket size, and aggregation type\n * @returns A promise that resolves with the aggregated samples\n * @throws An error if something went wrong\n */\n queryAggregated(options: QueryAggregatedOptions): Promise<QueryAggregatedResult>;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import Capacitor
|
|
|
3
3
|
|
|
4
4
|
@objc(HealthPlugin)
|
|
5
5
|
public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
-
private let pluginVersion: String = "8.
|
|
6
|
+
private let pluginVersion: String = "8.7.0"
|
|
7
7
|
public let identifier = "HealthPlugin"
|
|
8
8
|
public let jsName = "Health"
|
|
9
9
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -28,6 +28,9 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
28
28
|
@objc func requestAuthorization(_ call: CAPPluginCall) {
|
|
29
29
|
let read = (call.getArray("read") as? [String]) ?? []
|
|
30
30
|
let write = (call.getArray("write") as? [String]) ?? []
|
|
31
|
+
// `requestHistoryAccess` is Android-only (READ_HEALTH_DATA_HISTORY). HealthKit has no
|
|
32
|
+
// equivalent permission and no 30-day read cap, so we intentionally ignore it here and
|
|
33
|
+
// leave `historyAccessAuthorized` out of the returned status.
|
|
31
34
|
|
|
32
35
|
implementation.requestAuthorization(readIdentifiers: read, writeIdentifiers: write) { result in
|
|
33
36
|
DispatchQueue.main.async {
|
|
@@ -44,6 +47,7 @@ public class HealthPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
44
47
|
@objc func checkAuthorization(_ call: CAPPluginCall) {
|
|
45
48
|
let read = (call.getArray("read") as? [String]) ?? []
|
|
46
49
|
let write = (call.getArray("write") as? [String]) ?? []
|
|
50
|
+
// `requestHistoryAccess` is Android-only; intentionally ignored on iOS (see requestAuthorization).
|
|
47
51
|
|
|
48
52
|
implementation.checkAuthorization(readIdentifiers: read, writeIdentifiers: write) { result in
|
|
49
53
|
DispatchQueue.main.async {
|
package/package.json
CHANGED