@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.
- package/BotaDeviceSDK.podspec +55 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/android/build.gradle +76 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +172 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycle.kt +100 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +82 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidModels.kt +476 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTA.kt +58 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +180 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +751 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurity.kt +509 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFi.kt +125 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +46 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +723 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +211 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycleTest.kt +132 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +57 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTATest.kt +75 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +147 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +355 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurityTest.kt +523 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFiTest.kt +100 -0
- package/generated/codegen-contract.json +3593 -0
- package/ios/BotaDeviceSDK.mm +1300 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +2260 -0
- package/ios/BotaDeviceSDKAppleDevices.swift +191 -0
- package/ios/BotaDeviceSDKAppleLifecycle.swift +167 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +79 -0
- package/ios/BotaDeviceSDKAppleOTA.swift +73 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +178 -0
- package/ios/BotaDeviceSDKAppleRecordings.swift +670 -0
- package/ios/BotaDeviceSDKAppleSecurity.swift +586 -0
- package/ios/BotaDeviceSDKAppleWiFi.swift +128 -0
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +99 -0
- package/lib/commonjs/BotaClient.js +194 -0
- package/lib/commonjs/BotaClient.js.map +1 -0
- package/lib/commonjs/ble/constants.js +10 -0
- package/lib/commonjs/ble/constants.js.map +1 -0
- package/lib/commonjs/ble/deviceLogs.js +50 -0
- package/lib/commonjs/ble/deviceLogs.js.map +1 -0
- package/lib/commonjs/cache/DeviceStateCache.js +6 -0
- package/lib/commonjs/cache/DeviceStateCache.js.map +1 -0
- package/lib/commonjs/client.js +746 -0
- package/lib/commonjs/client.js.map +1 -0
- package/lib/commonjs/compatibility/runtime.js +44 -0
- package/lib/commonjs/compatibility/runtime.js.map +1 -0
- package/lib/commonjs/index.js +123 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/managers/DeviceManager.js +647 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -0
- package/lib/commonjs/managers/OTAManager.js +140 -0
- package/lib/commonjs/managers/OTAManager.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +444 -0
- package/lib/commonjs/managers/RecordingManager.js.map +1 -0
- package/lib/commonjs/managers/StreamingSession.js +175 -0
- package/lib/commonjs/managers/StreamingSession.js.map +1 -0
- package/lib/commonjs/managers/types.js +6 -0
- package/lib/commonjs/managers/types.js.map +1 -0
- package/lib/commonjs/models/Device.js +2 -0
- package/lib/commonjs/models/Device.js.map +1 -0
- package/lib/commonjs/models/Recording.js +2 -0
- package/lib/commonjs/models/Recording.js.map +1 -0
- package/lib/commonjs/models/Status.js +6 -0
- package/lib/commonjs/models/Status.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js +9 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/commonjs/sync/syncStatus.js +178 -0
- package/lib/commonjs/sync/syncStatus.js.map +1 -0
- package/lib/commonjs/utils/errors.js +194 -0
- package/lib/commonjs/utils/errors.js.map +1 -0
- package/lib/commonjs/utils/logger.js +84 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/module/BotaClient.js +189 -0
- package/lib/module/BotaClient.js.map +1 -0
- package/lib/module/ble/constants.js +6 -0
- package/lib/module/ble/constants.js.map +1 -0
- package/lib/module/ble/deviceLogs.js +45 -0
- package/lib/module/ble/deviceLogs.js.map +1 -0
- package/lib/module/cache/DeviceStateCache.js +4 -0
- package/lib/module/cache/DeviceStateCache.js.map +1 -0
- package/lib/module/client.js +740 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/compatibility/runtime.js +35 -0
- package/lib/module/compatibility/runtime.js.map +1 -0
- package/lib/module/index.js +17 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/managers/DeviceManager.js +641 -0
- package/lib/module/managers/DeviceManager.js.map +1 -0
- package/lib/module/managers/OTAManager.js +134 -0
- package/lib/module/managers/OTAManager.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +438 -0
- package/lib/module/managers/RecordingManager.js.map +1 -0
- package/lib/module/managers/StreamingSession.js +168 -0
- package/lib/module/managers/StreamingSession.js.map +1 -0
- package/lib/module/managers/types.js +4 -0
- package/lib/module/managers/types.js.map +1 -0
- package/lib/module/models/Device.js +2 -0
- package/lib/module/models/Device.js.map +1 -0
- package/lib/module/models/Recording.js +2 -0
- package/lib/module/models/Recording.js.map +1 -0
- package/lib/module/models/Status.js +4 -0
- package/lib/module/models/Status.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js +5 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/module/sync/syncStatus.js +174 -0
- package/lib/module/sync/syncStatus.js.map +1 -0
- package/lib/module/utils/errors.js +183 -0
- package/lib/module/utils/errors.js.map +1 -0
- package/lib/module/utils/logger.js +80 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/typescript/BotaClient.d.ts +40 -0
- package/lib/typescript/BotaClient.d.ts.map +1 -0
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -0
- package/lib/typescript/ble/deviceLogs.d.ts +10 -0
- package/lib/typescript/ble/deviceLogs.d.ts.map +1 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts +14 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +208 -0
- package/lib/typescript/client.d.ts.map +1 -0
- package/lib/typescript/compatibility/runtime.d.ts +11 -0
- package/lib/typescript/compatibility/runtime.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +19 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/managers/DeviceManager.d.ts +117 -0
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -0
- package/lib/typescript/managers/OTAManager.d.ts +25 -0
- package/lib/typescript/managers/OTAManager.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +54 -0
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -0
- package/lib/typescript/managers/StreamingSession.d.ts +35 -0
- package/lib/typescript/managers/StreamingSession.d.ts.map +1 -0
- package/lib/typescript/managers/types.d.ts +19 -0
- package/lib/typescript/managers/types.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +392 -0
- package/lib/typescript/models/Device.d.ts.map +1 -0
- package/lib/typescript/models/Recording.d.ts +259 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -0
- package/lib/typescript/models/Status.d.ts +106 -0
- package/lib/typescript/models/Status.d.ts.map +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +393 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -0
- package/lib/typescript/sync/syncStatus.d.ts +123 -0
- package/lib/typescript/sync/syncStatus.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts +83 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -0
- package/lib/typescript/utils/logger.d.ts +36 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/package.json +100 -0
- package/scripts/bota_device_sdk_spm_workaround.rb +53 -0
- package/src/BotaClient.ts +231 -0
- package/src/ble/constants.ts +3 -0
- package/src/ble/deviceLogs.ts +61 -0
- package/src/cache/DeviceStateCache.ts +20 -0
- package/src/client.ts +1410 -0
- package/src/compatibility/runtime.ts +55 -0
- package/src/index.ts +146 -0
- package/src/managers/DeviceManager.ts +927 -0
- package/src/managers/OTAManager.ts +195 -0
- package/src/managers/RecordingManager.ts +578 -0
- package/src/managers/StreamingSession.ts +212 -0
- package/src/managers/types.ts +36 -0
- package/src/models/Device.ts +484 -0
- package/src/models/Recording.ts +281 -0
- package/src/models/Status.ts +128 -0
- package/src/specs/NativeBotaDeviceSDK.ts +542 -0
- package/src/sync/syncStatus.ts +243 -0
- package/src/utils/errors.ts +327 -0
- package/src/utils/logger.ts +122 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
public class BotaDeviceSDKPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(
|
|
11
|
+
name: String,
|
|
12
|
+
reactContext: ReactApplicationContext,
|
|
13
|
+
): NativeModule? =
|
|
14
|
+
if (name == BotaDeviceSDKModule.NAME) {
|
|
15
|
+
BotaDeviceSDKModule(reactContext)
|
|
16
|
+
} else {
|
|
17
|
+
null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
|
|
21
|
+
ReactModuleInfoProvider {
|
|
22
|
+
mapOf(
|
|
23
|
+
BotaDeviceSDKModule.NAME to ReactModuleInfo(
|
|
24
|
+
name = BotaDeviceSDKModule.NAME,
|
|
25
|
+
className = BotaDeviceSDKModule::class.java.name,
|
|
26
|
+
canOverrideExistingModule = false,
|
|
27
|
+
needsEagerInit = false,
|
|
28
|
+
isCxxModule = false,
|
|
29
|
+
isTurboModule = true,
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import dev.bota.sdk.DeviceReconnectHint
|
|
4
|
+
import dev.bota.sdk.model.ConnectedDevice
|
|
5
|
+
import dev.bota.sdk.model.ConnectionState
|
|
6
|
+
import dev.bota.sdk.model.DeviceType
|
|
7
|
+
import dev.bota.sdk.model.DiscoveredDevice
|
|
8
|
+
import dev.bota.sdk.model.DeviceFlags
|
|
9
|
+
import dev.bota.sdk.model.DeviceState
|
|
10
|
+
import dev.bota.sdk.model.DeviceStatus
|
|
11
|
+
import dev.bota.sdk.model.LteStatus
|
|
12
|
+
import dev.bota.sdk.model.PairingState
|
|
13
|
+
import dev.bota.sdk.model.WifiRadioStatus
|
|
14
|
+
import dev.bota.sdk.model.WireValue
|
|
15
|
+
import java.time.Instant
|
|
16
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
17
|
+
import kotlinx.coroutines.awaitCancellation
|
|
18
|
+
import kotlinx.coroutines.flow.Flow
|
|
19
|
+
import kotlinx.coroutines.flow.flow
|
|
20
|
+
import kotlinx.coroutines.test.runTest
|
|
21
|
+
import org.junit.Assert.assertEquals
|
|
22
|
+
import org.junit.Test
|
|
23
|
+
|
|
24
|
+
class BotaDeviceSDKAndroidDevicesTest {
|
|
25
|
+
@Test
|
|
26
|
+
fun unknownPairingStateUsesTheFrozenUnpairedFallback() {
|
|
27
|
+
assertEquals("unpaired", PairingState.Unknown(0xFFu).toBridgeValue())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Test
|
|
31
|
+
fun scanAndConnectionsDelegateToTheAndroidFacade() = runTest {
|
|
32
|
+
val selected = DiscoveredDevice(
|
|
33
|
+
id = "selected",
|
|
34
|
+
name = "Bota Pin",
|
|
35
|
+
deviceType = DeviceType.BotaPin,
|
|
36
|
+
rssi = -42,
|
|
37
|
+
discoveredAt = Instant.ofEpochMilli(1_788_200_000_000),
|
|
38
|
+
)
|
|
39
|
+
val verified = ConnectedDevice(
|
|
40
|
+
id = "selected",
|
|
41
|
+
serialNumber = "EVFXXW67KP",
|
|
42
|
+
deviceType = DeviceType.BotaPin,
|
|
43
|
+
firmwareVersion = "1.0.11",
|
|
44
|
+
isProvisioned = false,
|
|
45
|
+
connectionState = ConnectionState.Connected,
|
|
46
|
+
mtu = 247,
|
|
47
|
+
)
|
|
48
|
+
val client = FakeDeviceClient(selected, verified)
|
|
49
|
+
val devices = BotaDeviceSDKAndroidDevices(client, backgroundScope)
|
|
50
|
+
val received = CompletableDeferred<DiscoveredDevice>()
|
|
51
|
+
|
|
52
|
+
devices.startScan(5_000u, allowDuplicates = true) { received.complete(it) }
|
|
53
|
+
assertEquals("selected", received.await().id)
|
|
54
|
+
val connected = devices.connect(selected)
|
|
55
|
+
val reconnected = devices.reconnect(
|
|
56
|
+
"EVFXXW67KP",
|
|
57
|
+
DeviceReconnectHint(
|
|
58
|
+
scanTimeoutMilliseconds = 7_000u,
|
|
59
|
+
connectionTimeoutMilliseconds = 8_000u,
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
devices.disconnect()
|
|
63
|
+
|
|
64
|
+
assertEquals(listOf(5_000uL to true), client.scanOptions)
|
|
65
|
+
assertEquals(listOf("selected"), client.selectedIds)
|
|
66
|
+
assertEquals(listOf("EVFXXW67KP"), client.reconnectSerials)
|
|
67
|
+
assertEquals(1, client.cancelCount)
|
|
68
|
+
assertEquals(1, client.disconnectCount)
|
|
69
|
+
assertEquals("EVFXXW67KP", connected.serialNumber)
|
|
70
|
+
assertEquals("EVFXXW67KP", reconnected.serialNumber)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
fun scanFailureIsReportedWithoutEscapingTheOwnedTask() = runTest {
|
|
75
|
+
val selected = DiscoveredDevice(
|
|
76
|
+
id = "selected",
|
|
77
|
+
name = "Bota Pin",
|
|
78
|
+
deviceType = DeviceType.BotaPin,
|
|
79
|
+
rssi = -42,
|
|
80
|
+
discoveredAt = Instant.ofEpochMilli(1_788_200_000_000),
|
|
81
|
+
)
|
|
82
|
+
val verified = ConnectedDevice(
|
|
83
|
+
id = "selected",
|
|
84
|
+
serialNumber = "EVFXXW67KP",
|
|
85
|
+
deviceType = DeviceType.BotaPin,
|
|
86
|
+
firmwareVersion = "1.0.11",
|
|
87
|
+
isProvisioned = false,
|
|
88
|
+
connectionState = ConnectionState.Connected,
|
|
89
|
+
mtu = 247,
|
|
90
|
+
)
|
|
91
|
+
val failure = IllegalStateException("scan failed")
|
|
92
|
+
val client = FakeDeviceClient(selected, verified, scanFailure = failure)
|
|
93
|
+
val devices = BotaDeviceSDKAndroidDevices(client, backgroundScope)
|
|
94
|
+
val reported = CompletableDeferred<Throwable>()
|
|
95
|
+
|
|
96
|
+
devices.startScan(
|
|
97
|
+
timeoutMilliseconds = 5_000u,
|
|
98
|
+
allowDuplicates = false,
|
|
99
|
+
onDevice = {},
|
|
100
|
+
onError = { reported.complete(it) },
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assertEquals(failure, reported.await())
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test
|
|
107
|
+
fun statusReadAndSubscriptionDelegateToTheAndroidFacade() = runTest {
|
|
108
|
+
val selected = DiscoveredDevice(id = "selected", name = "Bota Pin", rssi = -42)
|
|
109
|
+
val verified = ConnectedDevice(
|
|
110
|
+
id = "selected",
|
|
111
|
+
serialNumber = "EVFXXW67KP",
|
|
112
|
+
deviceType = DeviceType.BotaPin,
|
|
113
|
+
firmwareVersion = "1.0.11",
|
|
114
|
+
isProvisioned = false,
|
|
115
|
+
connectionState = ConnectionState.Connected,
|
|
116
|
+
mtu = 247,
|
|
117
|
+
)
|
|
118
|
+
val expected = testDeviceStatus()
|
|
119
|
+
val client = FakeDeviceClient(selected, verified, status = expected)
|
|
120
|
+
val devices = BotaDeviceSDKAndroidDevices(client, backgroundScope)
|
|
121
|
+
val received = CompletableDeferred<DeviceStatus>()
|
|
122
|
+
|
|
123
|
+
assertEquals(expected, devices.readStatus())
|
|
124
|
+
devices.startStatusUpdates { received.complete(it) }
|
|
125
|
+
assertEquals(expected, received.await())
|
|
126
|
+
devices.stopStatusUpdates()
|
|
127
|
+
|
|
128
|
+
assertEquals(1, client.statusReadCount)
|
|
129
|
+
assertEquals(1, client.statusCancelCount)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private class FakeDeviceClient(
|
|
133
|
+
private val discovered: DiscoveredDevice,
|
|
134
|
+
private val connected: ConnectedDevice,
|
|
135
|
+
private val scanFailure: Throwable? = null,
|
|
136
|
+
private val status: DeviceStatus = testDeviceStatus(),
|
|
137
|
+
) : BotaDeviceSDKAndroidDeviceClient {
|
|
138
|
+
val scanOptions = mutableListOf<Pair<ULong, Boolean>>()
|
|
139
|
+
val selectedIds = mutableListOf<String>()
|
|
140
|
+
val reconnectSerials = mutableListOf<String>()
|
|
141
|
+
var cancelCount = 0
|
|
142
|
+
var disconnectCount = 0
|
|
143
|
+
var statusReadCount = 0
|
|
144
|
+
var statusCancelCount = 0
|
|
145
|
+
|
|
146
|
+
override suspend fun startScan(
|
|
147
|
+
timeoutMilliseconds: ULong,
|
|
148
|
+
allowDuplicates: Boolean,
|
|
149
|
+
): Flow<DiscoveredDevice> = flow {
|
|
150
|
+
scanOptions += timeoutMilliseconds to allowDuplicates
|
|
151
|
+
scanFailure?.let { throw it }
|
|
152
|
+
emit(discovered)
|
|
153
|
+
awaitCancellation()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
override suspend fun cancelCurrentOperation() {
|
|
157
|
+
cancelCount += 1
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override suspend fun connect(device: DiscoveredDevice): ConnectedDevice {
|
|
161
|
+
selectedIds += device.id
|
|
162
|
+
return connected
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override suspend fun reconnect(serialNumber: String, hint: DeviceReconnectHint): ConnectedDevice {
|
|
166
|
+
reconnectSerials += serialNumber
|
|
167
|
+
return connected
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override suspend fun disconnect() {
|
|
171
|
+
disconnectCount += 1
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
override suspend fun readStatus(): DeviceStatus {
|
|
175
|
+
statusReadCount += 1
|
|
176
|
+
return status
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
override suspend fun statusUpdates(): Flow<DeviceStatus> = flow {
|
|
180
|
+
try {
|
|
181
|
+
emit(status)
|
|
182
|
+
awaitCancellation()
|
|
183
|
+
} finally {
|
|
184
|
+
statusCancelCount += 1
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private fun testDeviceStatus(): DeviceStatus = DeviceStatus(
|
|
191
|
+
batteryLevel = 72,
|
|
192
|
+
batteryMv = 3_842,
|
|
193
|
+
storageTotalMb = 8_192,
|
|
194
|
+
storageUsedMb = 512,
|
|
195
|
+
state = WireValue.Known(DeviceState.Idle),
|
|
196
|
+
pendingRecordings = 2,
|
|
197
|
+
lastTimeSyncAt = Instant.ofEpochSecond(1_788_200_000),
|
|
198
|
+
signalStrength = 4,
|
|
199
|
+
flags = DeviceFlags(
|
|
200
|
+
charging = false,
|
|
201
|
+
lowBattery = false,
|
|
202
|
+
storageFull = false,
|
|
203
|
+
wifiConnected = true,
|
|
204
|
+
lteConnected = false,
|
|
205
|
+
syncActive = false,
|
|
206
|
+
),
|
|
207
|
+
timestamp = 1_788_200_000u,
|
|
208
|
+
lteStatus = WireValue.Known(LteStatus.Off),
|
|
209
|
+
lteSignalQuality = 99,
|
|
210
|
+
wifiStatus = WireValue.Known(WifiRadioStatus.Connected),
|
|
211
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import java.io.File
|
|
4
|
+
import kotlinx.coroutines.CompletableDeferred
|
|
5
|
+
import kotlinx.coroutines.async
|
|
6
|
+
import kotlinx.coroutines.launch
|
|
7
|
+
import kotlinx.coroutines.test.runTest
|
|
8
|
+
import org.junit.Assert.assertEquals
|
|
9
|
+
import org.junit.Assert.assertFalse
|
|
10
|
+
import org.junit.Assert.assertTrue
|
|
11
|
+
import org.junit.Test
|
|
12
|
+
|
|
13
|
+
class BotaDeviceSDKAndroidLifecycleTest {
|
|
14
|
+
@Test
|
|
15
|
+
fun initialStateAndCapabilitiesDescribeAndroidAdapter() = runTest {
|
|
16
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(FakeClient())
|
|
17
|
+
|
|
18
|
+
assertEquals("uninitialized", lifecycle.state())
|
|
19
|
+
assertEquals(
|
|
20
|
+
BotaDeviceSDKAndroidCapabilities(
|
|
21
|
+
backgroundReconnect = false,
|
|
22
|
+
backgroundScan = false,
|
|
23
|
+
bluetooth = true,
|
|
24
|
+
nativeFileTransfer = true,
|
|
25
|
+
platform = "android",
|
|
26
|
+
),
|
|
27
|
+
lifecycle.capabilities(),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Test
|
|
32
|
+
fun configureForwardsExactStorageDirectoryAndBecomesReady() = runTest {
|
|
33
|
+
val client = FakeClient()
|
|
34
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(client)
|
|
35
|
+
val directory = File("/tmp/bota-rn-android-lifecycle")
|
|
36
|
+
|
|
37
|
+
lifecycle.configure(directory)
|
|
38
|
+
|
|
39
|
+
assertEquals(listOf(directory), client.configuredDirectories)
|
|
40
|
+
assertEquals("ready", lifecycle.state())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
fun concurrentConfigureCallsShareOneAndroidConfiguration() = runTest {
|
|
45
|
+
val client = FakeClient(blockConfigure = true)
|
|
46
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(client)
|
|
47
|
+
|
|
48
|
+
val first = async { lifecycle.configure(null) }
|
|
49
|
+
client.configureStarted.await()
|
|
50
|
+
val second = async { lifecycle.configure(null) }
|
|
51
|
+
|
|
52
|
+
assertEquals("initializing", lifecycle.state())
|
|
53
|
+
client.allowConfigure.complete(Unit)
|
|
54
|
+
first.await()
|
|
55
|
+
second.await()
|
|
56
|
+
|
|
57
|
+
assertEquals(1, client.configureCalls)
|
|
58
|
+
assertEquals("ready", lifecycle.state())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Test
|
|
62
|
+
fun configureFailureIsRecoverable() = runTest {
|
|
63
|
+
val client = FakeClient(failConfigure = true)
|
|
64
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(client)
|
|
65
|
+
|
|
66
|
+
val first = runCatching { lifecycle.configure(null) }
|
|
67
|
+
|
|
68
|
+
assertTrue(first.isFailure)
|
|
69
|
+
assertEquals("error", lifecycle.state())
|
|
70
|
+
client.failConfigure = false
|
|
71
|
+
lifecycle.configure(null)
|
|
72
|
+
assertEquals(2, client.configureCalls)
|
|
73
|
+
assertEquals("ready", lifecycle.state())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test
|
|
77
|
+
fun destroyWaitsForConfigurationAndReturnsToUninitialized() = runTest {
|
|
78
|
+
val client = FakeClient(blockConfigure = true)
|
|
79
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(client)
|
|
80
|
+
|
|
81
|
+
val configure = launch { lifecycle.configure(null) }
|
|
82
|
+
client.configureStarted.await()
|
|
83
|
+
val destroy = launch { lifecycle.destroy() }
|
|
84
|
+
|
|
85
|
+
assertFalse(client.destroyed.isCompleted)
|
|
86
|
+
client.allowConfigure.complete(Unit)
|
|
87
|
+
configure.join()
|
|
88
|
+
destroy.join()
|
|
89
|
+
|
|
90
|
+
assertEquals(1, client.destroyCalls)
|
|
91
|
+
assertTrue(client.destroyed.isCompleted)
|
|
92
|
+
assertEquals("uninitialized", lifecycle.state())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun destroyIsIdempotent() = runTest {
|
|
97
|
+
val client = FakeClient()
|
|
98
|
+
val lifecycle = BotaDeviceSDKAndroidLifecycle(client)
|
|
99
|
+
|
|
100
|
+
lifecycle.configure(null)
|
|
101
|
+
lifecycle.destroy()
|
|
102
|
+
lifecycle.destroy()
|
|
103
|
+
|
|
104
|
+
assertEquals(1, client.destroyCalls)
|
|
105
|
+
assertEquals("uninitialized", lifecycle.state())
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private class FakeClient(
|
|
109
|
+
private val blockConfigure: Boolean = false,
|
|
110
|
+
var failConfigure: Boolean = false,
|
|
111
|
+
) : BotaDeviceSDKAndroidClient {
|
|
112
|
+
val allowConfigure = CompletableDeferred<Unit>()
|
|
113
|
+
val configureStarted = CompletableDeferred<Unit>()
|
|
114
|
+
val configuredDirectories = mutableListOf<File?>()
|
|
115
|
+
val destroyed = CompletableDeferred<Unit>()
|
|
116
|
+
var configureCalls = 0
|
|
117
|
+
var destroyCalls = 0
|
|
118
|
+
|
|
119
|
+
override suspend fun configure(storageDirectory: File?) {
|
|
120
|
+
configureCalls += 1
|
|
121
|
+
configuredDirectories += storageDirectory
|
|
122
|
+
configureStarted.complete(Unit)
|
|
123
|
+
if (blockConfigure) allowConfigure.await()
|
|
124
|
+
if (failConfigure) error("configure failed")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
override suspend fun destroy() {
|
|
128
|
+
destroyCalls += 1
|
|
129
|
+
destroyed.complete(Unit)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import dev.bota.sdk.model.ConnectedDevice
|
|
4
|
+
import dev.bota.sdk.model.ConnectionState
|
|
5
|
+
import dev.bota.sdk.model.DeviceLogLine
|
|
6
|
+
import dev.bota.sdk.model.DeviceType
|
|
7
|
+
import kotlinx.coroutines.CoroutineScope
|
|
8
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
9
|
+
import kotlinx.coroutines.awaitCancellation
|
|
10
|
+
import kotlinx.coroutines.flow.Flow
|
|
11
|
+
import kotlinx.coroutines.flow.flow
|
|
12
|
+
import kotlinx.coroutines.test.runCurrent
|
|
13
|
+
import kotlinx.coroutines.test.runTest
|
|
14
|
+
import org.junit.Assert.assertEquals
|
|
15
|
+
import org.junit.Assert.assertTrue
|
|
16
|
+
import org.junit.Test
|
|
17
|
+
|
|
18
|
+
class BotaDeviceSDKAndroidLogsTest {
|
|
19
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
20
|
+
@Test
|
|
21
|
+
fun logStreamEmitsSanitizedLinesAndOwnsStop() = runTest {
|
|
22
|
+
val client = TestAndroidLogClient()
|
|
23
|
+
val logs = BotaDeviceSDKAndroidLogs(client, CoroutineScope(coroutineContext))
|
|
24
|
+
val lines = mutableListOf<DeviceLogLine>()
|
|
25
|
+
|
|
26
|
+
logs.start(
|
|
27
|
+
ConnectedDevice(
|
|
28
|
+
id = "selected",
|
|
29
|
+
serialNumber = "EVFXXW67KP",
|
|
30
|
+
deviceType = DeviceType.BotaPin,
|
|
31
|
+
firmwareVersion = "1.0.11",
|
|
32
|
+
isProvisioned = true,
|
|
33
|
+
connectionState = ConnectionState.Connected,
|
|
34
|
+
mtu = 247,
|
|
35
|
+
),
|
|
36
|
+
onLine = lines::add,
|
|
37
|
+
)
|
|
38
|
+
runCurrent()
|
|
39
|
+
|
|
40
|
+
assertEquals(listOf(DeviceLogLine("boot pass", isBacklog = true)), lines)
|
|
41
|
+
logs.stop()
|
|
42
|
+
assertTrue(client.stopped)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private class TestAndroidLogClient : BotaDeviceSDKAndroidLogClient {
|
|
46
|
+
var stopped = false
|
|
47
|
+
|
|
48
|
+
override fun streamLogs(device: ConnectedDevice): Flow<DeviceLogLine> = flow {
|
|
49
|
+
emit(DeviceLogLine("boot pass", isBacklog = true))
|
|
50
|
+
awaitCancellation()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override suspend fun stop() {
|
|
54
|
+
stopped = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import dev.bota.sdk.FirmwareImage
|
|
4
|
+
import dev.bota.sdk.model.ConnectedDevice
|
|
5
|
+
import dev.bota.sdk.model.ConnectionState
|
|
6
|
+
import dev.bota.sdk.model.DeviceType
|
|
7
|
+
import dev.bota.sdk.model.FirmwareUpdatePhase
|
|
8
|
+
import dev.bota.sdk.model.FirmwareUpdateProgress
|
|
9
|
+
import kotlinx.coroutines.flow.Flow
|
|
10
|
+
import kotlinx.coroutines.flow.flowOf
|
|
11
|
+
import kotlinx.coroutines.test.runTest
|
|
12
|
+
import org.junit.Assert.assertEquals
|
|
13
|
+
import org.junit.Assert.assertTrue
|
|
14
|
+
import org.junit.Test
|
|
15
|
+
|
|
16
|
+
class BotaDeviceSDKAndroidOTATest {
|
|
17
|
+
@Test
|
|
18
|
+
fun firmwareDownloadAndTransferStayNative() = runTest {
|
|
19
|
+
val client = TestAndroidOTAClient()
|
|
20
|
+
val ota = BotaDeviceSDKAndroidOTA(client)
|
|
21
|
+
val progress = mutableListOf<FirmwareUpdateProgress>()
|
|
22
|
+
|
|
23
|
+
ota.updateFirmware(
|
|
24
|
+
device = ConnectedDevice(
|
|
25
|
+
id = "selected",
|
|
26
|
+
serialNumber = "EVFXXW67KP",
|
|
27
|
+
deviceType = DeviceType.BotaPin,
|
|
28
|
+
firmwareVersion = "1.0.11",
|
|
29
|
+
isProvisioned = true,
|
|
30
|
+
connectionState = ConnectionState.Connected,
|
|
31
|
+
mtu = 247,
|
|
32
|
+
),
|
|
33
|
+
version = "1.0.12",
|
|
34
|
+
sizeBytes = 1_024_000u,
|
|
35
|
+
crc32 = 0x1234_5678u,
|
|
36
|
+
url = "https://firmware.bota.dev/update.ufw",
|
|
37
|
+
onProgress = progress::add,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
val image = requireNotNull(client.image)
|
|
41
|
+
assertEquals("1.0.12", image.version)
|
|
42
|
+
assertEquals(1_024_000u, image.sizeBytes)
|
|
43
|
+
assertEquals(0x1234_5678u, image.crc32)
|
|
44
|
+
assertEquals("https://firmware.bota.dev/update.ufw", image.request.url.toString())
|
|
45
|
+
assertEquals(
|
|
46
|
+
listOf(
|
|
47
|
+
FirmwareUpdateProgress(FirmwareUpdatePhase.Downloading, 512_000u, 1_024_000u),
|
|
48
|
+
FirmwareUpdateProgress(FirmwareUpdatePhase.Complete, 1_024_000u, 1_024_000u),
|
|
49
|
+
),
|
|
50
|
+
progress,
|
|
51
|
+
)
|
|
52
|
+
ota.cancelAll()
|
|
53
|
+
assertTrue(client.cancelled)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private class TestAndroidOTAClient : BotaDeviceSDKAndroidOTAClient {
|
|
57
|
+
var image: FirmwareImage? = null
|
|
58
|
+
var cancelled = false
|
|
59
|
+
|
|
60
|
+
override fun updateFirmware(
|
|
61
|
+
device: ConnectedDevice,
|
|
62
|
+
image: FirmwareImage,
|
|
63
|
+
): Flow<FirmwareUpdateProgress> {
|
|
64
|
+
this.image = image
|
|
65
|
+
return flowOf(
|
|
66
|
+
FirmwareUpdateProgress(FirmwareUpdatePhase.Downloading, 512_000u, 1_024_000u),
|
|
67
|
+
FirmwareUpdateProgress(FirmwareUpdatePhase.Complete, 1_024_000u, 1_024_000u),
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override suspend fun cancelCurrentOperation() {
|
|
72
|
+
cancelled = true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package dev.bota.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import java.io.File
|
|
4
|
+
import java.nio.file.Files
|
|
5
|
+
import java.util.Collections
|
|
6
|
+
import kotlinx.coroutines.test.runTest
|
|
7
|
+
import okhttp3.OkHttpClient
|
|
8
|
+
import okhttp3.Protocol
|
|
9
|
+
import okhttp3.Response
|
|
10
|
+
import okhttp3.ResponseBody.Companion.toResponseBody
|
|
11
|
+
import org.junit.Assert.assertEquals
|
|
12
|
+
import org.junit.Assert.assertFalse
|
|
13
|
+
import org.junit.Test
|
|
14
|
+
|
|
15
|
+
class BotaDeviceSDKAndroidRecordingUploadsTest {
|
|
16
|
+
@Test
|
|
17
|
+
fun plaintextUploadUsesPutCompletesAndDeletesNativeFile() = runTest {
|
|
18
|
+
val fixture = UploadFixture()
|
|
19
|
+
val requests = Collections.synchronizedList(mutableListOf<CapturedRequest>())
|
|
20
|
+
val uploads = BotaDeviceSDKAndroidRecordingUploads(
|
|
21
|
+
fixture.queueFile,
|
|
22
|
+
stubbedClient(requests),
|
|
23
|
+
)
|
|
24
|
+
val progress = mutableListOf<BotaRecordingUploadProgress>()
|
|
25
|
+
|
|
26
|
+
uploads.upload(
|
|
27
|
+
fixture.request(
|
|
28
|
+
taskId = "task-1",
|
|
29
|
+
completeUrl = "https://api.example/complete",
|
|
30
|
+
uploadToken = "up-token",
|
|
31
|
+
),
|
|
32
|
+
progress::add,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assertFalse(fixture.recordingFile.exists())
|
|
36
|
+
assertEquals(
|
|
37
|
+
listOf(
|
|
38
|
+
CapturedRequest("PUT", "https://s3.example/recording", null),
|
|
39
|
+
CapturedRequest(
|
|
40
|
+
"POST",
|
|
41
|
+
"https://api.example/complete",
|
|
42
|
+
"Bearer up-token",
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
requests,
|
|
46
|
+
)
|
|
47
|
+
assertEquals(listOf(0L, 4L), progress.map { it.completedBytes })
|
|
48
|
+
assertEquals(listOf(4L, 4L), progress.map { it.totalBytes })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Test
|
|
52
|
+
fun encryptedUploadUsesRelayAndSkipsCompletion() = runTest {
|
|
53
|
+
val fixture = UploadFixture()
|
|
54
|
+
val requests = Collections.synchronizedList(mutableListOf<CapturedRequest>())
|
|
55
|
+
val uploads = BotaDeviceSDKAndroidRecordingUploads(
|
|
56
|
+
fixture.queueFile,
|
|
57
|
+
stubbedClient(requests),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
uploads.upload(
|
|
61
|
+
fixture.request(
|
|
62
|
+
taskId = "task-2",
|
|
63
|
+
completeUrl = "https://api.example/unused",
|
|
64
|
+
uploadToken = "up-token",
|
|
65
|
+
relayUrl = "https://api.example/upload-relay",
|
|
66
|
+
relayBearerToken = "device-token",
|
|
67
|
+
),
|
|
68
|
+
) {}
|
|
69
|
+
|
|
70
|
+
assertEquals(
|
|
71
|
+
listOf(
|
|
72
|
+
CapturedRequest(
|
|
73
|
+
"POST",
|
|
74
|
+
"https://api.example/upload-relay",
|
|
75
|
+
"Bearer device-token",
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
requests,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Test
|
|
83
|
+
fun queueSaveAtomicallyReplacesExistingMetadata() = runTest {
|
|
84
|
+
val fixture = UploadFixture()
|
|
85
|
+
val uploads = BotaDeviceSDKAndroidRecordingUploads(
|
|
86
|
+
fixture.queueFile,
|
|
87
|
+
stubbedClient(mutableListOf()),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
uploads.saveQueue("[{\"id\":\"first\"}]")
|
|
91
|
+
uploads.saveQueue("[{\"id\":\"second\"}]")
|
|
92
|
+
|
|
93
|
+
assertEquals("[{\"id\":\"second\"}]", uploads.loadQueue())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private fun stubbedClient(requests: MutableList<CapturedRequest>): OkHttpClient =
|
|
97
|
+
OkHttpClient.Builder()
|
|
98
|
+
.addInterceptor { chain ->
|
|
99
|
+
val request = chain.request()
|
|
100
|
+
requests += CapturedRequest(
|
|
101
|
+
request.method,
|
|
102
|
+
request.url.toString(),
|
|
103
|
+
request.header("Authorization"),
|
|
104
|
+
)
|
|
105
|
+
Response.Builder()
|
|
106
|
+
.request(request)
|
|
107
|
+
.protocol(Protocol.HTTP_1_1)
|
|
108
|
+
.code(200)
|
|
109
|
+
.message("OK")
|
|
110
|
+
.body("".toResponseBody())
|
|
111
|
+
.build()
|
|
112
|
+
}
|
|
113
|
+
.build()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private data class CapturedRequest(
|
|
117
|
+
val method: String,
|
|
118
|
+
val url: String,
|
|
119
|
+
val authorization: String?,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
private class UploadFixture {
|
|
123
|
+
private val directory = Files.createTempDirectory("bota-recording-upload").toFile()
|
|
124
|
+
val recordingFile = File(directory, "recording.bin").apply {
|
|
125
|
+
writeBytes(byteArrayOf(1, 2, 3, 4))
|
|
126
|
+
}
|
|
127
|
+
val queueFile = File(directory, "queue.json")
|
|
128
|
+
|
|
129
|
+
fun request(
|
|
130
|
+
taskId: String,
|
|
131
|
+
completeUrl: String? = null,
|
|
132
|
+
uploadToken: String? = null,
|
|
133
|
+
relayUrl: String? = null,
|
|
134
|
+
relayBearerToken: String? = null,
|
|
135
|
+
): BotaRecordingUploadRequest = BotaRecordingUploadRequest(
|
|
136
|
+
taskId = taskId,
|
|
137
|
+
recordingId = "rec-1",
|
|
138
|
+
localPath = recordingFile.path,
|
|
139
|
+
uploadUrl = "https://s3.example/recording",
|
|
140
|
+
uploadToken = uploadToken,
|
|
141
|
+
completeUrl = completeUrl,
|
|
142
|
+
contentType = "audio/ogg",
|
|
143
|
+
contentSha256 = "abc123",
|
|
144
|
+
relayUrl = relayUrl,
|
|
145
|
+
relayBearerToken = relayBearerToken,
|
|
146
|
+
)
|
|
147
|
+
}
|