@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
package/src/client.ts
ADDED
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
NativeCapabilities,
|
|
3
|
+
NativeConnectedDevice,
|
|
4
|
+
NativeConfiguration,
|
|
5
|
+
NativeDeviceStatus,
|
|
6
|
+
NativeDeviceConnectionSettings,
|
|
7
|
+
NativeDeviceRecording,
|
|
8
|
+
NativeDiscoveredDevice,
|
|
9
|
+
NativeEncryptedUploadV2Capability,
|
|
10
|
+
NativeEncryptedUploadV2Checkpoint,
|
|
11
|
+
NativeEncryptedUploadV2Progress,
|
|
12
|
+
NativeEncryptedUploadV2Recording,
|
|
13
|
+
NativeFactoryResetCompletion,
|
|
14
|
+
NativeFactoryResetGrantRequest,
|
|
15
|
+
NativeFirmwareUpdateProgress,
|
|
16
|
+
NativeRecordingControlResult,
|
|
17
|
+
NativeRecordingState,
|
|
18
|
+
NativeRecordingTransferProgress,
|
|
19
|
+
NativeRecordingUploadRequest,
|
|
20
|
+
NativeStreamingChunkDestinationRequest,
|
|
21
|
+
NativeStreamingFinalizeRequest,
|
|
22
|
+
NativeStreamingStartRequest,
|
|
23
|
+
NativeStreamingUploadDestination,
|
|
24
|
+
NativeProvisioningMaterialRequest,
|
|
25
|
+
NativeUploadOwnershipResult,
|
|
26
|
+
NativeWiFiConfigResult,
|
|
27
|
+
NativeWiFiStatusInfo,
|
|
28
|
+
NativeDeviceWiFiScanResult,
|
|
29
|
+
Spec,
|
|
30
|
+
} from './specs/NativeBotaDeviceSDK';
|
|
31
|
+
import type {
|
|
32
|
+
ConnectedDevice,
|
|
33
|
+
ConnectionType,
|
|
34
|
+
ConnectionState,
|
|
35
|
+
DeviceConnectionSettings,
|
|
36
|
+
DeviceState,
|
|
37
|
+
DeviceStatus,
|
|
38
|
+
DeviceType,
|
|
39
|
+
DeviceLogEvent,
|
|
40
|
+
DiscoveredDevice,
|
|
41
|
+
PairingState,
|
|
42
|
+
ReconnectOptions,
|
|
43
|
+
ScanOptions,
|
|
44
|
+
LteStatus,
|
|
45
|
+
WiFiConfigGrant,
|
|
46
|
+
WiFiConfigResult,
|
|
47
|
+
WiFiCredentials,
|
|
48
|
+
WiFiStatus,
|
|
49
|
+
WiFiStatusInfo,
|
|
50
|
+
BleFactoryResetResultPersister,
|
|
51
|
+
DeviceWiFiScanResult,
|
|
52
|
+
Environment,
|
|
53
|
+
RecordingState,
|
|
54
|
+
WifiStatus,
|
|
55
|
+
} from './models/Device';
|
|
56
|
+
import { reportCompatibilityDisconnection } from './compatibility/runtime';
|
|
57
|
+
import type {
|
|
58
|
+
AudioCodec,
|
|
59
|
+
DeviceRecording,
|
|
60
|
+
UploadTask,
|
|
61
|
+
} from './models/Recording';
|
|
62
|
+
|
|
63
|
+
export type BotaLogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
64
|
+
|
|
65
|
+
export type BotaDeviceSDKConfiguration = {
|
|
66
|
+
applicationSupportDirectory?: string;
|
|
67
|
+
logLevel?: BotaLogLevel;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type BotaDeviceSDKState =
|
|
71
|
+
| 'uninitialized'
|
|
72
|
+
| 'initializing'
|
|
73
|
+
| 'ready'
|
|
74
|
+
| 'error';
|
|
75
|
+
|
|
76
|
+
export type BotaDeviceSDKCapabilities = NativeCapabilities;
|
|
77
|
+
|
|
78
|
+
export type BotaEventSubscription = {
|
|
79
|
+
remove(): void;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type BotaAsyncEventSubscription = {
|
|
83
|
+
remove(): Promise<void>;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type BotaProvisioningMaterialRequest = Omit<
|
|
87
|
+
NativeProvisioningMaterialRequest,
|
|
88
|
+
'requestId'
|
|
89
|
+
>;
|
|
90
|
+
|
|
91
|
+
export type BotaProvisioningMaterial = {
|
|
92
|
+
apiEndpoint: string;
|
|
93
|
+
deviceToken: string;
|
|
94
|
+
mtu: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type BotaProvisioningMaterialProvider = (
|
|
98
|
+
request: BotaProvisioningMaterialRequest
|
|
99
|
+
) => Promise<BotaProvisioningMaterial>;
|
|
100
|
+
|
|
101
|
+
export type BotaDeviceSDKProvisioningClient = {
|
|
102
|
+
provision(
|
|
103
|
+
device: ConnectedDevice,
|
|
104
|
+
provider: BotaProvisioningMaterialProvider
|
|
105
|
+
): Promise<void>;
|
|
106
|
+
deprovision(
|
|
107
|
+
device: ConnectedDevice,
|
|
108
|
+
grantBlob: string
|
|
109
|
+
): Promise<BotaDeprovisionResult>;
|
|
110
|
+
readConnectionSettings(device: ConnectedDevice): Promise<DeviceConnectionSettings>;
|
|
111
|
+
writeConnectionSettings(
|
|
112
|
+
device: ConnectedDevice,
|
|
113
|
+
settings: DeviceConnectionSettings
|
|
114
|
+
): Promise<void>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type BotaDeprovisionResult = {
|
|
118
|
+
success: boolean;
|
|
119
|
+
error?: string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export type BotaDeviceSDKControlClient = {
|
|
123
|
+
isProvisioned(device: ConnectedDevice): Promise<boolean>;
|
|
124
|
+
readPublicKey(device: ConnectedDevice): Promise<string | null>;
|
|
125
|
+
readAuthNonce(device: ConnectedDevice): Promise<string | null>;
|
|
126
|
+
setApiEndpoint(
|
|
127
|
+
device: ConnectedDevice,
|
|
128
|
+
environment: Environment
|
|
129
|
+
): Promise<void>;
|
|
130
|
+
deliverCertificate(
|
|
131
|
+
device: ConnectedDevice,
|
|
132
|
+
certificatePem: string,
|
|
133
|
+
privateKeyPem: string
|
|
134
|
+
): Promise<void>;
|
|
135
|
+
deliverBackendPublicKey(
|
|
136
|
+
device: ConnectedDevice,
|
|
137
|
+
publicKey: Uint8Array
|
|
138
|
+
): Promise<void>;
|
|
139
|
+
writeGrant(device: ConnectedDevice, grantBlob: string): Promise<void>;
|
|
140
|
+
syncTime(device: ConnectedDevice): Promise<void>;
|
|
141
|
+
requestStartRecording(
|
|
142
|
+
device: ConnectedDevice,
|
|
143
|
+
grantBlob: string
|
|
144
|
+
): Promise<BotaRecordingControlResult>;
|
|
145
|
+
requestStopRecording(
|
|
146
|
+
device: ConnectedDevice,
|
|
147
|
+
grantBlob: string
|
|
148
|
+
): Promise<BotaRecordingControlResult>;
|
|
149
|
+
readRecordingState(device: ConnectedDevice): Promise<RecordingState>;
|
|
150
|
+
subscribeToRecordingState(
|
|
151
|
+
device: ConnectedDevice,
|
|
152
|
+
onState: (state: RecordingState) => void
|
|
153
|
+
): Promise<BotaAsyncEventSubscription>;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type BotaRecordingControlResult = {
|
|
157
|
+
success: boolean;
|
|
158
|
+
error?: string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type BotaFactoryResetGrantRequest = Omit<
|
|
162
|
+
NativeFactoryResetGrantRequest,
|
|
163
|
+
'requestId'
|
|
164
|
+
>;
|
|
165
|
+
|
|
166
|
+
export type BotaFactoryResetGrantProvider = (
|
|
167
|
+
request: BotaFactoryResetGrantRequest
|
|
168
|
+
) => Promise<string>;
|
|
169
|
+
|
|
170
|
+
export type BotaFactoryResetOptions = {
|
|
171
|
+
commandId: string;
|
|
172
|
+
bindingGeneration: number;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type BotaFactoryResetCompletion = NativeFactoryResetCompletion;
|
|
176
|
+
|
|
177
|
+
export type BotaDeviceSDKFactoryResetClient = {
|
|
178
|
+
factoryReset(
|
|
179
|
+
device: ConnectedDevice,
|
|
180
|
+
options: BotaFactoryResetOptions,
|
|
181
|
+
provider: BotaFactoryResetGrantProvider,
|
|
182
|
+
persistResult?: BleFactoryResetResultPersister
|
|
183
|
+
): Promise<BotaFactoryResetCompletion>;
|
|
184
|
+
resumePendingFactoryReset(
|
|
185
|
+
device: ConnectedDevice,
|
|
186
|
+
currentBindingGeneration: number,
|
|
187
|
+
persistResult?: BleFactoryResetResultPersister
|
|
188
|
+
): Promise<BotaFactoryResetCompletion | null>;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export type BotaRecordingTransferProgress = {
|
|
192
|
+
completedBytes: number;
|
|
193
|
+
totalBytes: number;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type BotaEncryptedUploadV2Recording = NativeEncryptedUploadV2Recording;
|
|
197
|
+
|
|
198
|
+
export type BotaEncryptedUploadV2Capability = NativeEncryptedUploadV2Capability;
|
|
199
|
+
|
|
200
|
+
export type BotaEncryptedUploadV2Checkpoint = NativeEncryptedUploadV2Checkpoint;
|
|
201
|
+
|
|
202
|
+
export type BotaEncryptedUploadV2ProviderContext = {
|
|
203
|
+
recording: BotaEncryptedUploadV2Recording;
|
|
204
|
+
capability: BotaEncryptedUploadV2Capability;
|
|
205
|
+
checkpoint?: BotaEncryptedUploadV2Checkpoint;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export type BotaEncryptedUploadV2SecurityPolicy =
|
|
209
|
+
| 'legacy_allowed'
|
|
210
|
+
| 'v2_preferred'
|
|
211
|
+
| 'v2_required';
|
|
212
|
+
|
|
213
|
+
export type BotaEncryptedUploadV2ProfileDecision = {
|
|
214
|
+
profile: 'encrypted_upload_v2';
|
|
215
|
+
uploadSessionId: string;
|
|
216
|
+
ownerRevision: number;
|
|
217
|
+
securityPolicy: BotaEncryptedUploadV2SecurityPolicy;
|
|
218
|
+
materialRegistrationId: string;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export type BotaEncryptedUploadV2ProfileProvider = (
|
|
222
|
+
context: BotaEncryptedUploadV2ProviderContext
|
|
223
|
+
) => Promise<BotaEncryptedUploadV2ProfileDecision>;
|
|
224
|
+
|
|
225
|
+
export type BotaEncryptedUploadV2ProgressPhase =
|
|
226
|
+
| 'profile_requested'
|
|
227
|
+
| 'transferring'
|
|
228
|
+
| 'completed'
|
|
229
|
+
| 'failed';
|
|
230
|
+
|
|
231
|
+
export type BotaEncryptedUploadV2Progress = {
|
|
232
|
+
recordingUuid: string;
|
|
233
|
+
phase: BotaEncryptedUploadV2ProgressPhase;
|
|
234
|
+
completedBytes: string;
|
|
235
|
+
totalBytes: string;
|
|
236
|
+
checkpointRevision?: number;
|
|
237
|
+
errorCode?: string;
|
|
238
|
+
retryable?: boolean;
|
|
239
|
+
protocolStatus?: number;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type BotaUploadOwnershipRequest = {
|
|
243
|
+
recordingUuid: string;
|
|
244
|
+
uploadId: string;
|
|
245
|
+
destinationId: string;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export type BotaUploadOwnershipResult =
|
|
249
|
+
| { kind: 'device_upload_completed' }
|
|
250
|
+
| { kind: 'device_upload_preserved'; uploadId: string }
|
|
251
|
+
| {
|
|
252
|
+
kind: 'bluetooth_fallback';
|
|
253
|
+
recordingUuid: string;
|
|
254
|
+
uploadId: string;
|
|
255
|
+
destinationId: string;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export type BotaFirmwareImage = {
|
|
259
|
+
version: string;
|
|
260
|
+
sizeBytes: number;
|
|
261
|
+
crc32: number;
|
|
262
|
+
url: string;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export type BotaFirmwareUpdatePhase =
|
|
266
|
+
| 'downloading'
|
|
267
|
+
| 'awaiting_device'
|
|
268
|
+
| 'transferring'
|
|
269
|
+
| 'verifying'
|
|
270
|
+
| 'rebooting'
|
|
271
|
+
| 'reconnecting'
|
|
272
|
+
| 'complete';
|
|
273
|
+
|
|
274
|
+
export type BotaFirmwareUpdateProgress = {
|
|
275
|
+
phase: BotaFirmwareUpdatePhase;
|
|
276
|
+
completedBytes: number;
|
|
277
|
+
totalBytes: number;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export type BotaDeviceSDKOTAClient = {
|
|
281
|
+
updateFirmware(
|
|
282
|
+
device: ConnectedDevice,
|
|
283
|
+
image: BotaFirmwareImage,
|
|
284
|
+
onProgress?: (progress: BotaFirmwareUpdateProgress) => void
|
|
285
|
+
): Promise<void>;
|
|
286
|
+
cancelFirmwareUpdate(): Promise<void>;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export type BotaDeviceSDKLogClient = {
|
|
290
|
+
subscribe(
|
|
291
|
+
device: ConnectedDevice,
|
|
292
|
+
onLine: (line: DeviceLogEvent) => void
|
|
293
|
+
): Promise<BotaAsyncEventSubscription>;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export type BotaDeviceSDKWiFiClient = {
|
|
297
|
+
configure(
|
|
298
|
+
device: ConnectedDevice,
|
|
299
|
+
credentials: WiFiCredentials,
|
|
300
|
+
grant: WiFiConfigGrant
|
|
301
|
+
): Promise<WiFiConfigResult>;
|
|
302
|
+
disconnect(device: ConnectedDevice): Promise<WiFiConfigResult>;
|
|
303
|
+
readStatus(device: ConnectedDevice): Promise<WiFiStatusInfo>;
|
|
304
|
+
subscribeToStatus(
|
|
305
|
+
device: ConnectedDevice,
|
|
306
|
+
onStatus: (status: WiFiStatusInfo) => void
|
|
307
|
+
): Promise<BotaAsyncEventSubscription>;
|
|
308
|
+
scanNetworks(device: ConnectedDevice): Promise<DeviceWiFiScanResult>;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export type BotaDeviceSDKRecordingClient = {
|
|
312
|
+
listRecordings(device: ConnectedDevice): Promise<DeviceRecording[]>;
|
|
313
|
+
syncRecording(
|
|
314
|
+
device: ConnectedDevice,
|
|
315
|
+
recording: DeviceRecording,
|
|
316
|
+
onProgress?: (progress: BotaRecordingTransferProgress) => void,
|
|
317
|
+
sinkId?: string
|
|
318
|
+
): Promise<{
|
|
319
|
+
localPath: string;
|
|
320
|
+
e2eEncrypted: boolean;
|
|
321
|
+
contentSha256?: string;
|
|
322
|
+
}>;
|
|
323
|
+
syncEncryptedRecordingV2(
|
|
324
|
+
device: ConnectedDevice,
|
|
325
|
+
recording: BotaEncryptedUploadV2Recording,
|
|
326
|
+
provider: BotaEncryptedUploadV2ProfileProvider,
|
|
327
|
+
onProgress?: (progress: BotaEncryptedUploadV2Progress) => void
|
|
328
|
+
): Promise<void>;
|
|
329
|
+
confirmRecording(
|
|
330
|
+
device: ConnectedDevice,
|
|
331
|
+
recordingUuid: string
|
|
332
|
+
): Promise<void>;
|
|
333
|
+
uploadRecordingFile(
|
|
334
|
+
task: UploadTask,
|
|
335
|
+
onProgress?: (progress: BotaRecordingTransferProgress) => void
|
|
336
|
+
): Promise<void>;
|
|
337
|
+
cancelRecordingUpload(taskId: string): Promise<void>;
|
|
338
|
+
loadUploadQueue(): Promise<UploadTask[]>;
|
|
339
|
+
saveUploadQueue(tasks: UploadTask[]): Promise<void>;
|
|
340
|
+
destroyCompatibilityOperations(): Promise<void>;
|
|
341
|
+
observeUploadOwnership(
|
|
342
|
+
device: ConnectedDevice,
|
|
343
|
+
request: BotaUploadOwnershipRequest,
|
|
344
|
+
onProgress?: (progress: BotaRecordingTransferProgress) => void
|
|
345
|
+
): Promise<BotaUploadOwnershipResult>;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
export type BotaStreamingProgress = {
|
|
349
|
+
state: string;
|
|
350
|
+
bytesReceived: number;
|
|
351
|
+
chunksUploaded: number;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export type BotaStreamingChunkRequest = {
|
|
355
|
+
sequence: number;
|
|
356
|
+
encrypted: boolean;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
export type BotaStreamingFinalizeRequest = {
|
|
360
|
+
totalChunks: number;
|
|
361
|
+
durationMs: number;
|
|
362
|
+
fileSizeBytes: number;
|
|
363
|
+
encrypted: boolean;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export type BotaStreamingUploadDestination = NativeStreamingUploadDestination;
|
|
367
|
+
|
|
368
|
+
export type BotaDeviceSDKStreamingClient = {
|
|
369
|
+
startStreaming(
|
|
370
|
+
device: ConnectedDevice,
|
|
371
|
+
request: NativeStreamingStartRequest,
|
|
372
|
+
handlers: {
|
|
373
|
+
onProgress(progress: BotaStreamingProgress): void;
|
|
374
|
+
resolveChunkDestination(
|
|
375
|
+
request: BotaStreamingChunkRequest
|
|
376
|
+
): Promise<BotaStreamingUploadDestination>;
|
|
377
|
+
finalize(request: BotaStreamingFinalizeRequest): Promise<void>;
|
|
378
|
+
}
|
|
379
|
+
): Promise<{ totalBytes: number }>;
|
|
380
|
+
abortStreaming(sessionId: string): Promise<void>;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
export type BotaDeviceSDKDeviceClient = {
|
|
384
|
+
startScan(
|
|
385
|
+
options: ScanOptions | undefined,
|
|
386
|
+
onDevice: (device: DiscoveredDevice) => void
|
|
387
|
+
): Promise<BotaEventSubscription>;
|
|
388
|
+
stopScan(): Promise<void>;
|
|
389
|
+
connect(device: DiscoveredDevice): Promise<ConnectedDevice>;
|
|
390
|
+
reconnect(
|
|
391
|
+
serialNumber: string,
|
|
392
|
+
options?: ReconnectOptions
|
|
393
|
+
): Promise<ConnectedDevice>;
|
|
394
|
+
disconnect(): Promise<void>;
|
|
395
|
+
readStatus(): Promise<DeviceStatus>;
|
|
396
|
+
subscribeToStatus(
|
|
397
|
+
onStatus: (status: DeviceStatus) => void
|
|
398
|
+
): Promise<BotaAsyncEventSubscription>;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
export class BotaNativeModuleError extends Error {
|
|
402
|
+
readonly code = 'native_module_unavailable';
|
|
403
|
+
|
|
404
|
+
constructor() {
|
|
405
|
+
super(
|
|
406
|
+
'BotaDeviceSDK native module is unavailable. Rebuild the native application after installing the SDK.'
|
|
407
|
+
);
|
|
408
|
+
this.name = 'BotaNativeModuleError';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export type BotaDeviceSDKClient = {
|
|
413
|
+
readonly controls: BotaDeviceSDKControlClient;
|
|
414
|
+
readonly devices: BotaDeviceSDKDeviceClient;
|
|
415
|
+
readonly factoryReset: BotaDeviceSDKFactoryResetClient;
|
|
416
|
+
readonly logs: BotaDeviceSDKLogClient;
|
|
417
|
+
readonly ota: BotaDeviceSDKOTAClient;
|
|
418
|
+
readonly provisioning: BotaDeviceSDKProvisioningClient;
|
|
419
|
+
readonly recordings: BotaDeviceSDKRecordingClient;
|
|
420
|
+
readonly streaming: BotaDeviceSDKStreamingClient;
|
|
421
|
+
readonly wifi: BotaDeviceSDKWiFiClient;
|
|
422
|
+
configure(configuration?: BotaDeviceSDKConfiguration): Promise<void>;
|
|
423
|
+
destroy(): Promise<void>;
|
|
424
|
+
getCapabilities(): Promise<BotaDeviceSDKCapabilities>;
|
|
425
|
+
getState(): Promise<BotaDeviceSDKState>;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const mapDiscoveredDevice = (
|
|
429
|
+
device: NativeDiscoveredDevice
|
|
430
|
+
): DiscoveredDevice => ({
|
|
431
|
+
id: device.id,
|
|
432
|
+
name: device.name ?? '',
|
|
433
|
+
deviceType: (device.deviceType ?? 'bota_pin') as DeviceType,
|
|
434
|
+
firmwareVersion: device.firmwareVersion ?? '',
|
|
435
|
+
macAddress: device.macAddress ?? null,
|
|
436
|
+
pairingState: (device.pairingState ?? 'unpaired') as PairingState,
|
|
437
|
+
rssi: device.rssi,
|
|
438
|
+
discoveredAt: new Date(device.discoveredAtMs),
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const toNativeDiscoveredDevice = (
|
|
442
|
+
device: DiscoveredDevice
|
|
443
|
+
): NativeDiscoveredDevice => ({
|
|
444
|
+
id: device.id,
|
|
445
|
+
name: device.name,
|
|
446
|
+
deviceType: device.deviceType,
|
|
447
|
+
firmwareVersion: device.firmwareVersion,
|
|
448
|
+
...(device.macAddress ? { macAddress: device.macAddress } : {}),
|
|
449
|
+
pairingState: device.pairingState,
|
|
450
|
+
rssi: device.rssi,
|
|
451
|
+
discoveredAtMs: device.discoveredAt.getTime(),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const mapConnectedDevice = (
|
|
455
|
+
device: NativeConnectedDevice
|
|
456
|
+
): ConnectedDevice => ({
|
|
457
|
+
...device,
|
|
458
|
+
deviceType: device.deviceType as DeviceType,
|
|
459
|
+
connectionState: device.connectionState as ConnectionState,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const toNativeConnectedDevice = (
|
|
463
|
+
device: ConnectedDevice
|
|
464
|
+
): NativeConnectedDevice => ({
|
|
465
|
+
id: device.id,
|
|
466
|
+
serialNumber: device.serialNumber,
|
|
467
|
+
deviceType: device.deviceType,
|
|
468
|
+
firmwareVersion: device.firmwareVersion,
|
|
469
|
+
...(device.hardwareRevision === undefined
|
|
470
|
+
? {}
|
|
471
|
+
: { hardwareRevision: device.hardwareRevision }),
|
|
472
|
+
isProvisioned: device.isProvisioned,
|
|
473
|
+
connectionState: device.connectionState,
|
|
474
|
+
mtu: device.mtu,
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const errorMessage = (error: unknown): string =>
|
|
478
|
+
error instanceof Error ? error.message : String(error);
|
|
479
|
+
|
|
480
|
+
const createOpaqueId = (): string =>
|
|
481
|
+
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (value) => {
|
|
482
|
+
const random = Math.floor(Math.random() * 16);
|
|
483
|
+
const nibble = value === 'x' ? random : (random & 0x3) | 0x8;
|
|
484
|
+
return nibble.toString(16);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
const bytesToHex = (value: Uint8Array): string =>
|
|
488
|
+
Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
489
|
+
|
|
490
|
+
const toNativeConnectionSettings = (settings: DeviceConnectionSettings) => ({
|
|
491
|
+
enabledConnections: settings.enabled_connections,
|
|
492
|
+
heartbeatEnabledConnections:
|
|
493
|
+
settings.heartbeat_enabled_connections ?? { wifi: true, cellular: true },
|
|
494
|
+
uploadNetworkPreference: settings.upload_network_preference,
|
|
495
|
+
powerManagement: {
|
|
496
|
+
wifiIdleTimeoutSeconds:
|
|
497
|
+
settings.power_management?.wifi_idle_timeout_seconds ?? 180,
|
|
498
|
+
cellularIdleTimeoutSeconds:
|
|
499
|
+
settings.power_management?.cellular_idle_timeout_seconds ?? 180,
|
|
500
|
+
},
|
|
501
|
+
streamingEnabled: settings.streaming_enabled ?? true,
|
|
502
|
+
streamingFlushIntervalSeconds:
|
|
503
|
+
settings.streaming_flush_interval_seconds ?? 60,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const isConnectionType = (value: string): value is ConnectionType =>
|
|
507
|
+
value === 'wifi' || value === 'ble' || value === 'cellular';
|
|
508
|
+
|
|
509
|
+
const mapNativeConnectionSettings = (
|
|
510
|
+
settings: NativeDeviceConnectionSettings
|
|
511
|
+
): DeviceConnectionSettings => ({
|
|
512
|
+
enabled_connections: settings.enabledConnections,
|
|
513
|
+
heartbeat_enabled_connections: settings.heartbeatEnabledConnections,
|
|
514
|
+
upload_network_preference: settings.uploadNetworkPreference.filter(isConnectionType),
|
|
515
|
+
power_management: {
|
|
516
|
+
wifi_idle_timeout_seconds: settings.powerManagement.wifiIdleTimeoutSeconds,
|
|
517
|
+
cellular_idle_timeout_seconds:
|
|
518
|
+
settings.powerManagement.cellularIdleTimeoutSeconds,
|
|
519
|
+
},
|
|
520
|
+
streaming_enabled: settings.streamingEnabled,
|
|
521
|
+
streaming_flush_interval_seconds: settings.streamingFlushIntervalSeconds,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const matchesScanOptions = (
|
|
525
|
+
device: DiscoveredDevice,
|
|
526
|
+
options: ScanOptions | undefined
|
|
527
|
+
): boolean => {
|
|
528
|
+
if (options?.deviceTypes && !options.deviceTypes.includes(device.deviceType)) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
if (options?.pairingState && device.pairingState !== options.pairingState) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (options?.minRssi !== undefined && device.rssi < options.minRssi) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
return true;
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const mapDeviceStatus = (status: NativeDeviceStatus): DeviceStatus => ({
|
|
541
|
+
batteryLevel: status.batteryLevel,
|
|
542
|
+
...(status.batteryMv === undefined ? {} : { batteryMv: status.batteryMv }),
|
|
543
|
+
storageTotalMb: status.storageTotalMb,
|
|
544
|
+
storageUsedMb: status.storageUsedMb,
|
|
545
|
+
state: status.state as DeviceState,
|
|
546
|
+
pendingRecordings: status.pendingRecordings,
|
|
547
|
+
lastTimeSyncAt:
|
|
548
|
+
status.lastTimeSyncAtMs === undefined
|
|
549
|
+
? null
|
|
550
|
+
: new Date(status.lastTimeSyncAtMs),
|
|
551
|
+
signalStrength: status.signalStrength,
|
|
552
|
+
flags: status.flags,
|
|
553
|
+
timestamp: status.timestamp,
|
|
554
|
+
lteStatus: status.lteStatus as LteStatus,
|
|
555
|
+
...(status.lteSignalQuality === undefined
|
|
556
|
+
? {}
|
|
557
|
+
: { lteSignalQuality: status.lteSignalQuality }),
|
|
558
|
+
...(status.wifiStatus === undefined
|
|
559
|
+
? {}
|
|
560
|
+
: { wifiStatus: status.wifiStatus as WifiStatus }),
|
|
561
|
+
...(status.modemInfo === undefined ? {} : { modemInfo: status.modemInfo }),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const mapRecording = (recording: NativeDeviceRecording): DeviceRecording => ({
|
|
565
|
+
uuid: recording.uuid,
|
|
566
|
+
startedAt: new Date(recording.startedAtMs),
|
|
567
|
+
durationMs: recording.durationMs,
|
|
568
|
+
fileSizeBytes: recording.fileSize,
|
|
569
|
+
codec: recording.codec as AudioCodec,
|
|
570
|
+
isEncrypted: recording.isEncrypted,
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const toNativeRecording = (recording: DeviceRecording): NativeDeviceRecording => ({
|
|
574
|
+
uuid: recording.uuid,
|
|
575
|
+
startedAtMs: recording.startedAt.getTime(),
|
|
576
|
+
durationMs: recording.durationMs,
|
|
577
|
+
fileSize: recording.fileSizeBytes,
|
|
578
|
+
codec: recording.codec,
|
|
579
|
+
isEncrypted: recording.isEncrypted ?? false,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
const mapRecordingProgress = (
|
|
583
|
+
progress: NativeRecordingTransferProgress
|
|
584
|
+
): BotaRecordingTransferProgress => ({
|
|
585
|
+
completedBytes: progress.completedUnits,
|
|
586
|
+
totalBytes: progress.totalUnits,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const mapEncryptedUploadV2Progress = (
|
|
590
|
+
progress: NativeEncryptedUploadV2Progress
|
|
591
|
+
): BotaEncryptedUploadV2Progress => ({
|
|
592
|
+
recordingUuid: progress.recordingUuid,
|
|
593
|
+
phase: progress.phase as BotaEncryptedUploadV2ProgressPhase,
|
|
594
|
+
completedBytes: progress.completedBytes,
|
|
595
|
+
totalBytes: progress.totalBytes,
|
|
596
|
+
...(progress.checkpointRevision === undefined
|
|
597
|
+
? {}
|
|
598
|
+
: { checkpointRevision: progress.checkpointRevision }),
|
|
599
|
+
...(progress.errorCode === undefined ? {} : { errorCode: progress.errorCode }),
|
|
600
|
+
...(progress.retryable === undefined ? {} : { retryable: progress.retryable }),
|
|
601
|
+
...(progress.protocolStatus === undefined
|
|
602
|
+
? {}
|
|
603
|
+
: { protocolStatus: progress.protocolStatus }),
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const mapFirmwareProgress = (
|
|
607
|
+
progress: NativeFirmwareUpdateProgress
|
|
608
|
+
): BotaFirmwareUpdateProgress => ({
|
|
609
|
+
phase: progress.phase as BotaFirmwareUpdatePhase,
|
|
610
|
+
completedBytes: progress.completedUnits,
|
|
611
|
+
totalBytes: progress.totalUnits,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const isWiFiStatus = (value: string): value is WiFiStatus =>
|
|
615
|
+
value === 'idle' ||
|
|
616
|
+
value === 'connecting' ||
|
|
617
|
+
value === 'connected' ||
|
|
618
|
+
value === 'failed' ||
|
|
619
|
+
value === 'disconnected';
|
|
620
|
+
|
|
621
|
+
const mapWiFiStatus = (status: NativeWiFiStatusInfo): WiFiStatusInfo => ({
|
|
622
|
+
status: isWiFiStatus(status.status) ? status.status : 'idle',
|
|
623
|
+
...(status.signalStrength === undefined
|
|
624
|
+
? {}
|
|
625
|
+
: { signalStrength: status.signalStrength }),
|
|
626
|
+
...(status.ssid === undefined ? {} : { ssid: status.ssid }),
|
|
627
|
+
...(status.lastError === undefined ? {} : { lastError: status.lastError }),
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
const mapRecordingControlResult = (
|
|
631
|
+
result: NativeRecordingControlResult
|
|
632
|
+
): BotaRecordingControlResult => ({
|
|
633
|
+
success: result.success,
|
|
634
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const mapRecordingState = (state: NativeRecordingState): RecordingState => ({
|
|
638
|
+
active: state.active,
|
|
639
|
+
...(state.recordingId === undefined
|
|
640
|
+
? {}
|
|
641
|
+
: { recordingId: state.recordingId }),
|
|
642
|
+
initiatedBy: state.initiatedBy === 'remote' ? 'remote' : 'local',
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
const mapWiFiConfigResult = (
|
|
646
|
+
result: NativeWiFiConfigResult
|
|
647
|
+
): WiFiConfigResult => {
|
|
648
|
+
if (result.success) return { success: true };
|
|
649
|
+
switch (result.error) {
|
|
650
|
+
case 'invalid_grant':
|
|
651
|
+
case 'grant_expired':
|
|
652
|
+
case 'decryption_error':
|
|
653
|
+
case 'storage_error':
|
|
654
|
+
return { success: false, error: result.error };
|
|
655
|
+
default:
|
|
656
|
+
return { success: false, error: 'unknown' };
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const mapWiFiScanResult = (
|
|
661
|
+
result: NativeDeviceWiFiScanResult
|
|
662
|
+
): DeviceWiFiScanResult => ({
|
|
663
|
+
networks: result.networks.map((network) => ({ ...network })),
|
|
664
|
+
currentSsid: result.currentSsid ?? null,
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const requiredUploadOwnershipField = (
|
|
668
|
+
value: string | undefined,
|
|
669
|
+
field: string
|
|
670
|
+
): string => {
|
|
671
|
+
if (value === undefined) {
|
|
672
|
+
throw new Error(`native upload ownership result is missing ${field}`);
|
|
673
|
+
}
|
|
674
|
+
return value;
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
const mapUploadOwnershipResult = (
|
|
678
|
+
result: NativeUploadOwnershipResult
|
|
679
|
+
): BotaUploadOwnershipResult => {
|
|
680
|
+
switch (result.kind) {
|
|
681
|
+
case 'device_upload_completed':
|
|
682
|
+
return { kind: 'device_upload_completed' };
|
|
683
|
+
case 'device_upload_preserved':
|
|
684
|
+
return {
|
|
685
|
+
kind: 'device_upload_preserved',
|
|
686
|
+
uploadId: requiredUploadOwnershipField(result.uploadId, 'uploadId'),
|
|
687
|
+
};
|
|
688
|
+
case 'bluetooth_fallback':
|
|
689
|
+
return {
|
|
690
|
+
kind: 'bluetooth_fallback',
|
|
691
|
+
recordingUuid: requiredUploadOwnershipField(
|
|
692
|
+
result.recordingUuid,
|
|
693
|
+
'recordingUuid'
|
|
694
|
+
),
|
|
695
|
+
uploadId: requiredUploadOwnershipField(result.uploadId, 'uploadId'),
|
|
696
|
+
destinationId: requiredUploadOwnershipField(
|
|
697
|
+
result.destinationId,
|
|
698
|
+
'destinationId'
|
|
699
|
+
),
|
|
700
|
+
};
|
|
701
|
+
default:
|
|
702
|
+
throw new Error(`unsupported native upload ownership result: ${result.kind}`);
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
const toNativeRecordingUploadRequest = (
|
|
707
|
+
task: UploadTask
|
|
708
|
+
): NativeRecordingUploadRequest => ({
|
|
709
|
+
taskId: task.id,
|
|
710
|
+
recordingId: task.recordingId,
|
|
711
|
+
deviceId: task.deviceId,
|
|
712
|
+
localPath: task.localPath,
|
|
713
|
+
uploadUrl: task.uploadUrl,
|
|
714
|
+
...(task.uploadToken ? { uploadToken: task.uploadToken } : {}),
|
|
715
|
+
...(task.completeUrl ? { completeUrl: task.completeUrl } : {}),
|
|
716
|
+
...(task.contentType ? { contentType: task.contentType } : {}),
|
|
717
|
+
...(task.contentSha256 ? { contentSha256: task.contentSha256 } : {}),
|
|
718
|
+
...(task.relay
|
|
719
|
+
? {
|
|
720
|
+
relayUrl: task.relay.url,
|
|
721
|
+
relayBearerToken: task.relay.bearerToken,
|
|
722
|
+
}
|
|
723
|
+
: {}),
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
const parseUploadQueue = (serialized: string): UploadTask[] => {
|
|
727
|
+
const value: unknown = JSON.parse(serialized || '[]');
|
|
728
|
+
if (!Array.isArray(value)) return [];
|
|
729
|
+
return value.map((task) => {
|
|
730
|
+
const candidate = task as UploadTask & {
|
|
731
|
+
createdAt: string | Date;
|
|
732
|
+
updatedAt: string | Date;
|
|
733
|
+
};
|
|
734
|
+
return {
|
|
735
|
+
...candidate,
|
|
736
|
+
createdAt: new Date(candidate.createdAt),
|
|
737
|
+
updatedAt: new Date(candidate.updatedAt),
|
|
738
|
+
};
|
|
739
|
+
});
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKClient => {
|
|
743
|
+
const requireNativeModule = (): Spec => {
|
|
744
|
+
if (!nativeModule) throw new BotaNativeModuleError();
|
|
745
|
+
return nativeModule;
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const devices: BotaDeviceSDKDeviceClient = {
|
|
749
|
+
async startScan(options, onDevice) {
|
|
750
|
+
const module = requireNativeModule();
|
|
751
|
+
const subscription = module.onDeviceDiscovered((device) => {
|
|
752
|
+
const discovered = mapDiscoveredDevice(device);
|
|
753
|
+
if (matchesScanOptions(discovered, options)) onDevice(discovered);
|
|
754
|
+
});
|
|
755
|
+
try {
|
|
756
|
+
await module.startScan(
|
|
757
|
+
options?.timeout ?? 30_000,
|
|
758
|
+
options?.allowDuplicates ?? false
|
|
759
|
+
);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
subscription.remove();
|
|
762
|
+
throw error;
|
|
763
|
+
}
|
|
764
|
+
return subscription;
|
|
765
|
+
},
|
|
766
|
+
|
|
767
|
+
async stopScan() {
|
|
768
|
+
await requireNativeModule().stopScan();
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
async connect(device) {
|
|
772
|
+
return mapConnectedDevice(
|
|
773
|
+
await requireNativeModule().connectSelected(
|
|
774
|
+
toNativeDiscoveredDevice(device)
|
|
775
|
+
)
|
|
776
|
+
);
|
|
777
|
+
},
|
|
778
|
+
|
|
779
|
+
async reconnect(serialNumber, options = {}) {
|
|
780
|
+
return mapConnectedDevice(
|
|
781
|
+
await requireNativeModule().reconnect(serialNumber, {
|
|
782
|
+
scanTimeoutMs: options.scanTimeout ?? 10_000,
|
|
783
|
+
connectionTimeoutMs: 10_000,
|
|
784
|
+
})
|
|
785
|
+
);
|
|
786
|
+
},
|
|
787
|
+
|
|
788
|
+
async disconnect() {
|
|
789
|
+
await requireNativeModule().disconnect();
|
|
790
|
+
},
|
|
791
|
+
|
|
792
|
+
async readStatus() {
|
|
793
|
+
return mapDeviceStatus(await requireNativeModule().readStatus());
|
|
794
|
+
},
|
|
795
|
+
|
|
796
|
+
async subscribeToStatus(onStatus) {
|
|
797
|
+
const module = requireNativeModule();
|
|
798
|
+
const eventSubscription = module.onDeviceStatusUpdated((status) => {
|
|
799
|
+
onStatus(mapDeviceStatus(status));
|
|
800
|
+
});
|
|
801
|
+
try {
|
|
802
|
+
await module.startStatusUpdates();
|
|
803
|
+
} catch (error) {
|
|
804
|
+
eventSubscription.remove();
|
|
805
|
+
throw error;
|
|
806
|
+
}
|
|
807
|
+
let removed = false;
|
|
808
|
+
return {
|
|
809
|
+
async remove() {
|
|
810
|
+
if (removed) return;
|
|
811
|
+
removed = true;
|
|
812
|
+
eventSubscription.remove();
|
|
813
|
+
await module.stopStatusUpdates();
|
|
814
|
+
},
|
|
815
|
+
};
|
|
816
|
+
},
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const provisioning: BotaDeviceSDKProvisioningClient = {
|
|
820
|
+
async provision(device, provider) {
|
|
821
|
+
const module = requireNativeModule();
|
|
822
|
+
const subscription = module.onProvisioningMaterialRequested((request) => {
|
|
823
|
+
void (async () => {
|
|
824
|
+
try {
|
|
825
|
+
const material = await provider({
|
|
826
|
+
serialNumber: request.serialNumber,
|
|
827
|
+
nonce: request.nonce,
|
|
828
|
+
devicePublicKey: request.devicePublicKey,
|
|
829
|
+
});
|
|
830
|
+
await module.resolveProvisioningMaterial(request.requestId, material);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
await module.rejectApplicationMaterial(
|
|
833
|
+
request.requestId,
|
|
834
|
+
errorMessage(error)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
})().catch(() => {});
|
|
838
|
+
});
|
|
839
|
+
try {
|
|
840
|
+
await module.provision(toNativeConnectedDevice(device));
|
|
841
|
+
} finally {
|
|
842
|
+
subscription.remove();
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
|
|
846
|
+
async deprovision(device, grantBlob) {
|
|
847
|
+
const result = await requireNativeModule().deprovision(
|
|
848
|
+
toNativeConnectedDevice(device),
|
|
849
|
+
grantBlob
|
|
850
|
+
);
|
|
851
|
+
return result.error === undefined
|
|
852
|
+
? { success: result.success }
|
|
853
|
+
: { success: result.success, error: result.error };
|
|
854
|
+
},
|
|
855
|
+
|
|
856
|
+
async readConnectionSettings(device) {
|
|
857
|
+
return mapNativeConnectionSettings(
|
|
858
|
+
await requireNativeModule().readConnectionSettings(
|
|
859
|
+
toNativeConnectedDevice(device)
|
|
860
|
+
)
|
|
861
|
+
);
|
|
862
|
+
},
|
|
863
|
+
|
|
864
|
+
async writeConnectionSettings(device, settings) {
|
|
865
|
+
await requireNativeModule().writeConnectionSettings(
|
|
866
|
+
toNativeConnectedDevice(device),
|
|
867
|
+
toNativeConnectionSettings(settings)
|
|
868
|
+
);
|
|
869
|
+
},
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
const controls: BotaDeviceSDKControlClient = {
|
|
873
|
+
async isProvisioned(device) {
|
|
874
|
+
return requireNativeModule().isProvisioned(toNativeConnectedDevice(device));
|
|
875
|
+
},
|
|
876
|
+
|
|
877
|
+
async readPublicKey(device) {
|
|
878
|
+
return requireNativeModule().readPublicKey(toNativeConnectedDevice(device));
|
|
879
|
+
},
|
|
880
|
+
|
|
881
|
+
async readAuthNonce(device) {
|
|
882
|
+
return requireNativeModule().readAuthNonce(toNativeConnectedDevice(device));
|
|
883
|
+
},
|
|
884
|
+
|
|
885
|
+
async setApiEndpoint(device, environment) {
|
|
886
|
+
await requireNativeModule().setApiEndpoint(
|
|
887
|
+
toNativeConnectedDevice(device),
|
|
888
|
+
environment
|
|
889
|
+
);
|
|
890
|
+
},
|
|
891
|
+
|
|
892
|
+
async deliverCertificate(device, certificatePem, privateKeyPem) {
|
|
893
|
+
await requireNativeModule().deliverCertificate(
|
|
894
|
+
toNativeConnectedDevice(device),
|
|
895
|
+
certificatePem,
|
|
896
|
+
privateKeyPem
|
|
897
|
+
);
|
|
898
|
+
},
|
|
899
|
+
|
|
900
|
+
async deliverBackendPublicKey(device, publicKey) {
|
|
901
|
+
await requireNativeModule().deliverBackendPublicKey(
|
|
902
|
+
toNativeConnectedDevice(device),
|
|
903
|
+
bytesToHex(publicKey)
|
|
904
|
+
);
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
async writeGrant(device, grantBlob) {
|
|
908
|
+
await requireNativeModule().writeGrant(
|
|
909
|
+
toNativeConnectedDevice(device),
|
|
910
|
+
grantBlob
|
|
911
|
+
);
|
|
912
|
+
},
|
|
913
|
+
|
|
914
|
+
async syncTime(device) {
|
|
915
|
+
await requireNativeModule().syncTime(toNativeConnectedDevice(device));
|
|
916
|
+
},
|
|
917
|
+
|
|
918
|
+
async requestStartRecording(device, grantBlob) {
|
|
919
|
+
return mapRecordingControlResult(
|
|
920
|
+
await requireNativeModule().requestStartRecording(
|
|
921
|
+
toNativeConnectedDevice(device),
|
|
922
|
+
grantBlob
|
|
923
|
+
)
|
|
924
|
+
);
|
|
925
|
+
},
|
|
926
|
+
|
|
927
|
+
async requestStopRecording(device, grantBlob) {
|
|
928
|
+
return mapRecordingControlResult(
|
|
929
|
+
await requireNativeModule().requestStopRecording(
|
|
930
|
+
toNativeConnectedDevice(device),
|
|
931
|
+
grantBlob
|
|
932
|
+
)
|
|
933
|
+
);
|
|
934
|
+
},
|
|
935
|
+
|
|
936
|
+
async readRecordingState(device) {
|
|
937
|
+
return mapRecordingState(
|
|
938
|
+
await requireNativeModule().readRecordingState(
|
|
939
|
+
toNativeConnectedDevice(device)
|
|
940
|
+
)
|
|
941
|
+
);
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
async subscribeToRecordingState(device, onState) {
|
|
945
|
+
const module = requireNativeModule();
|
|
946
|
+
const eventSubscription = module.onRecordingStateUpdated((state) => {
|
|
947
|
+
onState(mapRecordingState(state));
|
|
948
|
+
});
|
|
949
|
+
try {
|
|
950
|
+
await module.startRecordingStateUpdates(toNativeConnectedDevice(device));
|
|
951
|
+
} catch (error) {
|
|
952
|
+
eventSubscription.remove();
|
|
953
|
+
throw error;
|
|
954
|
+
}
|
|
955
|
+
let removed = false;
|
|
956
|
+
return {
|
|
957
|
+
async remove() {
|
|
958
|
+
if (removed) return;
|
|
959
|
+
removed = true;
|
|
960
|
+
eventSubscription.remove();
|
|
961
|
+
await module.stopRecordingStateUpdates();
|
|
962
|
+
},
|
|
963
|
+
};
|
|
964
|
+
},
|
|
965
|
+
};
|
|
966
|
+
|
|
967
|
+
const factoryReset: BotaDeviceSDKFactoryResetClient = {
|
|
968
|
+
async factoryReset(device, options, provider, persistResult) {
|
|
969
|
+
const module = requireNativeModule();
|
|
970
|
+
const grantSubscription = module.onFactoryResetGrantRequested((request) => {
|
|
971
|
+
void (async () => {
|
|
972
|
+
try {
|
|
973
|
+
const grantBlob = await provider({
|
|
974
|
+
serialNumber: request.serialNumber,
|
|
975
|
+
nonce: request.nonce,
|
|
976
|
+
commandId: request.commandId,
|
|
977
|
+
bindingGeneration: request.bindingGeneration,
|
|
978
|
+
});
|
|
979
|
+
await module.resolveFactoryResetGrant(request.requestId, grantBlob);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
await module.rejectApplicationMaterial(
|
|
982
|
+
request.requestId,
|
|
983
|
+
errorMessage(error)
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
})().catch(() => {});
|
|
987
|
+
});
|
|
988
|
+
const persistenceSubscription = persistResult
|
|
989
|
+
? module.onFactoryResetResultPersistenceRequested((request) => {
|
|
990
|
+
void (async () => {
|
|
991
|
+
try {
|
|
992
|
+
await persistResult({
|
|
993
|
+
success: true,
|
|
994
|
+
localRecordingsDeleted: request.localRecordingsDeleted,
|
|
995
|
+
});
|
|
996
|
+
await module.resolveFactoryResetResultPersistence(request.requestId);
|
|
997
|
+
} catch (error) {
|
|
998
|
+
await module.rejectApplicationMaterial(
|
|
999
|
+
request.requestId,
|
|
1000
|
+
errorMessage(error)
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
})().catch(() => {});
|
|
1004
|
+
})
|
|
1005
|
+
: null;
|
|
1006
|
+
try {
|
|
1007
|
+
return await module.factoryReset(
|
|
1008
|
+
toNativeConnectedDevice(device),
|
|
1009
|
+
options.commandId,
|
|
1010
|
+
options.bindingGeneration,
|
|
1011
|
+
persistResult !== undefined
|
|
1012
|
+
);
|
|
1013
|
+
} finally {
|
|
1014
|
+
grantSubscription.remove();
|
|
1015
|
+
persistenceSubscription?.remove();
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
|
|
1019
|
+
async resumePendingFactoryReset(device, currentBindingGeneration, persistResult) {
|
|
1020
|
+
const module = requireNativeModule();
|
|
1021
|
+
const subscription = persistResult
|
|
1022
|
+
? module.onFactoryResetResultPersistenceRequested((request) => {
|
|
1023
|
+
void (async () => {
|
|
1024
|
+
try {
|
|
1025
|
+
await persistResult({
|
|
1026
|
+
success: true,
|
|
1027
|
+
localRecordingsDeleted: request.localRecordingsDeleted,
|
|
1028
|
+
});
|
|
1029
|
+
await module.resolveFactoryResetResultPersistence(request.requestId);
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
await module.rejectApplicationMaterial(
|
|
1032
|
+
request.requestId,
|
|
1033
|
+
errorMessage(error)
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
})().catch(() => {});
|
|
1037
|
+
})
|
|
1038
|
+
: null;
|
|
1039
|
+
try {
|
|
1040
|
+
return await module.resumePendingFactoryReset(
|
|
1041
|
+
toNativeConnectedDevice(device),
|
|
1042
|
+
currentBindingGeneration,
|
|
1043
|
+
persistResult !== undefined
|
|
1044
|
+
);
|
|
1045
|
+
} finally {
|
|
1046
|
+
subscription?.remove();
|
|
1047
|
+
}
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
const recordings: BotaDeviceSDKRecordingClient = {
|
|
1052
|
+
async listRecordings(device) {
|
|
1053
|
+
const values = await requireNativeModule().listRecordings(
|
|
1054
|
+
toNativeConnectedDevice(device)
|
|
1055
|
+
);
|
|
1056
|
+
return values.map(mapRecording);
|
|
1057
|
+
},
|
|
1058
|
+
|
|
1059
|
+
async syncRecording(device, recording, onProgress, sinkId) {
|
|
1060
|
+
const module = requireNativeModule();
|
|
1061
|
+
const subscription = module.onRecordingTransferProgress((progress) => {
|
|
1062
|
+
onProgress?.(mapRecordingProgress(progress));
|
|
1063
|
+
});
|
|
1064
|
+
try {
|
|
1065
|
+
return await module.syncRecording(
|
|
1066
|
+
toNativeConnectedDevice(device),
|
|
1067
|
+
toNativeRecording(recording),
|
|
1068
|
+
sinkId ?? createOpaqueId()
|
|
1069
|
+
);
|
|
1070
|
+
} finally {
|
|
1071
|
+
subscription.remove();
|
|
1072
|
+
}
|
|
1073
|
+
},
|
|
1074
|
+
|
|
1075
|
+
async syncEncryptedRecordingV2(device, recording, provider, onProgress) {
|
|
1076
|
+
const module = requireNativeModule();
|
|
1077
|
+
const operationId = createOpaqueId();
|
|
1078
|
+
const profileSubscription = module.onEncryptedUploadV2ProfileRequested(
|
|
1079
|
+
(request) => {
|
|
1080
|
+
if (request.operationId !== operationId) return;
|
|
1081
|
+
void (async () => {
|
|
1082
|
+
try {
|
|
1083
|
+
const decision = await provider({
|
|
1084
|
+
recording: request.recording,
|
|
1085
|
+
capability: request.capability,
|
|
1086
|
+
...(request.checkpoint === undefined
|
|
1087
|
+
? {}
|
|
1088
|
+
: { checkpoint: request.checkpoint }),
|
|
1089
|
+
});
|
|
1090
|
+
if (decision.profile !== 'encrypted_upload_v2') {
|
|
1091
|
+
throw new Error('encrypted upload v2 requires an explicit v2 profile');
|
|
1092
|
+
}
|
|
1093
|
+
await module.resolveEncryptedUploadV2Profile(
|
|
1094
|
+
request.requestId,
|
|
1095
|
+
decision
|
|
1096
|
+
);
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
await module.rejectEncryptedUploadV2Profile(
|
|
1099
|
+
request.requestId,
|
|
1100
|
+
'application_material_rejected'
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
})().catch(() => {});
|
|
1104
|
+
}
|
|
1105
|
+
);
|
|
1106
|
+
const progressSubscription = module.onEncryptedUploadV2Progress(
|
|
1107
|
+
(progress) => {
|
|
1108
|
+
if (progress.operationId !== operationId) return;
|
|
1109
|
+
onProgress?.(mapEncryptedUploadV2Progress(progress));
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
try {
|
|
1113
|
+
await module.syncEncryptedRecordingV2(
|
|
1114
|
+
toNativeConnectedDevice(device),
|
|
1115
|
+
recording,
|
|
1116
|
+
operationId
|
|
1117
|
+
);
|
|
1118
|
+
} finally {
|
|
1119
|
+
profileSubscription.remove();
|
|
1120
|
+
progressSubscription.remove();
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
|
|
1124
|
+
async uploadRecordingFile(task, onProgress) {
|
|
1125
|
+
const module = requireNativeModule();
|
|
1126
|
+
const subscription = module.onRecordingUploadProgress((progress) => {
|
|
1127
|
+
if (progress.taskId !== task.id) return;
|
|
1128
|
+
onProgress?.({
|
|
1129
|
+
completedBytes: progress.completedBytes,
|
|
1130
|
+
totalBytes: progress.totalBytes,
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
try {
|
|
1134
|
+
await module.uploadRecordingFile(toNativeRecordingUploadRequest(task));
|
|
1135
|
+
} finally {
|
|
1136
|
+
subscription.remove();
|
|
1137
|
+
}
|
|
1138
|
+
},
|
|
1139
|
+
|
|
1140
|
+
async confirmRecording(device, recordingUuid) {
|
|
1141
|
+
await requireNativeModule().confirmRecording(
|
|
1142
|
+
toNativeConnectedDevice(device),
|
|
1143
|
+
recordingUuid
|
|
1144
|
+
);
|
|
1145
|
+
},
|
|
1146
|
+
|
|
1147
|
+
async cancelRecordingUpload(taskId) {
|
|
1148
|
+
await requireNativeModule().cancelRecordingUpload(taskId);
|
|
1149
|
+
},
|
|
1150
|
+
|
|
1151
|
+
async loadUploadQueue() {
|
|
1152
|
+
return parseUploadQueue(
|
|
1153
|
+
await requireNativeModule().loadCompatibilityUploadQueue()
|
|
1154
|
+
);
|
|
1155
|
+
},
|
|
1156
|
+
|
|
1157
|
+
async saveUploadQueue(tasks) {
|
|
1158
|
+
await requireNativeModule().saveCompatibilityUploadQueue(
|
|
1159
|
+
JSON.stringify(tasks)
|
|
1160
|
+
);
|
|
1161
|
+
},
|
|
1162
|
+
|
|
1163
|
+
async destroyCompatibilityOperations() {
|
|
1164
|
+
await requireNativeModule().stopAllRecordingOperations();
|
|
1165
|
+
},
|
|
1166
|
+
|
|
1167
|
+
async observeUploadOwnership(device, request, onProgress) {
|
|
1168
|
+
const module = requireNativeModule();
|
|
1169
|
+
const subscription = module.onUploadOwnershipProgress((progress) => {
|
|
1170
|
+
onProgress?.(mapRecordingProgress(progress));
|
|
1171
|
+
});
|
|
1172
|
+
try {
|
|
1173
|
+
return mapUploadOwnershipResult(
|
|
1174
|
+
await module.observeUploadOwnership(
|
|
1175
|
+
toNativeConnectedDevice(device),
|
|
1176
|
+
request
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
} finally {
|
|
1180
|
+
subscription.remove();
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
const ota: BotaDeviceSDKOTAClient = {
|
|
1186
|
+
async updateFirmware(device, image, onProgress) {
|
|
1187
|
+
const module = requireNativeModule();
|
|
1188
|
+
const subscription = module.onFirmwareUpdateProgress((progress) => {
|
|
1189
|
+
onProgress?.(mapFirmwareProgress(progress));
|
|
1190
|
+
});
|
|
1191
|
+
try {
|
|
1192
|
+
await module.updateFirmware(toNativeConnectedDevice(device), {
|
|
1193
|
+
version: image.version,
|
|
1194
|
+
sizeUnits: image.sizeBytes,
|
|
1195
|
+
crc32: image.crc32,
|
|
1196
|
+
url: image.url,
|
|
1197
|
+
});
|
|
1198
|
+
} finally {
|
|
1199
|
+
subscription.remove();
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
|
|
1203
|
+
async cancelFirmwareUpdate() {
|
|
1204
|
+
await requireNativeModule().cancelFirmwareUpdate();
|
|
1205
|
+
},
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
const streaming: BotaDeviceSDKStreamingClient = {
|
|
1209
|
+
async startStreaming(device, request, handlers) {
|
|
1210
|
+
const module = requireNativeModule();
|
|
1211
|
+
const progressSubscription = module.onStreamingProgress((progress) => {
|
|
1212
|
+
if (progress.sessionId !== request.sessionId) return;
|
|
1213
|
+
handlers.onProgress({
|
|
1214
|
+
state: progress.state,
|
|
1215
|
+
bytesReceived: progress.bytesReceived,
|
|
1216
|
+
chunksUploaded: progress.chunksUploaded,
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
const destinationSubscription = module.onStreamingChunkDestinationRequested(
|
|
1220
|
+
(nativeRequest: NativeStreamingChunkDestinationRequest) => {
|
|
1221
|
+
if (nativeRequest.sessionId !== request.sessionId) return;
|
|
1222
|
+
void handlers
|
|
1223
|
+
.resolveChunkDestination({
|
|
1224
|
+
sequence: nativeRequest.sequence,
|
|
1225
|
+
encrypted: nativeRequest.encrypted,
|
|
1226
|
+
})
|
|
1227
|
+
.then(
|
|
1228
|
+
(destination) =>
|
|
1229
|
+
module.resolveStreamingChunkDestination(
|
|
1230
|
+
nativeRequest.requestId,
|
|
1231
|
+
destination
|
|
1232
|
+
),
|
|
1233
|
+
(error) =>
|
|
1234
|
+
module.rejectStreamingChunkDestination(
|
|
1235
|
+
nativeRequest.requestId,
|
|
1236
|
+
errorMessage(error)
|
|
1237
|
+
)
|
|
1238
|
+
)
|
|
1239
|
+
.catch(() => {});
|
|
1240
|
+
}
|
|
1241
|
+
);
|
|
1242
|
+
const finalizeSubscription = module.onStreamingFinalizeRequested(
|
|
1243
|
+
(nativeRequest: NativeStreamingFinalizeRequest) => {
|
|
1244
|
+
if (nativeRequest.sessionId !== request.sessionId) return;
|
|
1245
|
+
void handlers
|
|
1246
|
+
.finalize({
|
|
1247
|
+
totalChunks: nativeRequest.totalChunks,
|
|
1248
|
+
durationMs: nativeRequest.durationMs,
|
|
1249
|
+
fileSizeBytes: nativeRequest.fileSizeBytes,
|
|
1250
|
+
encrypted: nativeRequest.encrypted,
|
|
1251
|
+
})
|
|
1252
|
+
.then(
|
|
1253
|
+
() => module.resolveStreamingFinalize(nativeRequest.requestId),
|
|
1254
|
+
(error) =>
|
|
1255
|
+
module.rejectStreamingFinalize(
|
|
1256
|
+
nativeRequest.requestId,
|
|
1257
|
+
errorMessage(error)
|
|
1258
|
+
)
|
|
1259
|
+
)
|
|
1260
|
+
.catch(() => {});
|
|
1261
|
+
}
|
|
1262
|
+
);
|
|
1263
|
+
try {
|
|
1264
|
+
return await module.startStreaming(
|
|
1265
|
+
toNativeConnectedDevice(device),
|
|
1266
|
+
request
|
|
1267
|
+
);
|
|
1268
|
+
} finally {
|
|
1269
|
+
progressSubscription.remove();
|
|
1270
|
+
destinationSubscription.remove();
|
|
1271
|
+
finalizeSubscription.remove();
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
|
|
1275
|
+
async abortStreaming(sessionId) {
|
|
1276
|
+
await requireNativeModule().abortStreaming(sessionId);
|
|
1277
|
+
},
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
const logs: BotaDeviceSDKLogClient = {
|
|
1281
|
+
async subscribe(device, onLine) {
|
|
1282
|
+
const module = requireNativeModule();
|
|
1283
|
+
const eventSubscription = module.onDeviceLog((line) => {
|
|
1284
|
+
onLine({
|
|
1285
|
+
level: 'debug',
|
|
1286
|
+
message: line.message,
|
|
1287
|
+
isBacklog: line.isBacklog,
|
|
1288
|
+
});
|
|
1289
|
+
});
|
|
1290
|
+
try {
|
|
1291
|
+
await module.startDeviceLogs(toNativeConnectedDevice(device));
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
eventSubscription.remove();
|
|
1294
|
+
throw error;
|
|
1295
|
+
}
|
|
1296
|
+
let removed = false;
|
|
1297
|
+
return {
|
|
1298
|
+
async remove() {
|
|
1299
|
+
if (removed) return;
|
|
1300
|
+
removed = true;
|
|
1301
|
+
eventSubscription.remove();
|
|
1302
|
+
await module.stopDeviceLogs();
|
|
1303
|
+
},
|
|
1304
|
+
};
|
|
1305
|
+
},
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
const wifi: BotaDeviceSDKWiFiClient = {
|
|
1309
|
+
async configure(device, credentials, grant) {
|
|
1310
|
+
return mapWiFiConfigResult(
|
|
1311
|
+
await requireNativeModule().configureWiFi(
|
|
1312
|
+
toNativeConnectedDevice(device),
|
|
1313
|
+
credentials.ssid,
|
|
1314
|
+
credentials.password,
|
|
1315
|
+
grant.grantBlob
|
|
1316
|
+
)
|
|
1317
|
+
);
|
|
1318
|
+
},
|
|
1319
|
+
|
|
1320
|
+
async disconnect(device) {
|
|
1321
|
+
return mapWiFiConfigResult(
|
|
1322
|
+
await requireNativeModule().disconnectWiFi(
|
|
1323
|
+
toNativeConnectedDevice(device)
|
|
1324
|
+
)
|
|
1325
|
+
);
|
|
1326
|
+
},
|
|
1327
|
+
|
|
1328
|
+
async readStatus(device) {
|
|
1329
|
+
return mapWiFiStatus(
|
|
1330
|
+
await requireNativeModule().readWiFiStatus(
|
|
1331
|
+
toNativeConnectedDevice(device)
|
|
1332
|
+
)
|
|
1333
|
+
);
|
|
1334
|
+
},
|
|
1335
|
+
|
|
1336
|
+
async subscribeToStatus(device, onStatus) {
|
|
1337
|
+
const module = requireNativeModule();
|
|
1338
|
+
const eventSubscription = module.onWiFiStatusUpdated((status) => {
|
|
1339
|
+
onStatus(mapWiFiStatus(status));
|
|
1340
|
+
});
|
|
1341
|
+
try {
|
|
1342
|
+
await module.startWiFiStatusUpdates(toNativeConnectedDevice(device));
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
eventSubscription.remove();
|
|
1345
|
+
throw error;
|
|
1346
|
+
}
|
|
1347
|
+
let removed = false;
|
|
1348
|
+
return {
|
|
1349
|
+
async remove() {
|
|
1350
|
+
if (removed) return;
|
|
1351
|
+
removed = true;
|
|
1352
|
+
eventSubscription.remove();
|
|
1353
|
+
await module.stopWiFiStatusUpdates();
|
|
1354
|
+
},
|
|
1355
|
+
};
|
|
1356
|
+
},
|
|
1357
|
+
|
|
1358
|
+
async scanNetworks(device) {
|
|
1359
|
+
return mapWiFiScanResult(
|
|
1360
|
+
await requireNativeModule().scanWiFiNetworks(
|
|
1361
|
+
toNativeConnectedDevice(device)
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1364
|
+
},
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
const client: BotaDeviceSDKClient = {
|
|
1368
|
+
controls,
|
|
1369
|
+
devices,
|
|
1370
|
+
factoryReset,
|
|
1371
|
+
logs,
|
|
1372
|
+
ota,
|
|
1373
|
+
provisioning,
|
|
1374
|
+
recordings,
|
|
1375
|
+
streaming,
|
|
1376
|
+
wifi,
|
|
1377
|
+
|
|
1378
|
+
async configure(configuration = {}) {
|
|
1379
|
+
const nativeConfiguration: NativeConfiguration = {
|
|
1380
|
+
logLevel: configuration.logLevel ?? 'warn',
|
|
1381
|
+
...(configuration.applicationSupportDirectory
|
|
1382
|
+
? {
|
|
1383
|
+
applicationSupportDirectory:
|
|
1384
|
+
configuration.applicationSupportDirectory,
|
|
1385
|
+
}
|
|
1386
|
+
: {}),
|
|
1387
|
+
};
|
|
1388
|
+
await requireNativeModule().configure(nativeConfiguration);
|
|
1389
|
+
},
|
|
1390
|
+
|
|
1391
|
+
async destroy() {
|
|
1392
|
+
await requireNativeModule().destroy();
|
|
1393
|
+
},
|
|
1394
|
+
|
|
1395
|
+
async getCapabilities() {
|
|
1396
|
+
return requireNativeModule().getCapabilities();
|
|
1397
|
+
},
|
|
1398
|
+
|
|
1399
|
+
async getState() {
|
|
1400
|
+
return (await requireNativeModule().getState()) as BotaDeviceSDKState;
|
|
1401
|
+
},
|
|
1402
|
+
};
|
|
1403
|
+
nativeModule?.onDeviceDisconnected?.((event) => {
|
|
1404
|
+
reportCompatibilityDisconnection(
|
|
1405
|
+
client,
|
|
1406
|
+
event.error ? new Error(event.error) : undefined
|
|
1407
|
+
);
|
|
1408
|
+
});
|
|
1409
|
+
return client;
|
|
1410
|
+
};
|