@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
package/src/Terra.ts
CHANGED
|
@@ -13,19 +13,29 @@ import { GeneratedClient, type Invoker } from "./generated/client";
|
|
|
13
13
|
import { availableOn, type OpId, type StreamId } from "./generated/ops";
|
|
14
14
|
import { decodeStreamDelivery, type StreamElements } from "./generated/streams";
|
|
15
15
|
import type {
|
|
16
|
+
AthleteProfile,
|
|
16
17
|
BodyEntry,
|
|
18
|
+
BodyReading,
|
|
17
19
|
BootstrapState,
|
|
18
20
|
Connection,
|
|
19
21
|
DataCategory,
|
|
22
|
+
DayTotal,
|
|
20
23
|
DiagnosticsState,
|
|
24
|
+
DietaryQuantity,
|
|
21
25
|
EndUser,
|
|
22
26
|
HealthAccess,
|
|
23
27
|
HealthChange,
|
|
24
28
|
HeartRateEntry,
|
|
25
29
|
HeartRateSample,
|
|
30
|
+
HydrationSample,
|
|
31
|
+
IntradaySample,
|
|
32
|
+
MenstrualEvent,
|
|
33
|
+
MenstruationFlowDay,
|
|
26
34
|
NutritionEntry,
|
|
27
35
|
PermissionStatus,
|
|
28
36
|
PlannedWorkout,
|
|
37
|
+
RestingHeartRateSample,
|
|
38
|
+
StoreAvailability,
|
|
29
39
|
WorkoutSchedulingStatus,
|
|
30
40
|
SleepEntry,
|
|
31
41
|
SleepSegment,
|
|
@@ -37,6 +47,8 @@ import type {
|
|
|
37
47
|
TimeRange,
|
|
38
48
|
Workout,
|
|
39
49
|
WorkoutEntry,
|
|
50
|
+
WorkoutIntensitySample,
|
|
51
|
+
WorkoutRoute,
|
|
40
52
|
WorkoutScope,
|
|
41
53
|
} from "./generated/types";
|
|
42
54
|
|
|
@@ -113,6 +125,17 @@ interface Changes {
|
|
|
113
125
|
|
|
114
126
|
export interface BodyMetrics extends Changes {
|
|
115
127
|
heartRate(range: TimeRange): Promise<readonly HeartRateSample[]>;
|
|
128
|
+
/**
|
|
129
|
+
* The store's own resting heart rate, one per day it computed, never derived from `heartRate`.
|
|
130
|
+
* Health Connect grants it under `daily`, so on Android ask for `daily` as well as `body`.
|
|
131
|
+
*/
|
|
132
|
+
restingHeartRate(range: TimeRange): Promise<readonly RestingHeartRateSample[]>;
|
|
133
|
+
/**
|
|
134
|
+
* Weight, height, BMI (iOS), body fat, lean mass, heart rate variability, VO2 max, blood pressure,
|
|
135
|
+
* blood glucose, oxygen saturation, temperature and respiratory rate. Each reading's `kind` says
|
|
136
|
+
* what it measured and fixes the unit; blood glucose keeps the unit its writer chose.
|
|
137
|
+
*/
|
|
138
|
+
readings(range: TimeRange): Promise<readonly BodyReading[]>;
|
|
116
139
|
logHeartRate(entries: readonly HeartRateEntry[]): Promise<readonly string[]>;
|
|
117
140
|
log(entries: readonly BodyEntry[]): Promise<readonly string[]>;
|
|
118
141
|
delete(id: string): Promise<void>;
|
|
@@ -120,6 +143,14 @@ export interface BodyMetrics extends Changes {
|
|
|
120
143
|
|
|
121
144
|
export interface DailyMetrics extends Changes {
|
|
122
145
|
steps(range: TimeRange): Promise<readonly StepCount[]>;
|
|
146
|
+
/**
|
|
147
|
+
* The store's own total for each whole local day the range touches: distance, active and basal
|
|
148
|
+
* energy, flights climbed and, on iOS, exercise minutes. A day with nothing recorded has no total
|
|
149
|
+
* rather than a zero. Steps are `steps`, which keeps writers apart.
|
|
150
|
+
*/
|
|
151
|
+
totals(range: TimeRange): Promise<readonly DayTotal[]>;
|
|
152
|
+
/** The active energy, basal energy (iOS), distance and floors records the totals are made of. */
|
|
153
|
+
intraday(range: TimeRange): Promise<readonly IntradaySample[]>;
|
|
123
154
|
log(entries: readonly StepEntry[]): Promise<readonly string[]>;
|
|
124
155
|
delete(id: string): Promise<void>;
|
|
125
156
|
}
|
|
@@ -132,16 +163,51 @@ export interface SleepMetrics extends Changes {
|
|
|
132
163
|
|
|
133
164
|
export interface ActivityMetrics extends Changes {
|
|
134
165
|
workouts(range: TimeRange): Promise<readonly Workout[]>;
|
|
166
|
+
/** The GPS tracks of the workouts in the range, each naming its workout by `workoutId`. */
|
|
167
|
+
routes(range: TimeRange): Promise<readonly WorkoutRoute[]>;
|
|
168
|
+
/**
|
|
169
|
+
* Speed, power and cycling cadence readings in the range. iOS names each one's workout; Health
|
|
170
|
+
* Connect stores them free-standing, so on Android `workoutId` is absent and `start` equals `end`.
|
|
171
|
+
*/
|
|
172
|
+
intensity(range: TimeRange): Promise<readonly WorkoutIntensitySample[]>;
|
|
135
173
|
log(entries: readonly WorkoutEntry[]): Promise<readonly string[]>;
|
|
136
174
|
delete(id: string): Promise<void>;
|
|
137
175
|
}
|
|
138
176
|
|
|
139
|
-
/** Write-only on both platforms. */
|
|
140
177
|
export interface NutritionMetrics {
|
|
178
|
+
/**
|
|
179
|
+
* One quantity per nutrient a meal recorded in the range, not grouped into meals. Quantities of
|
|
180
|
+
* one meal share `foodCorrelationId`. Water is not among them. Each `value` is in its nutrient's
|
|
181
|
+
* fixed unit: kcal for `energyConsumed`, grams for the macronutrients, milligrams for the rest.
|
|
182
|
+
*/
|
|
183
|
+
quantities(range: TimeRange): Promise<readonly DietaryQuantity[]>;
|
|
184
|
+
/** Water drunk in the range, one sample per drink, in millilitres. */
|
|
185
|
+
hydration(range: TimeRange): Promise<readonly HydrationSample[]>;
|
|
141
186
|
log(entries: readonly NutritionEntry[]): Promise<readonly string[]>;
|
|
142
187
|
delete(id: string): Promise<void>;
|
|
143
188
|
}
|
|
144
189
|
|
|
190
|
+
/** Read-only on both platforms: neither SDK has a write route for menstruation. */
|
|
191
|
+
export interface MenstruationMetrics {
|
|
192
|
+
/**
|
|
193
|
+
* Logged flow days overlapping the range, one per day, each bounded by that day's local
|
|
194
|
+
* midnights. A day logged with no flow comes back as `none` rather than being left out.
|
|
195
|
+
*/
|
|
196
|
+
flow(range: TimeRange): Promise<readonly MenstruationFlowDay[]>;
|
|
197
|
+
/** Ovulation tests, cervical mucus and intermenstrual bleeding logged in the range. */
|
|
198
|
+
events(range: TimeRange): Promise<readonly MenstrualEvent[]>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** iOS only, and read-only. */
|
|
202
|
+
export interface AthleteMetrics {
|
|
203
|
+
/**
|
|
204
|
+
* HealthKit's characteristics as one snapshot. No range, because characteristics have no
|
|
205
|
+
* history. `undefined` where the store has no notion of them at all; a store that has them and
|
|
206
|
+
* holds no values answers a profile whose `biologicalSex` is `notSet`.
|
|
207
|
+
*/
|
|
208
|
+
profile(): Promise<AthleteProfile | undefined>;
|
|
209
|
+
}
|
|
210
|
+
|
|
145
211
|
/** iOS only. */
|
|
146
212
|
export interface PlannedWorkouts {
|
|
147
213
|
planned(): Promise<readonly PlannedWorkout[]>;
|
|
@@ -166,13 +232,21 @@ export class Terra {
|
|
|
166
232
|
readonly sleep: SleepMetrics;
|
|
167
233
|
readonly activity: ActivityMetrics;
|
|
168
234
|
readonly nutrition: NutritionMetrics;
|
|
235
|
+
readonly menstruation: MenstruationMetrics;
|
|
236
|
+
/** `undefined` on Android, which has no characteristics to read. */
|
|
237
|
+
readonly athlete: AthleteMetrics | undefined;
|
|
169
238
|
/** `undefined` on Android, which has no planned workouts. */
|
|
170
239
|
readonly plannedWorkouts: PlannedWorkouts | undefined;
|
|
171
240
|
|
|
172
241
|
private readonly ops: GeneratedClient;
|
|
173
242
|
private readonly streams: StreamHub;
|
|
174
|
-
|
|
175
|
-
private readonly
|
|
243
|
+
private readonly tokenProvider: TokenProvider | undefined;
|
|
244
|
+
private readonly tokenTimeoutMs: number;
|
|
245
|
+
/**
|
|
246
|
+
* The provider's registration, in flight or done. `undefined` before it starts and after it failed
|
|
247
|
+
* or its stream ended, so the next connect that needs it registers again.
|
|
248
|
+
*/
|
|
249
|
+
private tokenSetup: Promise<void> | undefined;
|
|
176
250
|
|
|
177
251
|
constructor(native: NativeBridge, platform: Platform, options: TerraOptions = {}) {
|
|
178
252
|
this.platform = platform;
|
|
@@ -180,11 +254,11 @@ export class Terra {
|
|
|
180
254
|
const ops = new GeneratedClient(invoker);
|
|
181
255
|
this.ops = ops;
|
|
182
256
|
this.streams = new StreamHub(native);
|
|
183
|
-
this.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
this.tokensReady
|
|
257
|
+
this.tokenProvider = options.tokenProvider;
|
|
258
|
+
this.tokenTimeoutMs = options.tokenTimeoutMs ?? 30_000;
|
|
259
|
+
// Registered now, so the provider is in place before anything asks. A failure here is reported
|
|
260
|
+
// to the connect that needs the provider, which also tries again.
|
|
261
|
+
void this.tokensReady();
|
|
188
262
|
|
|
189
263
|
const on = (op: OpId) => availableOn[op].includes(platform);
|
|
190
264
|
const stream = <S extends StreamId>(id: S) =>
|
|
@@ -232,6 +306,8 @@ export class Terra {
|
|
|
232
306
|
|
|
233
307
|
this.body = {
|
|
234
308
|
heartRate: async (range) => (await ops.bodyHeartRate({ range })).samples,
|
|
309
|
+
restingHeartRate: async (range) => (await ops.bodyRestingHeartRate({ range })).samples,
|
|
310
|
+
readings: async (range) => (await ops.bodyReadings({ range })).samples,
|
|
235
311
|
logHeartRate: async (entries) => (await ops.bodyLogHeartRate({ entries })).ids,
|
|
236
312
|
log: async (entries) => (await ops.bodyLog({ entries })).ids,
|
|
237
313
|
delete: async (id) => {
|
|
@@ -242,6 +318,8 @@ export class Terra {
|
|
|
242
318
|
|
|
243
319
|
this.daily = {
|
|
244
320
|
steps: async (range) => (await ops.dailySteps({ range })).samples,
|
|
321
|
+
totals: async (range) => (await ops.dailyTotals({ range })).totals,
|
|
322
|
+
intraday: async (range) => (await ops.dailyIntraday({ range })).samples,
|
|
245
323
|
log: async (entries) => (await ops.dailyLog({ entries })).ids,
|
|
246
324
|
delete: async (id) => {
|
|
247
325
|
await ops.dailyDelete({ id });
|
|
@@ -260,6 +338,8 @@ export class Terra {
|
|
|
260
338
|
|
|
261
339
|
this.activity = {
|
|
262
340
|
workouts: async (range) => (await ops.activityWorkouts({ range })).samples,
|
|
341
|
+
routes: async (range) => (await ops.activityRoutes({ range })).routes,
|
|
342
|
+
intensity: async (range) => (await ops.activityIntensity({ range })).samples,
|
|
263
343
|
log: async (entries) => (await ops.activityLog({ entries })).ids,
|
|
264
344
|
delete: async (id) => {
|
|
265
345
|
await ops.activityDelete({ id });
|
|
@@ -268,12 +348,23 @@ export class Terra {
|
|
|
268
348
|
};
|
|
269
349
|
|
|
270
350
|
this.nutrition = {
|
|
351
|
+
quantities: async (range) => (await ops.nutritionQuantities({ range })).samples,
|
|
352
|
+
hydration: async (range) => (await ops.nutritionHydration({ range })).samples,
|
|
271
353
|
log: async (entries) => (await ops.nutritionLog({ entries })).ids,
|
|
272
354
|
delete: async (id) => {
|
|
273
355
|
await ops.nutritionDelete({ id });
|
|
274
356
|
},
|
|
275
357
|
};
|
|
276
358
|
|
|
359
|
+
this.menstruation = {
|
|
360
|
+
flow: async (range) => (await ops.menstruationFlow({ range })).samples,
|
|
361
|
+
events: async (range) => (await ops.menstruationEvents({ range })).samples,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
this.athlete = on("athlete.profile")
|
|
365
|
+
? { profile: async () => (await ops.athleteProfile({})).profile }
|
|
366
|
+
: undefined;
|
|
367
|
+
|
|
277
368
|
this.plannedWorkouts = on("plannedWorkouts.planned")
|
|
278
369
|
? {
|
|
279
370
|
planned: async () => (await ops.plannedWorkoutsPlanned({})).workouts,
|
|
@@ -305,7 +396,8 @@ export class Terra {
|
|
|
305
396
|
* the `tokenProvider` is asked, and only when a new link is actually needed.
|
|
306
397
|
*/
|
|
307
398
|
async connect(source: Source, endUser: EndUser, options: { readonly token?: string } = {}): Promise<Connection> {
|
|
308
|
-
|
|
399
|
+
// A connect that carries its token never asks the provider, so it does not wait on it either.
|
|
400
|
+
if (options.token === undefined) await this.tokensReady();
|
|
309
401
|
return this.ops.clientConnect({ source, endUser, token: options.token });
|
|
310
402
|
}
|
|
311
403
|
|
|
@@ -317,9 +409,35 @@ export class Terra {
|
|
|
317
409
|
await this.ops.clientDisconnect({ source });
|
|
318
410
|
}
|
|
319
411
|
|
|
320
|
-
/**
|
|
321
|
-
|
|
322
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Whether the device can serve `source` at all, and if not, why. Needs no connection and prompts
|
|
414
|
+
* for nothing, so an app can decide whether to offer the link before `connect`. iOS answers
|
|
415
|
+
* `available` or `unsupportedDevice`; Android names the reason, such as `providerAppMissing` when
|
|
416
|
+
* Health Connect needs installing.
|
|
417
|
+
*/
|
|
418
|
+
async availability(source?: Source): Promise<StoreAvailability> {
|
|
419
|
+
return (await this.ops.clientAvailability({ source })).availability;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Settles once the token provider, when there is one, can be asked. */
|
|
423
|
+
private tokensReady(): Promise<void> {
|
|
424
|
+
const provider = this.tokenProvider;
|
|
425
|
+
if (provider === undefined) return Promise.resolve();
|
|
426
|
+
if (this.tokenSetup !== undefined) return this.tokenSetup;
|
|
427
|
+
const forget = () => {
|
|
428
|
+
if (this.tokenSetup === setup) this.tokenSetup = undefined;
|
|
429
|
+
};
|
|
430
|
+
const setup = this.answerTokens(provider, this.tokenTimeoutMs, forget);
|
|
431
|
+
this.tokenSetup = setup;
|
|
432
|
+
// Handles the failure, so it is never an unhandled rejection, and forgets the setup so the next
|
|
433
|
+
// connect tries again rather than rethrowing a failure that may have passed.
|
|
434
|
+
setup.catch(forget);
|
|
435
|
+
return setup;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** Answers the native broker's token requests with `provider` until the stream ends. */
|
|
439
|
+
private async answerTokens(provider: TokenProvider, timeoutMs: number, onEnd: () => void): Promise<void> {
|
|
440
|
+
const id = await this.streams.open("bridge.tokenRequest", ({ requestId }) => {
|
|
323
441
|
void (async () => {
|
|
324
442
|
let token: string;
|
|
325
443
|
try {
|
|
@@ -331,8 +449,14 @@ export class Terra {
|
|
|
331
449
|
}
|
|
332
450
|
await this.ops.tokenResolve({ requestId, token }).catch(() => undefined);
|
|
333
451
|
})();
|
|
334
|
-
});
|
|
335
|
-
|
|
452
|
+
}, undefined, onEnd);
|
|
453
|
+
try {
|
|
454
|
+
await this.ops.tokenSetProviderPresent({ present: true, timeoutMs });
|
|
455
|
+
} catch (failed) {
|
|
456
|
+
// A provider the broker was never told about answers nothing, so its stream is let go of.
|
|
457
|
+
this.streams.close(id);
|
|
458
|
+
throw failed;
|
|
459
|
+
}
|
|
336
460
|
}
|
|
337
461
|
}
|
|
338
462
|
|
|
@@ -344,11 +468,15 @@ class StreamHub {
|
|
|
344
468
|
|
|
345
469
|
constructor(private readonly native: NativeBridge) {}
|
|
346
470
|
|
|
347
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* Subscribes and answers once the native stream is open. `onEnd` runs once the stream ends from the
|
|
473
|
+
* native side, by error or by completing; not when it is closed from here.
|
|
474
|
+
*/
|
|
348
475
|
async open<S extends StreamId>(
|
|
349
476
|
stream: S,
|
|
350
477
|
listener: StreamListener<StreamElements[S]>,
|
|
351
478
|
onError?: (error: TerraError) => void,
|
|
479
|
+
onEnd?: () => void,
|
|
352
480
|
): Promise<string> {
|
|
353
481
|
this.listen();
|
|
354
482
|
this.pending += 1;
|
|
@@ -368,9 +496,11 @@ class StreamHub {
|
|
|
368
496
|
case "error":
|
|
369
497
|
this.ended(id);
|
|
370
498
|
onError?.(new TerraError(delivery.error));
|
|
499
|
+
onEnd?.();
|
|
371
500
|
return;
|
|
372
501
|
case "complete":
|
|
373
502
|
this.ended(id);
|
|
503
|
+
onEnd?.();
|
|
374
504
|
return;
|
|
375
505
|
}
|
|
376
506
|
};
|
|
@@ -427,7 +557,7 @@ class StreamHub {
|
|
|
427
557
|
};
|
|
428
558
|
}
|
|
429
559
|
|
|
430
|
-
|
|
560
|
+
close(id: string): void {
|
|
431
561
|
this.handlers.delete(id);
|
|
432
562
|
this.early.delete(id);
|
|
433
563
|
this.native.unsubscribe(id);
|
package/src/generated/client.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated from surface/terra-surface.json by surface/gen. Do not edit by hand.
|
|
2
2
|
// Regenerate with: node surface/gen/src/cli.ts generate
|
|
3
3
|
|
|
4
|
-
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";
|
|
5
|
-
import { encodeBodyEntryList, encodeCategoryRequest, encodeConnectRequest, encodeEmpty, encodeHeartRateEntryList, encodeMarkCompleteRequest, encodeNutritionEntryList, encodePermissionRequest, encodeRangeRequest, encodeRecordIdRequest, encodeSleepEntryList, encodeSourceRequest, encodeStepEntryList, encodeTokenProviderRequest, encodeTokenRejectRequest, encodeTokenResolveRequest, encodeWorkoutEntryList, decodeBoolResult, decodeBootstrapState, decodeConnection, decodeConnectionList, decodeDiagnosticsState, decodeEmpty, decodeHealthAccess, decodeHeartRateList, decodeLastDeliveryResult, decodePermissionGrant, decodePlannedWorkoutList, decodeRecordIdList, decodeSleepSegmentList, decodeStatusResult, decodeStatusesResult, decodeStepCountList, decodeSupportReport, decodeWorkoutList, decodeWorkoutScheduling } from "./codecs";
|
|
4
|
+
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";
|
|
5
|
+
import { encodeBodyEntryList, encodeCategoryRequest, encodeConnectRequest, encodeEmpty, encodeHeartRateEntryList, encodeMarkCompleteRequest, encodeNutritionEntryList, encodePermissionRequest, encodeRangeRequest, encodeRecordIdRequest, encodeSleepEntryList, encodeSourceRequest, encodeStepEntryList, encodeTokenProviderRequest, encodeTokenRejectRequest, encodeTokenResolveRequest, encodeWorkoutEntryList, decodeAthleteProfileResult, decodeAvailabilityResult, decodeBodyReadingList, decodeBoolResult, decodeBootstrapState, decodeConnection, decodeConnectionList, decodeDayTotalList, decodeDiagnosticsState, decodeDietaryQuantityList, decodeEmpty, decodeHealthAccess, decodeHeartRateList, decodeHydrationList, decodeIntradaySampleList, decodeLastDeliveryResult, decodeMenstrualEventList, decodeMenstruationFlowDayList, decodePermissionGrant, decodePlannedWorkoutList, decodeRecordIdList, decodeRestingHeartRateList, decodeSleepSegmentList, decodeStatusResult, decodeStatusesResult, decodeStepCountList, decodeSupportReport, decodeWorkoutIntensityList, decodeWorkoutList, decodeWorkoutRouteList, decodeWorkoutScheduling } from "./codecs";
|
|
6
6
|
import type { OpId } from "./ops";
|
|
7
7
|
import { TerraError } from "../TerraError";
|
|
8
8
|
|
|
@@ -60,6 +60,11 @@ export class GeneratedClient {
|
|
|
60
60
|
return this.call("client.connections", () => encodeEmpty(request), (raw) => decodeConnectionList(raw, "ConnectionList"));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Whether the device can serve the source at all. Needs no connection and prompts for nothing. */
|
|
64
|
+
async clientAvailability(request: SourceRequest): Promise<AvailabilityResult> {
|
|
65
|
+
return this.call("client.availability", () => encodeSourceRequest(request), (raw) => decodeAvailabilityResult(raw, "AvailabilityResult"));
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
async clientDisconnect(request: SourceRequest): Promise<Empty> {
|
|
64
69
|
return this.call("client.disconnect", () => encodeSourceRequest(request), (raw) => decodeEmpty(raw, "Empty"));
|
|
65
70
|
}
|
|
@@ -148,6 +153,18 @@ export class GeneratedClient {
|
|
|
148
153
|
return this.call("body.heartRate", () => encodeRangeRequest(request), (raw) => decodeHeartRateList(raw, "HeartRateList"));
|
|
149
154
|
}
|
|
150
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The store's own daily figures. Health Connect grants them under daily, so Android answers
|
|
158
|
+
* authorization_not_determined where only body was asked for.
|
|
159
|
+
*/
|
|
160
|
+
async bodyRestingHeartRate(request: RangeRequest): Promise<RestingHeartRateList> {
|
|
161
|
+
return this.call("body.restingHeartRate", () => encodeRangeRequest(request), (raw) => decodeRestingHeartRateList(raw, "RestingHeartRateList"));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async bodyReadings(request: RangeRequest): Promise<BodyReadingList> {
|
|
165
|
+
return this.call("body.readings", () => encodeRangeRequest(request), (raw) => decodeBodyReadingList(raw, "BodyReadingList"));
|
|
166
|
+
}
|
|
167
|
+
|
|
151
168
|
/** Its own op rather than an overload: Swift overloads log by element type and JS cannot. */
|
|
152
169
|
async bodyLogHeartRate(request: HeartRateEntryList): Promise<RecordIdList> {
|
|
153
170
|
return this.call("body.logHeartRate", () => encodeHeartRateEntryList(request), (raw) => decodeRecordIdList(raw, "RecordIdList"));
|
|
@@ -165,6 +182,19 @@ export class GeneratedClient {
|
|
|
165
182
|
return this.call("daily.steps", () => encodeRangeRequest(request), (raw) => decodeStepCountList(raw, "StepCountList"));
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
/**
|
|
186
|
+
* One total per metric per whole local day the range touches. A day with nothing recorded has
|
|
187
|
+
* no total, not a zero.
|
|
188
|
+
*/
|
|
189
|
+
async dailyTotals(request: RangeRequest): Promise<DayTotalList> {
|
|
190
|
+
return this.call("daily.totals", () => encodeRangeRequest(request), (raw) => decodeDayTotalList(raw, "DayTotalList"));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The active energy, basal energy, distance and floors records the totals are made of. */
|
|
194
|
+
async dailyIntraday(request: RangeRequest): Promise<IntradaySampleList> {
|
|
195
|
+
return this.call("daily.intraday", () => encodeRangeRequest(request), (raw) => decodeIntradaySampleList(raw, "IntradaySampleList"));
|
|
196
|
+
}
|
|
197
|
+
|
|
168
198
|
async dailyLog(request: StepEntryList): Promise<RecordIdList> {
|
|
169
199
|
return this.call("daily.log", () => encodeStepEntryList(request), (raw) => decodeRecordIdList(raw, "RecordIdList"));
|
|
170
200
|
}
|
|
@@ -189,6 +219,16 @@ export class GeneratedClient {
|
|
|
189
219
|
return this.call("activity.workouts", () => encodeRangeRequest(request), (raw) => decodeWorkoutList(raw, "WorkoutList"));
|
|
190
220
|
}
|
|
191
221
|
|
|
222
|
+
/** The GPS tracks of the workouts in the range, each naming its workout. */
|
|
223
|
+
async activityRoutes(request: RangeRequest): Promise<WorkoutRouteList> {
|
|
224
|
+
return this.call("activity.routes", () => encodeRangeRequest(request), (raw) => decodeWorkoutRouteList(raw, "WorkoutRouteList"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Speed, power and cycling cadence readings in the range. */
|
|
228
|
+
async activityIntensity(request: RangeRequest): Promise<WorkoutIntensityList> {
|
|
229
|
+
return this.call("activity.intensity", () => encodeRangeRequest(request), (raw) => decodeWorkoutIntensityList(raw, "WorkoutIntensityList"));
|
|
230
|
+
}
|
|
231
|
+
|
|
192
232
|
async activityLog(request: WorkoutEntryList): Promise<RecordIdList> {
|
|
193
233
|
return this.call("activity.log", () => encodeWorkoutEntryList(request), (raw) => decodeRecordIdList(raw, "RecordIdList"));
|
|
194
234
|
}
|
|
@@ -197,6 +237,11 @@ export class GeneratedClient {
|
|
|
197
237
|
return this.call("activity.delete", () => encodeRecordIdRequest(request), (raw) => decodeEmpty(raw, "Empty"));
|
|
198
238
|
}
|
|
199
239
|
|
|
240
|
+
/** One quantity per nutrient a meal recorded, not grouped into meals. Water is not among them. */
|
|
241
|
+
async nutritionQuantities(request: RangeRequest): Promise<DietaryQuantityList> {
|
|
242
|
+
return this.call("nutrition.quantities", () => encodeRangeRequest(request), (raw) => decodeDietaryQuantityList(raw, "DietaryQuantityList"));
|
|
243
|
+
}
|
|
244
|
+
|
|
200
245
|
async nutritionLog(request: NutritionEntryList): Promise<RecordIdList> {
|
|
201
246
|
return this.call("nutrition.log", () => encodeNutritionEntryList(request), (raw) => decodeRecordIdList(raw, "RecordIdList"));
|
|
202
247
|
}
|
|
@@ -205,6 +250,33 @@ export class GeneratedClient {
|
|
|
205
250
|
return this.call("nutrition.delete", () => encodeRecordIdRequest(request), (raw) => decodeEmpty(raw, "Empty"));
|
|
206
251
|
}
|
|
207
252
|
|
|
253
|
+
/** Water drunk in the range, one sample per drink. */
|
|
254
|
+
async nutritionHydration(request: RangeRequest): Promise<HydrationList> {
|
|
255
|
+
return this.call("nutrition.hydration", () => encodeRangeRequest(request), (raw) => decodeHydrationList(raw, "HydrationList"));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Logged flow days overlapping the range, one per day. A day logged with no flow is noFlow,
|
|
260
|
+
* not left out.
|
|
261
|
+
*/
|
|
262
|
+
async menstruationFlow(request: RangeRequest): Promise<MenstruationFlowDayList> {
|
|
263
|
+
return this.call("menstruation.flow", () => encodeRangeRequest(request), (raw) => decodeMenstruationFlowDayList(raw, "MenstruationFlowDayList"));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Ovulation tests, cervical mucus and intermenstrual bleeding logged in the range. */
|
|
267
|
+
async menstruationEvents(request: RangeRequest): Promise<MenstrualEventList> {
|
|
268
|
+
return this.call("menstruation.events", () => encodeRangeRequest(request), (raw) => decodeMenstrualEventList(raw, "MenstrualEventList"));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Characteristics have no history.
|
|
273
|
+
*
|
|
274
|
+
* Available on ios only.
|
|
275
|
+
*/
|
|
276
|
+
async athleteProfile(request: Empty): Promise<AthleteProfileResult> {
|
|
277
|
+
return this.call("athlete.profile", () => encodeEmpty(request), (raw) => decodeAthleteProfileResult(raw, "AthleteProfileResult"));
|
|
278
|
+
}
|
|
279
|
+
|
|
208
280
|
/** Available on ios only. */
|
|
209
281
|
async plannedWorkoutsPlanned(request: Empty): Promise<PlannedWorkoutList> {
|
|
210
282
|
return this.call("plannedWorkouts.planned", () => encodeEmpty(request), (raw) => decodePlannedWorkoutList(raw, "PlannedWorkoutList"));
|