@tryterra/react-native 2.1.0 → 2.2.1
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 +31 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +103 -14
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/ReadConversions.kt +250 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +47 -5
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +1 -3
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +89 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +672 -0
- package/docs/availability.md +154 -4
- package/frameworks/TerraSDK.xcframework/Info.plist +5 -5
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6589 -6468
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +1385 -226
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +37 -1
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +21 -21
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +2 -2
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6590 -6469
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6508 -6387
- package/ios/TerraBridgeCore/Conversions+Reads.swift +259 -0
- package/ios/TerraBridgeCore/Conversions.swift +129 -2
- package/ios/TerraBridgeCore/Generated/BridgeOps.swift +172 -0
- package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +734 -1
- package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +64 -0
- package/lib/Terra.d.ts +74 -5
- package/lib/Terra.js +69 -13
- package/lib/generated/client.d.ts +37 -1
- package/lib/generated/client.js +60 -0
- package/lib/generated/codecs.d.ts +85 -1
- package/lib/generated/codecs.js +710 -2
- package/lib/generated/ops.d.ts +1 -1
- package/lib/generated/ops.js +24 -0
- package/lib/generated/types.d.ts +314 -1
- package/lib/generated/types.js +133 -1
- package/lib/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/Terra.ts +146 -16
- package/src/generated/client.ts +74 -2
- package/src/generated/codecs.ts +705 -3
- package/src/generated/ops.ts +36 -0
- package/src/generated/types.ts +556 -1
- package/src/index.ts +2 -0
|
@@ -43,6 +43,11 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
43
43
|
ConnectionList(connections: try await terra.connections.map(Conversions.connection))
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
func clientAvailability(_ request: SourceRequest) async throws -> AvailabilityResult {
|
|
47
|
+
AvailabilityResult(availability: Conversions.availability(
|
|
48
|
+
await terra.availability(for: try Conversions.source(request.source))))
|
|
49
|
+
}
|
|
50
|
+
|
|
46
51
|
func clientDisconnect(_ request: SourceRequest) async throws -> Empty {
|
|
47
52
|
try await connection(request.source).disconnect()
|
|
48
53
|
return Empty()
|
|
@@ -127,6 +132,16 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
127
132
|
.map(Conversions.heartRate))
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
func bodyRestingHeartRate(_ request: RangeRequest) async throws -> RestingHeartRateList {
|
|
136
|
+
RestingHeartRateList(samples: try await terra.body.restingHeartRate(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
137
|
+
.map(Conversions.restingHeartRate))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
func bodyReadings(_ request: RangeRequest) async throws -> BodyReadingList {
|
|
141
|
+
BodyReadingList(samples: try await terra.body.readings(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
142
|
+
.compactMap(Conversions.bodyReading))
|
|
143
|
+
}
|
|
144
|
+
|
|
130
145
|
func bodyLogHeartRate(_ request: HeartRateEntryList) async throws -> RecordIdList {
|
|
131
146
|
ids(try await terra.body.log(entries(request.entries, Conversions.entry)))
|
|
132
147
|
}
|
|
@@ -147,6 +162,16 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
147
162
|
.map(Conversions.steps))
|
|
148
163
|
}
|
|
149
164
|
|
|
165
|
+
func dailyTotals(_ request: RangeRequest) async throws -> DayTotalList {
|
|
166
|
+
DayTotalList(totals: try await terra.daily.totals(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
167
|
+
.compactMap(Conversions.dayTotal))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
func dailyIntraday(_ request: RangeRequest) async throws -> IntradaySampleList {
|
|
171
|
+
IntradaySampleList(samples: try await terra.daily.intraday(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
172
|
+
.compactMap(Conversions.intraday))
|
|
173
|
+
}
|
|
174
|
+
|
|
150
175
|
func dailyLog(_ request: StepEntryList) async throws -> RecordIdList {
|
|
151
176
|
ids(try await terra.daily.log(entries(request.entries, Conversions.entry)))
|
|
152
177
|
}
|
|
@@ -179,6 +204,16 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
179
204
|
.map(Conversions.workout))
|
|
180
205
|
}
|
|
181
206
|
|
|
207
|
+
func activityRoutes(_ request: RangeRequest) async throws -> WorkoutRouteList {
|
|
208
|
+
WorkoutRouteList(routes: try await terra.activity.routes(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
209
|
+
.map(Conversions.route))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
func activityIntensity(_ request: RangeRequest) async throws -> WorkoutIntensityList {
|
|
213
|
+
WorkoutIntensityList(samples: try await terra.activity.intensity(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
214
|
+
.compactMap(Conversions.intensity))
|
|
215
|
+
}
|
|
216
|
+
|
|
182
217
|
func activityLog(_ request: WorkoutEntryList) async throws -> RecordIdList {
|
|
183
218
|
ids(try await terra.activity.log(entries(request.entries, Conversions.entry)))
|
|
184
219
|
}
|
|
@@ -190,6 +225,11 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
190
225
|
|
|
191
226
|
// MARK: nutrition
|
|
192
227
|
|
|
228
|
+
func nutritionQuantities(_ request: RangeRequest) async throws -> DietaryQuantityList {
|
|
229
|
+
DietaryQuantityList(samples: try await terra.nutrition.quantities(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
230
|
+
.compactMap(Conversions.quantity))
|
|
231
|
+
}
|
|
232
|
+
|
|
193
233
|
func nutritionLog(_ request: NutritionEntryList) async throws -> RecordIdList {
|
|
194
234
|
ids(try await terra.nutrition.log(entries(request.entries, Conversions.entry)))
|
|
195
235
|
}
|
|
@@ -199,6 +239,30 @@ struct TerraBridgeOpsImpl: TerraBridgeOps {
|
|
|
199
239
|
return Empty()
|
|
200
240
|
}
|
|
201
241
|
|
|
242
|
+
func nutritionHydration(_ request: RangeRequest) async throws -> HydrationList {
|
|
243
|
+
HydrationList(samples: try await terra.nutrition.hydration(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
244
|
+
.map(Conversions.hydration))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// MARK: menstruation
|
|
248
|
+
|
|
249
|
+
func menstruationFlow(_ request: RangeRequest) async throws -> MenstruationFlowDayList {
|
|
250
|
+
MenstruationFlowDayList(samples: try await terra.menstruation.flow(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
251
|
+
.map(Conversions.flowDay))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
func menstruationEvents(_ request: RangeRequest) async throws -> MenstrualEventList {
|
|
255
|
+
MenstrualEventList(samples: try await terra.menstruation.events(in: Conversions.timeRange(request.range, at: "$.range"))
|
|
256
|
+
.compactMap(Conversions.menstrualEvent))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// MARK: athlete
|
|
260
|
+
|
|
261
|
+
func athleteProfile(_ request: Empty) async throws -> AthleteProfileResult {
|
|
262
|
+
AthleteProfileResult(profile: try await terra.athlete.profile()
|
|
263
|
+
.map { Conversions.athleteProfile($0, zone: .current) })
|
|
264
|
+
}
|
|
265
|
+
|
|
202
266
|
// MARK: planned workouts
|
|
203
267
|
|
|
204
268
|
func plannedWorkoutsPlanned(_ request: Empty) async throws -> PlannedWorkoutList {
|
package/lib/Terra.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* here, so this file cannot quietly fall behind the surface the way the 1.x wrapper did.
|
|
9
9
|
*/
|
|
10
10
|
import { TerraError } from "./TerraError";
|
|
11
|
-
import type { BodyEntry, BootstrapState, Connection, DataCategory, DiagnosticsState, EndUser, HealthAccess, HealthChange, HeartRateEntry, HeartRateSample, NutritionEntry, PermissionStatus, PlannedWorkout, WorkoutSchedulingStatus, SleepEntry, SleepSegment, Source, StepCount, StepEntry, SupportReport, TerraEvent, TimeRange, Workout, WorkoutEntry } from "./generated/types";
|
|
11
|
+
import type { AthleteProfile, BodyEntry, BodyReading, BootstrapState, Connection, DataCategory, DayTotal, DiagnosticsState, DietaryQuantity, EndUser, HealthAccess, HealthChange, HeartRateEntry, HeartRateSample, HydrationSample, IntradaySample, MenstrualEvent, MenstruationFlowDay, NutritionEntry, PermissionStatus, PlannedWorkout, RestingHeartRateSample, StoreAvailability, WorkoutSchedulingStatus, SleepEntry, SleepSegment, Source, StepCount, StepEntry, SupportReport, TerraEvent, TimeRange, Workout, WorkoutEntry, WorkoutIntensitySample, WorkoutRoute } from "./generated/types";
|
|
12
12
|
/** What the facade needs from the native module. `NativeTerraBridge` satisfies it; so does a double. */
|
|
13
13
|
export interface NativeBridge {
|
|
14
14
|
invoke(op: string, payload: string): Promise<string>;
|
|
@@ -77,12 +77,31 @@ interface Changes {
|
|
|
77
77
|
}
|
|
78
78
|
export interface BodyMetrics extends Changes {
|
|
79
79
|
heartRate(range: TimeRange): Promise<readonly HeartRateSample[]>;
|
|
80
|
+
/**
|
|
81
|
+
* The store's own resting heart rate, one per day it computed, never derived from `heartRate`.
|
|
82
|
+
* Health Connect grants it under `daily`, so on Android ask for `daily` as well as `body`.
|
|
83
|
+
*/
|
|
84
|
+
restingHeartRate(range: TimeRange): Promise<readonly RestingHeartRateSample[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Weight, height, BMI (iOS), body fat, lean mass, heart rate variability, VO2 max, blood pressure,
|
|
87
|
+
* blood glucose, oxygen saturation, temperature and respiratory rate. Each reading's `kind` says
|
|
88
|
+
* what it measured and fixes the unit; blood glucose keeps the unit its writer chose.
|
|
89
|
+
*/
|
|
90
|
+
readings(range: TimeRange): Promise<readonly BodyReading[]>;
|
|
80
91
|
logHeartRate(entries: readonly HeartRateEntry[]): Promise<readonly string[]>;
|
|
81
92
|
log(entries: readonly BodyEntry[]): Promise<readonly string[]>;
|
|
82
93
|
delete(id: string): Promise<void>;
|
|
83
94
|
}
|
|
84
95
|
export interface DailyMetrics extends Changes {
|
|
85
96
|
steps(range: TimeRange): Promise<readonly StepCount[]>;
|
|
97
|
+
/**
|
|
98
|
+
* The store's own total for each whole local day the range touches: distance, active and basal
|
|
99
|
+
* energy, flights climbed and, on iOS, exercise minutes. A day with nothing recorded has no total
|
|
100
|
+
* rather than a zero. Steps are `steps`, which keeps writers apart.
|
|
101
|
+
*/
|
|
102
|
+
totals(range: TimeRange): Promise<readonly DayTotal[]>;
|
|
103
|
+
/** The active energy, basal energy (iOS), distance and floors records the totals are made of. */
|
|
104
|
+
intraday(range: TimeRange): Promise<readonly IntradaySample[]>;
|
|
86
105
|
log(entries: readonly StepEntry[]): Promise<readonly string[]>;
|
|
87
106
|
delete(id: string): Promise<void>;
|
|
88
107
|
}
|
|
@@ -93,14 +112,47 @@ export interface SleepMetrics extends Changes {
|
|
|
93
112
|
}
|
|
94
113
|
export interface ActivityMetrics extends Changes {
|
|
95
114
|
workouts(range: TimeRange): Promise<readonly Workout[]>;
|
|
115
|
+
/** The GPS tracks of the workouts in the range, each naming its workout by `workoutId`. */
|
|
116
|
+
routes(range: TimeRange): Promise<readonly WorkoutRoute[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Speed, power and cycling cadence readings in the range. iOS names each one's workout; Health
|
|
119
|
+
* Connect stores them free-standing, so on Android `workoutId` is absent and `start` equals `end`.
|
|
120
|
+
*/
|
|
121
|
+
intensity(range: TimeRange): Promise<readonly WorkoutIntensitySample[]>;
|
|
96
122
|
log(entries: readonly WorkoutEntry[]): Promise<readonly string[]>;
|
|
97
123
|
delete(id: string): Promise<void>;
|
|
98
124
|
}
|
|
99
|
-
/** Write-only on both platforms. */
|
|
100
125
|
export interface NutritionMetrics {
|
|
126
|
+
/**
|
|
127
|
+
* One quantity per nutrient a meal recorded in the range, not grouped into meals. Quantities of
|
|
128
|
+
* one meal share `foodCorrelationId`. Water is not among them. Each `value` is in its nutrient's
|
|
129
|
+
* fixed unit: kcal for `energyConsumed`, grams for the macronutrients, milligrams for the rest.
|
|
130
|
+
*/
|
|
131
|
+
quantities(range: TimeRange): Promise<readonly DietaryQuantity[]>;
|
|
132
|
+
/** Water drunk in the range, one sample per drink, in millilitres. */
|
|
133
|
+
hydration(range: TimeRange): Promise<readonly HydrationSample[]>;
|
|
101
134
|
log(entries: readonly NutritionEntry[]): Promise<readonly string[]>;
|
|
102
135
|
delete(id: string): Promise<void>;
|
|
103
136
|
}
|
|
137
|
+
/** Read-only on both platforms: neither SDK has a write route for menstruation. */
|
|
138
|
+
export interface MenstruationMetrics {
|
|
139
|
+
/**
|
|
140
|
+
* Logged flow days overlapping the range, one per day, each bounded by that day's local
|
|
141
|
+
* midnights. A day logged with no flow comes back as `none` rather than being left out.
|
|
142
|
+
*/
|
|
143
|
+
flow(range: TimeRange): Promise<readonly MenstruationFlowDay[]>;
|
|
144
|
+
/** Ovulation tests, cervical mucus and intermenstrual bleeding logged in the range. */
|
|
145
|
+
events(range: TimeRange): Promise<readonly MenstrualEvent[]>;
|
|
146
|
+
}
|
|
147
|
+
/** iOS only, and read-only. */
|
|
148
|
+
export interface AthleteMetrics {
|
|
149
|
+
/**
|
|
150
|
+
* HealthKit's characteristics as one snapshot. No range, because characteristics have no
|
|
151
|
+
* history. `undefined` where the store has no notion of them at all; a store that has them and
|
|
152
|
+
* holds no values answers a profile whose `biologicalSex` is `notSet`.
|
|
153
|
+
*/
|
|
154
|
+
profile(): Promise<AthleteProfile | undefined>;
|
|
155
|
+
}
|
|
104
156
|
/** iOS only. */
|
|
105
157
|
export interface PlannedWorkouts {
|
|
106
158
|
planned(): Promise<readonly PlannedWorkout[]>;
|
|
@@ -124,12 +176,20 @@ export declare class Terra {
|
|
|
124
176
|
readonly sleep: SleepMetrics;
|
|
125
177
|
readonly activity: ActivityMetrics;
|
|
126
178
|
readonly nutrition: NutritionMetrics;
|
|
179
|
+
readonly menstruation: MenstruationMetrics;
|
|
180
|
+
/** `undefined` on Android, which has no characteristics to read. */
|
|
181
|
+
readonly athlete: AthleteMetrics | undefined;
|
|
127
182
|
/** `undefined` on Android, which has no planned workouts. */
|
|
128
183
|
readonly plannedWorkouts: PlannedWorkouts | undefined;
|
|
129
184
|
private readonly ops;
|
|
130
185
|
private readonly streams;
|
|
131
|
-
|
|
132
|
-
private readonly
|
|
186
|
+
private readonly tokenProvider;
|
|
187
|
+
private readonly tokenTimeoutMs;
|
|
188
|
+
/**
|
|
189
|
+
* The provider's registration, in flight or done. `undefined` before it starts and after it failed
|
|
190
|
+
* or its stream ended, so the next connect that needs it registers again.
|
|
191
|
+
*/
|
|
192
|
+
private tokenSetup;
|
|
133
193
|
constructor(native: NativeBridge, platform: Platform, options?: TerraOptions);
|
|
134
194
|
/**
|
|
135
195
|
* The native client this runtime is attached to. Rejects with `bridge.not_bootstrapped`, carrying
|
|
@@ -148,7 +208,16 @@ export declare class Terra {
|
|
|
148
208
|
}): Promise<Connection>;
|
|
149
209
|
connections(): Promise<readonly Connection[]>;
|
|
150
210
|
disconnect(source?: Source): Promise<void>;
|
|
151
|
-
/**
|
|
211
|
+
/**
|
|
212
|
+
* Whether the device can serve `source` at all, and if not, why. Needs no connection and prompts
|
|
213
|
+
* for nothing, so an app can decide whether to offer the link before `connect`. iOS answers
|
|
214
|
+
* `available` or `unsupportedDevice`; Android names the reason, such as `providerAppMissing` when
|
|
215
|
+
* Health Connect needs installing.
|
|
216
|
+
*/
|
|
217
|
+
availability(source?: Source): Promise<StoreAvailability>;
|
|
218
|
+
/** Settles once the token provider, when there is one, can be asked. */
|
|
219
|
+
private tokensReady;
|
|
220
|
+
/** Answers the native broker's token requests with `provider` until the stream ends. */
|
|
152
221
|
private answerTokens;
|
|
153
222
|
}
|
|
154
223
|
export {};
|
package/lib/Terra.js
CHANGED
|
@@ -21,11 +21,11 @@ class Terra {
|
|
|
21
21
|
const ops = new client_1.GeneratedClient(invoker);
|
|
22
22
|
this.ops = ops;
|
|
23
23
|
this.streams = new StreamHub(native);
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
//
|
|
28
|
-
this.tokensReady
|
|
24
|
+
this.tokenProvider = options.tokenProvider;
|
|
25
|
+
this.tokenTimeoutMs = options.tokenTimeoutMs ?? 30000;
|
|
26
|
+
// Registered now, so the provider is in place before anything asks. A failure here is reported
|
|
27
|
+
// to the connect that needs the provider, which also tries again.
|
|
28
|
+
void this.tokensReady();
|
|
29
29
|
const on = (op) => ops_1.availableOn[op].includes(platform);
|
|
30
30
|
const stream = (id) => (listener, onError) => this.streams.subscribe(id, listener, onError);
|
|
31
31
|
this.permissions = {
|
|
@@ -66,6 +66,8 @@ class Terra {
|
|
|
66
66
|
};
|
|
67
67
|
this.body = {
|
|
68
68
|
heartRate: async (range) => (await ops.bodyHeartRate({ range })).samples,
|
|
69
|
+
restingHeartRate: async (range) => (await ops.bodyRestingHeartRate({ range })).samples,
|
|
70
|
+
readings: async (range) => (await ops.bodyReadings({ range })).samples,
|
|
69
71
|
logHeartRate: async (entries) => (await ops.bodyLogHeartRate({ entries })).ids,
|
|
70
72
|
log: async (entries) => (await ops.bodyLog({ entries })).ids,
|
|
71
73
|
delete: async (id) => {
|
|
@@ -75,6 +77,8 @@ class Terra {
|
|
|
75
77
|
};
|
|
76
78
|
this.daily = {
|
|
77
79
|
steps: async (range) => (await ops.dailySteps({ range })).samples,
|
|
80
|
+
totals: async (range) => (await ops.dailyTotals({ range })).totals,
|
|
81
|
+
intraday: async (range) => (await ops.dailyIntraday({ range })).samples,
|
|
78
82
|
log: async (entries) => (await ops.dailyLog({ entries })).ids,
|
|
79
83
|
delete: async (id) => {
|
|
80
84
|
await ops.dailyDelete({ id });
|
|
@@ -91,6 +95,8 @@ class Terra {
|
|
|
91
95
|
};
|
|
92
96
|
this.activity = {
|
|
93
97
|
workouts: async (range) => (await ops.activityWorkouts({ range })).samples,
|
|
98
|
+
routes: async (range) => (await ops.activityRoutes({ range })).routes,
|
|
99
|
+
intensity: async (range) => (await ops.activityIntensity({ range })).samples,
|
|
94
100
|
log: async (entries) => (await ops.activityLog({ entries })).ids,
|
|
95
101
|
delete: async (id) => {
|
|
96
102
|
await ops.activityDelete({ id });
|
|
@@ -98,11 +104,20 @@ class Terra {
|
|
|
98
104
|
changes: stream("activity.changes"),
|
|
99
105
|
};
|
|
100
106
|
this.nutrition = {
|
|
107
|
+
quantities: async (range) => (await ops.nutritionQuantities({ range })).samples,
|
|
108
|
+
hydration: async (range) => (await ops.nutritionHydration({ range })).samples,
|
|
101
109
|
log: async (entries) => (await ops.nutritionLog({ entries })).ids,
|
|
102
110
|
delete: async (id) => {
|
|
103
111
|
await ops.nutritionDelete({ id });
|
|
104
112
|
},
|
|
105
113
|
};
|
|
114
|
+
this.menstruation = {
|
|
115
|
+
flow: async (range) => (await ops.menstruationFlow({ range })).samples,
|
|
116
|
+
events: async (range) => (await ops.menstruationEvents({ range })).samples,
|
|
117
|
+
};
|
|
118
|
+
this.athlete = on("athlete.profile")
|
|
119
|
+
? { profile: async () => (await ops.athleteProfile({})).profile }
|
|
120
|
+
: undefined;
|
|
106
121
|
this.plannedWorkouts = on("plannedWorkouts.planned")
|
|
107
122
|
? {
|
|
108
123
|
planned: async () => (await ops.plannedWorkoutsPlanned({})).workouts,
|
|
@@ -132,7 +147,9 @@ class Terra {
|
|
|
132
147
|
* the `tokenProvider` is asked, and only when a new link is actually needed.
|
|
133
148
|
*/
|
|
134
149
|
async connect(source, endUser, options = {}) {
|
|
135
|
-
|
|
150
|
+
// A connect that carries its token never asks the provider, so it does not wait on it either.
|
|
151
|
+
if (options.token === undefined)
|
|
152
|
+
await this.tokensReady();
|
|
136
153
|
return this.ops.clientConnect({ source, endUser, token: options.token });
|
|
137
154
|
}
|
|
138
155
|
async connections() {
|
|
@@ -141,9 +158,36 @@ class Terra {
|
|
|
141
158
|
async disconnect(source) {
|
|
142
159
|
await this.ops.clientDisconnect({ source });
|
|
143
160
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Whether the device can serve `source` at all, and if not, why. Needs no connection and prompts
|
|
163
|
+
* for nothing, so an app can decide whether to offer the link before `connect`. iOS answers
|
|
164
|
+
* `available` or `unsupportedDevice`; Android names the reason, such as `providerAppMissing` when
|
|
165
|
+
* Health Connect needs installing.
|
|
166
|
+
*/
|
|
167
|
+
async availability(source) {
|
|
168
|
+
return (await this.ops.clientAvailability({ source })).availability;
|
|
169
|
+
}
|
|
170
|
+
/** Settles once the token provider, when there is one, can be asked. */
|
|
171
|
+
tokensReady() {
|
|
172
|
+
const provider = this.tokenProvider;
|
|
173
|
+
if (provider === undefined)
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
if (this.tokenSetup !== undefined)
|
|
176
|
+
return this.tokenSetup;
|
|
177
|
+
const forget = () => {
|
|
178
|
+
if (this.tokenSetup === setup)
|
|
179
|
+
this.tokenSetup = undefined;
|
|
180
|
+
};
|
|
181
|
+
const setup = this.answerTokens(provider, this.tokenTimeoutMs, forget);
|
|
182
|
+
this.tokenSetup = setup;
|
|
183
|
+
// Handles the failure, so it is never an unhandled rejection, and forgets the setup so the next
|
|
184
|
+
// connect tries again rather than rethrowing a failure that may have passed.
|
|
185
|
+
setup.catch(forget);
|
|
186
|
+
return setup;
|
|
187
|
+
}
|
|
188
|
+
/** Answers the native broker's token requests with `provider` until the stream ends. */
|
|
189
|
+
async answerTokens(provider, timeoutMs, onEnd) {
|
|
190
|
+
const id = await this.streams.open("bridge.tokenRequest", ({ requestId }) => {
|
|
147
191
|
void (async () => {
|
|
148
192
|
let token;
|
|
149
193
|
try {
|
|
@@ -156,8 +200,15 @@ class Terra {
|
|
|
156
200
|
}
|
|
157
201
|
await this.ops.tokenResolve({ requestId, token }).catch(() => undefined);
|
|
158
202
|
})();
|
|
159
|
-
});
|
|
160
|
-
|
|
203
|
+
}, undefined, onEnd);
|
|
204
|
+
try {
|
|
205
|
+
await this.ops.tokenSetProviderPresent({ present: true, timeoutMs });
|
|
206
|
+
}
|
|
207
|
+
catch (failed) {
|
|
208
|
+
// A provider the broker was never told about answers nothing, so its stream is let go of.
|
|
209
|
+
this.streams.close(id);
|
|
210
|
+
throw failed;
|
|
211
|
+
}
|
|
161
212
|
}
|
|
162
213
|
}
|
|
163
214
|
exports.Terra = Terra;
|
|
@@ -168,8 +219,11 @@ class StreamHub {
|
|
|
168
219
|
this.early = new Map();
|
|
169
220
|
this.pending = 0;
|
|
170
221
|
}
|
|
171
|
-
/**
|
|
172
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Subscribes and answers once the native stream is open. `onEnd` runs once the stream ends from the
|
|
224
|
+
* native side, by error or by completing; not when it is closed from here.
|
|
225
|
+
*/
|
|
226
|
+
async open(stream, listener, onError, onEnd) {
|
|
173
227
|
this.listen();
|
|
174
228
|
this.pending += 1;
|
|
175
229
|
let id;
|
|
@@ -189,9 +243,11 @@ class StreamHub {
|
|
|
189
243
|
case "error":
|
|
190
244
|
this.ended(id);
|
|
191
245
|
onError?.(new TerraError_1.TerraError(delivery.error));
|
|
246
|
+
onEnd?.();
|
|
192
247
|
return;
|
|
193
248
|
case "complete":
|
|
194
249
|
this.ended(id);
|
|
250
|
+
onEnd?.();
|
|
195
251
|
return;
|
|
196
252
|
}
|
|
197
253
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BodyEntryList, BoolResult, BootstrapState, CategoryRequest, ConnectRequest, Connection, ConnectionList, DiagnosticsState, Empty, HealthAccess, HeartRateEntryList, HeartRateList, LastDeliveryResult, MarkCompleteRequest, NutritionEntryList, PermissionGrant, PermissionRequest, PlannedWorkoutList, RangeRequest, RecordIdList, RecordIdRequest, SleepEntryList, SleepSegmentList, SourceRequest, StatusResult, StatusesResult, StepCountList, StepEntryList, SupportReport, TokenProviderRequest, TokenRejectRequest, TokenResolveRequest, WorkoutEntryList, WorkoutList, WorkoutScheduling } from "./types";
|
|
1
|
+
import type { AthleteProfileResult, AvailabilityResult, BodyEntryList, BodyReadingList, BoolResult, BootstrapState, CategoryRequest, ConnectRequest, Connection, ConnectionList, DayTotalList, DiagnosticsState, DietaryQuantityList, Empty, HealthAccess, HeartRateEntryList, HeartRateList, HydrationList, IntradaySampleList, LastDeliveryResult, MarkCompleteRequest, MenstrualEventList, MenstruationFlowDayList, NutritionEntryList, PermissionGrant, PermissionRequest, PlannedWorkoutList, RangeRequest, RecordIdList, RecordIdRequest, RestingHeartRateList, SleepEntryList, SleepSegmentList, SourceRequest, StatusResult, StatusesResult, StepCountList, StepEntryList, SupportReport, TokenProviderRequest, TokenRejectRequest, TokenResolveRequest, WorkoutEntryList, WorkoutIntensityList, WorkoutList, WorkoutRouteList, WorkoutScheduling } from "./types";
|
|
2
2
|
import type { OpId } from "./ops";
|
|
3
3
|
/** What the facade needs from the native module. The TurboModule spec satisfies it; so does a test double. */
|
|
4
4
|
export interface Invoker {
|
|
@@ -21,6 +21,8 @@ export declare class GeneratedClient {
|
|
|
21
21
|
clientAttach(request: Empty): Promise<BootstrapState>;
|
|
22
22
|
clientConnect(request: ConnectRequest): Promise<Connection>;
|
|
23
23
|
clientConnections(request: Empty): Promise<ConnectionList>;
|
|
24
|
+
/** Whether the device can serve the source at all. Needs no connection and prompts for nothing. */
|
|
25
|
+
clientAvailability(request: SourceRequest): Promise<AvailabilityResult>;
|
|
24
26
|
clientDisconnect(request: SourceRequest): Promise<Empty>;
|
|
25
27
|
/**
|
|
26
28
|
* Tells the broker whether JS can answer at all. Without it a link asks a provider that is not
|
|
@@ -55,21 +57,55 @@ export declare class GeneratedClient {
|
|
|
55
57
|
/** Available on android only. */
|
|
56
58
|
backgroundCancel(request: Empty): Promise<BoolResult>;
|
|
57
59
|
bodyHeartRate(request: RangeRequest): Promise<HeartRateList>;
|
|
60
|
+
/**
|
|
61
|
+
* The store's own daily figures. Health Connect grants them under daily, so Android answers
|
|
62
|
+
* authorization_not_determined where only body was asked for.
|
|
63
|
+
*/
|
|
64
|
+
bodyRestingHeartRate(request: RangeRequest): Promise<RestingHeartRateList>;
|
|
65
|
+
bodyReadings(request: RangeRequest): Promise<BodyReadingList>;
|
|
58
66
|
/** Its own op rather than an overload: Swift overloads log by element type and JS cannot. */
|
|
59
67
|
bodyLogHeartRate(request: HeartRateEntryList): Promise<RecordIdList>;
|
|
60
68
|
bodyLog(request: BodyEntryList): Promise<RecordIdList>;
|
|
61
69
|
bodyDelete(request: RecordIdRequest): Promise<Empty>;
|
|
62
70
|
dailySteps(request: RangeRequest): Promise<StepCountList>;
|
|
71
|
+
/**
|
|
72
|
+
* One total per metric per whole local day the range touches. A day with nothing recorded has
|
|
73
|
+
* no total, not a zero.
|
|
74
|
+
*/
|
|
75
|
+
dailyTotals(request: RangeRequest): Promise<DayTotalList>;
|
|
76
|
+
/** The active energy, basal energy, distance and floors records the totals are made of. */
|
|
77
|
+
dailyIntraday(request: RangeRequest): Promise<IntradaySampleList>;
|
|
63
78
|
dailyLog(request: StepEntryList): Promise<RecordIdList>;
|
|
64
79
|
dailyDelete(request: RecordIdRequest): Promise<Empty>;
|
|
65
80
|
sleepSegments(request: RangeRequest): Promise<SleepSegmentList>;
|
|
66
81
|
sleepLog(request: SleepEntryList): Promise<RecordIdList>;
|
|
67
82
|
sleepDelete(request: RecordIdRequest): Promise<Empty>;
|
|
68
83
|
activityWorkouts(request: RangeRequest): Promise<WorkoutList>;
|
|
84
|
+
/** The GPS tracks of the workouts in the range, each naming its workout. */
|
|
85
|
+
activityRoutes(request: RangeRequest): Promise<WorkoutRouteList>;
|
|
86
|
+
/** Speed, power and cycling cadence readings in the range. */
|
|
87
|
+
activityIntensity(request: RangeRequest): Promise<WorkoutIntensityList>;
|
|
69
88
|
activityLog(request: WorkoutEntryList): Promise<RecordIdList>;
|
|
70
89
|
activityDelete(request: RecordIdRequest): Promise<Empty>;
|
|
90
|
+
/** One quantity per nutrient a meal recorded, not grouped into meals. Water is not among them. */
|
|
91
|
+
nutritionQuantities(request: RangeRequest): Promise<DietaryQuantityList>;
|
|
71
92
|
nutritionLog(request: NutritionEntryList): Promise<RecordIdList>;
|
|
72
93
|
nutritionDelete(request: RecordIdRequest): Promise<Empty>;
|
|
94
|
+
/** Water drunk in the range, one sample per drink. */
|
|
95
|
+
nutritionHydration(request: RangeRequest): Promise<HydrationList>;
|
|
96
|
+
/**
|
|
97
|
+
* Logged flow days overlapping the range, one per day. A day logged with no flow is noFlow,
|
|
98
|
+
* not left out.
|
|
99
|
+
*/
|
|
100
|
+
menstruationFlow(request: RangeRequest): Promise<MenstruationFlowDayList>;
|
|
101
|
+
/** Ovulation tests, cervical mucus and intermenstrual bleeding logged in the range. */
|
|
102
|
+
menstruationEvents(request: RangeRequest): Promise<MenstrualEventList>;
|
|
103
|
+
/**
|
|
104
|
+
* Characteristics have no history.
|
|
105
|
+
*
|
|
106
|
+
* Available on ios only.
|
|
107
|
+
*/
|
|
108
|
+
athleteProfile(request: Empty): Promise<AthleteProfileResult>;
|
|
73
109
|
/** Available on ios only. */
|
|
74
110
|
plannedWorkoutsPlanned(request: Empty): Promise<PlannedWorkoutList>;
|
|
75
111
|
/** Available on ios only. */
|
package/lib/generated/client.js
CHANGED
|
@@ -50,6 +50,10 @@ class GeneratedClient {
|
|
|
50
50
|
async clientConnections(request) {
|
|
51
51
|
return this.call("client.connections", () => (0, codecs_1.encodeEmpty)(request), (raw) => (0, codecs_1.decodeConnectionList)(raw, "ConnectionList"));
|
|
52
52
|
}
|
|
53
|
+
/** Whether the device can serve the source at all. Needs no connection and prompts for nothing. */
|
|
54
|
+
async clientAvailability(request) {
|
|
55
|
+
return this.call("client.availability", () => (0, codecs_1.encodeSourceRequest)(request), (raw) => (0, codecs_1.decodeAvailabilityResult)(raw, "AvailabilityResult"));
|
|
56
|
+
}
|
|
53
57
|
async clientDisconnect(request) {
|
|
54
58
|
return this.call("client.disconnect", () => (0, codecs_1.encodeSourceRequest)(request), (raw) => (0, codecs_1.decodeEmpty)(raw, "Empty"));
|
|
55
59
|
}
|
|
@@ -120,6 +124,16 @@ class GeneratedClient {
|
|
|
120
124
|
async bodyHeartRate(request) {
|
|
121
125
|
return this.call("body.heartRate", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeHeartRateList)(raw, "HeartRateList"));
|
|
122
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* The store's own daily figures. Health Connect grants them under daily, so Android answers
|
|
129
|
+
* authorization_not_determined where only body was asked for.
|
|
130
|
+
*/
|
|
131
|
+
async bodyRestingHeartRate(request) {
|
|
132
|
+
return this.call("body.restingHeartRate", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeRestingHeartRateList)(raw, "RestingHeartRateList"));
|
|
133
|
+
}
|
|
134
|
+
async bodyReadings(request) {
|
|
135
|
+
return this.call("body.readings", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeBodyReadingList)(raw, "BodyReadingList"));
|
|
136
|
+
}
|
|
123
137
|
/** Its own op rather than an overload: Swift overloads log by element type and JS cannot. */
|
|
124
138
|
async bodyLogHeartRate(request) {
|
|
125
139
|
return this.call("body.logHeartRate", () => (0, codecs_1.encodeHeartRateEntryList)(request), (raw) => (0, codecs_1.decodeRecordIdList)(raw, "RecordIdList"));
|
|
@@ -133,6 +147,17 @@ class GeneratedClient {
|
|
|
133
147
|
async dailySteps(request) {
|
|
134
148
|
return this.call("daily.steps", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeStepCountList)(raw, "StepCountList"));
|
|
135
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* One total per metric per whole local day the range touches. A day with nothing recorded has
|
|
152
|
+
* no total, not a zero.
|
|
153
|
+
*/
|
|
154
|
+
async dailyTotals(request) {
|
|
155
|
+
return this.call("daily.totals", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeDayTotalList)(raw, "DayTotalList"));
|
|
156
|
+
}
|
|
157
|
+
/** The active energy, basal energy, distance and floors records the totals are made of. */
|
|
158
|
+
async dailyIntraday(request) {
|
|
159
|
+
return this.call("daily.intraday", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeIntradaySampleList)(raw, "IntradaySampleList"));
|
|
160
|
+
}
|
|
136
161
|
async dailyLog(request) {
|
|
137
162
|
return this.call("daily.log", () => (0, codecs_1.encodeStepEntryList)(request), (raw) => (0, codecs_1.decodeRecordIdList)(raw, "RecordIdList"));
|
|
138
163
|
}
|
|
@@ -151,18 +176,53 @@ class GeneratedClient {
|
|
|
151
176
|
async activityWorkouts(request) {
|
|
152
177
|
return this.call("activity.workouts", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeWorkoutList)(raw, "WorkoutList"));
|
|
153
178
|
}
|
|
179
|
+
/** The GPS tracks of the workouts in the range, each naming its workout. */
|
|
180
|
+
async activityRoutes(request) {
|
|
181
|
+
return this.call("activity.routes", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeWorkoutRouteList)(raw, "WorkoutRouteList"));
|
|
182
|
+
}
|
|
183
|
+
/** Speed, power and cycling cadence readings in the range. */
|
|
184
|
+
async activityIntensity(request) {
|
|
185
|
+
return this.call("activity.intensity", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeWorkoutIntensityList)(raw, "WorkoutIntensityList"));
|
|
186
|
+
}
|
|
154
187
|
async activityLog(request) {
|
|
155
188
|
return this.call("activity.log", () => (0, codecs_1.encodeWorkoutEntryList)(request), (raw) => (0, codecs_1.decodeRecordIdList)(raw, "RecordIdList"));
|
|
156
189
|
}
|
|
157
190
|
async activityDelete(request) {
|
|
158
191
|
return this.call("activity.delete", () => (0, codecs_1.encodeRecordIdRequest)(request), (raw) => (0, codecs_1.decodeEmpty)(raw, "Empty"));
|
|
159
192
|
}
|
|
193
|
+
/** One quantity per nutrient a meal recorded, not grouped into meals. Water is not among them. */
|
|
194
|
+
async nutritionQuantities(request) {
|
|
195
|
+
return this.call("nutrition.quantities", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeDietaryQuantityList)(raw, "DietaryQuantityList"));
|
|
196
|
+
}
|
|
160
197
|
async nutritionLog(request) {
|
|
161
198
|
return this.call("nutrition.log", () => (0, codecs_1.encodeNutritionEntryList)(request), (raw) => (0, codecs_1.decodeRecordIdList)(raw, "RecordIdList"));
|
|
162
199
|
}
|
|
163
200
|
async nutritionDelete(request) {
|
|
164
201
|
return this.call("nutrition.delete", () => (0, codecs_1.encodeRecordIdRequest)(request), (raw) => (0, codecs_1.decodeEmpty)(raw, "Empty"));
|
|
165
202
|
}
|
|
203
|
+
/** Water drunk in the range, one sample per drink. */
|
|
204
|
+
async nutritionHydration(request) {
|
|
205
|
+
return this.call("nutrition.hydration", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeHydrationList)(raw, "HydrationList"));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Logged flow days overlapping the range, one per day. A day logged with no flow is noFlow,
|
|
209
|
+
* not left out.
|
|
210
|
+
*/
|
|
211
|
+
async menstruationFlow(request) {
|
|
212
|
+
return this.call("menstruation.flow", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeMenstruationFlowDayList)(raw, "MenstruationFlowDayList"));
|
|
213
|
+
}
|
|
214
|
+
/** Ovulation tests, cervical mucus and intermenstrual bleeding logged in the range. */
|
|
215
|
+
async menstruationEvents(request) {
|
|
216
|
+
return this.call("menstruation.events", () => (0, codecs_1.encodeRangeRequest)(request), (raw) => (0, codecs_1.decodeMenstrualEventList)(raw, "MenstrualEventList"));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Characteristics have no history.
|
|
220
|
+
*
|
|
221
|
+
* Available on ios only.
|
|
222
|
+
*/
|
|
223
|
+
async athleteProfile(request) {
|
|
224
|
+
return this.call("athlete.profile", () => (0, codecs_1.encodeEmpty)(request), (raw) => (0, codecs_1.decodeAthleteProfileResult)(raw, "AthleteProfileResult"));
|
|
225
|
+
}
|
|
166
226
|
/** Available on ios only. */
|
|
167
227
|
async plannedWorkoutsPlanned(request) {
|
|
168
228
|
return this.call("plannedWorkouts.planned", () => (0, codecs_1.encodeEmpty)(request), (raw) => (0, codecs_1.decodePlannedWorkoutList)(raw, "PlannedWorkoutList"));
|