@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,723 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.module.annotations.ReactModule
8
+ import dev.bota.sdk.DeviceReconnectHint
9
+ import dev.bota.sdk.DeviceApiEnvironment
10
+ import dev.bota.sdk.EncryptedUploadV2Recording
11
+ import java.io.File
12
+ import kotlinx.coroutines.CoroutineScope
13
+ import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.SupervisorJob
15
+ import kotlinx.coroutines.cancel
16
+ import kotlinx.coroutines.launch
17
+
18
+ @ReactModule(name = NativeBotaDeviceSDKSpec.NAME)
19
+ internal class BotaDeviceSDKModule(
20
+ reactContext: ReactApplicationContext,
21
+ private val lifecycle: BotaDeviceSDKAndroidLifecycle =
22
+ BotaDeviceSDKAndroidLifecycle(BotaDeviceSDKSharedAndroidClient(reactContext)),
23
+ private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default),
24
+ ) : NativeBotaDeviceSDKSpec(reactContext) {
25
+ private val devices = BotaDeviceSDKAndroidDevices(BotaDeviceSDKSharedAndroidDeviceClient(), scope)
26
+ private val logs = BotaDeviceSDKAndroidLogs(BotaDeviceSDKSharedAndroidLogClient(), scope)
27
+ private val ota = BotaDeviceSDKAndroidOTA()
28
+ private val recordings = BotaDeviceSDKAndroidRecordings()
29
+ private val recordingUploads = BotaDeviceSDKAndroidRecordingUploads(reactContext)
30
+ private val security = BotaDeviceSDKAndroidSecurity(
31
+ BotaDeviceSDKSharedAndroidSecurityClient(),
32
+ scope,
33
+ )
34
+ private val wifi = BotaDeviceSDKAndroidWiFi(BotaDeviceSDKSharedAndroidWiFiClient(), scope)
35
+
36
+ override fun configure(configuration: ReadableMap, promise: Promise) {
37
+ val storageDirectory =
38
+ if (configuration.hasKey("applicationSupportDirectory") &&
39
+ !configuration.isNull("applicationSupportDirectory")
40
+ ) {
41
+ configuration.getString("applicationSupportDirectory")?.let(::File)
42
+ } else {
43
+ null
44
+ }
45
+ launch(promise) { lifecycle.configure(storageDirectory) }
46
+ }
47
+
48
+ override fun destroy(promise: Promise) {
49
+ launch(promise) {
50
+ security.cancelAll()
51
+ wifi.cancelAll()
52
+ logs.cancelAll()
53
+ ota.cancelAll()
54
+ recordingUploads.cancelAll()
55
+ recordings.cancelAll()
56
+ devices.stopAll()
57
+ lifecycle.destroy()
58
+ }
59
+ }
60
+
61
+ override fun startScan(timeoutMs: Double, allowDuplicates: Boolean, promise: Promise) {
62
+ launch(promise) {
63
+ devices.startScan(timeoutMs.toTimeoutMilliseconds(), allowDuplicates) {
64
+ emitOnDeviceDiscovered(it.toWritableMap())
65
+ }
66
+ }
67
+ }
68
+
69
+ override fun stopScan(promise: Promise) {
70
+ launch(promise) { devices.stopScan() }
71
+ }
72
+
73
+ override fun connectSelected(device: ReadableMap, promise: Promise) {
74
+ launchValue(promise) { devices.connect(device.toDiscoveredDevice()).toWritableMap() }
75
+ }
76
+
77
+ override fun reconnect(serialNumber: String, options: ReadableMap, promise: Promise) {
78
+ launchValue(promise) {
79
+ devices.reconnect(
80
+ serialNumber,
81
+ DeviceReconnectHint(
82
+ scanTimeoutMilliseconds = options.getDouble("scanTimeoutMs").toTimeoutMilliseconds(),
83
+ connectionTimeoutMilliseconds = options
84
+ .getDouble("connectionTimeoutMs")
85
+ .toTimeoutMilliseconds(),
86
+ ),
87
+ ).toWritableMap()
88
+ }
89
+ }
90
+
91
+ override fun disconnect(promise: Promise) {
92
+ launch(promise) { devices.disconnect() }
93
+ }
94
+
95
+ override fun isProvisioned(device: ReadableMap, promise: Promise) {
96
+ launchValue(promise) { security.isProvisioned(device.toConnectedDevice()) }
97
+ }
98
+
99
+ override fun readPublicKey(device: ReadableMap, promise: Promise) {
100
+ launchValue(promise) { security.readPublicKey(device.toConnectedDevice()) }
101
+ }
102
+
103
+ override fun readAuthNonce(device: ReadableMap, promise: Promise) {
104
+ launchValue(promise) { security.readAuthNonce(device.toConnectedDevice()) }
105
+ }
106
+
107
+ override fun setApiEndpoint(device: ReadableMap, environment: String, promise: Promise) {
108
+ launch(promise) {
109
+ security.setApiEndpoint(environment.toDeviceApiEnvironment(), device.toConnectedDevice())
110
+ }
111
+ }
112
+
113
+ override fun deliverCertificate(
114
+ device: ReadableMap,
115
+ certificatePem: String,
116
+ privateKeyPem: String,
117
+ promise: Promise,
118
+ ) {
119
+ launch(promise) {
120
+ security.deliverCertificate(certificatePem, privateKeyPem, device.toConnectedDevice())
121
+ }
122
+ }
123
+
124
+ override fun deliverBackendPublicKey(
125
+ device: ReadableMap,
126
+ publicKeyHex: String,
127
+ promise: Promise,
128
+ ) {
129
+ launch(promise) {
130
+ security.deliverBackendPublicKey(publicKeyHex.hexBytes(), device.toConnectedDevice())
131
+ }
132
+ }
133
+
134
+ override fun writeGrant(device: ReadableMap, grantBlob: String, promise: Promise) {
135
+ launch(promise) { security.writeGrant(grantBlob, device.toConnectedDevice()) }
136
+ }
137
+
138
+ override fun syncTime(device: ReadableMap, promise: Promise) {
139
+ launch(promise) { security.syncTime(device.toConnectedDevice()) }
140
+ }
141
+
142
+ override fun requestStartRecording(
143
+ device: ReadableMap,
144
+ grantBlob: String,
145
+ promise: Promise,
146
+ ) {
147
+ launchValue(promise) {
148
+ security.requestStartRecording(device.toConnectedDevice(), grantBlob).toWritableMap()
149
+ }
150
+ }
151
+
152
+ override fun requestStopRecording(
153
+ device: ReadableMap,
154
+ grantBlob: String,
155
+ promise: Promise,
156
+ ) {
157
+ launchValue(promise) {
158
+ security.requestStopRecording(device.toConnectedDevice(), grantBlob).toWritableMap()
159
+ }
160
+ }
161
+
162
+ override fun readRecordingState(device: ReadableMap, promise: Promise) {
163
+ launchValue(promise) {
164
+ security.readRecordingState(device.toConnectedDevice()).toWritableMap()
165
+ }
166
+ }
167
+
168
+ override fun startRecordingStateUpdates(device: ReadableMap, promise: Promise) {
169
+ launch(promise) {
170
+ security.startRecordingStateUpdates(device.toConnectedDevice()) {
171
+ emitOnRecordingStateUpdated(it.toWritableMap())
172
+ }
173
+ }
174
+ }
175
+
176
+ override fun stopRecordingStateUpdates(promise: Promise) {
177
+ launch(promise) { security.stopRecordingStateUpdates() }
178
+ }
179
+
180
+ override fun configureWiFi(
181
+ device: ReadableMap,
182
+ ssid: String,
183
+ password: String,
184
+ grantBlob: String,
185
+ promise: Promise,
186
+ ) {
187
+ launchValue(promise) {
188
+ wifi.configure(device.toConnectedDevice(), ssid, password, grantBlob).toWritableMap()
189
+ }
190
+ }
191
+
192
+ override fun disconnectWiFi(device: ReadableMap, promise: Promise) {
193
+ launchValue(promise) { wifi.disconnect(device.toConnectedDevice()).toWritableMap() }
194
+ }
195
+
196
+ override fun readWiFiStatus(device: ReadableMap, promise: Promise) {
197
+ launchValue(promise) { wifi.readStatus(device.toConnectedDevice()).toWritableMap() }
198
+ }
199
+
200
+ override fun startWiFiStatusUpdates(device: ReadableMap, promise: Promise) {
201
+ launch(promise) {
202
+ wifi.startStatusUpdates(device.toConnectedDevice()) {
203
+ emitOnWiFiStatusUpdated(it.toWritableMap())
204
+ }
205
+ }
206
+ }
207
+
208
+ override fun stopWiFiStatusUpdates(promise: Promise) {
209
+ launch(promise) { wifi.stopStatusUpdates() }
210
+ }
211
+
212
+ override fun scanWiFiNetworks(device: ReadableMap, promise: Promise) {
213
+ launchValue(promise) { wifi.scanNetworks(device.toConnectedDevice()).toWritableMap() }
214
+ }
215
+
216
+ override fun listRecordings(device: ReadableMap, promise: Promise) {
217
+ launchValue(promise) {
218
+ Arguments.createArray().apply {
219
+ recordings.listRecordings(device.toConnectedDevice()).forEach {
220
+ pushMap(it.toWritableMap())
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ override fun syncRecording(
227
+ device: ReadableMap,
228
+ recording: ReadableMap,
229
+ sinkId: String,
230
+ promise: Promise,
231
+ ) {
232
+ launchValue(promise) {
233
+ recordings.syncRecording(
234
+ device.toConnectedDevice(),
235
+ recording.toDeviceRecording(),
236
+ sinkId,
237
+ ) {
238
+ emitOnRecordingTransferProgress(it.toWritableMap())
239
+ }.toWritableMap()
240
+ }
241
+ }
242
+
243
+ override fun syncEncryptedRecordingV2(
244
+ device: ReadableMap,
245
+ recording: ReadableMap,
246
+ operationId: String,
247
+ promise: Promise,
248
+ ) {
249
+ launchEncryptedUploadV2(promise) {
250
+ recordings.syncEncryptedRecordingV2(
251
+ device.toConnectedDevice(),
252
+ recording.toEncryptedUploadV2Recording(),
253
+ operationId,
254
+ onProfileRequest = {
255
+ emitOnEncryptedUploadV2ProfileRequested(it.toWritableMap())
256
+ },
257
+ onProgress = { emitOnEncryptedUploadV2Progress(it.toWritableMap()) },
258
+ )
259
+ }
260
+ }
261
+
262
+ override fun resolveEncryptedUploadV2Profile(
263
+ requestId: String,
264
+ decision: ReadableMap,
265
+ promise: Promise,
266
+ ) {
267
+ launch(promise) {
268
+ recordings.resolveEncryptedUploadV2Profile(
269
+ requestId,
270
+ decision.getString("profile")
271
+ ?: error("encrypted upload v2 profile is required"),
272
+ decision.getString("uploadSessionId")
273
+ ?: error("encrypted upload v2 session ID is required"),
274
+ decision.getDouble("ownerRevision").toUnsignedInt(),
275
+ decision.getString("securityPolicy")
276
+ ?: error("encrypted upload v2 security policy is required"),
277
+ decision.getString("materialRegistrationId")
278
+ ?: error("encrypted upload v2 material registration is required"),
279
+ )
280
+ }
281
+ }
282
+
283
+ override fun rejectEncryptedUploadV2Profile(
284
+ requestId: String,
285
+ errorCode: String,
286
+ promise: Promise,
287
+ ) {
288
+ launch(promise) { recordings.rejectEncryptedUploadV2Profile(requestId, errorCode) }
289
+ }
290
+
291
+ override fun startStreaming(
292
+ device: ReadableMap,
293
+ request: ReadableMap,
294
+ promise: Promise,
295
+ ) {
296
+ launchValue(promise) {
297
+ val sessionId = request.getString("sessionId")
298
+ ?: error("streaming session ID is required")
299
+ val totalBytes = recordings.streamRecording(
300
+ device = device.toConnectedDevice(),
301
+ recordingUuid = request.getString("recordingUuid")
302
+ ?: error("streaming recording UUID is required"),
303
+ sessionId = sessionId,
304
+ chunkSizeBytes = request.getDouble("chunkSizeBytes").toPositiveInt(),
305
+ flushIntervalMilliseconds = request
306
+ .getDouble("flushIntervalMs")
307
+ .toUnsignedInteger(),
308
+ onProgress = { emitOnStreamingProgress(it.toWritableMap()) },
309
+ onDestinationRequest = {
310
+ emitOnStreamingChunkDestinationRequested(it.toWritableMap())
311
+ },
312
+ onFinalizeRequest = { emitOnStreamingFinalizeRequested(it.toWritableMap()) },
313
+ )
314
+ Arguments.createMap().apply { putDouble("totalBytes", totalBytes.toDouble()) }
315
+ }
316
+ }
317
+
318
+ override fun abortStreaming(sessionId: String, promise: Promise) {
319
+ launch(promise) { recordings.abortStreaming(sessionId) }
320
+ }
321
+
322
+ override fun resolveStreamingChunkDestination(
323
+ requestId: String,
324
+ destination: ReadableMap,
325
+ promise: Promise,
326
+ ) {
327
+ launch(promise) {
328
+ recordings.resolveStreamingDestination(
329
+ requestId = requestId,
330
+ url = destination.getString("url") ?: error("streaming destination URL is required"),
331
+ method = destination.getString("method")
332
+ ?: error("streaming destination method is required"),
333
+ contentType = destination.getString("contentType")
334
+ ?: error("streaming destination content type is required"),
335
+ bearerToken = if (destination.hasKey("bearerToken") &&
336
+ !destination.isNull("bearerToken")
337
+ ) {
338
+ destination.getString("bearerToken")
339
+ } else {
340
+ null
341
+ },
342
+ )
343
+ }
344
+ }
345
+
346
+ override fun rejectStreamingChunkDestination(
347
+ requestId: String,
348
+ message: String,
349
+ promise: Promise,
350
+ ) {
351
+ launch(promise) { recordings.rejectStreamingDestination(requestId, message) }
352
+ }
353
+
354
+ override fun resolveStreamingFinalize(requestId: String, promise: Promise) {
355
+ launch(promise) { recordings.resolveStreamingFinalize(requestId) }
356
+ }
357
+
358
+ override fun rejectStreamingFinalize(
359
+ requestId: String,
360
+ message: String,
361
+ promise: Promise,
362
+ ) {
363
+ launch(promise) { recordings.rejectStreamingFinalize(requestId, message) }
364
+ }
365
+
366
+ override fun confirmRecording(
367
+ device: ReadableMap,
368
+ recordingUuid: String,
369
+ promise: Promise,
370
+ ) {
371
+ launch(promise) {
372
+ recordings.confirmRecording(device.toConnectedDevice(), recordingUuid)
373
+ }
374
+ }
375
+
376
+ override fun uploadRecordingFile(request: ReadableMap, promise: Promise) {
377
+ launch(promise) {
378
+ recordingUploads.upload(request.toRecordingUploadRequest()) {
379
+ emitOnRecordingUploadProgress(it.toWritableMap())
380
+ }
381
+ }
382
+ }
383
+
384
+ override fun cancelRecordingUpload(taskId: String, promise: Promise) {
385
+ launch(promise) { recordingUploads.cancel(taskId) }
386
+ }
387
+
388
+ override fun loadCompatibilityUploadQueue(promise: Promise) {
389
+ launchValue(promise) { recordingUploads.loadQueue() }
390
+ }
391
+
392
+ override fun saveCompatibilityUploadQueue(serializedTasks: String, promise: Promise) {
393
+ launch(promise) { recordingUploads.saveQueue(serializedTasks) }
394
+ }
395
+
396
+ override fun stopAllRecordingOperations(promise: Promise) {
397
+ launch(promise) {
398
+ recordingUploads.cancelAll()
399
+ recordings.cancelAll()
400
+ }
401
+ }
402
+
403
+ override fun observeUploadOwnership(
404
+ device: ReadableMap,
405
+ request: ReadableMap,
406
+ promise: Promise,
407
+ ) {
408
+ launchValue(promise) {
409
+ recordings.observeUploadOwnership(
410
+ device = device.toConnectedDevice(),
411
+ recordingUuid = request.getString("recordingUuid")
412
+ ?: error("recording UUID is required"),
413
+ uploadId = request.getString("uploadId") ?: error("upload ID is required"),
414
+ destinationId = request.getString("destinationId")
415
+ ?: error("destination ID is required"),
416
+ ) {
417
+ emitOnUploadOwnershipProgress(it.toWritableMap())
418
+ }.toWritableMap()
419
+ }
420
+ }
421
+
422
+ override fun updateFirmware(
423
+ device: ReadableMap,
424
+ image: ReadableMap,
425
+ promise: Promise,
426
+ ) {
427
+ launch(promise) {
428
+ ota.updateFirmware(
429
+ device = device.toConnectedDevice(),
430
+ version = image.getString("version") ?: error("firmware version is required"),
431
+ sizeBytes = image.getDouble("sizeUnits").toUnsignedInt(),
432
+ crc32 = image.getDouble("crc32").toUnsignedInt(),
433
+ url = image.getString("url") ?: error("firmware URL is required"),
434
+ ) {
435
+ emitOnFirmwareUpdateProgress(it.toWritableMap())
436
+ }
437
+ }
438
+ }
439
+
440
+ override fun cancelFirmwareUpdate(promise: Promise) {
441
+ launch(promise) { ota.cancelAll() }
442
+ }
443
+
444
+ override fun startDeviceLogs(device: ReadableMap, promise: Promise) {
445
+ launch(promise) {
446
+ logs.start(device.toConnectedDevice()) {
447
+ emitOnDeviceLog(it.toWritableMap())
448
+ }
449
+ }
450
+ }
451
+
452
+ override fun stopDeviceLogs(promise: Promise) {
453
+ launch(promise) { logs.stop() }
454
+ }
455
+
456
+ override fun readStatus(promise: Promise) {
457
+ launchValue(promise) { devices.readStatus().toWritableMap() }
458
+ }
459
+
460
+ override fun startStatusUpdates(promise: Promise) {
461
+ launch(promise) {
462
+ devices.startStatusUpdates(
463
+ onError = { error ->
464
+ emitOnDeviceDisconnected(
465
+ Arguments.createMap().apply {
466
+ putString("error", error.message ?: "Bluetooth link lost")
467
+ },
468
+ )
469
+ },
470
+ onStatus = { emitOnDeviceStatusUpdated(it.toWritableMap()) },
471
+ )
472
+ }
473
+ }
474
+
475
+ override fun stopStatusUpdates(promise: Promise) {
476
+ launch(promise) { devices.stopStatusUpdates() }
477
+ }
478
+
479
+ override fun provision(device: ReadableMap, promise: Promise) {
480
+ launch(promise) {
481
+ security.provision(device.toConnectedDevice()) {
482
+ emitOnProvisioningMaterialRequested(it.toWritableMap())
483
+ }
484
+ }
485
+ }
486
+
487
+ override fun deprovision(device: ReadableMap, grantBlob: String, promise: Promise) {
488
+ launchValue(promise) {
489
+ security.deprovision(device.toConnectedDevice(), grantBlob).toWritableMap()
490
+ }
491
+ }
492
+
493
+ override fun readConnectionSettings(device: ReadableMap, promise: Promise) {
494
+ launchValue(promise) {
495
+ security.readConnectionSettings(device.toConnectedDevice()).toWritableMap()
496
+ }
497
+ }
498
+
499
+ override fun writeConnectionSettings(
500
+ device: ReadableMap,
501
+ settings: ReadableMap,
502
+ promise: Promise,
503
+ ) {
504
+ launch(promise) {
505
+ security.writeConnectionSettings(
506
+ settings.toDeviceConnectionSettings(),
507
+ device.toConnectedDevice(),
508
+ )
509
+ }
510
+ }
511
+
512
+ override fun factoryReset(
513
+ device: ReadableMap,
514
+ commandId: String,
515
+ bindingGeneration: Double,
516
+ requiresApplicationPersistence: Boolean,
517
+ promise: Promise,
518
+ ) {
519
+ launchValue(promise) {
520
+ security.factoryReset(
521
+ device = device.toConnectedDevice(),
522
+ commandId = commandId,
523
+ bindingGeneration = bindingGeneration.toUnsignedInteger(),
524
+ onGrantRequest = { emitOnFactoryResetGrantRequested(it.toWritableMap()) },
525
+ onPersistenceRequest = if (requiresApplicationPersistence) {
526
+ { emitOnFactoryResetResultPersistenceRequested(it.toWritableMap()) }
527
+ } else {
528
+ null
529
+ },
530
+ ).toWritableMap()
531
+ }
532
+ }
533
+
534
+ override fun resumePendingFactoryReset(
535
+ device: ReadableMap,
536
+ currentBindingGeneration: Double,
537
+ requiresApplicationPersistence: Boolean,
538
+ promise: Promise,
539
+ ) {
540
+ launchValue(promise) {
541
+ security.resumePendingFactoryReset(
542
+ device.toConnectedDevice(),
543
+ currentBindingGeneration.toUnsignedInteger(),
544
+ if (requiresApplicationPersistence) {
545
+ { emitOnFactoryResetResultPersistenceRequested(it.toWritableMap()) }
546
+ } else {
547
+ null
548
+ },
549
+ )?.toWritableMap()
550
+ }
551
+ }
552
+
553
+ override fun resolveProvisioningMaterial(
554
+ requestId: String,
555
+ material: ReadableMap,
556
+ promise: Promise,
557
+ ) {
558
+ launch(promise) {
559
+ security.resolveProvisioningMaterial(
560
+ requestId = requestId,
561
+ apiEndpoint = material.getString("apiEndpoint")
562
+ ?: error("provisioning API endpoint is required"),
563
+ deviceToken = material.getString("deviceToken")
564
+ ?: error("provisioning device token is required"),
565
+ mtu = material.getDouble("mtu").toUnsignedInteger(),
566
+ )
567
+ }
568
+ }
569
+
570
+ override fun resolveFactoryResetGrant(
571
+ requestId: String,
572
+ grantBlob: String,
573
+ promise: Promise,
574
+ ) {
575
+ launch(promise) { security.resolveFactoryResetGrant(requestId, grantBlob) }
576
+ }
577
+
578
+ override fun resolveFactoryResetResultPersistence(requestId: String, promise: Promise) {
579
+ launch(promise) { security.resolveFactoryResetResultPersistence(requestId) }
580
+ }
581
+
582
+ override fun rejectApplicationMaterial(requestId: String, message: String, promise: Promise) {
583
+ launch(promise) { security.rejectApplicationMaterial(requestId, message) }
584
+ }
585
+
586
+ override fun getCapabilities(promise: Promise) {
587
+ val capabilities = lifecycle.capabilities()
588
+ promise.resolve(
589
+ Arguments.createMap().apply {
590
+ putBoolean("backgroundReconnect", capabilities.backgroundReconnect)
591
+ putBoolean("backgroundScan", capabilities.backgroundScan)
592
+ putBoolean("bluetooth", capabilities.bluetooth)
593
+ putBoolean("nativeFileTransfer", capabilities.nativeFileTransfer)
594
+ putString("platform", capabilities.platform)
595
+ },
596
+ )
597
+ }
598
+
599
+ override fun getState(promise: Promise) {
600
+ promise.resolve(lifecycle.state())
601
+ }
602
+
603
+ override fun invalidate() {
604
+ scope.launch {
605
+ try {
606
+ security.cancelAll()
607
+ wifi.cancelAll()
608
+ logs.cancelAll()
609
+ ota.cancelAll()
610
+ recordings.cancelAll()
611
+ devices.stopAll()
612
+ lifecycle.destroy()
613
+ } finally {
614
+ scope.cancel()
615
+ }
616
+ }
617
+ super.invalidate()
618
+ }
619
+
620
+ private fun launch(promise: Promise, operation: suspend () -> Unit) {
621
+ scope.launch {
622
+ try {
623
+ operation()
624
+ promise.resolve(null)
625
+ } catch (error: Throwable) {
626
+ promise.reject(ERROR_CODE, error.message ?: "Bota Android SDK operation failed", error)
627
+ }
628
+ }
629
+ }
630
+
631
+ private fun launchValue(promise: Promise, operation: suspend () -> Any?) {
632
+ scope.launch {
633
+ try {
634
+ promise.resolve(operation())
635
+ } catch (error: Throwable) {
636
+ promise.reject(ERROR_CODE, error.message ?: "Bota Android SDK operation failed", error)
637
+ }
638
+ }
639
+ }
640
+
641
+ private fun launchEncryptedUploadV2(promise: Promise, operation: suspend () -> Unit) {
642
+ scope.launch {
643
+ try {
644
+ operation()
645
+ promise.resolve(null)
646
+ } catch (_: Throwable) {
647
+ promise.reject(ENCRYPTED_UPLOAD_V2_ERROR_CODE, "encrypted upload v2 failed")
648
+ }
649
+ }
650
+ }
651
+
652
+ companion object {
653
+ const val NAME = NativeBotaDeviceSDKSpec.NAME
654
+ private const val ERROR_CODE = "android_sdk_error"
655
+ private const val ENCRYPTED_UPLOAD_V2_ERROR_CODE = "encrypted_upload_v2_failed"
656
+ }
657
+ }
658
+
659
+ private fun Double.toTimeoutMilliseconds(): ULong {
660
+ require(isFinite() && this >= 0 && this <= Long.MAX_VALUE.toDouble()) {
661
+ "timeout must be a finite non-negative number"
662
+ }
663
+ return toLong().toULong()
664
+ }
665
+
666
+ private fun Double.toUnsignedInteger(): ULong {
667
+ require(isFinite() && this >= 0 && this <= 9_007_199_254_740_991.0 && this % 1.0 == 0.0) {
668
+ "value must be a finite non-negative integer"
669
+ }
670
+ return toLong().toULong()
671
+ }
672
+
673
+ private fun Double.toUnsignedInt(): UInt {
674
+ val value = toUnsignedInteger()
675
+ require(value <= UInt.MAX_VALUE.toULong()) {
676
+ "value must fit an unsigned 32-bit integer"
677
+ }
678
+ return value.toUInt()
679
+ }
680
+
681
+ private fun Double.toPositiveInt(): Int {
682
+ val value = toUnsignedInteger()
683
+ require(value in 1uL..Int.MAX_VALUE.toULong()) {
684
+ "value must fit a positive signed 32-bit integer"
685
+ }
686
+ return value.toInt()
687
+ }
688
+
689
+ private fun String.toDeviceApiEnvironment(): DeviceApiEnvironment = when (this) {
690
+ "development" -> DeviceApiEnvironment.Development
691
+ "gamma" -> DeviceApiEnvironment.Gamma
692
+ "production" -> DeviceApiEnvironment.Production
693
+ else -> error("unsupported API environment: $this")
694
+ }
695
+
696
+ private fun String.hexBytes(): ByteArray {
697
+ require(length % 2 == 0 && all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) {
698
+ "public key must be lowercase or uppercase hexadecimal"
699
+ }
700
+ return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
701
+ }
702
+
703
+ private fun ReadableMap.toEncryptedUploadV2Recording(): EncryptedUploadV2Recording {
704
+ val length = getString("ciphertextLength")
705
+ ?.takeIf { it.isNotEmpty() && it.all(Char::isDigit) }
706
+ ?.toULongOrNull()
707
+ ?: error("encrypted upload v2 ciphertext length must be an unsigned decimal integer")
708
+ val digest = getString("ciphertextSha256")?.sha256Bytes()
709
+ ?: error("encrypted upload v2 ciphertext SHA-256 is required")
710
+ return EncryptedUploadV2Recording(
711
+ getString("uuid") ?: error("encrypted upload v2 recording UUID is required"),
712
+ getDouble("generation").toUnsignedInt(),
713
+ length,
714
+ digest,
715
+ )
716
+ }
717
+
718
+ private fun String.sha256Bytes(): ByteArray {
719
+ require(length == 64 && all { it in '0'..'9' || it in 'a'..'f' || it in 'A'..'F' }) {
720
+ "encrypted upload v2 ciphertext SHA-256 must contain exactly 32 hexadecimal bytes"
721
+ }
722
+ return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
723
+ }