@tryterra/react-native 0.0.0-stage → 2.1.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/LICENSE +21 -0
- package/README.md +111 -2
- package/TerraReactNative.podspec +48 -0
- package/android/build.gradle +87 -0
- package/android/src/bridgeCore/kotlin/README.md +1 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +468 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +258 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +52 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +493 -0
- package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +897 -0
- package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
- package/android/src/main/AndroidManifest.xml +24 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
- package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
- package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
- package/docs/availability.md +246 -0
- package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
- 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 +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2150 -0
- 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 +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
- 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 +6473 -0
- 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 +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2150 -0
- 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 +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +81513 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2150 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2150 -0
- 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 +2127 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
- 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 +223 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
- 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 +6474 -0
- package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6392 -0
- package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
- package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
- package/ios/TerraBridgeCore/Conversions.swift +467 -0
- package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
- package/ios/TerraBridgeCore/Generated/BridgeOps.swift +795 -0
- package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +1317 -0
- package/ios/TerraBridgeCore/Locked.swift +15 -0
- package/ios/TerraBridgeCore/README.md +1 -0
- package/ios/TerraBridgeCore/Rejections.swift +130 -0
- package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
- package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
- package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
- package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +254 -0
- package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
- package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
- package/ios/TerraBridgeModule.mm +79 -0
- package/ios/TerraBridgeModuleImpl.swift +49 -0
- package/ios/TerraReactNative.swift +19 -0
- package/lib/EndUser.d.ts +12 -0
- package/lib/EndUser.js +13 -0
- package/lib/NativeTerraBridge.d.ts +23 -0
- package/lib/NativeTerraBridge.js +13 -0
- package/lib/Terra.d.ts +154 -0
- package/lib/Terra.js +280 -0
- package/lib/TerraError.d.ts +42 -0
- package/lib/TerraError.js +95 -0
- package/lib/createTerra.d.ts +8 -0
- package/lib/createTerra.js +19 -0
- package/lib/generated/client.d.ts +92 -0
- package/lib/generated/client.js +196 -0
- package/lib/generated/codecs.d.ts +135 -0
- package/lib/generated/codecs.js +1358 -0
- package/lib/generated/errors.d.ts +13 -0
- package/lib/generated/errors.js +68 -0
- package/lib/generated/ops.d.ts +13 -0
- package/lib/generated/ops.js +137 -0
- package/lib/generated/streams.d.ts +23 -0
- package/lib/generated/streams.js +58 -0
- package/lib/generated/types.d.ts +481 -0
- package/lib/generated/types.js +92 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +55 -0
- package/package.json +71 -4
- package/src/EndUser.ts +15 -0
- package/src/NativeTerraBridge.ts +27 -0
- package/src/Terra.ts +462 -0
- package/src/TerraError.ts +116 -0
- package/src/createTerra.ts +18 -0
- package/src/generated/client.ts +243 -0
- package/src/generated/codecs.ts +1287 -0
- package/src/generated/errors.ts +113 -0
- package/src/generated/ops.ts +197 -0
- package/src/generated/streams.ts +74 -0
- package/src/generated/types.ts +667 -0
- package/src/index.ts +52 -0
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
// Generated from surface/terra-surface.json by surface/gen. Do not edit by hand.
|
|
2
|
+
// Regenerate with: node surface/gen/src/cli.ts generate
|
|
3
|
+
|
|
4
|
+
import Foundation
|
|
5
|
+
|
|
6
|
+
/// Every op the schema declares, including the ones this platform does not implement.
|
|
7
|
+
///
|
|
8
|
+
/// The unsupported ones are cases here rather than absent so that `dispatch` can answer
|
|
9
|
+
/// `bridge.unsupported_on_platform` deliberately, instead of failing to parse the op and
|
|
10
|
+
/// answering something that reads like a bug in the caller.
|
|
11
|
+
public enum BridgeOp: String, CaseIterable, Sendable {
|
|
12
|
+
case clientAttach = "client.attach"
|
|
13
|
+
case clientConnect = "client.connect"
|
|
14
|
+
case clientConnections = "client.connections"
|
|
15
|
+
case clientDisconnect = "client.disconnect"
|
|
16
|
+
case tokenSetProviderPresent = "token.setProviderPresent"
|
|
17
|
+
case tokenResolve = "token.resolve"
|
|
18
|
+
case tokenReject = "token.reject"
|
|
19
|
+
case permissionsRequest = "permissions.request"
|
|
20
|
+
case permissionsStatus = "permissions.status"
|
|
21
|
+
case permissionsWriteStatus = "permissions.writeStatus"
|
|
22
|
+
case permissionsRequested = "permissions.requested"
|
|
23
|
+
case permissionsStatuses = "permissions.statuses"
|
|
24
|
+
case permissionsBackgroundReadStatus = "permissions.backgroundReadStatus"
|
|
25
|
+
case permissionsHistoryReadStatus = "permissions.historyReadStatus"
|
|
26
|
+
case diagnosticsState = "diagnostics.state"
|
|
27
|
+
case diagnosticsLastDelivery = "diagnostics.lastDelivery"
|
|
28
|
+
case diagnosticsReport = "diagnostics.report"
|
|
29
|
+
case diagnosticsSend = "diagnostics.send"
|
|
30
|
+
case backgroundIsRegistered = "background.isRegistered"
|
|
31
|
+
case backgroundCancel = "background.cancel"
|
|
32
|
+
case bodyHeartRate = "body.heartRate"
|
|
33
|
+
case bodyLogHeartRate = "body.logHeartRate"
|
|
34
|
+
case bodyLog = "body.log"
|
|
35
|
+
case bodyDelete = "body.delete"
|
|
36
|
+
case dailySteps = "daily.steps"
|
|
37
|
+
case dailyLog = "daily.log"
|
|
38
|
+
case dailyDelete = "daily.delete"
|
|
39
|
+
case sleepSegments = "sleep.segments"
|
|
40
|
+
case sleepLog = "sleep.log"
|
|
41
|
+
case sleepDelete = "sleep.delete"
|
|
42
|
+
case activityWorkouts = "activity.workouts"
|
|
43
|
+
case activityLog = "activity.log"
|
|
44
|
+
case activityDelete = "activity.delete"
|
|
45
|
+
case nutritionLog = "nutrition.log"
|
|
46
|
+
case nutritionDelete = "nutrition.delete"
|
|
47
|
+
case plannedWorkoutsPlanned = "plannedWorkouts.planned"
|
|
48
|
+
case plannedWorkoutsMarkComplete = "plannedWorkouts.markComplete"
|
|
49
|
+
case plannedWorkoutsDelete = "plannedWorkouts.delete"
|
|
50
|
+
case plannedWorkoutsSchedulingStatus = "plannedWorkouts.schedulingStatus"
|
|
51
|
+
case plannedWorkoutsRequestScheduling = "plannedWorkouts.requestScheduling"
|
|
52
|
+
|
|
53
|
+
/// Whether this platform implements the op.
|
|
54
|
+
public var isSupported: Bool {
|
|
55
|
+
switch self {
|
|
56
|
+
case .clientAttach: return true
|
|
57
|
+
case .clientConnect: return true
|
|
58
|
+
case .clientConnections: return true
|
|
59
|
+
case .clientDisconnect: return true
|
|
60
|
+
case .tokenSetProviderPresent: return true
|
|
61
|
+
case .tokenResolve: return true
|
|
62
|
+
case .tokenReject: return true
|
|
63
|
+
case .permissionsRequest: return true
|
|
64
|
+
case .permissionsStatus: return true
|
|
65
|
+
case .permissionsWriteStatus: return true
|
|
66
|
+
case .permissionsRequested: return true
|
|
67
|
+
case .permissionsStatuses: return false
|
|
68
|
+
case .permissionsBackgroundReadStatus: return false
|
|
69
|
+
case .permissionsHistoryReadStatus: return false
|
|
70
|
+
case .diagnosticsState: return true
|
|
71
|
+
case .diagnosticsLastDelivery: return true
|
|
72
|
+
case .diagnosticsReport: return true
|
|
73
|
+
case .diagnosticsSend: return true
|
|
74
|
+
case .backgroundIsRegistered: return true
|
|
75
|
+
case .backgroundCancel: return false
|
|
76
|
+
case .bodyHeartRate: return true
|
|
77
|
+
case .bodyLogHeartRate: return true
|
|
78
|
+
case .bodyLog: return true
|
|
79
|
+
case .bodyDelete: return true
|
|
80
|
+
case .dailySteps: return true
|
|
81
|
+
case .dailyLog: return true
|
|
82
|
+
case .dailyDelete: return true
|
|
83
|
+
case .sleepSegments: return true
|
|
84
|
+
case .sleepLog: return true
|
|
85
|
+
case .sleepDelete: return true
|
|
86
|
+
case .activityWorkouts: return true
|
|
87
|
+
case .activityLog: return true
|
|
88
|
+
case .activityDelete: return true
|
|
89
|
+
case .nutritionLog: return true
|
|
90
|
+
case .nutritionDelete: return true
|
|
91
|
+
case .plannedWorkoutsPlanned: return true
|
|
92
|
+
case .plannedWorkoutsMarkComplete: return true
|
|
93
|
+
case .plannedWorkoutsDelete: return true
|
|
94
|
+
case .plannedWorkoutsSchedulingStatus: return true
|
|
95
|
+
case .plannedWorkoutsRequestScheduling: return true
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// What P3 implements. One method per op this platform supports, and no `default` anywhere:
|
|
101
|
+
/// an op added to the schema breaks this build rather than reaching a customer as a rejection.
|
|
102
|
+
public protocol TerraBridgeOps: Sendable {
|
|
103
|
+
/// Reports the native client the JS side is attaching to. Idempotent, and safe to call after a
|
|
104
|
+
/// JS reload.
|
|
105
|
+
func clientAttach(_ request: Empty) async throws -> BootstrapState
|
|
106
|
+
func clientConnect(_ request: ConnectRequest) async throws -> Connection
|
|
107
|
+
func clientConnections(_ request: Empty) async throws -> ConnectionList
|
|
108
|
+
func clientDisconnect(_ request: SourceRequest) async throws -> Empty
|
|
109
|
+
/// Tells the broker whether JS can answer at all. Without it a link asks a provider that is not
|
|
110
|
+
/// there and waits out the timeout.
|
|
111
|
+
func tokenSetProviderPresent(_ request: TokenProviderRequest) async throws -> Empty
|
|
112
|
+
/// An unknown or already-settled requestId is ignored rather than rejected, so a reload cannot
|
|
113
|
+
/// fail a link that has already moved on.
|
|
114
|
+
func tokenResolve(_ request: TokenResolveRequest) async throws -> Empty
|
|
115
|
+
func tokenReject(_ request: TokenRejectRequest) async throws -> Empty
|
|
116
|
+
func permissionsRequest(_ request: PermissionRequest) async throws -> PermissionGrant
|
|
117
|
+
func permissionsStatus(_ request: CategoryRequest) async throws -> StatusResult
|
|
118
|
+
func permissionsWriteStatus(_ request: CategoryRequest) async throws -> StatusResult
|
|
119
|
+
func permissionsRequested(_ request: SourceRequest) async throws -> HealthAccess
|
|
120
|
+
func diagnosticsState(_ request: SourceRequest) async throws -> DiagnosticsState
|
|
121
|
+
func diagnosticsLastDelivery(_ request: SourceRequest) async throws -> LastDeliveryResult
|
|
122
|
+
func diagnosticsReport(_ request: SourceRequest) async throws -> SupportReport
|
|
123
|
+
func diagnosticsSend(_ request: SourceRequest) async throws -> Empty
|
|
124
|
+
/// False means bootstrap did not run. The SDK reports the same thing independently as
|
|
125
|
+
/// backgroundRefreshUnscheduled.
|
|
126
|
+
func backgroundIsRegistered(_ request: Empty) async throws -> BoolResult
|
|
127
|
+
func bodyHeartRate(_ request: RangeRequest) async throws -> HeartRateList
|
|
128
|
+
/// Its own op rather than an overload: Swift overloads log by element type and JS cannot.
|
|
129
|
+
func bodyLogHeartRate(_ request: HeartRateEntryList) async throws -> RecordIdList
|
|
130
|
+
func bodyLog(_ request: BodyEntryList) async throws -> RecordIdList
|
|
131
|
+
func bodyDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
132
|
+
func dailySteps(_ request: RangeRequest) async throws -> StepCountList
|
|
133
|
+
func dailyLog(_ request: StepEntryList) async throws -> RecordIdList
|
|
134
|
+
func dailyDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
135
|
+
func sleepSegments(_ request: RangeRequest) async throws -> SleepSegmentList
|
|
136
|
+
func sleepLog(_ request: SleepEntryList) async throws -> RecordIdList
|
|
137
|
+
func sleepDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
138
|
+
func activityWorkouts(_ request: RangeRequest) async throws -> WorkoutList
|
|
139
|
+
func activityLog(_ request: WorkoutEntryList) async throws -> RecordIdList
|
|
140
|
+
func activityDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
141
|
+
func nutritionLog(_ request: NutritionEntryList) async throws -> RecordIdList
|
|
142
|
+
func nutritionDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
143
|
+
/// Available on ios only.
|
|
144
|
+
func plannedWorkoutsPlanned(_ request: Empty) async throws -> PlannedWorkoutList
|
|
145
|
+
/// Available on ios only.
|
|
146
|
+
func plannedWorkoutsMarkComplete(_ request: MarkCompleteRequest) async throws -> Empty
|
|
147
|
+
/// Available on ios only.
|
|
148
|
+
func plannedWorkoutsDelete(_ request: RecordIdRequest) async throws -> Empty
|
|
149
|
+
/// Never prompts.
|
|
150
|
+
///
|
|
151
|
+
/// Available on ios only.
|
|
152
|
+
func plannedWorkoutsSchedulingStatus(_ request: Empty) async throws -> WorkoutScheduling
|
|
153
|
+
/// Shows WorkoutKit's sheet if the person has not been asked, and reconciles straight away once
|
|
154
|
+
/// authorized.
|
|
155
|
+
///
|
|
156
|
+
/// Available on ios only.
|
|
157
|
+
func plannedWorkoutsRequestScheduling(_ request: Empty) async throws -> WorkoutScheduling
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Decodes one side of an exchange and re-encodes it, for the bridge-vector runner.
|
|
161
|
+
///
|
|
162
|
+
/// The runner cannot name the request and response types itself without keeping a second copy
|
|
163
|
+
/// of the op table, and a second copy of the op table is the thing this schema exists to
|
|
164
|
+
/// remove. A round trip is also exactly what a vector asserts: the bytes in are the bytes out,
|
|
165
|
+
/// which pins the DTOs and their coders against the same file the other two runners read.
|
|
166
|
+
public enum BridgeCodecs {
|
|
167
|
+
public static func roundTripRequest(_ op: BridgeOp, _ payload: Data) throws -> Data {
|
|
168
|
+
let decoder = BridgeDispatch.makeDecoder()
|
|
169
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
170
|
+
switch op {
|
|
171
|
+
case .clientAttach:
|
|
172
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
173
|
+
return try encoder.encode(value)
|
|
174
|
+
case .clientConnect:
|
|
175
|
+
let value = try decoder.decode(ConnectRequest.self, from: payload)
|
|
176
|
+
return try encoder.encode(value)
|
|
177
|
+
case .clientConnections:
|
|
178
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
179
|
+
return try encoder.encode(value)
|
|
180
|
+
case .clientDisconnect:
|
|
181
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
182
|
+
return try encoder.encode(value)
|
|
183
|
+
case .tokenSetProviderPresent:
|
|
184
|
+
let value = try decoder.decode(TokenProviderRequest.self, from: payload)
|
|
185
|
+
return try encoder.encode(value)
|
|
186
|
+
case .tokenResolve:
|
|
187
|
+
let value = try decoder.decode(TokenResolveRequest.self, from: payload)
|
|
188
|
+
return try encoder.encode(value)
|
|
189
|
+
case .tokenReject:
|
|
190
|
+
let value = try decoder.decode(TokenRejectRequest.self, from: payload)
|
|
191
|
+
return try encoder.encode(value)
|
|
192
|
+
case .permissionsRequest:
|
|
193
|
+
let value = try decoder.decode(PermissionRequest.self, from: payload)
|
|
194
|
+
return try encoder.encode(value)
|
|
195
|
+
case .permissionsStatus:
|
|
196
|
+
let value = try decoder.decode(CategoryRequest.self, from: payload)
|
|
197
|
+
return try encoder.encode(value)
|
|
198
|
+
case .permissionsWriteStatus:
|
|
199
|
+
let value = try decoder.decode(CategoryRequest.self, from: payload)
|
|
200
|
+
return try encoder.encode(value)
|
|
201
|
+
case .permissionsRequested:
|
|
202
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
203
|
+
return try encoder.encode(value)
|
|
204
|
+
case .permissionsStatuses:
|
|
205
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
206
|
+
return try encoder.encode(value)
|
|
207
|
+
case .permissionsBackgroundReadStatus:
|
|
208
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
209
|
+
return try encoder.encode(value)
|
|
210
|
+
case .permissionsHistoryReadStatus:
|
|
211
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
212
|
+
return try encoder.encode(value)
|
|
213
|
+
case .diagnosticsState:
|
|
214
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
215
|
+
return try encoder.encode(value)
|
|
216
|
+
case .diagnosticsLastDelivery:
|
|
217
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
218
|
+
return try encoder.encode(value)
|
|
219
|
+
case .diagnosticsReport:
|
|
220
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
221
|
+
return try encoder.encode(value)
|
|
222
|
+
case .diagnosticsSend:
|
|
223
|
+
let value = try decoder.decode(SourceRequest.self, from: payload)
|
|
224
|
+
return try encoder.encode(value)
|
|
225
|
+
case .backgroundIsRegistered:
|
|
226
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
227
|
+
return try encoder.encode(value)
|
|
228
|
+
case .backgroundCancel:
|
|
229
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
230
|
+
return try encoder.encode(value)
|
|
231
|
+
case .bodyHeartRate:
|
|
232
|
+
let value = try decoder.decode(RangeRequest.self, from: payload)
|
|
233
|
+
return try encoder.encode(value)
|
|
234
|
+
case .bodyLogHeartRate:
|
|
235
|
+
let value = try decoder.decode(HeartRateEntryList.self, from: payload)
|
|
236
|
+
return try encoder.encode(value)
|
|
237
|
+
case .bodyLog:
|
|
238
|
+
let value = try decoder.decode(BodyEntryList.self, from: payload)
|
|
239
|
+
return try encoder.encode(value)
|
|
240
|
+
case .bodyDelete:
|
|
241
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
242
|
+
return try encoder.encode(value)
|
|
243
|
+
case .dailySteps:
|
|
244
|
+
let value = try decoder.decode(RangeRequest.self, from: payload)
|
|
245
|
+
return try encoder.encode(value)
|
|
246
|
+
case .dailyLog:
|
|
247
|
+
let value = try decoder.decode(StepEntryList.self, from: payload)
|
|
248
|
+
return try encoder.encode(value)
|
|
249
|
+
case .dailyDelete:
|
|
250
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
251
|
+
return try encoder.encode(value)
|
|
252
|
+
case .sleepSegments:
|
|
253
|
+
let value = try decoder.decode(RangeRequest.self, from: payload)
|
|
254
|
+
return try encoder.encode(value)
|
|
255
|
+
case .sleepLog:
|
|
256
|
+
let value = try decoder.decode(SleepEntryList.self, from: payload)
|
|
257
|
+
return try encoder.encode(value)
|
|
258
|
+
case .sleepDelete:
|
|
259
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
260
|
+
return try encoder.encode(value)
|
|
261
|
+
case .activityWorkouts:
|
|
262
|
+
let value = try decoder.decode(RangeRequest.self, from: payload)
|
|
263
|
+
return try encoder.encode(value)
|
|
264
|
+
case .activityLog:
|
|
265
|
+
let value = try decoder.decode(WorkoutEntryList.self, from: payload)
|
|
266
|
+
return try encoder.encode(value)
|
|
267
|
+
case .activityDelete:
|
|
268
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
269
|
+
return try encoder.encode(value)
|
|
270
|
+
case .nutritionLog:
|
|
271
|
+
let value = try decoder.decode(NutritionEntryList.self, from: payload)
|
|
272
|
+
return try encoder.encode(value)
|
|
273
|
+
case .nutritionDelete:
|
|
274
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
275
|
+
return try encoder.encode(value)
|
|
276
|
+
case .plannedWorkoutsPlanned:
|
|
277
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
278
|
+
return try encoder.encode(value)
|
|
279
|
+
case .plannedWorkoutsMarkComplete:
|
|
280
|
+
let value = try decoder.decode(MarkCompleteRequest.self, from: payload)
|
|
281
|
+
return try encoder.encode(value)
|
|
282
|
+
case .plannedWorkoutsDelete:
|
|
283
|
+
let value = try decoder.decode(RecordIdRequest.self, from: payload)
|
|
284
|
+
return try encoder.encode(value)
|
|
285
|
+
case .plannedWorkoutsSchedulingStatus:
|
|
286
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
287
|
+
return try encoder.encode(value)
|
|
288
|
+
case .plannedWorkoutsRequestScheduling:
|
|
289
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
290
|
+
return try encoder.encode(value)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public static func roundTripResponse(_ op: BridgeOp, _ payload: Data) throws -> Data {
|
|
295
|
+
let decoder = BridgeDispatch.makeDecoder()
|
|
296
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
297
|
+
switch op {
|
|
298
|
+
case .clientAttach:
|
|
299
|
+
let value = try decoder.decode(BootstrapState.self, from: payload)
|
|
300
|
+
return try encoder.encode(value)
|
|
301
|
+
case .clientConnect:
|
|
302
|
+
let value = try decoder.decode(Connection.self, from: payload)
|
|
303
|
+
return try encoder.encode(value)
|
|
304
|
+
case .clientConnections:
|
|
305
|
+
let value = try decoder.decode(ConnectionList.self, from: payload)
|
|
306
|
+
return try encoder.encode(value)
|
|
307
|
+
case .clientDisconnect:
|
|
308
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
309
|
+
return try encoder.encode(value)
|
|
310
|
+
case .tokenSetProviderPresent:
|
|
311
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
312
|
+
return try encoder.encode(value)
|
|
313
|
+
case .tokenResolve:
|
|
314
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
315
|
+
return try encoder.encode(value)
|
|
316
|
+
case .tokenReject:
|
|
317
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
318
|
+
return try encoder.encode(value)
|
|
319
|
+
case .permissionsRequest:
|
|
320
|
+
let value = try decoder.decode(PermissionGrant.self, from: payload)
|
|
321
|
+
return try encoder.encode(value)
|
|
322
|
+
case .permissionsStatus:
|
|
323
|
+
let value = try decoder.decode(StatusResult.self, from: payload)
|
|
324
|
+
return try encoder.encode(value)
|
|
325
|
+
case .permissionsWriteStatus:
|
|
326
|
+
let value = try decoder.decode(StatusResult.self, from: payload)
|
|
327
|
+
return try encoder.encode(value)
|
|
328
|
+
case .permissionsRequested:
|
|
329
|
+
let value = try decoder.decode(HealthAccess.self, from: payload)
|
|
330
|
+
return try encoder.encode(value)
|
|
331
|
+
case .permissionsStatuses:
|
|
332
|
+
let value = try decoder.decode(StatusesResult.self, from: payload)
|
|
333
|
+
return try encoder.encode(value)
|
|
334
|
+
case .permissionsBackgroundReadStatus:
|
|
335
|
+
let value = try decoder.decode(StatusResult.self, from: payload)
|
|
336
|
+
return try encoder.encode(value)
|
|
337
|
+
case .permissionsHistoryReadStatus:
|
|
338
|
+
let value = try decoder.decode(StatusResult.self, from: payload)
|
|
339
|
+
return try encoder.encode(value)
|
|
340
|
+
case .diagnosticsState:
|
|
341
|
+
let value = try decoder.decode(DiagnosticsState.self, from: payload)
|
|
342
|
+
return try encoder.encode(value)
|
|
343
|
+
case .diagnosticsLastDelivery:
|
|
344
|
+
let value = try decoder.decode(LastDeliveryResult.self, from: payload)
|
|
345
|
+
return try encoder.encode(value)
|
|
346
|
+
case .diagnosticsReport:
|
|
347
|
+
let value = try decoder.decode(SupportReport.self, from: payload)
|
|
348
|
+
try SupportReportEnvelope.validate(value)
|
|
349
|
+
return try encoder.encode(value)
|
|
350
|
+
case .diagnosticsSend:
|
|
351
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
352
|
+
return try encoder.encode(value)
|
|
353
|
+
case .backgroundIsRegistered:
|
|
354
|
+
let value = try decoder.decode(BoolResult.self, from: payload)
|
|
355
|
+
return try encoder.encode(value)
|
|
356
|
+
case .backgroundCancel:
|
|
357
|
+
let value = try decoder.decode(BoolResult.self, from: payload)
|
|
358
|
+
return try encoder.encode(value)
|
|
359
|
+
case .bodyHeartRate:
|
|
360
|
+
let value = try decoder.decode(HeartRateList.self, from: payload)
|
|
361
|
+
return try encoder.encode(value)
|
|
362
|
+
case .bodyLogHeartRate:
|
|
363
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
364
|
+
return try encoder.encode(value)
|
|
365
|
+
case .bodyLog:
|
|
366
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
367
|
+
return try encoder.encode(value)
|
|
368
|
+
case .bodyDelete:
|
|
369
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
370
|
+
return try encoder.encode(value)
|
|
371
|
+
case .dailySteps:
|
|
372
|
+
let value = try decoder.decode(StepCountList.self, from: payload)
|
|
373
|
+
return try encoder.encode(value)
|
|
374
|
+
case .dailyLog:
|
|
375
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
376
|
+
return try encoder.encode(value)
|
|
377
|
+
case .dailyDelete:
|
|
378
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
379
|
+
return try encoder.encode(value)
|
|
380
|
+
case .sleepSegments:
|
|
381
|
+
let value = try decoder.decode(SleepSegmentList.self, from: payload)
|
|
382
|
+
return try encoder.encode(value)
|
|
383
|
+
case .sleepLog:
|
|
384
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
385
|
+
return try encoder.encode(value)
|
|
386
|
+
case .sleepDelete:
|
|
387
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
388
|
+
return try encoder.encode(value)
|
|
389
|
+
case .activityWorkouts:
|
|
390
|
+
let value = try decoder.decode(WorkoutList.self, from: payload)
|
|
391
|
+
return try encoder.encode(value)
|
|
392
|
+
case .activityLog:
|
|
393
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
394
|
+
return try encoder.encode(value)
|
|
395
|
+
case .activityDelete:
|
|
396
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
397
|
+
return try encoder.encode(value)
|
|
398
|
+
case .nutritionLog:
|
|
399
|
+
let value = try decoder.decode(RecordIdList.self, from: payload)
|
|
400
|
+
return try encoder.encode(value)
|
|
401
|
+
case .nutritionDelete:
|
|
402
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
403
|
+
return try encoder.encode(value)
|
|
404
|
+
case .plannedWorkoutsPlanned:
|
|
405
|
+
let value = try decoder.decode(PlannedWorkoutList.self, from: payload)
|
|
406
|
+
return try encoder.encode(value)
|
|
407
|
+
case .plannedWorkoutsMarkComplete:
|
|
408
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
409
|
+
return try encoder.encode(value)
|
|
410
|
+
case .plannedWorkoutsDelete:
|
|
411
|
+
let value = try decoder.decode(Empty.self, from: payload)
|
|
412
|
+
return try encoder.encode(value)
|
|
413
|
+
case .plannedWorkoutsSchedulingStatus:
|
|
414
|
+
let value = try decoder.decode(WorkoutScheduling.self, from: payload)
|
|
415
|
+
return try encoder.encode(value)
|
|
416
|
+
case .plannedWorkoutsRequestScheduling:
|
|
417
|
+
let value = try decoder.decode(WorkoutScheduling.self, from: payload)
|
|
418
|
+
return try encoder.encode(value)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/// One message as a subscription actually delivers it: the envelope, and the element inside.
|
|
423
|
+
///
|
|
424
|
+
/// Both halves, because checking only the envelope would accept a `data` carrying anything
|
|
425
|
+
/// at all, and checking only the element would accept a stream that never says it failed.
|
|
426
|
+
public static func roundTripStreamMessage(_ stream: String, _ payload: Data) throws -> Data {
|
|
427
|
+
let decoder = BridgeDispatch.makeDecoder()
|
|
428
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
429
|
+
let message = try decoder.decode(StreamMessage.self, from: payload)
|
|
430
|
+
guard case .data(let element) = message else { return try encoder.encode(message) }
|
|
431
|
+
let inner = try roundTripStreamElement(stream, try encoder.encode(element))
|
|
432
|
+
let checked = try decoder.decode(BridgeJSON.self, from: inner)
|
|
433
|
+
return try encoder.encode(StreamMessage.data(element: checked))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/// One element of a stream, by that stream's declared type.
|
|
437
|
+
public static func roundTripStreamElement(_ stream: String, _ payload: Data) throws -> Data {
|
|
438
|
+
let decoder = BridgeDispatch.makeDecoder()
|
|
439
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
440
|
+
switch stream {
|
|
441
|
+
case "diagnostics.events":
|
|
442
|
+
let value = try decoder.decode(TerraEvent.self, from: payload)
|
|
443
|
+
try TerraEventEnvelope.validate(value)
|
|
444
|
+
return try encoder.encode(value)
|
|
445
|
+
case "body.changes":
|
|
446
|
+
let value = try decoder.decode(HealthChange.self, from: payload)
|
|
447
|
+
return try encoder.encode(value)
|
|
448
|
+
case "daily.changes":
|
|
449
|
+
let value = try decoder.decode(HealthChange.self, from: payload)
|
|
450
|
+
return try encoder.encode(value)
|
|
451
|
+
case "sleep.changes":
|
|
452
|
+
let value = try decoder.decode(HealthChange.self, from: payload)
|
|
453
|
+
return try encoder.encode(value)
|
|
454
|
+
case "activity.changes":
|
|
455
|
+
let value = try decoder.decode(HealthChange.self, from: payload)
|
|
456
|
+
return try encoder.encode(value)
|
|
457
|
+
case "bridge.tokenRequest":
|
|
458
|
+
let value = try decoder.decode(TokenRequest.self, from: payload)
|
|
459
|
+
return try encoder.encode(value)
|
|
460
|
+
default:
|
|
461
|
+
throw BridgeError.unsupportedOnPlatform(op: stream)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/// Turns an op string and a JSON payload into a typed call and a JSON answer.
|
|
467
|
+
///
|
|
468
|
+
/// The encoder and decoder are built once and configured for ISO 8601: an instant spelled two
|
|
469
|
+
/// ways between a request and its response is a bug the vectors would catch and the compiler
|
|
470
|
+
/// would not.
|
|
471
|
+
public struct BridgeDispatch: Sendable {
|
|
472
|
+
private let ops: any TerraBridgeOps
|
|
473
|
+
|
|
474
|
+
public init(ops: any TerraBridgeOps) { self.ops = ops }
|
|
475
|
+
|
|
476
|
+
public static func makeDecoder() -> JSONDecoder { JSONDecoder() }
|
|
477
|
+
|
|
478
|
+
/// Sorted keys, so that two encodings of one value are the same bytes. That is what lets a
|
|
479
|
+
/// vector be compared rather than parsed and walked, and what makes the three runners
|
|
480
|
+
/// compare like for like.
|
|
481
|
+
public static func makeEncoder() -> JSONEncoder {
|
|
482
|
+
let encoder = JSONEncoder()
|
|
483
|
+
encoder.outputFormatting = [.sortedKeys]
|
|
484
|
+
return encoder
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
public static func decodeRequest<Request: Decodable>(
|
|
488
|
+
_ type: Request.Type,
|
|
489
|
+
from payload: Data,
|
|
490
|
+
validate: (Request) throws -> Void = { _ in }
|
|
491
|
+
) throws -> Request {
|
|
492
|
+
do {
|
|
493
|
+
let request = try makeDecoder().decode(Request.self, from: payload)
|
|
494
|
+
try validate(request)
|
|
495
|
+
return request
|
|
496
|
+
} catch let error as DecodingError {
|
|
497
|
+
throw BridgeError.invalidPayload(path: path(of: error))
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
static func path(of error: DecodingError) -> String {
|
|
502
|
+
let keys: [any CodingKey]
|
|
503
|
+
switch error {
|
|
504
|
+
case .typeMismatch(_, let context), .valueNotFound(_, let context), .dataCorrupted(let context):
|
|
505
|
+
keys = context.codingPath
|
|
506
|
+
case .keyNotFound(let key, let context):
|
|
507
|
+
keys = context.codingPath + [key]
|
|
508
|
+
@unknown default:
|
|
509
|
+
keys = []
|
|
510
|
+
}
|
|
511
|
+
return keys.reduce("$") { path, key in
|
|
512
|
+
if let index = key.intValue { return "\(path)[\(index)]" }
|
|
513
|
+
return "\(path).\(key.stringValue)"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
public func callAsFunction(op: String, payload: Data) async throws -> Data {
|
|
518
|
+
// An op string no version of the schema declared: a wrapper and a native half that
|
|
519
|
+
// disagree. Reported as the same code an op this platform lacks gets, because from the
|
|
520
|
+
// caller's side the two are indistinguishable and both mean "not available here".
|
|
521
|
+
guard let known = BridgeOp(rawValue: op) else { throw BridgeError.unsupportedOnPlatform(op: op) }
|
|
522
|
+
return try await dispatch(known, payload)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private func dispatch(_ op: BridgeOp, _ payload: Data) async throws -> Data {
|
|
526
|
+
let encoder = Self.makeEncoder()
|
|
527
|
+
switch op {
|
|
528
|
+
case .clientAttach:
|
|
529
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
530
|
+
let response = try await ops.clientAttach(request)
|
|
531
|
+
return try encoder.encode(response)
|
|
532
|
+
case .clientConnect:
|
|
533
|
+
let request = try Self.decodeRequest(ConnectRequest.self, from: payload)
|
|
534
|
+
let response = try await ops.clientConnect(request)
|
|
535
|
+
return try encoder.encode(response)
|
|
536
|
+
case .clientConnections:
|
|
537
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
538
|
+
let response = try await ops.clientConnections(request)
|
|
539
|
+
return try encoder.encode(response)
|
|
540
|
+
case .clientDisconnect:
|
|
541
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
542
|
+
let response = try await ops.clientDisconnect(request)
|
|
543
|
+
return try encoder.encode(response)
|
|
544
|
+
case .tokenSetProviderPresent:
|
|
545
|
+
let request = try Self.decodeRequest(TokenProviderRequest.self, from: payload)
|
|
546
|
+
let response = try await ops.tokenSetProviderPresent(request)
|
|
547
|
+
return try encoder.encode(response)
|
|
548
|
+
case .tokenResolve:
|
|
549
|
+
let request = try Self.decodeRequest(TokenResolveRequest.self, from: payload)
|
|
550
|
+
let response = try await ops.tokenResolve(request)
|
|
551
|
+
return try encoder.encode(response)
|
|
552
|
+
case .tokenReject:
|
|
553
|
+
let request = try Self.decodeRequest(TokenRejectRequest.self, from: payload)
|
|
554
|
+
let response = try await ops.tokenReject(request)
|
|
555
|
+
return try encoder.encode(response)
|
|
556
|
+
case .permissionsRequest:
|
|
557
|
+
let request = try Self.decodeRequest(PermissionRequest.self, from: payload)
|
|
558
|
+
let response = try await ops.permissionsRequest(request)
|
|
559
|
+
return try encoder.encode(response)
|
|
560
|
+
case .permissionsStatus:
|
|
561
|
+
let request = try Self.decodeRequest(CategoryRequest.self, from: payload)
|
|
562
|
+
let response = try await ops.permissionsStatus(request)
|
|
563
|
+
return try encoder.encode(response)
|
|
564
|
+
case .permissionsWriteStatus:
|
|
565
|
+
let request = try Self.decodeRequest(CategoryRequest.self, from: payload)
|
|
566
|
+
let response = try await ops.permissionsWriteStatus(request)
|
|
567
|
+
return try encoder.encode(response)
|
|
568
|
+
case .permissionsRequested:
|
|
569
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
570
|
+
let response = try await ops.permissionsRequested(request)
|
|
571
|
+
return try encoder.encode(response)
|
|
572
|
+
case .permissionsStatuses:
|
|
573
|
+
throw BridgeError.unsupportedOnPlatform(op: op.rawValue)
|
|
574
|
+
case .permissionsBackgroundReadStatus:
|
|
575
|
+
throw BridgeError.unsupportedOnPlatform(op: op.rawValue)
|
|
576
|
+
case .permissionsHistoryReadStatus:
|
|
577
|
+
throw BridgeError.unsupportedOnPlatform(op: op.rawValue)
|
|
578
|
+
case .diagnosticsState:
|
|
579
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
580
|
+
let response = try await ops.diagnosticsState(request)
|
|
581
|
+
return try encoder.encode(response)
|
|
582
|
+
case .diagnosticsLastDelivery:
|
|
583
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
584
|
+
let response = try await ops.diagnosticsLastDelivery(request)
|
|
585
|
+
return try encoder.encode(response)
|
|
586
|
+
case .diagnosticsReport:
|
|
587
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
588
|
+
let response = try await ops.diagnosticsReport(request)
|
|
589
|
+
try SupportReportEnvelope.validate(response)
|
|
590
|
+
return try encoder.encode(response)
|
|
591
|
+
case .diagnosticsSend:
|
|
592
|
+
let request = try Self.decodeRequest(SourceRequest.self, from: payload)
|
|
593
|
+
let response = try await ops.diagnosticsSend(request)
|
|
594
|
+
return try encoder.encode(response)
|
|
595
|
+
case .backgroundIsRegistered:
|
|
596
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
597
|
+
let response = try await ops.backgroundIsRegistered(request)
|
|
598
|
+
return try encoder.encode(response)
|
|
599
|
+
case .backgroundCancel:
|
|
600
|
+
throw BridgeError.unsupportedOnPlatform(op: op.rawValue)
|
|
601
|
+
case .bodyHeartRate:
|
|
602
|
+
let request = try Self.decodeRequest(RangeRequest.self, from: payload)
|
|
603
|
+
let response = try await ops.bodyHeartRate(request)
|
|
604
|
+
return try encoder.encode(response)
|
|
605
|
+
case .bodyLogHeartRate:
|
|
606
|
+
let request = try Self.decodeRequest(HeartRateEntryList.self, from: payload)
|
|
607
|
+
let response = try await ops.bodyLogHeartRate(request)
|
|
608
|
+
return try encoder.encode(response)
|
|
609
|
+
case .bodyLog:
|
|
610
|
+
let request = try Self.decodeRequest(BodyEntryList.self, from: payload)
|
|
611
|
+
let response = try await ops.bodyLog(request)
|
|
612
|
+
return try encoder.encode(response)
|
|
613
|
+
case .bodyDelete:
|
|
614
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
615
|
+
let response = try await ops.bodyDelete(request)
|
|
616
|
+
return try encoder.encode(response)
|
|
617
|
+
case .dailySteps:
|
|
618
|
+
let request = try Self.decodeRequest(RangeRequest.self, from: payload)
|
|
619
|
+
let response = try await ops.dailySteps(request)
|
|
620
|
+
return try encoder.encode(response)
|
|
621
|
+
case .dailyLog:
|
|
622
|
+
let request = try Self.decodeRequest(StepEntryList.self, from: payload)
|
|
623
|
+
let response = try await ops.dailyLog(request)
|
|
624
|
+
return try encoder.encode(response)
|
|
625
|
+
case .dailyDelete:
|
|
626
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
627
|
+
let response = try await ops.dailyDelete(request)
|
|
628
|
+
return try encoder.encode(response)
|
|
629
|
+
case .sleepSegments:
|
|
630
|
+
let request = try Self.decodeRequest(RangeRequest.self, from: payload)
|
|
631
|
+
let response = try await ops.sleepSegments(request)
|
|
632
|
+
return try encoder.encode(response)
|
|
633
|
+
case .sleepLog:
|
|
634
|
+
let request = try Self.decodeRequest(SleepEntryList.self, from: payload)
|
|
635
|
+
let response = try await ops.sleepLog(request)
|
|
636
|
+
return try encoder.encode(response)
|
|
637
|
+
case .sleepDelete:
|
|
638
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
639
|
+
let response = try await ops.sleepDelete(request)
|
|
640
|
+
return try encoder.encode(response)
|
|
641
|
+
case .activityWorkouts:
|
|
642
|
+
let request = try Self.decodeRequest(RangeRequest.self, from: payload)
|
|
643
|
+
let response = try await ops.activityWorkouts(request)
|
|
644
|
+
return try encoder.encode(response)
|
|
645
|
+
case .activityLog:
|
|
646
|
+
let request = try Self.decodeRequest(WorkoutEntryList.self, from: payload)
|
|
647
|
+
let response = try await ops.activityLog(request)
|
|
648
|
+
return try encoder.encode(response)
|
|
649
|
+
case .activityDelete:
|
|
650
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
651
|
+
let response = try await ops.activityDelete(request)
|
|
652
|
+
return try encoder.encode(response)
|
|
653
|
+
case .nutritionLog:
|
|
654
|
+
let request = try Self.decodeRequest(NutritionEntryList.self, from: payload)
|
|
655
|
+
let response = try await ops.nutritionLog(request)
|
|
656
|
+
return try encoder.encode(response)
|
|
657
|
+
case .nutritionDelete:
|
|
658
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
659
|
+
let response = try await ops.nutritionDelete(request)
|
|
660
|
+
return try encoder.encode(response)
|
|
661
|
+
case .plannedWorkoutsPlanned:
|
|
662
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
663
|
+
let response = try await ops.plannedWorkoutsPlanned(request)
|
|
664
|
+
return try encoder.encode(response)
|
|
665
|
+
case .plannedWorkoutsMarkComplete:
|
|
666
|
+
let request = try Self.decodeRequest(MarkCompleteRequest.self, from: payload)
|
|
667
|
+
let response = try await ops.plannedWorkoutsMarkComplete(request)
|
|
668
|
+
return try encoder.encode(response)
|
|
669
|
+
case .plannedWorkoutsDelete:
|
|
670
|
+
let request = try Self.decodeRequest(RecordIdRequest.self, from: payload)
|
|
671
|
+
let response = try await ops.plannedWorkoutsDelete(request)
|
|
672
|
+
return try encoder.encode(response)
|
|
673
|
+
case .plannedWorkoutsSchedulingStatus:
|
|
674
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
675
|
+
let response = try await ops.plannedWorkoutsSchedulingStatus(request)
|
|
676
|
+
return try encoder.encode(response)
|
|
677
|
+
case .plannedWorkoutsRequestScheduling:
|
|
678
|
+
let request = try Self.decodeRequest(Empty.self, from: payload)
|
|
679
|
+
let response = try await ops.plannedWorkoutsRequestScheduling(request)
|
|
680
|
+
return try encoder.encode(response)
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/// Every stream the schema declares, including the ones this platform does not publish.
|
|
686
|
+
public enum BridgeStream: String, CaseIterable, Sendable {
|
|
687
|
+
case diagnosticsEvents = "diagnostics.events"
|
|
688
|
+
case bodyChanges = "body.changes"
|
|
689
|
+
case dailyChanges = "daily.changes"
|
|
690
|
+
case sleepChanges = "sleep.changes"
|
|
691
|
+
case activityChanges = "activity.changes"
|
|
692
|
+
case bridgeTokenRequest = "bridge.tokenRequest"
|
|
693
|
+
|
|
694
|
+
/// Whether this platform publishes the stream.
|
|
695
|
+
public var isSupported: Bool {
|
|
696
|
+
switch self {
|
|
697
|
+
case .diagnosticsEvents: return true
|
|
698
|
+
case .bodyChanges: return true
|
|
699
|
+
case .dailyChanges: return true
|
|
700
|
+
case .sleepChanges: return true
|
|
701
|
+
case .activityChanges: return true
|
|
702
|
+
case .bridgeTokenRequest: return true
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/// What P3 publishes. One method per stream this platform supports, each a fresh stream per call:
|
|
708
|
+
/// a subscriber that goes away cancels its own source and nobody else's.
|
|
709
|
+
public protocol TerraBridgeStreams: Sendable {
|
|
710
|
+
/// Every subscriber gets its own native stream, matching the SDK's fresh-stream-per-call
|
|
711
|
+
/// contract.
|
|
712
|
+
func diagnosticsEvents(_ params: Empty) async throws -> AsyncStream<TerraEvent>
|
|
713
|
+
func bodyChanges(_ params: Empty) async throws -> AsyncStream<HealthChange>
|
|
714
|
+
func dailyChanges(_ params: Empty) async throws -> AsyncStream<HealthChange>
|
|
715
|
+
func sleepChanges(_ params: Empty) async throws -> AsyncStream<HealthChange>
|
|
716
|
+
func activityChanges(_ params: Empty) async throws -> AsyncStream<HealthChange>
|
|
717
|
+
/// Bridge-owned. The broker's ask, not an SDK stream.
|
|
718
|
+
func bridgeTokenRequest(_ params: Empty) async throws -> AsyncStream<TokenRequest>
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
public struct BridgeStreamDispatch: Sendable {
|
|
722
|
+
private let streams: any TerraBridgeStreams
|
|
723
|
+
private let errorPayload: @Sendable (any Error) -> TerraErrorPayload
|
|
724
|
+
|
|
725
|
+
public init(streams: any TerraBridgeStreams, errorPayload: @escaping @Sendable (any Error) -> TerraErrorPayload) {
|
|
726
|
+
self.streams = streams
|
|
727
|
+
self.errorPayload = errorPayload
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
public func open(stream: String, params: Data) async throws -> AsyncStream<Data> {
|
|
731
|
+
guard let known = BridgeStream(rawValue: stream) else { throw BridgeError.unsupportedOnPlatform(op: stream) }
|
|
732
|
+
switch known {
|
|
733
|
+
case .diagnosticsEvents:
|
|
734
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
735
|
+
return await framed({ try await streams.diagnosticsEvents(request) }) { try TerraEventEnvelope.validate($0) }
|
|
736
|
+
case .bodyChanges:
|
|
737
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
738
|
+
return await framed({ try await streams.bodyChanges(request) })
|
|
739
|
+
case .dailyChanges:
|
|
740
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
741
|
+
return await framed({ try await streams.dailyChanges(request) })
|
|
742
|
+
case .sleepChanges:
|
|
743
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
744
|
+
return await framed({ try await streams.sleepChanges(request) })
|
|
745
|
+
case .activityChanges:
|
|
746
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
747
|
+
return await framed({ try await streams.activityChanges(request) })
|
|
748
|
+
case .bridgeTokenRequest:
|
|
749
|
+
let request = try BridgeDispatch.decodeRequest(Empty.self, from: params)
|
|
750
|
+
return await framed({ try await streams.bridgeTokenRequest(request) })
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private static func frame<Element: Encodable>(_ element: Element) throws -> Data {
|
|
755
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
756
|
+
let object = try BridgeDispatch.makeDecoder().decode(BridgeJSON.self, from: try encoder.encode(element))
|
|
757
|
+
return try encoder.encode(StreamMessage.data(element: object))
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
private func framed<Element: Encodable & Sendable>(
|
|
761
|
+
_ open: () async throws -> AsyncStream<Element>,
|
|
762
|
+
validate: @escaping @Sendable (Element) throws -> Void = { _ in }
|
|
763
|
+
) async -> AsyncStream<Data> {
|
|
764
|
+
let errorPayload = self.errorPayload
|
|
765
|
+
let source: AsyncStream<Element>
|
|
766
|
+
do {
|
|
767
|
+
source = try await open()
|
|
768
|
+
} catch {
|
|
769
|
+
let failed = try? BridgeDispatch.makeEncoder().encode(StreamMessage.error(error: errorPayload(error)))
|
|
770
|
+
return AsyncStream { continuation in
|
|
771
|
+
if let failed { continuation.yield(failed) }
|
|
772
|
+
continuation.finish()
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return AsyncStream { continuation in
|
|
776
|
+
let task = Task {
|
|
777
|
+
let encoder = BridgeDispatch.makeEncoder()
|
|
778
|
+
do {
|
|
779
|
+
for await element in source {
|
|
780
|
+
try validate(element)
|
|
781
|
+
continuation.yield(try Self.frame(element))
|
|
782
|
+
}
|
|
783
|
+
continuation.yield(try encoder.encode(StreamMessage.complete))
|
|
784
|
+
} catch {
|
|
785
|
+
if let failed = try? encoder.encode(StreamMessage.error(error: errorPayload(error))) {
|
|
786
|
+
continuation.yield(failed)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
continuation.finish()
|
|
790
|
+
}
|
|
791
|
+
continuation.onTermination = { _ in task.cancel() }
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|