@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,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
|
+
}
|