@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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -2
  3. package/TerraReactNative.podspec +48 -0
  4. package/android/build.gradle +87 -0
  5. package/android/src/bridgeCore/kotlin/README.md +1 -0
  6. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeBootstrap.kt +133 -0
  7. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/BridgeHost.kt +32 -0
  8. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Conversions.kt +468 -0
  9. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/LauncherSlots.kt +41 -0
  10. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/Rejections.kt +150 -0
  11. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/StreamRegistry.kt +77 -0
  12. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridge.kt +86 -0
  13. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeOpsImpl.kt +258 -0
  14. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TerraBridgeStreamsImpl.kt +52 -0
  15. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/TokenBroker.kt +198 -0
  16. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeErrors.kt +217 -0
  17. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeOps.kt +493 -0
  18. package/android/src/bridgeCore/kotlin/co/tryterra/sdk/bridge/generated/BridgeTypes.kt +897 -0
  19. package/android/src/healthConnectOnly/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +11 -0
  20. package/android/src/main/AndroidManifest.xml +24 -0
  21. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraBridgeModule.kt +96 -0
  22. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraReactNativePackage.kt +26 -0
  23. package/android/src/main/java/co/tryterra/sdk/reactnative/TerraStartup.kt +46 -0
  24. package/android/src/samsung/kotlin/co/tryterra/sdk/reactnative/TerraStores.kt +13 -0
  25. package/docs/availability.md +246 -0
  26. package/frameworks/TerraSDK.xcframework/Info.plist +48 -0
  27. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Info.plist +0 -0
  28. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.abi.json +81513 -0
  29. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.package.swiftinterface +2150 -0
  30. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2150 -0
  31. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  32. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios.swiftinterface +2127 -0
  33. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  34. package/frameworks/TerraSDK.xcframework/ios-arm64/TerraSDK.framework/TerraSDK +0 -0
  35. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  36. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  37. package/frameworks/TerraSDK.xcframework/ios-arm64/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6473 -0
  38. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Info.plist +0 -0
  39. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +81513 -0
  40. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.package.swiftinterface +2150 -0
  41. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2150 -0
  42. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  43. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2127 -0
  44. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +81513 -0
  45. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.package.swiftinterface +2150 -0
  46. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2150 -0
  47. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  48. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/Modules/TerraSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2127 -0
  49. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/PrivacyInfo.xcprivacy +96 -0
  50. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/TerraSDK +0 -0
  51. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/TerraSDK.framework/_CodeSignature/CodeResources +223 -0
  52. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Info.plist +20 -0
  53. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/DWARF/TerraSDK +0 -0
  54. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/aarch64/TerraSDK.yml +6474 -0
  55. package/frameworks/TerraSDK.xcframework/ios-arm64_x86_64-simulator/dSYMs/TerraSDK.framework.dSYM/Contents/Resources/Relocations/x86_64/TerraSDK.yml +6392 -0
  56. package/ios/TerraBridgeCore/BridgeBootstrap.swift +89 -0
  57. package/ios/TerraBridgeCore/BridgeHost.swift +13 -0
  58. package/ios/TerraBridgeCore/Conversions.swift +467 -0
  59. package/ios/TerraBridgeCore/Generated/BridgeErrors.swift +179 -0
  60. package/ios/TerraBridgeCore/Generated/BridgeOps.swift +795 -0
  61. package/ios/TerraBridgeCore/Generated/BridgeTypes.swift +1317 -0
  62. package/ios/TerraBridgeCore/Locked.swift +15 -0
  63. package/ios/TerraBridgeCore/README.md +1 -0
  64. package/ios/TerraBridgeCore/Rejections.swift +130 -0
  65. package/ios/TerraBridgeCore/RuntimeAttachment.swift +60 -0
  66. package/ios/TerraBridgeCore/StreamRegistry.swift +59 -0
  67. package/ios/TerraBridgeCore/TerraBridge.swift +122 -0
  68. package/ios/TerraBridgeCore/TerraBridgeOpsImpl.swift +254 -0
  69. package/ios/TerraBridgeCore/TerraBridgeStreamsImpl.swift +60 -0
  70. package/ios/TerraBridgeCore/TokenBroker.swift +147 -0
  71. package/ios/TerraBridgeModule.mm +79 -0
  72. package/ios/TerraBridgeModuleImpl.swift +49 -0
  73. package/ios/TerraReactNative.swift +19 -0
  74. package/lib/EndUser.d.ts +12 -0
  75. package/lib/EndUser.js +13 -0
  76. package/lib/NativeTerraBridge.d.ts +23 -0
  77. package/lib/NativeTerraBridge.js +13 -0
  78. package/lib/Terra.d.ts +154 -0
  79. package/lib/Terra.js +280 -0
  80. package/lib/TerraError.d.ts +42 -0
  81. package/lib/TerraError.js +95 -0
  82. package/lib/createTerra.d.ts +8 -0
  83. package/lib/createTerra.js +19 -0
  84. package/lib/generated/client.d.ts +92 -0
  85. package/lib/generated/client.js +196 -0
  86. package/lib/generated/codecs.d.ts +135 -0
  87. package/lib/generated/codecs.js +1358 -0
  88. package/lib/generated/errors.d.ts +13 -0
  89. package/lib/generated/errors.js +68 -0
  90. package/lib/generated/ops.d.ts +13 -0
  91. package/lib/generated/ops.js +137 -0
  92. package/lib/generated/streams.d.ts +23 -0
  93. package/lib/generated/streams.js +58 -0
  94. package/lib/generated/types.d.ts +481 -0
  95. package/lib/generated/types.js +92 -0
  96. package/lib/index.d.ts +29 -0
  97. package/lib/index.js +55 -0
  98. package/package.json +71 -4
  99. package/src/EndUser.ts +15 -0
  100. package/src/NativeTerraBridge.ts +27 -0
  101. package/src/Terra.ts +462 -0
  102. package/src/TerraError.ts +116 -0
  103. package/src/createTerra.ts +18 -0
  104. package/src/generated/client.ts +243 -0
  105. package/src/generated/codecs.ts +1287 -0
  106. package/src/generated/errors.ts +113 -0
  107. package/src/generated/ops.ts +197 -0
  108. package/src/generated/streams.ts +74 -0
  109. package/src/generated/types.ts +667 -0
  110. package/src/index.ts +52 -0
@@ -0,0 +1,493 @@
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
+ package co.tryterra.sdk.bridge.generated
5
+
6
+ import kotlinx.coroutines.flow.Flow
7
+ import kotlinx.coroutines.flow.catch
8
+ import kotlinx.coroutines.flow.emitAll
9
+ import kotlinx.coroutines.flow.flow
10
+ import kotlinx.coroutines.flow.map
11
+ import kotlinx.coroutines.flow.onCompletion
12
+ import kotlinx.serialization.KSerializer
13
+ import kotlinx.serialization.json.Json
14
+ import kotlinx.serialization.json.JsonObject
15
+ import kotlinx.serialization.json.jsonObject
16
+ import kotlinx.serialization.serializer
17
+
18
+ /**
19
+ * Every op the schema declares, including the ones this platform does not implement.
20
+ *
21
+ * The unsupported ones are entries here rather than absent so that dispatch can answer
22
+ * bridge.unsupported_on_platform deliberately, instead of failing to parse the op.
23
+ */
24
+ public enum class BridgeOp(public val wire: String, public val isSupported: Boolean) {
25
+ CLIENT_ATTACH("client.attach", true),
26
+ CLIENT_CONNECT("client.connect", true),
27
+ CLIENT_CONNECTIONS("client.connections", true),
28
+ CLIENT_DISCONNECT("client.disconnect", true),
29
+ TOKEN_SET_PROVIDER_PRESENT("token.setProviderPresent", true),
30
+ TOKEN_RESOLVE("token.resolve", true),
31
+ TOKEN_REJECT("token.reject", true),
32
+ PERMISSIONS_REQUEST("permissions.request", true),
33
+ PERMISSIONS_STATUS("permissions.status", true),
34
+ PERMISSIONS_WRITE_STATUS("permissions.writeStatus", true),
35
+ PERMISSIONS_REQUESTED("permissions.requested", true),
36
+ PERMISSIONS_STATUSES("permissions.statuses", true),
37
+ PERMISSIONS_BACKGROUND_READ_STATUS("permissions.backgroundReadStatus", true),
38
+ PERMISSIONS_HISTORY_READ_STATUS("permissions.historyReadStatus", true),
39
+ DIAGNOSTICS_STATE("diagnostics.state", true),
40
+ DIAGNOSTICS_LAST_DELIVERY("diagnostics.lastDelivery", true),
41
+ DIAGNOSTICS_REPORT("diagnostics.report", true),
42
+ DIAGNOSTICS_SEND("diagnostics.send", true),
43
+ BACKGROUND_IS_REGISTERED("background.isRegistered", true),
44
+ BACKGROUND_CANCEL("background.cancel", true),
45
+ BODY_HEART_RATE("body.heartRate", true),
46
+ BODY_LOG_HEART_RATE("body.logHeartRate", true),
47
+ BODY_LOG("body.log", true),
48
+ BODY_DELETE("body.delete", true),
49
+ DAILY_STEPS("daily.steps", true),
50
+ DAILY_LOG("daily.log", true),
51
+ DAILY_DELETE("daily.delete", true),
52
+ SLEEP_SEGMENTS("sleep.segments", true),
53
+ SLEEP_LOG("sleep.log", true),
54
+ SLEEP_DELETE("sleep.delete", true),
55
+ ACTIVITY_WORKOUTS("activity.workouts", true),
56
+ ACTIVITY_LOG("activity.log", true),
57
+ ACTIVITY_DELETE("activity.delete", true),
58
+ NUTRITION_LOG("nutrition.log", true),
59
+ NUTRITION_DELETE("nutrition.delete", true),
60
+ PLANNED_WORKOUTS_PLANNED("plannedWorkouts.planned", false),
61
+ PLANNED_WORKOUTS_MARK_COMPLETE("plannedWorkouts.markComplete", false),
62
+ PLANNED_WORKOUTS_DELETE("plannedWorkouts.delete", false),
63
+ PLANNED_WORKOUTS_SCHEDULING_STATUS("plannedWorkouts.schedulingStatus", false),
64
+ PLANNED_WORKOUTS_REQUEST_SCHEDULING("plannedWorkouts.requestScheduling", false),
65
+ ;
66
+
67
+ public companion object {
68
+ private val byWire: Map<String, BridgeOp> = entries.associateBy { it.wire }
69
+
70
+ public fun of(wire: String): BridgeOp? = byWire[wire]
71
+ }
72
+ }
73
+
74
+ /**
75
+ * What P2 implements. One suspend function per op this platform supports.
76
+ *
77
+ * An op added to the schema stops this module compiling until the implementation grows a
78
+ * member for it, which is the whole reason the dispatch is generated from the same file.
79
+ */
80
+ public interface TerraBridgeOps {
81
+ /**
82
+ * Reports the native client the JS side is attaching to. Idempotent, and safe to call after a
83
+ * JS reload.
84
+ */
85
+ public suspend fun clientAttach(request: Empty): BootstrapState
86
+ public suspend fun clientConnect(request: ConnectRequest): Connection
87
+ public suspend fun clientConnections(request: Empty): ConnectionList
88
+ public suspend fun clientDisconnect(request: SourceRequest): Empty
89
+ /**
90
+ * Tells the broker whether JS can answer at all. Without it a link asks a provider that is not
91
+ * there and waits out the timeout.
92
+ */
93
+ public suspend fun tokenSetProviderPresent(request: TokenProviderRequest): Empty
94
+ /**
95
+ * An unknown or already-settled requestId is ignored rather than rejected, so a reload cannot
96
+ * fail a link that has already moved on.
97
+ */
98
+ public suspend fun tokenResolve(request: TokenResolveRequest): Empty
99
+ public suspend fun tokenReject(request: TokenRejectRequest): Empty
100
+ public suspend fun permissionsRequest(request: PermissionRequest): PermissionGrant
101
+ public suspend fun permissionsStatus(request: CategoryRequest): StatusResult
102
+ public suspend fun permissionsWriteStatus(request: CategoryRequest): StatusResult
103
+ public suspend fun permissionsRequested(request: SourceRequest): HealthAccess
104
+ /** Available on android only. */
105
+ public suspend fun permissionsStatuses(request: SourceRequest): StatusesResult
106
+ /** Available on android only. */
107
+ public suspend fun permissionsBackgroundReadStatus(request: SourceRequest): StatusResult
108
+ /** Available on android only. */
109
+ public suspend fun permissionsHistoryReadStatus(request: SourceRequest): StatusResult
110
+ public suspend fun diagnosticsState(request: SourceRequest): DiagnosticsState
111
+ public suspend fun diagnosticsLastDelivery(request: SourceRequest): LastDeliveryResult
112
+ public suspend fun diagnosticsReport(request: SourceRequest): SupportReport
113
+ public suspend fun diagnosticsSend(request: SourceRequest): Empty
114
+ /**
115
+ * False means bootstrap did not run. The SDK reports the same thing independently as
116
+ * backgroundRefreshUnscheduled.
117
+ */
118
+ public suspend fun backgroundIsRegistered(request: Empty): BoolResult
119
+ /** Available on android only. */
120
+ public suspend fun backgroundCancel(request: Empty): BoolResult
121
+ public suspend fun bodyHeartRate(request: RangeRequest): HeartRateList
122
+ /** Its own op rather than an overload: Swift overloads log by element type and JS cannot. */
123
+ public suspend fun bodyLogHeartRate(request: HeartRateEntryList): RecordIdList
124
+ public suspend fun bodyLog(request: BodyEntryList): RecordIdList
125
+ public suspend fun bodyDelete(request: RecordIdRequest): Empty
126
+ public suspend fun dailySteps(request: RangeRequest): StepCountList
127
+ public suspend fun dailyLog(request: StepEntryList): RecordIdList
128
+ public suspend fun dailyDelete(request: RecordIdRequest): Empty
129
+ public suspend fun sleepSegments(request: RangeRequest): SleepSegmentList
130
+ public suspend fun sleepLog(request: SleepEntryList): RecordIdList
131
+ public suspend fun sleepDelete(request: RecordIdRequest): Empty
132
+ public suspend fun activityWorkouts(request: RangeRequest): WorkoutList
133
+ public suspend fun activityLog(request: WorkoutEntryList): RecordIdList
134
+ public suspend fun activityDelete(request: RecordIdRequest): Empty
135
+ public suspend fun nutritionLog(request: NutritionEntryList): RecordIdList
136
+ public suspend fun nutritionDelete(request: RecordIdRequest): Empty
137
+ }
138
+
139
+ /**
140
+ * Decodes one side of an exchange and re-encodes it, for the bridge-vector runner.
141
+ *
142
+ * The runner cannot name the request and response types itself without keeping a second copy
143
+ * of the op table, and a second copy of the op table is the thing this schema exists to
144
+ * remove. A round trip is also exactly what a vector asserts: the bytes in are the bytes out,
145
+ * which pins the DTOs and their serializer against the same file the other two runners read.
146
+ */
147
+ public object BridgeCodecs {
148
+ public fun roundTripRequest(op: BridgeOp, payload: String, json: Json = BridgeDispatch.defaultJson): String =
149
+ when (op) {
150
+ BridgeOp.CLIENT_ATTACH -> json.encodeToString(json.decodeFromString<Empty>(payload))
151
+ BridgeOp.CLIENT_CONNECT -> json.encodeToString(json.decodeFromString<ConnectRequest>(payload))
152
+ BridgeOp.CLIENT_CONNECTIONS -> json.encodeToString(json.decodeFromString<Empty>(payload))
153
+ BridgeOp.CLIENT_DISCONNECT -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
154
+ BridgeOp.TOKEN_SET_PROVIDER_PRESENT -> json.encodeToString(json.decodeFromString<TokenProviderRequest>(payload))
155
+ BridgeOp.TOKEN_RESOLVE -> json.encodeToString(json.decodeFromString<TokenResolveRequest>(payload))
156
+ BridgeOp.TOKEN_REJECT -> json.encodeToString(json.decodeFromString<TokenRejectRequest>(payload))
157
+ BridgeOp.PERMISSIONS_REQUEST -> json.encodeToString(json.decodeFromString<PermissionRequest>(payload))
158
+ BridgeOp.PERMISSIONS_STATUS -> json.encodeToString(json.decodeFromString<CategoryRequest>(payload))
159
+ BridgeOp.PERMISSIONS_WRITE_STATUS -> json.encodeToString(json.decodeFromString<CategoryRequest>(payload))
160
+ BridgeOp.PERMISSIONS_REQUESTED -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
161
+ BridgeOp.PERMISSIONS_STATUSES -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
162
+ BridgeOp.PERMISSIONS_BACKGROUND_READ_STATUS -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
163
+ BridgeOp.PERMISSIONS_HISTORY_READ_STATUS -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
164
+ BridgeOp.DIAGNOSTICS_STATE -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
165
+ BridgeOp.DIAGNOSTICS_LAST_DELIVERY -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
166
+ BridgeOp.DIAGNOSTICS_REPORT -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
167
+ BridgeOp.DIAGNOSTICS_SEND -> json.encodeToString(json.decodeFromString<SourceRequest>(payload))
168
+ BridgeOp.BACKGROUND_IS_REGISTERED -> json.encodeToString(json.decodeFromString<Empty>(payload))
169
+ BridgeOp.BACKGROUND_CANCEL -> json.encodeToString(json.decodeFromString<Empty>(payload))
170
+ BridgeOp.BODY_HEART_RATE -> json.encodeToString(json.decodeFromString<RangeRequest>(payload))
171
+ BridgeOp.BODY_LOG_HEART_RATE -> json.encodeToString(json.decodeFromString<HeartRateEntryList>(payload))
172
+ BridgeOp.BODY_LOG -> json.encodeToString(json.decodeFromString<BodyEntryList>(payload))
173
+ BridgeOp.BODY_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
174
+ BridgeOp.DAILY_STEPS -> json.encodeToString(json.decodeFromString<RangeRequest>(payload))
175
+ BridgeOp.DAILY_LOG -> json.encodeToString(json.decodeFromString<StepEntryList>(payload))
176
+ BridgeOp.DAILY_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
177
+ BridgeOp.SLEEP_SEGMENTS -> json.encodeToString(json.decodeFromString<RangeRequest>(payload))
178
+ BridgeOp.SLEEP_LOG -> json.encodeToString(json.decodeFromString<SleepEntryList>(payload))
179
+ BridgeOp.SLEEP_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
180
+ BridgeOp.ACTIVITY_WORKOUTS -> json.encodeToString(json.decodeFromString<RangeRequest>(payload))
181
+ BridgeOp.ACTIVITY_LOG -> json.encodeToString(json.decodeFromString<WorkoutEntryList>(payload))
182
+ BridgeOp.ACTIVITY_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
183
+ BridgeOp.NUTRITION_LOG -> json.encodeToString(json.decodeFromString<NutritionEntryList>(payload))
184
+ BridgeOp.NUTRITION_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
185
+ BridgeOp.PLANNED_WORKOUTS_PLANNED -> json.encodeToString(json.decodeFromString<Empty>(payload))
186
+ BridgeOp.PLANNED_WORKOUTS_MARK_COMPLETE -> json.encodeToString(json.decodeFromString<MarkCompleteRequest>(payload))
187
+ BridgeOp.PLANNED_WORKOUTS_DELETE -> json.encodeToString(json.decodeFromString<RecordIdRequest>(payload))
188
+ BridgeOp.PLANNED_WORKOUTS_SCHEDULING_STATUS -> json.encodeToString(json.decodeFromString<Empty>(payload))
189
+ BridgeOp.PLANNED_WORKOUTS_REQUEST_SCHEDULING -> json.encodeToString(json.decodeFromString<Empty>(payload))
190
+ }
191
+
192
+ public fun roundTripResponse(op: BridgeOp, payload: String, json: Json = BridgeDispatch.defaultJson): String =
193
+ when (op) {
194
+ BridgeOp.CLIENT_ATTACH -> json.encodeToString(json.decodeFromString<BootstrapState>(payload))
195
+ BridgeOp.CLIENT_CONNECT -> json.encodeToString(json.decodeFromString<Connection>(payload))
196
+ BridgeOp.CLIENT_CONNECTIONS -> json.encodeToString(json.decodeFromString<ConnectionList>(payload))
197
+ BridgeOp.CLIENT_DISCONNECT -> json.encodeToString(json.decodeFromString<Empty>(payload))
198
+ BridgeOp.TOKEN_SET_PROVIDER_PRESENT -> json.encodeToString(json.decodeFromString<Empty>(payload))
199
+ BridgeOp.TOKEN_RESOLVE -> json.encodeToString(json.decodeFromString<Empty>(payload))
200
+ BridgeOp.TOKEN_REJECT -> json.encodeToString(json.decodeFromString<Empty>(payload))
201
+ BridgeOp.PERMISSIONS_REQUEST -> json.encodeToString(json.decodeFromString<PermissionGrant>(payload))
202
+ BridgeOp.PERMISSIONS_STATUS -> json.encodeToString(json.decodeFromString<StatusResult>(payload))
203
+ BridgeOp.PERMISSIONS_WRITE_STATUS -> json.encodeToString(json.decodeFromString<StatusResult>(payload))
204
+ BridgeOp.PERMISSIONS_REQUESTED -> json.encodeToString(json.decodeFromString<HealthAccess>(payload))
205
+ BridgeOp.PERMISSIONS_STATUSES -> json.encodeToString(json.decodeFromString<StatusesResult>(payload))
206
+ BridgeOp.PERMISSIONS_BACKGROUND_READ_STATUS -> json.encodeToString(json.decodeFromString<StatusResult>(payload))
207
+ BridgeOp.PERMISSIONS_HISTORY_READ_STATUS -> json.encodeToString(json.decodeFromString<StatusResult>(payload))
208
+ BridgeOp.DIAGNOSTICS_STATE -> json.encodeToString(json.decodeFromString<DiagnosticsState>(payload))
209
+ BridgeOp.DIAGNOSTICS_LAST_DELIVERY -> json.encodeToString(json.decodeFromString<LastDeliveryResult>(payload))
210
+ BridgeOp.DIAGNOSTICS_REPORT -> {
211
+ val value = json.decodeFromString<SupportReport>(payload)
212
+ SupportReportEnvelope.validate(value)
213
+ json.encodeToString(value)
214
+ }
215
+ BridgeOp.DIAGNOSTICS_SEND -> json.encodeToString(json.decodeFromString<Empty>(payload))
216
+ BridgeOp.BACKGROUND_IS_REGISTERED -> json.encodeToString(json.decodeFromString<BoolResult>(payload))
217
+ BridgeOp.BACKGROUND_CANCEL -> json.encodeToString(json.decodeFromString<BoolResult>(payload))
218
+ BridgeOp.BODY_HEART_RATE -> json.encodeToString(json.decodeFromString<HeartRateList>(payload))
219
+ BridgeOp.BODY_LOG_HEART_RATE -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
220
+ BridgeOp.BODY_LOG -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
221
+ BridgeOp.BODY_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
222
+ BridgeOp.DAILY_STEPS -> json.encodeToString(json.decodeFromString<StepCountList>(payload))
223
+ BridgeOp.DAILY_LOG -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
224
+ BridgeOp.DAILY_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
225
+ BridgeOp.SLEEP_SEGMENTS -> json.encodeToString(json.decodeFromString<SleepSegmentList>(payload))
226
+ BridgeOp.SLEEP_LOG -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
227
+ BridgeOp.SLEEP_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
228
+ BridgeOp.ACTIVITY_WORKOUTS -> json.encodeToString(json.decodeFromString<WorkoutList>(payload))
229
+ BridgeOp.ACTIVITY_LOG -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
230
+ BridgeOp.ACTIVITY_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
231
+ BridgeOp.NUTRITION_LOG -> json.encodeToString(json.decodeFromString<RecordIdList>(payload))
232
+ BridgeOp.NUTRITION_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
233
+ BridgeOp.PLANNED_WORKOUTS_PLANNED -> json.encodeToString(json.decodeFromString<PlannedWorkoutList>(payload))
234
+ BridgeOp.PLANNED_WORKOUTS_MARK_COMPLETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
235
+ BridgeOp.PLANNED_WORKOUTS_DELETE -> json.encodeToString(json.decodeFromString<Empty>(payload))
236
+ BridgeOp.PLANNED_WORKOUTS_SCHEDULING_STATUS -> json.encodeToString(json.decodeFromString<WorkoutScheduling>(payload))
237
+ BridgeOp.PLANNED_WORKOUTS_REQUEST_SCHEDULING -> json.encodeToString(json.decodeFromString<WorkoutScheduling>(payload))
238
+ }
239
+
240
+ /** One element of a stream, by that stream's declared type. */
241
+ public fun roundTripStreamElement(stream: String, payload: String, json: Json = BridgeDispatch.defaultJson): String =
242
+ when (stream) {
243
+ "diagnostics.events" -> {
244
+ val value = json.decodeFromString<TerraEvent>(payload)
245
+ TerraEventEnvelope.validate(value)
246
+ json.encodeToString(value)
247
+ }
248
+ "body.changes" -> json.encodeToString(json.decodeFromString<HealthChange>(payload))
249
+ "daily.changes" -> json.encodeToString(json.decodeFromString<HealthChange>(payload))
250
+ "sleep.changes" -> json.encodeToString(json.decodeFromString<HealthChange>(payload))
251
+ "activity.changes" -> json.encodeToString(json.decodeFromString<HealthChange>(payload))
252
+ "bridge.tokenRequest" -> json.encodeToString(json.decodeFromString<TokenRequest>(payload))
253
+ else -> throw BridgeError.UnsupportedOnPlatform(stream)
254
+ }
255
+
256
+ /**
257
+ * One message as a subscription actually delivers it: the envelope, and the element inside.
258
+ *
259
+ * Both halves, because checking only the envelope would accept a `data` carrying anything at
260
+ * all, and checking only the element would accept a stream that never says it failed.
261
+ */
262
+ public fun roundTripStreamMessage(stream: String, payload: String, json: Json = BridgeDispatch.defaultJson): String {
263
+ val message = json.decodeFromString<StreamMessage>(payload)
264
+ return when (message) {
265
+ is StreamMessage.Data ->
266
+ json.encodeToString<StreamMessage>(
267
+ StreamMessage.Data(
268
+ element = json.decodeFromString<JsonObject>(
269
+ roundTripStreamElement(stream, json.encodeToString(message.element), json),
270
+ ),
271
+ ),
272
+ )
273
+ else -> json.encodeToString<StreamMessage>(message)
274
+ }
275
+ }
276
+ }
277
+
278
+ /**
279
+ * kotlinx's own message carries the input, which may be a health sample, so only the path is
280
+ * lifted out of it.
281
+ */
282
+ internal inline fun <reified T> Json.decodeRequest(payload: String, validate: (T) -> Unit = {}): T =
283
+ try {
284
+ decodeFromString<T>(payload).also(validate)
285
+ } catch (malformed: IllegalArgumentException) {
286
+ throw BridgeError.InvalidPayload(BridgeDispatch.pathOf(malformed))
287
+ }
288
+
289
+ /**
290
+ * Turns an op string and a JSON payload into a typed call and a JSON answer.
291
+ *
292
+ * `when` as an expression over BridgeOp, with no else: the compiler, not a test, is what
293
+ * notices an op the schema added and this file has not handled.
294
+ */
295
+ public class BridgeDispatch(
296
+ private val ops: TerraBridgeOps,
297
+ private val json: Json = defaultJson,
298
+ ) {
299
+ public suspend fun dispatch(op: String, payload: String): String {
300
+ val known = BridgeOp.of(op) ?: throw BridgeError.UnsupportedOnPlatform(op)
301
+ return when (known) {
302
+ BridgeOp.CLIENT_ATTACH ->
303
+ json.encodeToString(ops.clientAttach(json.decodeRequest<Empty>(payload)))
304
+ BridgeOp.CLIENT_CONNECT ->
305
+ json.encodeToString(ops.clientConnect(json.decodeRequest<ConnectRequest>(payload)))
306
+ BridgeOp.CLIENT_CONNECTIONS ->
307
+ json.encodeToString(ops.clientConnections(json.decodeRequest<Empty>(payload)))
308
+ BridgeOp.CLIENT_DISCONNECT ->
309
+ json.encodeToString(ops.clientDisconnect(json.decodeRequest<SourceRequest>(payload)))
310
+ BridgeOp.TOKEN_SET_PROVIDER_PRESENT ->
311
+ json.encodeToString(ops.tokenSetProviderPresent(json.decodeRequest<TokenProviderRequest>(payload)))
312
+ BridgeOp.TOKEN_RESOLVE ->
313
+ json.encodeToString(ops.tokenResolve(json.decodeRequest<TokenResolveRequest>(payload)))
314
+ BridgeOp.TOKEN_REJECT ->
315
+ json.encodeToString(ops.tokenReject(json.decodeRequest<TokenRejectRequest>(payload)))
316
+ BridgeOp.PERMISSIONS_REQUEST ->
317
+ json.encodeToString(ops.permissionsRequest(json.decodeRequest<PermissionRequest>(payload)))
318
+ BridgeOp.PERMISSIONS_STATUS ->
319
+ json.encodeToString(ops.permissionsStatus(json.decodeRequest<CategoryRequest>(payload)))
320
+ BridgeOp.PERMISSIONS_WRITE_STATUS ->
321
+ json.encodeToString(ops.permissionsWriteStatus(json.decodeRequest<CategoryRequest>(payload)))
322
+ BridgeOp.PERMISSIONS_REQUESTED ->
323
+ json.encodeToString(ops.permissionsRequested(json.decodeRequest<SourceRequest>(payload)))
324
+ BridgeOp.PERMISSIONS_STATUSES ->
325
+ json.encodeToString(ops.permissionsStatuses(json.decodeRequest<SourceRequest>(payload)))
326
+ BridgeOp.PERMISSIONS_BACKGROUND_READ_STATUS ->
327
+ json.encodeToString(ops.permissionsBackgroundReadStatus(json.decodeRequest<SourceRequest>(payload)))
328
+ BridgeOp.PERMISSIONS_HISTORY_READ_STATUS ->
329
+ json.encodeToString(ops.permissionsHistoryReadStatus(json.decodeRequest<SourceRequest>(payload)))
330
+ BridgeOp.DIAGNOSTICS_STATE ->
331
+ json.encodeToString(ops.diagnosticsState(json.decodeRequest<SourceRequest>(payload)))
332
+ BridgeOp.DIAGNOSTICS_LAST_DELIVERY ->
333
+ json.encodeToString(ops.diagnosticsLastDelivery(json.decodeRequest<SourceRequest>(payload)))
334
+ BridgeOp.DIAGNOSTICS_REPORT -> {
335
+ val request = json.decodeRequest<SourceRequest>(payload)
336
+ val response = ops.diagnosticsReport(request)
337
+ SupportReportEnvelope.validate(response)
338
+ json.encodeToString(response)
339
+ }
340
+ BridgeOp.DIAGNOSTICS_SEND ->
341
+ json.encodeToString(ops.diagnosticsSend(json.decodeRequest<SourceRequest>(payload)))
342
+ BridgeOp.BACKGROUND_IS_REGISTERED ->
343
+ json.encodeToString(ops.backgroundIsRegistered(json.decodeRequest<Empty>(payload)))
344
+ BridgeOp.BACKGROUND_CANCEL ->
345
+ json.encodeToString(ops.backgroundCancel(json.decodeRequest<Empty>(payload)))
346
+ BridgeOp.BODY_HEART_RATE ->
347
+ json.encodeToString(ops.bodyHeartRate(json.decodeRequest<RangeRequest>(payload)))
348
+ BridgeOp.BODY_LOG_HEART_RATE ->
349
+ json.encodeToString(ops.bodyLogHeartRate(json.decodeRequest<HeartRateEntryList>(payload)))
350
+ BridgeOp.BODY_LOG ->
351
+ json.encodeToString(ops.bodyLog(json.decodeRequest<BodyEntryList>(payload)))
352
+ BridgeOp.BODY_DELETE ->
353
+ json.encodeToString(ops.bodyDelete(json.decodeRequest<RecordIdRequest>(payload)))
354
+ BridgeOp.DAILY_STEPS ->
355
+ json.encodeToString(ops.dailySteps(json.decodeRequest<RangeRequest>(payload)))
356
+ BridgeOp.DAILY_LOG ->
357
+ json.encodeToString(ops.dailyLog(json.decodeRequest<StepEntryList>(payload)))
358
+ BridgeOp.DAILY_DELETE ->
359
+ json.encodeToString(ops.dailyDelete(json.decodeRequest<RecordIdRequest>(payload)))
360
+ BridgeOp.SLEEP_SEGMENTS ->
361
+ json.encodeToString(ops.sleepSegments(json.decodeRequest<RangeRequest>(payload)))
362
+ BridgeOp.SLEEP_LOG ->
363
+ json.encodeToString(ops.sleepLog(json.decodeRequest<SleepEntryList>(payload)))
364
+ BridgeOp.SLEEP_DELETE ->
365
+ json.encodeToString(ops.sleepDelete(json.decodeRequest<RecordIdRequest>(payload)))
366
+ BridgeOp.ACTIVITY_WORKOUTS ->
367
+ json.encodeToString(ops.activityWorkouts(json.decodeRequest<RangeRequest>(payload)))
368
+ BridgeOp.ACTIVITY_LOG ->
369
+ json.encodeToString(ops.activityLog(json.decodeRequest<WorkoutEntryList>(payload)))
370
+ BridgeOp.ACTIVITY_DELETE ->
371
+ json.encodeToString(ops.activityDelete(json.decodeRequest<RecordIdRequest>(payload)))
372
+ BridgeOp.NUTRITION_LOG ->
373
+ json.encodeToString(ops.nutritionLog(json.decodeRequest<NutritionEntryList>(payload)))
374
+ BridgeOp.NUTRITION_DELETE ->
375
+ json.encodeToString(ops.nutritionDelete(json.decodeRequest<RecordIdRequest>(payload)))
376
+ BridgeOp.PLANNED_WORKOUTS_PLANNED -> throw BridgeError.UnsupportedOnPlatform(known.wire)
377
+ BridgeOp.PLANNED_WORKOUTS_MARK_COMPLETE -> throw BridgeError.UnsupportedOnPlatform(known.wire)
378
+ BridgeOp.PLANNED_WORKOUTS_DELETE -> throw BridgeError.UnsupportedOnPlatform(known.wire)
379
+ BridgeOp.PLANNED_WORKOUTS_SCHEDULING_STATUS -> throw BridgeError.UnsupportedOnPlatform(known.wire)
380
+ BridgeOp.PLANNED_WORKOUTS_REQUEST_SCHEDULING -> throw BridgeError.UnsupportedOnPlatform(known.wire)
381
+ }
382
+ }
383
+
384
+ public companion object {
385
+ /**
386
+ * encodeDefaults is on because a DTO field left at its default still has to reach the
387
+ * other side: a response that omitted `userEntered: false` would decode as absent there.
388
+ */
389
+ private val PATH: Regex = Regex("""at path: (\$\S*)""")
390
+
391
+ /** `$.entries[2].start`, or `$` when the failure names no path. */
392
+ public fun pathOf(error: Throwable): String = error.message?.let { PATH.find(it)?.groupValues?.get(1) } ?: "$"
393
+
394
+ public val defaultJson: Json = Json {
395
+ encodeDefaults = true
396
+ explicitNulls = false
397
+ ignoreUnknownKeys = true
398
+ }
399
+ }
400
+ }
401
+
402
+ /** Every stream the schema declares, including the ones this platform does not publish. */
403
+ public enum class BridgeStream(public val wire: String, public val isSupported: Boolean) {
404
+ DIAGNOSTICS_EVENTS("diagnostics.events", true),
405
+ BODY_CHANGES("body.changes", true),
406
+ DAILY_CHANGES("daily.changes", true),
407
+ SLEEP_CHANGES("sleep.changes", true),
408
+ ACTIVITY_CHANGES("activity.changes", true),
409
+ BRIDGE_TOKEN_REQUEST("bridge.tokenRequest", true),
410
+ ;
411
+
412
+ public companion object {
413
+ private val byWire: Map<String, BridgeStream> = entries.associateBy { it.wire }
414
+
415
+ public fun of(wire: String): BridgeStream? = byWire[wire]
416
+ }
417
+ }
418
+
419
+ /**
420
+ * What P2 publishes. One function per stream this platform supports, each a fresh flow per
421
+ * call: a subscriber that goes away cancels its own collection and nobody else's.
422
+ */
423
+ public interface TerraBridgeStreams {
424
+ /**
425
+ * Every subscriber gets its own native stream, matching the SDK's fresh-stream-per-call
426
+ * contract.
427
+ */
428
+ public fun diagnosticsEvents(params: Empty): Flow<TerraEvent>
429
+ public fun bodyChanges(params: Empty): Flow<HealthChange>
430
+ public fun dailyChanges(params: Empty): Flow<HealthChange>
431
+ public fun sleepChanges(params: Empty): Flow<HealthChange>
432
+ public fun activityChanges(params: Empty): Flow<HealthChange>
433
+ /** Bridge-owned. The broker's ask, not an SDK stream. */
434
+ public fun bridgeTokenRequest(params: Empty): Flow<TokenRequest>
435
+ }
436
+
437
+ public class BridgeStreamDispatch(
438
+ private val streams: TerraBridgeStreams,
439
+ private val errorPayload: (Throwable) -> TerraErrorPayload,
440
+ private val json: Json = BridgeDispatch.defaultJson,
441
+ ) {
442
+ public fun open(stream: String, params: String): Flow<String> {
443
+ val known = BridgeStream.of(stream) ?: throw BridgeError.UnsupportedOnPlatform(stream)
444
+ val messages = when (known) {
445
+ BridgeStream.DIAGNOSTICS_EVENTS -> {
446
+ val request = json.decodeRequest<Empty>(params)
447
+ flow { emitAll(streams.diagnosticsEvents(request)) }.map { element ->
448
+ TerraEventEnvelope.validate(element)
449
+ frame(serializer<TerraEvent>(), element)
450
+ }
451
+ }
452
+ BridgeStream.BODY_CHANGES -> {
453
+ val request = json.decodeRequest<Empty>(params)
454
+ flow { emitAll(streams.bodyChanges(request)) }.map { element ->
455
+ frame(serializer<HealthChange>(), element)
456
+ }
457
+ }
458
+ BridgeStream.DAILY_CHANGES -> {
459
+ val request = json.decodeRequest<Empty>(params)
460
+ flow { emitAll(streams.dailyChanges(request)) }.map { element ->
461
+ frame(serializer<HealthChange>(), element)
462
+ }
463
+ }
464
+ BridgeStream.SLEEP_CHANGES -> {
465
+ val request = json.decodeRequest<Empty>(params)
466
+ flow { emitAll(streams.sleepChanges(request)) }.map { element ->
467
+ frame(serializer<HealthChange>(), element)
468
+ }
469
+ }
470
+ BridgeStream.ACTIVITY_CHANGES -> {
471
+ val request = json.decodeRequest<Empty>(params)
472
+ flow { emitAll(streams.activityChanges(request)) }.map { element ->
473
+ frame(serializer<HealthChange>(), element)
474
+ }
475
+ }
476
+ BridgeStream.BRIDGE_TOKEN_REQUEST -> {
477
+ val request = json.decodeRequest<Empty>(params)
478
+ flow { emitAll(streams.bridgeTokenRequest(request)) }.map { element ->
479
+ frame(serializer<TokenRequest>(), element)
480
+ }
481
+ }
482
+ }
483
+ return messages
484
+ .onCompletion { cause -> if (cause == null) emit(encode(StreamMessage.Complete)) }
485
+ .catch { failure -> emit(encode(StreamMessage.Error(error = errorPayload(failure)))) }
486
+ }
487
+
488
+ private fun <T> frame(serializer: KSerializer<T>, element: T): String =
489
+ encode(StreamMessage.Data(element = json.encodeToJsonElement(serializer, element).jsonObject))
490
+
491
+ private fun encode(message: StreamMessage): String = json.encodeToString(StreamMessage.serializer(), message)
492
+ }
493
+