@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,55 @@
1
+ require "json"
2
+ require_relative "scripts/bota_device_sdk_spm_workaround"
3
+
4
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
5
+ apple = package.fetch("bota").fetch("apple")
6
+ version = package.fetch("version")
7
+
8
+ Pod::Spec.new do |spec|
9
+ spec.name = apple.fetch("podName")
10
+ spec.module_name = apple.fetch("moduleName")
11
+ spec.version = version
12
+ spec.summary = package.fetch("description")
13
+ spec.homepage = "https://docs.bota.dev"
14
+ spec.license = { type: package.fetch("license") }
15
+ spec.author = package.fetch("author")
16
+ spec.source = {
17
+ git: package.fetch("repository").fetch("url"),
18
+ tag: "v#{version}",
19
+ }
20
+ spec.platforms = { ios: apple.fetch("deploymentTarget") }
21
+ spec.cocoapods_version = ">= #{apple.fetch("cocoapodsVersion")}"
22
+ spec.swift_version = apple.fetch("swiftVersion")
23
+ spec.source_files = "ios/**/*.{h,m,mm,swift}"
24
+ spec.pod_target_xcconfig = {
25
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
26
+ "DEFINES_MODULE" => "YES",
27
+ "SWIFT_STRICT_CONCURRENCY" => "complete",
28
+ "SWIFT_TREAT_WARNINGS_AS_ERRORS" => "YES",
29
+ }
30
+
31
+ if respond_to?(:install_modules_dependencies, true)
32
+ install_modules_dependencies(spec)
33
+ else
34
+ spec.dependency "React-Core"
35
+ spec.dependency "React-Codegen"
36
+ spec.dependency "ReactCommon/turbomodule/core"
37
+ end
38
+
39
+ if respond_to?(:spm_dependency, true)
40
+ BotaDeviceSDKSPMWorkaround.install!(SPM)
41
+ local_path = ENV[apple.fetch("localPackagePathEnvironment")]
42
+ source = local_path.nil? || local_path.empty? ? apple.fetch("packageUrl") : File.expand_path(local_path)
43
+ requirement = local_path.nil? || local_path.empty? ? {
44
+ kind: apple.fetch("packageRequirement"),
45
+ version: version,
46
+ } : {}
47
+
48
+ spm_dependency(
49
+ spec,
50
+ url: source,
51
+ requirement: requirement,
52
+ products: [apple.fetch("packageProduct")],
53
+ )
54
+ end
55
+ end
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bota
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Bota SDK for React Native
2
+
3
+ Bluetooth device SDK for Bota Pin and Bota Note applications. The 2.x candidate
4
+ preserves the public TypeScript API of `@bota.dev/react-native-sdk@0.0.65`
5
+ while moving Bluetooth workflows and recording, streaming, and firmware bytes
6
+ into the native Apple and Android SDKs.
7
+
8
+ ## Requirements
9
+
10
+ - React Native `0.86.3` or newer with the New Architecture enabled
11
+ - iOS `15.1` or newer
12
+ - Android API `26` or newer
13
+ - Node.js `22` or newer for installation and application builds
14
+
15
+ ## Install
16
+
17
+ Prepared instructions for `2.0.0-beta.0`; registry publication is pending.
18
+ Remove `@bota.dev/react-native-sdk` before adding the replacement; do not
19
+ co-install both. Production maintenance 0.0.x consumers need not migrate.
20
+
21
+ ```bash
22
+ npm install --save-exact @bota.dev/react-native-app-sdk@2.0.0-beta.0
23
+ npx pod-install
24
+ ```
25
+
26
+ Rebuild the native iOS and Android applications after installation. An Expo Go
27
+ runtime cannot load this native module; use a development or production build.
28
+
29
+ Expo prebuild and EAS projects must set the Android minimum SDK explicitly. Add
30
+ the Expo-compatible `expo-build-properties` package and configure it in the app
31
+ config:
32
+
33
+ ```ts
34
+ plugins: [
35
+ [
36
+ 'expo-build-properties',
37
+ {
38
+ android: {
39
+ minSdkVersion: 26,
40
+ },
41
+ },
42
+ ],
43
+ ],
44
+ ```
45
+
46
+ ## Configure
47
+
48
+ ```ts
49
+ import { BotaClient } from '@bota.dev/react-native-app-sdk';
50
+
51
+ await BotaClient.configure({
52
+ environment: 'production',
53
+ });
54
+
55
+ await BotaClient.waitForBluetooth();
56
+ ```
57
+
58
+ The `BotaClient.devices`, `BotaClient.recordings`, and `BotaClient.ota`
59
+ managers become available after configuration completes.
60
+
61
+ ## Encrypted upload v2
62
+
63
+ The target encrypted-upload-v2 runtime is an additive `BotaDeviceSDK` API; it
64
+ does not change the legacy `BotaClient` recording providers or events:
65
+
66
+ ```ts
67
+ import { BotaDeviceSDK } from '@bota.dev/react-native-app-sdk';
68
+
69
+ await BotaDeviceSDK.recordings.syncEncryptedRecordingV2(
70
+ device,
71
+ recording,
72
+ async (request) => selectNativeEncryptedUploadV2(request),
73
+ (progress) => updateUploadProgress(progress),
74
+ );
75
+ ```
76
+
77
+ The provider receives a fresh native capability snapshot, immutable recording
78
+ identity, and optional versioned checkpoint. It returns only the upload
79
+ session, owner revision, security policy, and an opaque native material
80
+ registration ID. Before returning that ID, application-native Swift or Kotlin
81
+ code must register the complete material once with
82
+ `BotaDeviceSDKEncryptedUploadV2Materials.register(...)`. Authorization,
83
+ manifest, receipt, staging URL/header/credentials, native file bytes/paths,
84
+ keys, nonces, tags, ciphertext, and plaintext must remain in that native
85
+ adapter and never cross JavaScript or Codegen. Selection is explicit and never
86
+ downgrades to a legacy profile.
87
+
88
+ Runtime compatibility metadata remains disabled until the separate firmware,
89
+ release, and hardware gates pass; this API does not advertise device support.
90
+ Its target-facade tests and the maintenance SDK `0.0.67` runtime tests are
91
+ cross-referenced by the same canonical v2 workflow evidence in CI and tagged
92
+ release verification. The frozen public compatibility surface remains `0.0.65`.
93
+
94
+ ## Documentation
95
+
96
+ See [docs.bota.dev](https://docs.bota.dev) for pairing, provisioning,
97
+ recording transfer, WiFi, OTA, and device-management guides.
98
+
99
+ ## License
100
+
101
+ MIT
@@ -0,0 +1,76 @@
1
+ import groovy.json.JsonSlurper
2
+
3
+ def packageJson = new JsonSlurper().parse(file("../package.json"))
4
+ def androidMetadata = packageJson.bota.android
5
+ def packageVersion = packageJson.version
6
+
7
+ buildscript {
8
+ ext.BotaDeviceSDK = [
9
+ kotlinVersion: "2.1.20",
10
+ minSdkVersion: 26,
11
+ compileSdkVersion: 36,
12
+ ]
13
+
14
+ ext.getExtOrDefault = { prop ->
15
+ if (rootProject.ext.has(prop)) {
16
+ return rootProject.ext.get(prop)
17
+ }
18
+ return BotaDeviceSDK[prop]
19
+ }
20
+
21
+ repositories {
22
+ google()
23
+ mavenCentral()
24
+ }
25
+
26
+ dependencies {
27
+ classpath "com.android.tools.build:gradle:8.13.2"
28
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
29
+ }
30
+ }
31
+
32
+ apply plugin: "com.android.library"
33
+ apply plugin: "org.jetbrains.kotlin.android"
34
+ apply plugin: "com.facebook.react"
35
+
36
+ react {
37
+ root = file("..")
38
+ reactNativeDir = file("../node_modules/react-native")
39
+ codegenDir = file("../node_modules/@react-native/codegen")
40
+ jsRootDir = file("../src")
41
+ libraryName = "BotaDeviceSDKSpec"
42
+ codegenJavaPackageName = "dev.bota.sdk.reactnative"
43
+ }
44
+
45
+ android {
46
+ namespace "dev.bota.sdk.reactnative"
47
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
48
+
49
+ defaultConfig {
50
+ minSdkVersion getExtOrDefault("minSdkVersion")
51
+ }
52
+
53
+ compileOptions {
54
+ sourceCompatibility JavaVersion.VERSION_17
55
+ targetCompatibility JavaVersion.VERSION_17
56
+ }
57
+
58
+ kotlinOptions {
59
+ jvmTarget = "17"
60
+ allWarningsAsErrors = true
61
+ }
62
+
63
+ lint {
64
+ abortOnError = true
65
+ checkReleaseBuilds = true
66
+ warningsAsErrors = true
67
+ }
68
+ }
69
+
70
+ dependencies {
71
+ implementation "com.facebook.react:react-android"
72
+ implementation "${androidMetadata.mavenCoordinate}:${packageVersion}"
73
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${androidMetadata.coroutinesVersion}"
74
+ testImplementation "junit:junit:4.13.2"
75
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:${androidMetadata.coroutinesVersion}"
76
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,172 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import dev.bota.sdk.BotaDeviceClient
4
+ import dev.bota.sdk.DeviceReconnectHint
5
+ import dev.bota.sdk.model.ConnectedDevice
6
+ import dev.bota.sdk.model.DeviceStatus
7
+ import dev.bota.sdk.model.DiscoveredDevice
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 BotaDeviceSDKAndroidDeviceClient {
20
+ suspend fun startScan(timeoutMilliseconds: ULong, allowDuplicates: Boolean): Flow<DiscoveredDevice>
21
+
22
+ suspend fun cancelCurrentOperation()
23
+
24
+ suspend fun connect(device: DiscoveredDevice): ConnectedDevice
25
+
26
+ suspend fun reconnect(serialNumber: String, hint: DeviceReconnectHint): ConnectedDevice
27
+
28
+ suspend fun disconnect()
29
+
30
+ suspend fun readStatus(): DeviceStatus
31
+
32
+ suspend fun statusUpdates(): Flow<DeviceStatus>
33
+ }
34
+
35
+ internal class BotaDeviceSDKSharedAndroidDeviceClient(
36
+ private val client: BotaDeviceClient = BotaDeviceClient.shared,
37
+ ) : BotaDeviceSDKAndroidDeviceClient {
38
+ override suspend fun startScan(
39
+ timeoutMilliseconds: ULong,
40
+ allowDuplicates: Boolean,
41
+ ): Flow<DiscoveredDevice> = client.devices.startScan(timeoutMilliseconds, allowDuplicates)
42
+
43
+ override suspend fun cancelCurrentOperation() {
44
+ client.devices.cancelCurrentOperation()
45
+ }
46
+
47
+ override suspend fun connect(device: DiscoveredDevice): ConnectedDevice = client.devices.connect(device)
48
+
49
+ override suspend fun reconnect(
50
+ serialNumber: String,
51
+ hint: DeviceReconnectHint,
52
+ ): ConnectedDevice = client.devices.reconnect(serialNumber, hint)
53
+
54
+ override suspend fun disconnect() {
55
+ client.devices.disconnect()
56
+ }
57
+
58
+ override suspend fun readStatus(): DeviceStatus = client.devices.readStatus()
59
+
60
+ override suspend fun statusUpdates(): Flow<DeviceStatus> = client.devices.statusUpdates()
61
+ }
62
+
63
+ internal class BotaDeviceSDKAndroidDevices(
64
+ private val client: BotaDeviceSDKAndroidDeviceClient,
65
+ private val scope: CoroutineScope,
66
+ ) {
67
+ private val operations = Mutex()
68
+ private val scanLock = Any()
69
+ private val statusLock = Any()
70
+ private var activeScan: Job? = null
71
+ private var activeStatusUpdates: Job? = null
72
+
73
+ suspend fun startScan(
74
+ timeoutMilliseconds: ULong,
75
+ allowDuplicates: Boolean,
76
+ onError: (Throwable) -> Unit = {},
77
+ onDevice: (DiscoveredDevice) -> Unit,
78
+ ) = operations.withLock {
79
+ stopScanOwned()
80
+ val stream = client.startScan(timeoutMilliseconds, allowDuplicates)
81
+ lateinit var task: Job
82
+ task = scope.launch(start = CoroutineStart.LAZY) {
83
+ try {
84
+ stream.collect(onDevice)
85
+ } catch (_: CancellationException) {
86
+ // Explicit stop is not a scan failure.
87
+ } catch (error: Throwable) {
88
+ onError(error)
89
+ } finally {
90
+ synchronized(scanLock) {
91
+ if (activeScan === task) activeScan = null
92
+ }
93
+ }
94
+ }
95
+ synchronized(scanLock) { activeScan = task }
96
+ task.start()
97
+ }
98
+
99
+ suspend fun stopScan() = operations.withLock {
100
+ stopScanOwned()
101
+ }
102
+
103
+ suspend fun connect(device: DiscoveredDevice): ConnectedDevice = operations.withLock {
104
+ stopScanOwned()
105
+ stopStatusUpdatesOwned()
106
+ client.connect(device)
107
+ }
108
+
109
+ suspend fun reconnect(serialNumber: String, hint: DeviceReconnectHint): ConnectedDevice = operations.withLock {
110
+ stopScanOwned()
111
+ stopStatusUpdatesOwned()
112
+ client.reconnect(serialNumber, hint)
113
+ }
114
+
115
+ suspend fun disconnect() = operations.withLock {
116
+ stopScanOwned()
117
+ stopStatusUpdatesOwned()
118
+ client.disconnect()
119
+ }
120
+
121
+ suspend fun readStatus(): DeviceStatus = operations.withLock {
122
+ client.readStatus()
123
+ }
124
+
125
+ suspend fun startStatusUpdates(
126
+ onError: (Throwable) -> Unit = {},
127
+ onStatus: (DeviceStatus) -> Unit,
128
+ ) = operations.withLock {
129
+ stopStatusUpdatesOwned()
130
+ val stream = client.statusUpdates()
131
+ lateinit var task: Job
132
+ task = scope.launch(start = CoroutineStart.LAZY) {
133
+ try {
134
+ stream.collect(onStatus)
135
+ } catch (_: CancellationException) {
136
+ // Explicit stop is not a status subscription failure.
137
+ } catch (error: Throwable) {
138
+ onError(error)
139
+ } finally {
140
+ synchronized(statusLock) {
141
+ if (activeStatusUpdates === task) activeStatusUpdates = null
142
+ }
143
+ }
144
+ }
145
+ synchronized(statusLock) { activeStatusUpdates = task }
146
+ task.start()
147
+ }
148
+
149
+ suspend fun stopStatusUpdates() = operations.withLock {
150
+ stopStatusUpdatesOwned()
151
+ }
152
+
153
+ suspend fun stopAll() = operations.withLock {
154
+ stopScanOwned()
155
+ stopStatusUpdatesOwned()
156
+ }
157
+
158
+ private suspend fun stopScanOwned() {
159
+ val scan = synchronized(scanLock) {
160
+ activeScan.also { activeScan = null }
161
+ } ?: return
162
+ scan.cancelAndJoin()
163
+ runCatching { client.cancelCurrentOperation() }
164
+ }
165
+
166
+ private suspend fun stopStatusUpdatesOwned() {
167
+ val updates = synchronized(statusLock) {
168
+ activeStatusUpdates.also { activeStatusUpdates = null }
169
+ } ?: return
170
+ updates.cancelAndJoin()
171
+ }
172
+ }
@@ -0,0 +1,100 @@
1
+ package dev.bota.sdk.reactnative
2
+
3
+ import android.content.Context
4
+ import dev.bota.sdk.BotaConfiguration
5
+ import dev.bota.sdk.BotaDeviceClient
6
+ import java.io.File
7
+ import kotlinx.coroutines.sync.Mutex
8
+ import kotlinx.coroutines.sync.withLock
9
+
10
+ internal interface BotaDeviceSDKAndroidClient {
11
+ suspend fun configure(storageDirectory: File?)
12
+
13
+ suspend fun destroy()
14
+ }
15
+
16
+ internal class BotaDeviceSDKSharedAndroidClient(
17
+ context: Context,
18
+ private val client: BotaDeviceClient = BotaDeviceClient.shared,
19
+ ) : BotaDeviceSDKAndroidClient {
20
+ private val applicationContext = context.applicationContext
21
+
22
+ override suspend fun configure(storageDirectory: File?) {
23
+ client.configure(
24
+ BotaConfiguration(
25
+ context = applicationContext,
26
+ storageDirectory = storageDirectory,
27
+ ),
28
+ )
29
+ }
30
+
31
+ override suspend fun destroy() {
32
+ client.destroy()
33
+ }
34
+ }
35
+
36
+ internal data class BotaDeviceSDKAndroidCapabilities(
37
+ val backgroundReconnect: Boolean,
38
+ val backgroundScan: Boolean,
39
+ val bluetooth: Boolean,
40
+ val nativeFileTransfer: Boolean,
41
+ val platform: String,
42
+ ) {
43
+ companion object {
44
+ val current = BotaDeviceSDKAndroidCapabilities(
45
+ backgroundReconnect = false,
46
+ backgroundScan = false,
47
+ bluetooth = true,
48
+ nativeFileTransfer = true,
49
+ platform = "android",
50
+ )
51
+ }
52
+ }
53
+
54
+ internal class BotaDeviceSDKAndroidLifecycle(
55
+ private val client: BotaDeviceSDKAndroidClient,
56
+ ) {
57
+ private enum class Phase(val externalName: String) {
58
+ UNINITIALIZED("uninitialized"),
59
+ INITIALIZING("initializing"),
60
+ READY("ready"),
61
+ ERROR("error"),
62
+ DESTROYING("uninitialized"),
63
+ }
64
+
65
+ private val lifecycle = Mutex()
66
+
67
+ @Volatile
68
+ private var phase = Phase.UNINITIALIZED
69
+
70
+ suspend fun configure(storageDirectory: File?) {
71
+ lifecycle.withLock {
72
+ if (phase == Phase.READY) return
73
+ phase = Phase.INITIALIZING
74
+ try {
75
+ client.configure(storageDirectory)
76
+ phase = Phase.READY
77
+ } catch (error: Throwable) {
78
+ phase = Phase.ERROR
79
+ throw error
80
+ }
81
+ }
82
+ }
83
+
84
+ suspend fun destroy() {
85
+ lifecycle.withLock {
86
+ if (phase == Phase.UNINITIALIZED) return
87
+ phase = Phase.DESTROYING
88
+ try {
89
+ client.destroy()
90
+ } finally {
91
+ phase = Phase.UNINITIALIZED
92
+ }
93
+ }
94
+ }
95
+
96
+ fun state(): String = phase.externalName
97
+
98
+ fun capabilities(): BotaDeviceSDKAndroidCapabilities =
99
+ BotaDeviceSDKAndroidCapabilities.current
100
+ }
@@ -0,0 +1,82 @@
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.DeviceLogLine
6
+ import kotlinx.coroutines.CancellationException
7
+ import kotlinx.coroutines.CoroutineScope
8
+ import kotlinx.coroutines.CoroutineStart
9
+ import kotlinx.coroutines.Job
10
+ import kotlinx.coroutines.cancelAndJoin
11
+ import kotlinx.coroutines.flow.Flow
12
+ import kotlinx.coroutines.flow.collect
13
+ import kotlinx.coroutines.launch
14
+ import kotlinx.coroutines.sync.Mutex
15
+ import kotlinx.coroutines.sync.withLock
16
+
17
+ internal interface BotaDeviceSDKAndroidLogClient {
18
+ fun streamLogs(device: ConnectedDevice): Flow<DeviceLogLine>
19
+
20
+ suspend fun stop()
21
+ }
22
+
23
+ internal class BotaDeviceSDKSharedAndroidLogClient(
24
+ private val client: BotaDeviceClient = BotaDeviceClient.shared,
25
+ ) : BotaDeviceSDKAndroidLogClient {
26
+ override fun streamLogs(device: ConnectedDevice): Flow<DeviceLogLine> =
27
+ client.logs.streamLogs(device)
28
+
29
+ override suspend fun stop() {
30
+ client.logs.stop()
31
+ }
32
+ }
33
+
34
+ internal class BotaDeviceSDKAndroidLogs(
35
+ private val client: BotaDeviceSDKAndroidLogClient,
36
+ private val scope: CoroutineScope,
37
+ ) {
38
+ private val operations = Mutex()
39
+ private val streamLock = Any()
40
+ private var activeStream: Job? = null
41
+
42
+ suspend fun start(
43
+ device: ConnectedDevice,
44
+ onError: (Throwable) -> Unit = {},
45
+ onLine: (DeviceLogLine) -> Unit,
46
+ ) = operations.withLock {
47
+ stopOwned()
48
+ val stream = client.streamLogs(device)
49
+ lateinit var task: Job
50
+ task = scope.launch(start = CoroutineStart.LAZY) {
51
+ try {
52
+ stream.collect(onLine)
53
+ } catch (_: CancellationException) {
54
+ // Explicit stop is not a log-stream failure.
55
+ } catch (error: Throwable) {
56
+ onError(error)
57
+ } finally {
58
+ synchronized(streamLock) {
59
+ if (activeStream === task) activeStream = null
60
+ }
61
+ }
62
+ }
63
+ synchronized(streamLock) { activeStream = task }
64
+ task.start()
65
+ }
66
+
67
+ suspend fun stop() = operations.withLock {
68
+ stopOwned()
69
+ }
70
+
71
+ suspend fun cancelAll() {
72
+ runCatching { stop() }
73
+ }
74
+
75
+ private suspend fun stopOwned() {
76
+ val stream = synchronized(streamLock) {
77
+ activeStream.also { activeStream = null }
78
+ } ?: return
79
+ stream.cancelAndJoin()
80
+ client.stop()
81
+ }
82
+ }