@bota.dev/react-native-app-sdk 2.0.0-beta.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 (173) hide show
  1. package/BotaDeviceSDK.podspec +55 -0
  2. package/LICENSE +21 -0
  3. package/README.md +101 -0
  4. package/android/build.gradle +76 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +172 -0
  7. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycle.kt +100 -0
  8. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +82 -0
  9. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidModels.kt +476 -0
  10. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTA.kt +58 -0
  11. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +180 -0
  12. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +751 -0
  13. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurity.kt +509 -0
  14. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFi.kt +125 -0
  15. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +46 -0
  16. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +723 -0
  17. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +33 -0
  18. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +211 -0
  19. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycleTest.kt +132 -0
  20. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +57 -0
  21. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTATest.kt +75 -0
  22. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +147 -0
  23. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +355 -0
  24. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurityTest.kt +523 -0
  25. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFiTest.kt +100 -0
  26. package/generated/codegen-contract.json +3593 -0
  27. package/ios/BotaDeviceSDK.mm +1300 -0
  28. package/ios/BotaDeviceSDKAppleBridge.swift +2260 -0
  29. package/ios/BotaDeviceSDKAppleDevices.swift +191 -0
  30. package/ios/BotaDeviceSDKAppleLifecycle.swift +167 -0
  31. package/ios/BotaDeviceSDKAppleLogs.swift +79 -0
  32. package/ios/BotaDeviceSDKAppleOTA.swift +73 -0
  33. package/ios/BotaDeviceSDKAppleRecordingUploads.swift +178 -0
  34. package/ios/BotaDeviceSDKAppleRecordings.swift +670 -0
  35. package/ios/BotaDeviceSDKAppleSecurity.swift +586 -0
  36. package/ios/BotaDeviceSDKAppleWiFi.swift +128 -0
  37. package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +99 -0
  38. package/lib/commonjs/BotaClient.js +194 -0
  39. package/lib/commonjs/BotaClient.js.map +1 -0
  40. package/lib/commonjs/ble/constants.js +10 -0
  41. package/lib/commonjs/ble/constants.js.map +1 -0
  42. package/lib/commonjs/ble/deviceLogs.js +50 -0
  43. package/lib/commonjs/ble/deviceLogs.js.map +1 -0
  44. package/lib/commonjs/cache/DeviceStateCache.js +6 -0
  45. package/lib/commonjs/cache/DeviceStateCache.js.map +1 -0
  46. package/lib/commonjs/client.js +746 -0
  47. package/lib/commonjs/client.js.map +1 -0
  48. package/lib/commonjs/compatibility/runtime.js +44 -0
  49. package/lib/commonjs/compatibility/runtime.js.map +1 -0
  50. package/lib/commonjs/index.js +123 -0
  51. package/lib/commonjs/index.js.map +1 -0
  52. package/lib/commonjs/managers/DeviceManager.js +647 -0
  53. package/lib/commonjs/managers/DeviceManager.js.map +1 -0
  54. package/lib/commonjs/managers/OTAManager.js +140 -0
  55. package/lib/commonjs/managers/OTAManager.js.map +1 -0
  56. package/lib/commonjs/managers/RecordingManager.js +444 -0
  57. package/lib/commonjs/managers/RecordingManager.js.map +1 -0
  58. package/lib/commonjs/managers/StreamingSession.js +175 -0
  59. package/lib/commonjs/managers/StreamingSession.js.map +1 -0
  60. package/lib/commonjs/managers/types.js +6 -0
  61. package/lib/commonjs/managers/types.js.map +1 -0
  62. package/lib/commonjs/models/Device.js +2 -0
  63. package/lib/commonjs/models/Device.js.map +1 -0
  64. package/lib/commonjs/models/Recording.js +2 -0
  65. package/lib/commonjs/models/Recording.js.map +1 -0
  66. package/lib/commonjs/models/Status.js +6 -0
  67. package/lib/commonjs/models/Status.js.map +1 -0
  68. package/lib/commonjs/package.json +1 -0
  69. package/lib/commonjs/specs/NativeBotaDeviceSDK.js +9 -0
  70. package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -0
  71. package/lib/commonjs/sync/syncStatus.js +178 -0
  72. package/lib/commonjs/sync/syncStatus.js.map +1 -0
  73. package/lib/commonjs/utils/errors.js +194 -0
  74. package/lib/commonjs/utils/errors.js.map +1 -0
  75. package/lib/commonjs/utils/logger.js +84 -0
  76. package/lib/commonjs/utils/logger.js.map +1 -0
  77. package/lib/module/BotaClient.js +189 -0
  78. package/lib/module/BotaClient.js.map +1 -0
  79. package/lib/module/ble/constants.js +6 -0
  80. package/lib/module/ble/constants.js.map +1 -0
  81. package/lib/module/ble/deviceLogs.js +45 -0
  82. package/lib/module/ble/deviceLogs.js.map +1 -0
  83. package/lib/module/cache/DeviceStateCache.js +4 -0
  84. package/lib/module/cache/DeviceStateCache.js.map +1 -0
  85. package/lib/module/client.js +740 -0
  86. package/lib/module/client.js.map +1 -0
  87. package/lib/module/compatibility/runtime.js +35 -0
  88. package/lib/module/compatibility/runtime.js.map +1 -0
  89. package/lib/module/index.js +17 -0
  90. package/lib/module/index.js.map +1 -0
  91. package/lib/module/managers/DeviceManager.js +641 -0
  92. package/lib/module/managers/DeviceManager.js.map +1 -0
  93. package/lib/module/managers/OTAManager.js +134 -0
  94. package/lib/module/managers/OTAManager.js.map +1 -0
  95. package/lib/module/managers/RecordingManager.js +438 -0
  96. package/lib/module/managers/RecordingManager.js.map +1 -0
  97. package/lib/module/managers/StreamingSession.js +168 -0
  98. package/lib/module/managers/StreamingSession.js.map +1 -0
  99. package/lib/module/managers/types.js +4 -0
  100. package/lib/module/managers/types.js.map +1 -0
  101. package/lib/module/models/Device.js +2 -0
  102. package/lib/module/models/Device.js.map +1 -0
  103. package/lib/module/models/Recording.js +2 -0
  104. package/lib/module/models/Recording.js.map +1 -0
  105. package/lib/module/models/Status.js +4 -0
  106. package/lib/module/models/Status.js.map +1 -0
  107. package/lib/module/specs/NativeBotaDeviceSDK.js +5 -0
  108. package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -0
  109. package/lib/module/sync/syncStatus.js +174 -0
  110. package/lib/module/sync/syncStatus.js.map +1 -0
  111. package/lib/module/utils/errors.js +183 -0
  112. package/lib/module/utils/errors.js.map +1 -0
  113. package/lib/module/utils/logger.js +80 -0
  114. package/lib/module/utils/logger.js.map +1 -0
  115. package/lib/typescript/BotaClient.d.ts +40 -0
  116. package/lib/typescript/BotaClient.d.ts.map +1 -0
  117. package/lib/typescript/ble/constants.d.ts +4 -0
  118. package/lib/typescript/ble/constants.d.ts.map +1 -0
  119. package/lib/typescript/ble/deviceLogs.d.ts +10 -0
  120. package/lib/typescript/ble/deviceLogs.d.ts.map +1 -0
  121. package/lib/typescript/cache/DeviceStateCache.d.ts +14 -0
  122. package/lib/typescript/cache/DeviceStateCache.d.ts.map +1 -0
  123. package/lib/typescript/client.d.ts +208 -0
  124. package/lib/typescript/client.d.ts.map +1 -0
  125. package/lib/typescript/compatibility/runtime.d.ts +11 -0
  126. package/lib/typescript/compatibility/runtime.d.ts.map +1 -0
  127. package/lib/typescript/index.d.ts +19 -0
  128. package/lib/typescript/index.d.ts.map +1 -0
  129. package/lib/typescript/managers/DeviceManager.d.ts +117 -0
  130. package/lib/typescript/managers/DeviceManager.d.ts.map +1 -0
  131. package/lib/typescript/managers/OTAManager.d.ts +25 -0
  132. package/lib/typescript/managers/OTAManager.d.ts.map +1 -0
  133. package/lib/typescript/managers/RecordingManager.d.ts +54 -0
  134. package/lib/typescript/managers/RecordingManager.d.ts.map +1 -0
  135. package/lib/typescript/managers/StreamingSession.d.ts +35 -0
  136. package/lib/typescript/managers/StreamingSession.d.ts.map +1 -0
  137. package/lib/typescript/managers/types.d.ts +19 -0
  138. package/lib/typescript/managers/types.d.ts.map +1 -0
  139. package/lib/typescript/models/Device.d.ts +392 -0
  140. package/lib/typescript/models/Device.d.ts.map +1 -0
  141. package/lib/typescript/models/Recording.d.ts +259 -0
  142. package/lib/typescript/models/Recording.d.ts.map +1 -0
  143. package/lib/typescript/models/Status.d.ts +106 -0
  144. package/lib/typescript/models/Status.d.ts.map +1 -0
  145. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +393 -0
  146. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -0
  147. package/lib/typescript/sync/syncStatus.d.ts +123 -0
  148. package/lib/typescript/sync/syncStatus.d.ts.map +1 -0
  149. package/lib/typescript/utils/errors.d.ts +83 -0
  150. package/lib/typescript/utils/errors.d.ts.map +1 -0
  151. package/lib/typescript/utils/logger.d.ts +36 -0
  152. package/lib/typescript/utils/logger.d.ts.map +1 -0
  153. package/package.json +100 -0
  154. package/scripts/bota_device_sdk_spm_workaround.rb +53 -0
  155. package/src/BotaClient.ts +231 -0
  156. package/src/ble/constants.ts +3 -0
  157. package/src/ble/deviceLogs.ts +61 -0
  158. package/src/cache/DeviceStateCache.ts +20 -0
  159. package/src/client.ts +1410 -0
  160. package/src/compatibility/runtime.ts +55 -0
  161. package/src/index.ts +146 -0
  162. package/src/managers/DeviceManager.ts +927 -0
  163. package/src/managers/OTAManager.ts +195 -0
  164. package/src/managers/RecordingManager.ts +578 -0
  165. package/src/managers/StreamingSession.ts +212 -0
  166. package/src/managers/types.ts +36 -0
  167. package/src/models/Device.ts +484 -0
  168. package/src/models/Recording.ts +281 -0
  169. package/src/models/Status.ts +128 -0
  170. package/src/specs/NativeBotaDeviceSDK.ts +542 -0
  171. package/src/sync/syncStatus.ts +243 -0
  172. package/src/utils/errors.ts +327 -0
  173. package/src/utils/logger.ts +122 -0
@@ -0,0 +1,509 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import dev.bota.sdk.BotaDeviceClient
4
+ import dev.bota.sdk.DeviceApiEnvironment
5
+ import dev.bota.sdk.model.ConnectedDevice
6
+ import dev.bota.sdk.model.DeviceConnectionSettings
7
+ import dev.bota.sdk.model.DeprovisionResult
8
+ import dev.bota.sdk.model.FactoryResetCompletion
9
+ import dev.bota.sdk.model.FactoryResetGrantRequest
10
+ import dev.bota.sdk.model.FactoryResetPersistenceResult
11
+ import dev.bota.sdk.model.ProvisioningMaterial
12
+ import dev.bota.sdk.model.ProvisioningMaterialRequest
13
+ import dev.bota.sdk.model.RecordingControlResult
14
+ import dev.bota.sdk.model.RecordingState
15
+ import java.util.Base64
16
+ import java.util.UUID
17
+ import kotlinx.coroutines.CancellationException
18
+ import kotlinx.coroutines.CompletableDeferred
19
+ import kotlinx.coroutines.CoroutineScope
20
+ import kotlinx.coroutines.CoroutineStart
21
+ import kotlinx.coroutines.Dispatchers
22
+ import kotlinx.coroutines.Job
23
+ import kotlinx.coroutines.SupervisorJob
24
+ import kotlinx.coroutines.cancelAndJoin
25
+ import kotlinx.coroutines.flow.Flow
26
+ import kotlinx.coroutines.flow.collect
27
+ import kotlinx.coroutines.launch
28
+ import kotlinx.coroutines.sync.Mutex
29
+ import kotlinx.coroutines.sync.withLock
30
+
31
+ internal data class BotaDeviceSDKAndroidProvisioningRequest(
32
+ val requestId: String,
33
+ val serialNumber: String,
34
+ val nonce: String,
35
+ val devicePublicKey: String,
36
+ )
37
+
38
+ internal data class BotaDeviceSDKAndroidFactoryResetRequest(
39
+ val requestId: String,
40
+ val serialNumber: String,
41
+ val nonce: String,
42
+ val commandId: String,
43
+ val bindingGeneration: ULong,
44
+ )
45
+
46
+ internal data class BotaDeviceSDKAndroidFactoryResetPersistenceRequest(
47
+ val requestId: String,
48
+ val localRecordingsDeleted: UShort,
49
+ )
50
+
51
+ internal interface BotaDeviceSDKAndroidSecurityClient {
52
+ suspend fun isProvisioned(device: ConnectedDevice): Boolean
53
+ suspend fun readPublicKey(device: ConnectedDevice): String?
54
+ suspend fun readAuthNonce(device: ConnectedDevice): String?
55
+ suspend fun setApiEndpoint(environment: DeviceApiEnvironment, device: ConnectedDevice)
56
+ suspend fun deliverCertificate(certificatePem: String, privateKeyPem: String, device: ConnectedDevice)
57
+ suspend fun deliverBackendPublicKey(publicKey: ByteArray, device: ConnectedDevice)
58
+ suspend fun writeGrant(grantBlob: String, device: ConnectedDevice)
59
+ suspend fun syncTime(device: ConnectedDevice)
60
+ suspend fun requestStartRecording(
61
+ device: ConnectedDevice,
62
+ grantBlob: String,
63
+ ): RecordingControlResult
64
+
65
+ suspend fun requestStopRecording(
66
+ device: ConnectedDevice,
67
+ grantBlob: String,
68
+ ): RecordingControlResult
69
+
70
+ suspend fun readRecordingState(device: ConnectedDevice): RecordingState
71
+
72
+ fun recordingStateUpdates(device: ConnectedDevice): Flow<RecordingState>
73
+ suspend fun provision(
74
+ device: ConnectedDevice,
75
+ provider: suspend (ProvisioningMaterialRequest) -> ProvisioningMaterial,
76
+ )
77
+
78
+ suspend fun deprovision(device: ConnectedDevice, grantBlob: String): DeprovisionResult
79
+
80
+ suspend fun readConnectionSettings(device: ConnectedDevice): DeviceConnectionSettings
81
+
82
+ suspend fun writeConnectionSettings(
83
+ settings: DeviceConnectionSettings,
84
+ device: ConnectedDevice,
85
+ )
86
+
87
+ suspend fun factoryReset(
88
+ device: ConnectedDevice,
89
+ commandId: String,
90
+ bindingGeneration: ULong,
91
+ persistResult: (suspend (FactoryResetPersistenceResult) -> Unit)?,
92
+ provider: suspend (FactoryResetGrantRequest) -> ByteArray,
93
+ ): FactoryResetCompletion
94
+
95
+ suspend fun resumePendingFactoryReset(
96
+ device: ConnectedDevice,
97
+ currentBindingGeneration: ULong,
98
+ persistResult: (suspend (FactoryResetPersistenceResult) -> Unit)?,
99
+ ): FactoryResetCompletion?
100
+
101
+ suspend fun resumeUnjournaledFactoryReset(
102
+ device: ConnectedDevice,
103
+ commandId: String,
104
+ bindingGeneration: ULong,
105
+ persistResult: suspend (FactoryResetPersistenceResult) -> Unit,
106
+ ): FactoryResetCompletion
107
+
108
+ suspend fun cancelCurrentOperation()
109
+
110
+ suspend fun cancelFactoryReset()
111
+ }
112
+
113
+ internal class BotaDeviceSDKSharedAndroidSecurityClient(
114
+ private val client: BotaDeviceClient = BotaDeviceClient.shared,
115
+ ) : BotaDeviceSDKAndroidSecurityClient {
116
+ override suspend fun isProvisioned(device: ConnectedDevice): Boolean =
117
+ client.controls.isProvisioned(device)
118
+
119
+ override suspend fun readPublicKey(device: ConnectedDevice): String? =
120
+ client.controls.readPublicKey(device)
121
+
122
+ override suspend fun readAuthNonce(device: ConnectedDevice): String? =
123
+ client.controls.readAuthNonce(device)
124
+
125
+ override suspend fun setApiEndpoint(environment: DeviceApiEnvironment, device: ConnectedDevice) {
126
+ client.controls.setApiEndpoint(environment, device)
127
+ }
128
+
129
+ override suspend fun deliverCertificate(
130
+ certificatePem: String,
131
+ privateKeyPem: String,
132
+ device: ConnectedDevice,
133
+ ) {
134
+ client.controls.deliverCertificate(certificatePem, privateKeyPem, device)
135
+ }
136
+
137
+ override suspend fun deliverBackendPublicKey(publicKey: ByteArray, device: ConnectedDevice) {
138
+ client.controls.deliverBackendPublicKey(publicKey, device)
139
+ }
140
+
141
+ override suspend fun writeGrant(grantBlob: String, device: ConnectedDevice) {
142
+ client.controls.writeGrant(grantBlob, device)
143
+ }
144
+
145
+ override suspend fun syncTime(device: ConnectedDevice) {
146
+ client.controls.syncTime(device = device)
147
+ }
148
+
149
+ override suspend fun requestStartRecording(
150
+ device: ConnectedDevice,
151
+ grantBlob: String,
152
+ ): RecordingControlResult = client.controls.requestStartRecording(device, grantBlob)
153
+
154
+ override suspend fun requestStopRecording(
155
+ device: ConnectedDevice,
156
+ grantBlob: String,
157
+ ): RecordingControlResult = client.controls.requestStopRecording(device, grantBlob)
158
+
159
+ override suspend fun readRecordingState(device: ConnectedDevice): RecordingState =
160
+ client.controls.readRecordingState(device)
161
+
162
+ override fun recordingStateUpdates(device: ConnectedDevice): Flow<RecordingState> =
163
+ client.controls.recordingStateUpdates(device)
164
+
165
+ override suspend fun provision(
166
+ device: ConnectedDevice,
167
+ provider: suspend (ProvisioningMaterialRequest) -> ProvisioningMaterial,
168
+ ) {
169
+ client.provisioning.provision(device, provider)
170
+ }
171
+
172
+ override suspend fun deprovision(
173
+ device: ConnectedDevice,
174
+ grantBlob: String,
175
+ ): DeprovisionResult = client.provisioning.deprovision(device, grantBlob)
176
+
177
+ override suspend fun readConnectionSettings(device: ConnectedDevice): DeviceConnectionSettings =
178
+ client.provisioning.readConnectionSettings(device)
179
+
180
+ override suspend fun writeConnectionSettings(
181
+ settings: DeviceConnectionSettings,
182
+ device: ConnectedDevice,
183
+ ) {
184
+ client.provisioning.writeConnectionSettings(settings, device)
185
+ }
186
+
187
+ override suspend fun factoryReset(
188
+ device: ConnectedDevice,
189
+ commandId: String,
190
+ bindingGeneration: ULong,
191
+ persistResult: (suspend (FactoryResetPersistenceResult) -> Unit)?,
192
+ provider: suspend (FactoryResetGrantRequest) -> ByteArray,
193
+ ): FactoryResetCompletion = client.factoryReset.factoryReset(
194
+ device,
195
+ commandId,
196
+ bindingGeneration,
197
+ persistResult,
198
+ provider,
199
+ )
200
+
201
+ override suspend fun resumePendingFactoryReset(
202
+ device: ConnectedDevice,
203
+ currentBindingGeneration: ULong,
204
+ persistResult: (suspend (FactoryResetPersistenceResult) -> Unit)?,
205
+ ): FactoryResetCompletion? = client.factoryReset.resumePendingFactoryReset(
206
+ device,
207
+ currentBindingGeneration,
208
+ persistResult,
209
+ )
210
+
211
+ override suspend fun resumeUnjournaledFactoryReset(
212
+ device: ConnectedDevice,
213
+ commandId: String,
214
+ bindingGeneration: ULong,
215
+ persistResult: suspend (FactoryResetPersistenceResult) -> Unit,
216
+ ): FactoryResetCompletion = client.factoryReset.resumeUnjournaledFactoryReset(
217
+ device,
218
+ commandId,
219
+ bindingGeneration,
220
+ persistResult,
221
+ )
222
+
223
+ override suspend fun cancelCurrentOperation() {
224
+ client.provisioning.cancelCurrentOperation()
225
+ }
226
+
227
+ override suspend fun cancelFactoryReset() {
228
+ client.factoryReset.cancelCurrentOperation()
229
+ }
230
+ }
231
+
232
+ internal class BotaDeviceSDKAndroidSecurity(
233
+ private val client: BotaDeviceSDKAndroidSecurityClient = BotaDeviceSDKSharedAndroidSecurityClient(),
234
+ private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
235
+ ) {
236
+ private val lock = Any()
237
+ private val streamOperations = Mutex()
238
+ private val streamLock = Any()
239
+ private val provisioningRequests = mutableMapOf<String, CompletableDeferred<ProvisioningMaterial>>()
240
+ private val factoryResetRequests = mutableMapOf<String, CompletableDeferred<ByteArray>>()
241
+ private val factoryResetPersistenceRequests = mutableMapOf<String, CompletableDeferred<Unit>>()
242
+ private var recordingStateStream: Job? = null
243
+
244
+ suspend fun provision(
245
+ device: ConnectedDevice,
246
+ onMaterialRequest: (BotaDeviceSDKAndroidProvisioningRequest) -> Unit,
247
+ ) {
248
+ client.provision(device) { request ->
249
+ requestProvisioningMaterial(request, onMaterialRequest)
250
+ }
251
+ }
252
+
253
+ suspend fun deprovision(device: ConnectedDevice, grantBlob: String): DeprovisionResult =
254
+ client.deprovision(device, grantBlob)
255
+
256
+ suspend fun isProvisioned(device: ConnectedDevice): Boolean = client.isProvisioned(device)
257
+ suspend fun readPublicKey(device: ConnectedDevice): String? = client.readPublicKey(device)
258
+ suspend fun readAuthNonce(device: ConnectedDevice): String? = client.readAuthNonce(device)
259
+ suspend fun setApiEndpoint(environment: DeviceApiEnvironment, device: ConnectedDevice) =
260
+ client.setApiEndpoint(environment, device)
261
+ suspend fun deliverCertificate(certificatePem: String, privateKeyPem: String, device: ConnectedDevice) =
262
+ client.deliverCertificate(certificatePem, privateKeyPem, device)
263
+ suspend fun deliverBackendPublicKey(publicKey: ByteArray, device: ConnectedDevice) =
264
+ client.deliverBackendPublicKey(publicKey, device)
265
+ suspend fun writeGrant(grantBlob: String, device: ConnectedDevice) = client.writeGrant(grantBlob, device)
266
+ suspend fun syncTime(device: ConnectedDevice) = client.syncTime(device)
267
+ suspend fun requestStartRecording(
268
+ device: ConnectedDevice,
269
+ grantBlob: String,
270
+ ): RecordingControlResult = client.requestStartRecording(device, grantBlob)
271
+ suspend fun requestStopRecording(
272
+ device: ConnectedDevice,
273
+ grantBlob: String,
274
+ ): RecordingControlResult = client.requestStopRecording(device, grantBlob)
275
+ suspend fun readRecordingState(device: ConnectedDevice): RecordingState =
276
+ client.readRecordingState(device)
277
+
278
+ suspend fun startRecordingStateUpdates(
279
+ device: ConnectedDevice,
280
+ onError: (Throwable) -> Unit = {},
281
+ onState: (RecordingState) -> Unit,
282
+ ) = streamOperations.withLock {
283
+ stopOwnedRecordingStateStream()
284
+ val updates = client.recordingStateUpdates(device)
285
+ lateinit var task: Job
286
+ task = scope.launch(start = CoroutineStart.LAZY) {
287
+ try {
288
+ updates.collect(onState)
289
+ } catch (_: CancellationException) {
290
+ // Explicit stop is not a recording-state stream failure.
291
+ } catch (error: Throwable) {
292
+ onError(error)
293
+ } finally {
294
+ synchronized(streamLock) {
295
+ if (recordingStateStream === task) recordingStateStream = null
296
+ }
297
+ }
298
+ }
299
+ synchronized(streamLock) { recordingStateStream = task }
300
+ task.start()
301
+ }
302
+
303
+ suspend fun stopRecordingStateUpdates() = streamOperations.withLock {
304
+ stopOwnedRecordingStateStream()
305
+ }
306
+
307
+ suspend fun readConnectionSettings(device: ConnectedDevice): DeviceConnectionSettings =
308
+ client.readConnectionSettings(device)
309
+
310
+ suspend fun writeConnectionSettings(
311
+ settings: DeviceConnectionSettings,
312
+ device: ConnectedDevice,
313
+ ) {
314
+ client.writeConnectionSettings(settings, device)
315
+ }
316
+
317
+ suspend fun factoryReset(
318
+ device: ConnectedDevice,
319
+ commandId: String,
320
+ bindingGeneration: ULong,
321
+ onGrantRequest: (BotaDeviceSDKAndroidFactoryResetRequest) -> Unit,
322
+ onPersistenceRequest: ((BotaDeviceSDKAndroidFactoryResetPersistenceRequest) -> Unit)?,
323
+ ): FactoryResetCompletion = client.factoryReset(
324
+ device = device,
325
+ commandId = commandId,
326
+ bindingGeneration = bindingGeneration,
327
+ persistResult = onPersistenceRequest?.let { onRequest ->
328
+ { result -> requestFactoryResetPersistence(result, onRequest) }
329
+ },
330
+ provider = { request -> requestFactoryResetGrant(request, onGrantRequest) },
331
+ )
332
+
333
+ suspend fun resumePendingFactoryReset(
334
+ device: ConnectedDevice,
335
+ currentBindingGeneration: ULong,
336
+ onPersistenceRequest: ((BotaDeviceSDKAndroidFactoryResetPersistenceRequest) -> Unit)?,
337
+ ): FactoryResetCompletion? {
338
+ val persister: (suspend (FactoryResetPersistenceResult) -> Unit)? =
339
+ if (onPersistenceRequest == null) {
340
+ null
341
+ } else {
342
+ { result ->
343
+ requestFactoryResetPersistence(result, onPersistenceRequest)
344
+ }
345
+ }
346
+ val pending = client.resumePendingFactoryReset(
347
+ device = device,
348
+ currentBindingGeneration = currentBindingGeneration,
349
+ persistResult = persister,
350
+ )
351
+ if (pending != null || persister == null) return pending
352
+ return client.resumeUnjournaledFactoryReset(
353
+ device = device,
354
+ commandId = "rn-resume-${UUID.randomUUID()}",
355
+ bindingGeneration = currentBindingGeneration,
356
+ persistResult = persister,
357
+ )
358
+ }
359
+
360
+ fun resolveProvisioningMaterial(
361
+ requestId: String,
362
+ apiEndpoint: String,
363
+ deviceToken: String,
364
+ mtu: ULong,
365
+ ) {
366
+ pendingRequest(requestId).complete(
367
+ ProvisioningMaterial(
368
+ apiEndpoint = apiEndpoint.encodeToByteArray(),
369
+ deviceToken = deviceToken.encodeToByteArray(),
370
+ mtu = mtu,
371
+ ),
372
+ )
373
+ }
374
+
375
+ fun rejectApplicationMaterial(requestId: String, message: String) {
376
+ synchronized(lock) { provisioningRequests.remove(requestId) }
377
+ ?.completeExceptionally(IllegalStateException(message))
378
+ ?: synchronized(lock) { factoryResetRequests.remove(requestId) }
379
+ ?.completeExceptionally(IllegalStateException(message))
380
+ ?: synchronized(lock) { factoryResetPersistenceRequests.remove(requestId) }
381
+ ?.completeExceptionally(IllegalStateException(message))
382
+ ?: error("application material request is no longer pending")
383
+ }
384
+
385
+ fun resolveFactoryResetGrant(requestId: String, grantBlob: String) {
386
+ val pending = synchronized(lock) { factoryResetRequests.remove(requestId) }
387
+ ?: error("application material request is no longer pending")
388
+ val grant = runCatching { Base64.getDecoder().decode(grantBlob) }
389
+ .getOrElse {
390
+ pending.completeExceptionally(
391
+ IllegalArgumentException("factory reset grant is not valid encoded data"),
392
+ )
393
+ return
394
+ }
395
+ if (grant.isEmpty()) {
396
+ pending.completeExceptionally(
397
+ IllegalArgumentException("factory reset grant is not valid encoded data"),
398
+ )
399
+ return
400
+ }
401
+ pending.complete(grant)
402
+ }
403
+
404
+ fun resolveFactoryResetResultPersistence(requestId: String) {
405
+ val pending = synchronized(lock) { factoryResetPersistenceRequests.remove(requestId) }
406
+ ?: error("application material request is no longer pending")
407
+ pending.complete(Unit)
408
+ }
409
+
410
+ suspend fun cancelAll() {
411
+ runCatching { stopRecordingStateUpdates() }
412
+ val pending = synchronized(lock) {
413
+ provisioningRequests.values.toList().also { provisioningRequests.clear() }
414
+ }
415
+ pending.forEach { it.cancel() }
416
+ val pendingResets = synchronized(lock) {
417
+ factoryResetRequests.values.toList().also { factoryResetRequests.clear() }
418
+ }
419
+ pendingResets.forEach { it.cancel() }
420
+ val pendingPersistence = synchronized(lock) {
421
+ factoryResetPersistenceRequests.values.toList().also {
422
+ factoryResetPersistenceRequests.clear()
423
+ }
424
+ }
425
+ pendingPersistence.forEach { it.cancel() }
426
+ runCatching { client.cancelCurrentOperation() }
427
+ runCatching { client.cancelFactoryReset() }
428
+ }
429
+
430
+ private suspend fun stopOwnedRecordingStateStream() {
431
+ val stream = synchronized(streamLock) {
432
+ recordingStateStream.also { recordingStateStream = null }
433
+ } ?: return
434
+ stream.cancelAndJoin()
435
+ }
436
+
437
+ private suspend fun requestProvisioningMaterial(
438
+ request: ProvisioningMaterialRequest,
439
+ onRequest: (BotaDeviceSDKAndroidProvisioningRequest) -> Unit,
440
+ ): ProvisioningMaterial {
441
+ val requestId = UUID.randomUUID().toString()
442
+ val pending = CompletableDeferred<ProvisioningMaterial>()
443
+ synchronized(lock) { provisioningRequests[requestId] = pending }
444
+ return try {
445
+ onRequest(
446
+ BotaDeviceSDKAndroidProvisioningRequest(
447
+ requestId = requestId,
448
+ serialNumber = request.serialNumber,
449
+ nonce = request.nonce.hexString(),
450
+ devicePublicKey = request.devicePublicKey.hexString(),
451
+ ),
452
+ )
453
+ pending.await()
454
+ } finally {
455
+ synchronized(lock) { provisioningRequests.remove(requestId, pending) }
456
+ }
457
+ }
458
+
459
+ private fun pendingRequest(requestId: String): CompletableDeferred<ProvisioningMaterial> =
460
+ synchronized(lock) { provisioningRequests.remove(requestId) }
461
+ ?: error("application material request is no longer pending")
462
+
463
+ private suspend fun requestFactoryResetGrant(
464
+ request: FactoryResetGrantRequest,
465
+ onRequest: (BotaDeviceSDKAndroidFactoryResetRequest) -> Unit,
466
+ ): ByteArray {
467
+ val requestId = UUID.randomUUID().toString()
468
+ val pending = CompletableDeferred<ByteArray>()
469
+ synchronized(lock) { factoryResetRequests[requestId] = pending }
470
+ return try {
471
+ onRequest(
472
+ BotaDeviceSDKAndroidFactoryResetRequest(
473
+ requestId = requestId,
474
+ serialNumber = request.serialNumber,
475
+ nonce = request.nonce.hexString(),
476
+ commandId = request.commandId,
477
+ bindingGeneration = request.bindingGeneration,
478
+ ),
479
+ )
480
+ pending.await()
481
+ } finally {
482
+ synchronized(lock) { factoryResetRequests.remove(requestId, pending) }
483
+ }
484
+ }
485
+
486
+ private suspend fun requestFactoryResetPersistence(
487
+ result: FactoryResetPersistenceResult,
488
+ onRequest: (BotaDeviceSDKAndroidFactoryResetPersistenceRequest) -> Unit,
489
+ ) {
490
+ val requestId = UUID.randomUUID().toString()
491
+ val pending = CompletableDeferred<Unit>()
492
+ synchronized(lock) { factoryResetPersistenceRequests[requestId] = pending }
493
+ try {
494
+ onRequest(
495
+ BotaDeviceSDKAndroidFactoryResetPersistenceRequest(
496
+ requestId = requestId,
497
+ localRecordingsDeleted = result.localRecordingsDeleted,
498
+ ),
499
+ )
500
+ pending.await()
501
+ } finally {
502
+ synchronized(lock) { factoryResetPersistenceRequests.remove(requestId, pending) }
503
+ }
504
+ }
505
+ }
506
+
507
+ private fun ByteArray.hexString(): String = joinToString("") { byte ->
508
+ "%02x".format(byte.toInt() and 0xFF)
509
+ }
@@ -0,0 +1,125 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import dev.bota.sdk.BotaDeviceClient
4
+ import dev.bota.sdk.model.ConnectedDevice
5
+ import dev.bota.sdk.model.DeviceWiFiScanResult
6
+ import dev.bota.sdk.model.WiFiConfigResult
7
+ import dev.bota.sdk.model.WiFiStatusInfo
8
+ import kotlinx.coroutines.CancellationException
9
+ import kotlinx.coroutines.CoroutineScope
10
+ import kotlinx.coroutines.CoroutineStart
11
+ import kotlinx.coroutines.Job
12
+ import kotlinx.coroutines.cancelAndJoin
13
+ import kotlinx.coroutines.flow.Flow
14
+ import kotlinx.coroutines.flow.collect
15
+ import kotlinx.coroutines.launch
16
+ import kotlinx.coroutines.sync.Mutex
17
+ import kotlinx.coroutines.sync.withLock
18
+
19
+ internal interface BotaDeviceSDKAndroidWiFiClient {
20
+ suspend fun configure(
21
+ device: ConnectedDevice,
22
+ ssid: String,
23
+ password: String,
24
+ grantBlob: String,
25
+ ): WiFiConfigResult
26
+
27
+ suspend fun disconnect(device: ConnectedDevice): WiFiConfigResult
28
+
29
+ suspend fun readStatus(device: ConnectedDevice): WiFiStatusInfo
30
+
31
+ fun statusUpdates(device: ConnectedDevice): Flow<WiFiStatusInfo>
32
+
33
+ suspend fun scanNetworks(device: ConnectedDevice): DeviceWiFiScanResult
34
+
35
+ suspend fun cancelCurrentOperation()
36
+ }
37
+
38
+ internal class BotaDeviceSDKSharedAndroidWiFiClient(
39
+ private val client: BotaDeviceClient = BotaDeviceClient.shared,
40
+ ) : BotaDeviceSDKAndroidWiFiClient {
41
+ override suspend fun configure(
42
+ device: ConnectedDevice,
43
+ ssid: String,
44
+ password: String,
45
+ grantBlob: String,
46
+ ): WiFiConfigResult = client.wifi.configure(device, ssid, password, grantBlob)
47
+
48
+ override suspend fun disconnect(device: ConnectedDevice): WiFiConfigResult =
49
+ client.wifi.disconnect(device)
50
+
51
+ override suspend fun readStatus(device: ConnectedDevice): WiFiStatusInfo =
52
+ client.wifi.readStatus(device)
53
+
54
+ override fun statusUpdates(device: ConnectedDevice): Flow<WiFiStatusInfo> =
55
+ client.wifi.statusUpdates(device)
56
+
57
+ override suspend fun scanNetworks(device: ConnectedDevice): DeviceWiFiScanResult =
58
+ client.wifi.scanNetworks(device)
59
+
60
+ override suspend fun cancelCurrentOperation() {
61
+ client.wifi.cancelCurrentOperation()
62
+ }
63
+ }
64
+
65
+ internal class BotaDeviceSDKAndroidWiFi(
66
+ private val client: BotaDeviceSDKAndroidWiFiClient,
67
+ private val scope: CoroutineScope,
68
+ ) {
69
+ private val operations = Mutex()
70
+ private val streamLock = Any()
71
+ private var statusStream: Job? = null
72
+
73
+ suspend fun configure(
74
+ device: ConnectedDevice,
75
+ ssid: String,
76
+ password: String,
77
+ grantBlob: String,
78
+ ): WiFiConfigResult = client.configure(device, ssid, password, grantBlob)
79
+
80
+ suspend fun disconnect(device: ConnectedDevice): WiFiConfigResult = client.disconnect(device)
81
+
82
+ suspend fun readStatus(device: ConnectedDevice): WiFiStatusInfo = client.readStatus(device)
83
+
84
+ suspend fun scanNetworks(device: ConnectedDevice): DeviceWiFiScanResult =
85
+ client.scanNetworks(device)
86
+
87
+ suspend fun startStatusUpdates(
88
+ device: ConnectedDevice,
89
+ onError: (Throwable) -> Unit = {},
90
+ onStatus: (WiFiStatusInfo) -> Unit,
91
+ ) = operations.withLock {
92
+ stopOwned()
93
+ val updates = client.statusUpdates(device)
94
+ lateinit var task: Job
95
+ task = scope.launch(start = CoroutineStart.LAZY) {
96
+ try {
97
+ updates.collect(onStatus)
98
+ } catch (_: CancellationException) {
99
+ // Explicit stop is not a WiFi status-stream failure.
100
+ } catch (error: Throwable) {
101
+ onError(error)
102
+ } finally {
103
+ synchronized(streamLock) {
104
+ if (statusStream === task) statusStream = null
105
+ }
106
+ }
107
+ }
108
+ synchronized(streamLock) { statusStream = task }
109
+ task.start()
110
+ }
111
+
112
+ suspend fun stopStatusUpdates() = operations.withLock {
113
+ stopOwned()
114
+ }
115
+
116
+ suspend fun cancelAll() {
117
+ runCatching { stopStatusUpdates() }
118
+ runCatching { client.cancelCurrentOperation() }
119
+ }
120
+
121
+ private suspend fun stopOwned() {
122
+ val stream = synchronized(streamLock) { statusStream.also { statusStream = null } } ?: return
123
+ stream.cancelAndJoin()
124
+ }
125
+ }
@@ -0,0 +1,46 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import dev.bota.sdk.EncryptedUploadV2Material
4
+ import dev.bota.sdk.EncryptedUploadV2Recording
5
+ import dev.bota.sdk.EncryptedUploadV2SecurityPolicy
6
+ import java.util.UUID
7
+ import java.util.concurrent.ConcurrentHashMap
8
+
9
+ /** Holds v2 application material in native memory and exposes only opaque IDs to JavaScript. */
10
+ public object BotaDeviceSDKEncryptedUploadV2Materials {
11
+ private val materials = ConcurrentHashMap<String, EncryptedUploadV2Material>()
12
+
13
+ public fun register(material: EncryptedUploadV2Material): String =
14
+ UUID.randomUUID().toString().also { materials[it] = material }
15
+
16
+ public fun remove(registrationId: String): EncryptedUploadV2Material? =
17
+ materials.remove(registrationId)
18
+
19
+ internal fun consume(
20
+ registrationId: String,
21
+ recording: EncryptedUploadV2Recording,
22
+ uploadSessionId: UUID,
23
+ ownerRevision: UInt,
24
+ securityPolicy: String,
25
+ ): EncryptedUploadV2Material {
26
+ val material = materials[registrationId]
27
+ ?: error("encrypted upload v2 material registration is unavailable")
28
+ require(
29
+ material.recordingId == recording.uuid &&
30
+ material.uploadSessionId == uploadSessionId &&
31
+ material.ownerRevision == ownerRevision &&
32
+ material.policy == securityPolicy.toNativePolicy(),
33
+ ) { "encrypted upload v2 material does not match the selected profile" }
34
+ check(materials.remove(registrationId, material)) {
35
+ "encrypted upload v2 material registration is unavailable"
36
+ }
37
+ return material
38
+ }
39
+ }
40
+
41
+ private fun String.toNativePolicy(): EncryptedUploadV2SecurityPolicy = when (this) {
42
+ "legacy_allowed" -> EncryptedUploadV2SecurityPolicy.LegacyAllowed
43
+ "v2_preferred" -> EncryptedUploadV2SecurityPolicy.V2Preferred
44
+ "v2_required" -> EncryptedUploadV2SecurityPolicy.V2Required
45
+ else -> error("encrypted upload v2 security policy is unsupported")
46
+ }