@bota.dev/react-native-app-sdk 2.0.0-beta.1 → 2.0.0-beta.10
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/README.md +104 -4
- package/android/build.gradle +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +22 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDiagnostics.kt +53 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +16 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +144 -14
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +150 -29
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +27 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +86 -3
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +35 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +135 -6
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +236 -10
- package/generated/codegen-contract.json +568 -8
- package/ios/BotaDeviceSDK.mm +114 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +141 -4
- package/ios/BotaDeviceSDKAppleDevices.swift +11 -0
- package/ios/BotaDeviceSDKAppleDiagnostics.swift +40 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +18 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +138 -6
- package/ios/BotaDeviceSDKAppleRecordings.swift +113 -5
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +40 -0
- package/lib/commonjs/BotaClient.js +16 -2
- package/lib/commonjs/BotaClient.js.map +1 -1
- package/lib/commonjs/ble/constants.js +5 -1
- package/lib/commonjs/ble/constants.js.map +1 -1
- package/lib/commonjs/ble/deviceDiagnostics.js +240 -0
- package/lib/commonjs/ble/deviceDiagnostics.js.map +1 -0
- package/lib/commonjs/client.js +183 -28
- package/lib/commonjs/client.js.map +1 -1
- package/lib/commonjs/clientPresence.js +26 -0
- package/lib/commonjs/clientPresence.js.map +1 -0
- package/lib/commonjs/index.js +29 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +23 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -1
- package/lib/commonjs/managers/NativeUploadQueue.js +386 -0
- package/lib/commonjs/managers/NativeUploadQueue.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +83 -102
- package/lib/commonjs/managers/RecordingManager.js.map +1 -1
- package/lib/commonjs/managers/uploadRecoveryMetadata.js +83 -0
- package/lib/commonjs/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/commonjs/sdkIdentity.js +10 -0
- package/lib/commonjs/sdkIdentity.js.map +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/commonjs/uploadRecovery.js +68 -0
- package/lib/commonjs/uploadRecovery.js.map +1 -0
- package/lib/module/BotaClient.js +16 -2
- package/lib/module/BotaClient.js.map +1 -1
- package/lib/module/ble/constants.js +4 -0
- package/lib/module/ble/constants.js.map +1 -1
- package/lib/module/ble/deviceDiagnostics.js +234 -0
- package/lib/module/ble/deviceDiagnostics.js.map +1 -0
- package/lib/module/client.js +183 -28
- package/lib/module/client.js.map +1 -1
- package/lib/module/clientPresence.js +22 -0
- package/lib/module/clientPresence.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +23 -0
- package/lib/module/managers/DeviceManager.js.map +1 -1
- package/lib/module/managers/NativeUploadQueue.js +380 -0
- package/lib/module/managers/NativeUploadQueue.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +83 -102
- package/lib/module/managers/RecordingManager.js.map +1 -1
- package/lib/module/managers/uploadRecoveryMetadata.js +77 -0
- package/lib/module/managers/uploadRecoveryMetadata.js.map +1 -0
- package/lib/module/sdkIdentity.js +6 -0
- package/lib/module/sdkIdentity.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -1
- package/lib/module/uploadRecovery.js +64 -0
- package/lib/module/uploadRecovery.js.map +1 -0
- package/lib/typescript/BotaClient.d.ts +2 -0
- package/lib/typescript/BotaClient.d.ts.map +1 -1
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -1
- package/lib/typescript/ble/deviceDiagnostics.d.ts +10 -0
- package/lib/typescript/ble/deviceDiagnostics.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +24 -3
- package/lib/typescript/client.d.ts.map +1 -1
- package/lib/typescript/clientPresence.d.ts +26 -0
- package/lib/typescript/clientPresence.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +10 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/managers/DeviceManager.d.ts +5 -1
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -1
- package/lib/typescript/managers/NativeUploadQueue.d.ts +41 -0
- package/lib/typescript/managers/NativeUploadQueue.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +3 -8
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -1
- package/lib/typescript/managers/types.d.ts +6 -1
- package/lib/typescript/managers/types.d.ts.map +1 -1
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts +6 -0
- package/lib/typescript/managers/uploadRecoveryMetadata.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +42 -0
- package/lib/typescript/models/Device.d.ts.map +1 -1
- package/lib/typescript/models/Recording.d.ts +47 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -1
- package/lib/typescript/models/Status.d.ts +5 -1
- package/lib/typescript/models/Status.d.ts.map +1 -1
- package/lib/typescript/sdkIdentity.d.ts +3 -0
- package/lib/typescript/sdkIdentity.d.ts.map +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +70 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -1
- package/lib/typescript/uploadRecovery.d.ts +27 -0
- package/lib/typescript/uploadRecovery.d.ts.map +1 -0
- package/package.json +1 -1
- package/scripts/bota_device_sdk_spm_workaround.rb +26 -15
- package/src/BotaClient.ts +17 -3
- package/src/ble/constants.ts +5 -0
- package/src/ble/deviceDiagnostics.ts +238 -0
- package/src/client.ts +189 -18
- package/src/clientPresence.ts +33 -0
- package/src/index.ts +18 -0
- package/src/managers/DeviceManager.ts +23 -0
- package/src/managers/NativeUploadQueue.ts +330 -0
- package/src/managers/RecordingManager.ts +83 -116
- package/src/managers/types.ts +7 -1
- package/src/managers/uploadRecoveryMetadata.ts +59 -0
- package/src/models/Device.ts +43 -0
- package/src/models/Recording.ts +52 -0
- package/src/models/Status.ts +5 -1
- package/src/sdkIdentity.ts +3 -0
- package/src/specs/NativeBotaDeviceSDK.ts +66 -0
- package/src/uploadRecovery.ts +66 -0
package/src/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
NativeClientContext,
|
|
2
3
|
NativeCapabilities,
|
|
3
4
|
NativeConnectedDevice,
|
|
4
5
|
NativeConfiguration,
|
|
@@ -10,6 +11,7 @@ import type {
|
|
|
10
11
|
NativeEncryptedUploadV2Checkpoint,
|
|
11
12
|
NativeEncryptedUploadV2Progress,
|
|
12
13
|
NativeEncryptedUploadV2Recording,
|
|
14
|
+
NativePendingRecording,
|
|
13
15
|
NativeFactoryResetCompletion,
|
|
14
16
|
NativeFactoryResetGrantRequest,
|
|
15
17
|
NativeFirmwareUpdateProgress,
|
|
@@ -28,6 +30,12 @@ import type {
|
|
|
28
30
|
NativeDeviceWiFiScanResult,
|
|
29
31
|
Spec,
|
|
30
32
|
} from './specs/NativeBotaDeviceSDK';
|
|
33
|
+
import { SDK_PACKAGE, SDK_VERSION } from './sdkIdentity';
|
|
34
|
+
|
|
35
|
+
export type BotaClientPresenceContext = NativeClientContext;
|
|
36
|
+
export type BotaClientPresence = {
|
|
37
|
+
nextReport(deviceId: string): Promise<BotaClientPresenceContext | null>;
|
|
38
|
+
};
|
|
31
39
|
import type {
|
|
32
40
|
ConnectedDevice,
|
|
33
41
|
ConnectionType,
|
|
@@ -37,6 +45,9 @@ import type {
|
|
|
37
45
|
DeviceStatus,
|
|
38
46
|
DeviceType,
|
|
39
47
|
DeviceLogEvent,
|
|
48
|
+
DeviceDiagnosticEventType,
|
|
49
|
+
DeviceDiagnosticReasonCode,
|
|
50
|
+
DeviceDiagnosticsBatch,
|
|
40
51
|
DiscoveredDevice,
|
|
41
52
|
PairingState,
|
|
42
53
|
ReconnectOptions,
|
|
@@ -195,11 +206,24 @@ export type BotaRecordingTransferProgress = {
|
|
|
195
206
|
|
|
196
207
|
export type BotaEncryptedUploadV2Recording = NativeEncryptedUploadV2Recording;
|
|
197
208
|
|
|
209
|
+
export type BotaEncryptedUploadV2PendingRecording = Omit<BotaEncryptedUploadV2Recording, 'durationMs'> & {
|
|
210
|
+
storageFormat: 3;
|
|
211
|
+
startedAt: Date;
|
|
212
|
+
durationMs: number;
|
|
213
|
+
plaintextLength: string;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export type BotaEncryptedUploadV2SyncOptions = {
|
|
217
|
+
signal?: AbortSignal;
|
|
218
|
+
operationId?: string;
|
|
219
|
+
};
|
|
220
|
+
|
|
198
221
|
export type BotaEncryptedUploadV2Capability = NativeEncryptedUploadV2Capability;
|
|
199
222
|
|
|
200
223
|
export type BotaEncryptedUploadV2Checkpoint = NativeEncryptedUploadV2Checkpoint;
|
|
201
224
|
|
|
202
225
|
export type BotaEncryptedUploadV2ProviderContext = {
|
|
226
|
+
operationId: string;
|
|
203
227
|
recording: BotaEncryptedUploadV2Recording;
|
|
204
228
|
capability: BotaEncryptedUploadV2Capability;
|
|
205
229
|
checkpoint?: BotaEncryptedUploadV2Checkpoint;
|
|
@@ -287,6 +311,8 @@ export type BotaDeviceSDKOTAClient = {
|
|
|
287
311
|
};
|
|
288
312
|
|
|
289
313
|
export type BotaDeviceSDKLogClient = {
|
|
314
|
+
readDiagnosticEvents(device: ConnectedDevice): Promise<DeviceDiagnosticsBatch>;
|
|
315
|
+
acknowledgeDiagnosticEvents(device: ConnectedDevice, acceptedEventIds: string[]): Promise<void>;
|
|
290
316
|
subscribe(
|
|
291
317
|
device: ConnectedDevice,
|
|
292
318
|
onLine: (line: DeviceLogEvent) => void
|
|
@@ -310,6 +336,7 @@ export type BotaDeviceSDKWiFiClient = {
|
|
|
310
336
|
|
|
311
337
|
export type BotaDeviceSDKRecordingClient = {
|
|
312
338
|
listRecordings(device: ConnectedDevice): Promise<DeviceRecording[]>;
|
|
339
|
+
listPendingRecordings(device: ConnectedDevice): Promise<Array<DeviceRecording | BotaEncryptedUploadV2PendingRecording>>;
|
|
313
340
|
syncRecording(
|
|
314
341
|
device: ConnectedDevice,
|
|
315
342
|
recording: DeviceRecording,
|
|
@@ -319,12 +346,14 @@ export type BotaDeviceSDKRecordingClient = {
|
|
|
319
346
|
localPath: string;
|
|
320
347
|
e2eEncrypted: boolean;
|
|
321
348
|
contentSha256?: string;
|
|
349
|
+
fileSizeBytes: number;
|
|
322
350
|
}>;
|
|
323
351
|
syncEncryptedRecordingV2(
|
|
324
352
|
device: ConnectedDevice,
|
|
325
353
|
recording: BotaEncryptedUploadV2Recording,
|
|
326
354
|
provider: BotaEncryptedUploadV2ProfileProvider,
|
|
327
|
-
onProgress?: (progress: BotaEncryptedUploadV2Progress) => void
|
|
355
|
+
onProgress?: (progress: BotaEncryptedUploadV2Progress) => void,
|
|
356
|
+
options?: BotaEncryptedUploadV2SyncOptions
|
|
328
357
|
): Promise<void>;
|
|
329
358
|
confirmRecording(
|
|
330
359
|
device: ConnectedDevice,
|
|
@@ -335,6 +364,7 @@ export type BotaDeviceSDKRecordingClient = {
|
|
|
335
364
|
onProgress?: (progress: BotaRecordingTransferProgress) => void
|
|
336
365
|
): Promise<void>;
|
|
337
366
|
cancelRecordingUpload(taskId: string): Promise<void>;
|
|
367
|
+
releaseRecordingFile(taskId: string, localPath: string): Promise<void>;
|
|
338
368
|
loadUploadQueue(): Promise<UploadTask[]>;
|
|
339
369
|
saveUploadQueue(tasks: UploadTask[]): Promise<void>;
|
|
340
370
|
destroyCompatibilityOperations(): Promise<void>;
|
|
@@ -410,6 +440,7 @@ export class BotaNativeModuleError extends Error {
|
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
export type BotaDeviceSDKClient = {
|
|
443
|
+
readonly clientPresence: BotaClientPresence;
|
|
413
444
|
readonly controls: BotaDeviceSDKControlClient;
|
|
414
445
|
readonly devices: BotaDeviceSDKDeviceClient;
|
|
415
446
|
readonly factoryReset: BotaDeviceSDKFactoryResetClient;
|
|
@@ -425,6 +456,13 @@ export type BotaDeviceSDKClient = {
|
|
|
425
456
|
getState(): Promise<BotaDeviceSDKState>;
|
|
426
457
|
};
|
|
427
458
|
|
|
459
|
+
const formatAdvertisedMac = (value: string | undefined): string | null => {
|
|
460
|
+
if (!value || !/^(?:[0-9a-f]{12}|(?:[0-9a-f]{2}:){5}[0-9a-f]{2})$/i.test(value)) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
return value.replace(/:/g, '').toUpperCase().match(/.{2}/g)!.join(':');
|
|
464
|
+
};
|
|
465
|
+
|
|
428
466
|
const mapDiscoveredDevice = (
|
|
429
467
|
device: NativeDiscoveredDevice
|
|
430
468
|
): DiscoveredDevice => ({
|
|
@@ -432,7 +470,7 @@ const mapDiscoveredDevice = (
|
|
|
432
470
|
name: device.name ?? '',
|
|
433
471
|
deviceType: (device.deviceType ?? 'bota_pin') as DeviceType,
|
|
434
472
|
firmwareVersion: device.firmwareVersion ?? '',
|
|
435
|
-
macAddress: device.macAddress
|
|
473
|
+
macAddress: formatAdvertisedMac(device.macAddress),
|
|
436
474
|
pairingState: (device.pairingState ?? 'unpaired') as PairingState,
|
|
437
475
|
rssi: device.rssi,
|
|
438
476
|
discoveredAt: new Date(device.discoveredAtMs),
|
|
@@ -484,6 +522,48 @@ const createOpaqueId = (): string =>
|
|
|
484
522
|
return nibble.toString(16);
|
|
485
523
|
});
|
|
486
524
|
|
|
525
|
+
const catalogInteger = (value: string | undefined, maximum = BigInt(Number.MAX_SAFE_INTEGER)): number => {
|
|
526
|
+
if (typeof value !== 'string' || !/^(0|[1-9][0-9]{0,19})$/.test(value) || BigInt(value) > maximum) {
|
|
527
|
+
throw new Error('invalid native recording catalog metadata');
|
|
528
|
+
}
|
|
529
|
+
return Number(value);
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const mapPendingRecording = (value: NativePendingRecording): DeviceRecording | BotaEncryptedUploadV2PendingRecording => {
|
|
533
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
534
|
+
if (value.profile === 'legacy' && value.legacy && !value.encrypted) {
|
|
535
|
+
const recording = value.legacy;
|
|
536
|
+
if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) ||
|
|
537
|
+
!Number.isSafeInteger(recording.startedAtMs) || recording.startedAtMs < 0 || recording.startedAtMs > 8640000000000000 ||
|
|
538
|
+
!Number.isSafeInteger(recording.durationMs) || recording.durationMs < 0 ||
|
|
539
|
+
!Number.isSafeInteger(recording.fileSize) || recording.fileSize < 0 ||
|
|
540
|
+
typeof recording.codec !== 'string' || typeof recording.isEncrypted !== 'boolean') {
|
|
541
|
+
throw new Error('invalid native recording catalog metadata');
|
|
542
|
+
}
|
|
543
|
+
return mapRecording(recording);
|
|
544
|
+
}
|
|
545
|
+
if (value.profile !== 'encrypted_upload_v2' || !value.encrypted || value.legacy) {
|
|
546
|
+
throw new Error('invalid native recording catalog profile');
|
|
547
|
+
}
|
|
548
|
+
const recording = value.encrypted;
|
|
549
|
+
const plaintextLength = recording.plaintextLength;
|
|
550
|
+
if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) ||
|
|
551
|
+
recording.storageFormat !== 3 || typeof plaintextLength !== 'string' ||
|
|
552
|
+
!/^(0|[1-9][0-9]{0,19})$/.test(plaintextLength) || BigInt(plaintextLength) > 0xffffffffffffffffn ||
|
|
553
|
+
typeof recording.ciphertextLength !== 'string' || !/^[1-9][0-9]{0,19}$/.test(recording.ciphertextLength) || BigInt(recording.ciphertextLength) > 0xffffffffffffffffn ||
|
|
554
|
+
typeof recording.ciphertextSha256 !== 'string' || !/^[0-9a-f]{64}$/.test(recording.ciphertextSha256) ||
|
|
555
|
+
!Number.isInteger(recording.generation) || recording.generation < 0 || recording.generation > 0xffffffff) {
|
|
556
|
+
throw new Error('invalid native recording catalog identity');
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
uuid: recording.uuid, generation: recording.generation,
|
|
560
|
+
ciphertextLength: recording.ciphertextLength, ciphertextSha256: recording.ciphertextSha256,
|
|
561
|
+
storageFormat: 3, plaintextLength, startedAtMs: recording.startedAtMs,
|
|
562
|
+
startedAt: new Date(catalogInteger(recording.startedAtMs, 8640000000000000n)),
|
|
563
|
+
durationMs: catalogInteger(recording.durationMs),
|
|
564
|
+
};
|
|
565
|
+
};
|
|
566
|
+
|
|
487
567
|
const bytesToHex = (value: Uint8Array): string =>
|
|
488
568
|
Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
489
569
|
|
|
@@ -710,6 +790,7 @@ const toNativeRecordingUploadRequest = (
|
|
|
710
790
|
recordingId: task.recordingId,
|
|
711
791
|
deviceId: task.deviceId,
|
|
712
792
|
localPath: task.localPath,
|
|
793
|
+
...(task.fileSizeBytes === undefined ? {} : { fileSizeBytes: task.fileSizeBytes }),
|
|
713
794
|
uploadUrl: task.uploadUrl,
|
|
714
795
|
...(task.uploadToken ? { uploadToken: task.uploadToken } : {}),
|
|
715
796
|
...(task.completeUrl ? { completeUrl: task.completeUrl } : {}),
|
|
@@ -724,9 +805,13 @@ const toNativeRecordingUploadRequest = (
|
|
|
724
805
|
});
|
|
725
806
|
|
|
726
807
|
const parseUploadQueue = (serialized: string): UploadTask[] => {
|
|
727
|
-
const value: unknown = JSON.parse(serialized
|
|
728
|
-
if (!Array.isArray(value))
|
|
808
|
+
const value: unknown = JSON.parse(serialized);
|
|
809
|
+
if (!Array.isArray(value)) throw new Error('Invalid upload recovery journal');
|
|
729
810
|
return value.map((task) => {
|
|
811
|
+
if (!task || typeof task !== 'object' || Array.isArray(task) ||
|
|
812
|
+
typeof task.createdAt !== 'string' || typeof task.updatedAt !== 'string') {
|
|
813
|
+
throw new Error('Invalid upload recovery journal');
|
|
814
|
+
}
|
|
730
815
|
const candidate = task as UploadTask & {
|
|
731
816
|
createdAt: string | Date;
|
|
732
817
|
updatedAt: string | Date;
|
|
@@ -740,6 +825,8 @@ const parseUploadQueue = (serialized: string): UploadTask[] => {
|
|
|
740
825
|
};
|
|
741
826
|
|
|
742
827
|
export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKClient => {
|
|
828
|
+
let presenceStopped = false;
|
|
829
|
+
let presenceGeneration = 0;
|
|
743
830
|
const requireNativeModule = (): Spec => {
|
|
744
831
|
if (!nativeModule) throw new BotaNativeModuleError();
|
|
745
832
|
return nativeModule;
|
|
@@ -1056,6 +1143,11 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1056
1143
|
return values.map(mapRecording);
|
|
1057
1144
|
},
|
|
1058
1145
|
|
|
1146
|
+
async listPendingRecordings(device) {
|
|
1147
|
+
const values = await requireNativeModule().listPendingRecordings(toNativeConnectedDevice(device));
|
|
1148
|
+
return values.map(mapPendingRecording);
|
|
1149
|
+
},
|
|
1150
|
+
|
|
1059
1151
|
async syncRecording(device, recording, onProgress, sinkId) {
|
|
1060
1152
|
const module = requireNativeModule();
|
|
1061
1153
|
const subscription = module.onRecordingTransferProgress((progress) => {
|
|
@@ -1072,21 +1164,48 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1072
1164
|
}
|
|
1073
1165
|
},
|
|
1074
1166
|
|
|
1075
|
-
async syncEncryptedRecordingV2(device, recording, provider, onProgress) {
|
|
1167
|
+
async syncEncryptedRecordingV2(device, recording, provider, onProgress, options = {}) {
|
|
1168
|
+
if (options.signal?.aborted) throw new Error('encrypted upload v2 cancelled');
|
|
1076
1169
|
const module = requireNativeModule();
|
|
1077
|
-
const operationId = createOpaqueId();
|
|
1078
|
-
|
|
1170
|
+
const operationId = options.operationId ?? createOpaqueId();
|
|
1171
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(operationId)) {
|
|
1172
|
+
throw new Error('invalid encrypted upload v2 operation ID');
|
|
1173
|
+
}
|
|
1174
|
+
let active = true;
|
|
1175
|
+
let nativeStarted = false;
|
|
1176
|
+
let cancellation: Promise<void> | undefined;
|
|
1177
|
+
let cancellationFailed = false;
|
|
1178
|
+
const cancel = () => {
|
|
1179
|
+
if (nativeStarted && !cancellation) {
|
|
1180
|
+
try {
|
|
1181
|
+
cancellation = module.cancelEncryptedRecordingV2(operationId).catch(() => { cancellationFailed = true; });
|
|
1182
|
+
} catch {
|
|
1183
|
+
cancellationFailed = true;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
let profileSubscription: BotaEventSubscription | undefined;
|
|
1188
|
+
let progressSubscription: BotaEventSubscription | undefined;
|
|
1189
|
+
try {
|
|
1190
|
+
options.signal?.addEventListener('abort', cancel, { once: true });
|
|
1191
|
+
profileSubscription = module.onEncryptedUploadV2ProfileRequested(
|
|
1079
1192
|
(request) => {
|
|
1080
|
-
if (request.operationId !== operationId) return;
|
|
1193
|
+
if (!active || options.signal?.aborted || request.operationId !== operationId) return;
|
|
1081
1194
|
void (async () => {
|
|
1195
|
+
let decision: BotaEncryptedUploadV2ProfileDecision | undefined;
|
|
1082
1196
|
try {
|
|
1083
|
-
|
|
1197
|
+
decision = await provider({
|
|
1198
|
+
operationId,
|
|
1084
1199
|
recording: request.recording,
|
|
1085
1200
|
capability: request.capability,
|
|
1086
1201
|
...(request.checkpoint === undefined
|
|
1087
1202
|
? {}
|
|
1088
1203
|
: { checkpoint: request.checkpoint }),
|
|
1089
1204
|
});
|
|
1205
|
+
if (!active || options.signal?.aborted) {
|
|
1206
|
+
await module.releaseEncryptedUploadV2Material(decision.materialRegistrationId);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1090
1209
|
if (decision.profile !== 'encrypted_upload_v2') {
|
|
1091
1210
|
throw new Error('encrypted upload v2 requires an explicit v2 profile');
|
|
1092
1211
|
}
|
|
@@ -1095,29 +1214,40 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1095
1214
|
decision
|
|
1096
1215
|
);
|
|
1097
1216
|
} catch (error) {
|
|
1098
|
-
await module.
|
|
1099
|
-
|
|
1100
|
-
'application_material_rejected'
|
|
1101
|
-
|
|
1217
|
+
if (decision) await module.releaseEncryptedUploadV2Material(decision.materialRegistrationId).catch(() => {});
|
|
1218
|
+
if (active && !options.signal?.aborted) {
|
|
1219
|
+
await module.rejectEncryptedUploadV2Profile(request.requestId, 'application_material_rejected');
|
|
1220
|
+
}
|
|
1102
1221
|
}
|
|
1103
1222
|
})().catch(() => {});
|
|
1104
1223
|
}
|
|
1105
1224
|
);
|
|
1106
|
-
|
|
1225
|
+
progressSubscription = module.onEncryptedUploadV2Progress(
|
|
1107
1226
|
(progress) => {
|
|
1108
|
-
if (progress.operationId !== operationId) return;
|
|
1227
|
+
if (!active || options.signal?.aborted || progress.operationId !== operationId) return;
|
|
1109
1228
|
onProgress?.(mapEncryptedUploadV2Progress(progress));
|
|
1110
1229
|
}
|
|
1111
1230
|
);
|
|
1112
|
-
|
|
1231
|
+
if (options.signal?.aborted) throw new Error('encrypted upload v2 cancelled');
|
|
1232
|
+
nativeStarted = true;
|
|
1113
1233
|
await module.syncEncryptedRecordingV2(
|
|
1114
1234
|
toNativeConnectedDevice(device),
|
|
1115
1235
|
recording,
|
|
1116
1236
|
operationId
|
|
1117
1237
|
);
|
|
1118
1238
|
} finally {
|
|
1119
|
-
|
|
1120
|
-
|
|
1239
|
+
active = false;
|
|
1240
|
+
let cleanupFailed = false;
|
|
1241
|
+
for (const remove of [
|
|
1242
|
+
() => options.signal?.removeEventListener('abort', cancel),
|
|
1243
|
+
() => profileSubscription?.remove(),
|
|
1244
|
+
() => progressSubscription?.remove(),
|
|
1245
|
+
]) {
|
|
1246
|
+
try { remove(); } catch { cleanupFailed = true; }
|
|
1247
|
+
}
|
|
1248
|
+
await cancellation;
|
|
1249
|
+
if (cancellationFailed) throw new Error('encrypted upload v2 cancellation failed');
|
|
1250
|
+
if (cleanupFailed) throw new Error('encrypted upload v2 listener cleanup failed');
|
|
1121
1251
|
}
|
|
1122
1252
|
},
|
|
1123
1253
|
|
|
@@ -1148,6 +1278,10 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1148
1278
|
await requireNativeModule().cancelRecordingUpload(taskId);
|
|
1149
1279
|
},
|
|
1150
1280
|
|
|
1281
|
+
async releaseRecordingFile(taskId, localPath) {
|
|
1282
|
+
await requireNativeModule().releaseRecordingFile(taskId, localPath);
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1151
1285
|
async loadUploadQueue() {
|
|
1152
1286
|
return parseUploadQueue(
|
|
1153
1287
|
await requireNativeModule().loadCompatibilityUploadQueue()
|
|
@@ -1278,6 +1412,30 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1278
1412
|
};
|
|
1279
1413
|
|
|
1280
1414
|
const logs: BotaDeviceSDKLogClient = {
|
|
1415
|
+
async readDiagnosticEvents(device) {
|
|
1416
|
+
const batch = await requireNativeModule().readDiagnosticEvents(toNativeConnectedDevice(device));
|
|
1417
|
+
if (batch.schema_version !== 1) throw new Error('Unsupported diagnostic batch schema');
|
|
1418
|
+
return {
|
|
1419
|
+
schema_version: 1,
|
|
1420
|
+
events: batch.events.map(({ report, ...event }) => ({
|
|
1421
|
+
...event,
|
|
1422
|
+
event_type: event.event_type as DeviceDiagnosticEventType,
|
|
1423
|
+
reason_code: event.reason_code as DeviceDiagnosticReasonCode,
|
|
1424
|
+
...(report ? { report: {
|
|
1425
|
+
fault: report.fault,
|
|
1426
|
+
execution: { ...report.execution, pc_trace: [...report.execution.pc_trace] },
|
|
1427
|
+
...(report.runtime ? { runtime: report.runtime } : {}),
|
|
1428
|
+
...(report.breadcrumbs ? { breadcrumbs: [...report.breadcrumbs] } : {}),
|
|
1429
|
+
} } : {}),
|
|
1430
|
+
})),
|
|
1431
|
+
};
|
|
1432
|
+
},
|
|
1433
|
+
async acknowledgeDiagnosticEvents(device, acceptedEventIds) {
|
|
1434
|
+
if (acceptedEventIds.some((id) => !/^[0-9a-f]{16}$/.test(id))) {
|
|
1435
|
+
throw new Error('Diagnostic event_id must be 16 lowercase hex characters');
|
|
1436
|
+
}
|
|
1437
|
+
await requireNativeModule().acknowledgeDiagnosticEvents(toNativeConnectedDevice(device), [...acceptedEventIds]);
|
|
1438
|
+
},
|
|
1281
1439
|
async subscribe(device, onLine) {
|
|
1282
1440
|
const module = requireNativeModule();
|
|
1283
1441
|
const eventSubscription = module.onDeviceLog((line) => {
|
|
@@ -1365,6 +1523,15 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1365
1523
|
};
|
|
1366
1524
|
|
|
1367
1525
|
const client: BotaDeviceSDKClient = {
|
|
1526
|
+
clientPresence: {
|
|
1527
|
+
async nextReport(deviceId) {
|
|
1528
|
+
if (presenceStopped) return null;
|
|
1529
|
+
const generation = presenceGeneration;
|
|
1530
|
+
const report = await requireNativeModule().nextClientPresence(deviceId);
|
|
1531
|
+
if (presenceStopped || generation !== presenceGeneration) return null;
|
|
1532
|
+
return report === null ? null : { ...report, sdkPackage: SDK_PACKAGE, sdkVersion: SDK_VERSION };
|
|
1533
|
+
},
|
|
1534
|
+
},
|
|
1368
1535
|
controls,
|
|
1369
1536
|
devices,
|
|
1370
1537
|
factoryReset,
|
|
@@ -1376,6 +1543,7 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1376
1543
|
wifi,
|
|
1377
1544
|
|
|
1378
1545
|
async configure(configuration = {}) {
|
|
1546
|
+
const generation = presenceGeneration;
|
|
1379
1547
|
const nativeConfiguration: NativeConfiguration = {
|
|
1380
1548
|
logLevel: configuration.logLevel ?? 'warn',
|
|
1381
1549
|
...(configuration.applicationSupportDirectory
|
|
@@ -1386,9 +1554,12 @@ export const createBotaDeviceSDK = (nativeModule: Spec | null): BotaDeviceSDKCli
|
|
|
1386
1554
|
: {}),
|
|
1387
1555
|
};
|
|
1388
1556
|
await requireNativeModule().configure(nativeConfiguration);
|
|
1557
|
+
if (generation === presenceGeneration) presenceStopped = false;
|
|
1389
1558
|
},
|
|
1390
1559
|
|
|
1391
1560
|
async destroy() {
|
|
1561
|
+
presenceStopped = true;
|
|
1562
|
+
presenceGeneration++;
|
|
1392
1563
|
await requireNativeModule().destroy();
|
|
1393
1564
|
},
|
|
1394
1565
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Optional diagnostics, not device attestation or command authority. */
|
|
2
|
+
export interface SdkClientContext {
|
|
3
|
+
schema_version: 1;
|
|
4
|
+
session_id: string;
|
|
5
|
+
sequence: number;
|
|
6
|
+
platform: 'ios' | 'android';
|
|
7
|
+
sdk_package: string;
|
|
8
|
+
sdk_version: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ClientPresence {
|
|
12
|
+
nextReport(deviceId: string): Promise<SdkClientContext | null>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Read fresh BLE status only while the same verified connection and host scope survive.
|
|
16
|
+
* The caller owns authentication, binding generation, scheduling and HTTP.
|
|
17
|
+
* Unavailable presence or a changed scope/session skips reporting without BLE writes.
|
|
18
|
+
*/
|
|
19
|
+
export async function readClientPresenceObservation<T>(options: {
|
|
20
|
+
presence: ClientPresence;
|
|
21
|
+
deviceId: string;
|
|
22
|
+
isCurrent(): boolean;
|
|
23
|
+
readStatus(): Promise<T>;
|
|
24
|
+
}): Promise<{ status: T; clientContext: SdkClientContext } | null> {
|
|
25
|
+
if (!options.isCurrent()) return null;
|
|
26
|
+
const before = await options.presence.nextReport(options.deviceId);
|
|
27
|
+
if (!before || !options.isCurrent()) return null;
|
|
28
|
+
const status = await options.readStatus();
|
|
29
|
+
if (!options.isCurrent()) return null;
|
|
30
|
+
const after = await options.presence.nextReport(options.deviceId);
|
|
31
|
+
if (!after || !options.isCurrent() || before.session_id !== after.session_id) return null;
|
|
32
|
+
return { status, clientContext: after };
|
|
33
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
createBotaDeviceSDK,
|
|
8
8
|
} from './client';
|
|
9
9
|
export type {
|
|
10
|
+
BotaClientPresenceContext,
|
|
11
|
+
BotaClientPresence,
|
|
10
12
|
BotaAsyncEventSubscription,
|
|
11
13
|
BotaDeviceSDKCapabilities,
|
|
12
14
|
BotaDeviceSDKClient,
|
|
@@ -19,6 +21,8 @@ export type {
|
|
|
19
21
|
BotaEncryptedUploadV2ProgressPhase,
|
|
20
22
|
BotaEncryptedUploadV2ProviderContext,
|
|
21
23
|
BotaEncryptedUploadV2Recording,
|
|
24
|
+
BotaEncryptedUploadV2PendingRecording,
|
|
25
|
+
BotaEncryptedUploadV2SyncOptions,
|
|
22
26
|
BotaEncryptedUploadV2SecurityPolicy,
|
|
23
27
|
BotaDeprovisionResult,
|
|
24
28
|
BotaDeviceSDKDeviceClient,
|
|
@@ -51,6 +55,10 @@ export const BotaDeviceSDK = createBotaDeviceSDK(NativeBotaDeviceSDK);
|
|
|
51
55
|
setDefaultCompatibilityClient(BotaDeviceSDK);
|
|
52
56
|
|
|
53
57
|
export { BotaClient } from './BotaClient';
|
|
58
|
+
export type { ClientPresence, SdkClientContext } from './clientPresence';
|
|
59
|
+
export { readClientPresenceObservation } from './clientPresence';
|
|
60
|
+
export { createUploadRecoveryProvider } from './uploadRecovery';
|
|
61
|
+
export type { UploadRecoveryBackend } from './uploadRecovery';
|
|
54
62
|
|
|
55
63
|
export type {
|
|
56
64
|
BleFactoryResetResult,
|
|
@@ -62,6 +70,10 @@ export type {
|
|
|
62
70
|
DeviceConnectionSettings,
|
|
63
71
|
DeviceFlags,
|
|
64
72
|
DeviceLogEvent,
|
|
73
|
+
DeviceDiagnosticEvent,
|
|
74
|
+
DeviceDiagnosticEventType,
|
|
75
|
+
DeviceDiagnosticReasonCode,
|
|
76
|
+
DeviceDiagnosticsBatch,
|
|
65
77
|
DeviceState,
|
|
66
78
|
DeviceStatus,
|
|
67
79
|
DeviceType,
|
|
@@ -87,6 +99,9 @@ export type {
|
|
|
87
99
|
export type {
|
|
88
100
|
AudioCodec,
|
|
89
101
|
DeviceRecording,
|
|
102
|
+
RecordingDataStore,
|
|
103
|
+
UploadRecoveryContext,
|
|
104
|
+
UploadRecoveryProvider,
|
|
90
105
|
StreamingSessionEvents,
|
|
91
106
|
StreamingState,
|
|
92
107
|
StreamingSyncOptions,
|
|
@@ -118,13 +133,16 @@ export type {
|
|
|
118
133
|
FirmwareInfo,
|
|
119
134
|
OtaProgress,
|
|
120
135
|
OtaStage,
|
|
136
|
+
RecordingManagerOptions,
|
|
121
137
|
UploadInfoProvider,
|
|
122
138
|
} from './managers/types';
|
|
123
139
|
|
|
124
140
|
export { DeviceLogDecoder } from './ble/deviceLogs';
|
|
141
|
+
export { DeviceDiagnosticsDecoder, diagnosticEventIdCommand } from './ble/deviceDiagnostics';
|
|
125
142
|
export { DeviceManager } from './managers/DeviceManager';
|
|
126
143
|
export { OTAManager } from './managers/OTAManager';
|
|
127
144
|
export { RecordingManager } from './managers/RecordingManager';
|
|
145
|
+
export const UPLOAD_RECOVERY_VERSION = 1;
|
|
128
146
|
export { StreamingSession } from './managers/StreamingSession';
|
|
129
147
|
export {
|
|
130
148
|
BotaError,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import EventEmitter from 'eventemitter3';
|
|
2
|
+
import type { ClientPresence } from '../clientPresence';
|
|
2
3
|
|
|
3
4
|
import type {
|
|
4
5
|
BotaAsyncEventSubscription,
|
|
@@ -16,6 +17,7 @@ import type {
|
|
|
16
17
|
ConnectedDevice,
|
|
17
18
|
DeviceConnectionSettings,
|
|
18
19
|
DeviceLogEvent,
|
|
20
|
+
DeviceDiagnosticsBatch,
|
|
19
21
|
DeviceStatus,
|
|
20
22
|
DeviceWiFiScanResult,
|
|
21
23
|
DiscoveredDevice,
|
|
@@ -54,6 +56,17 @@ type CacheListener = (
|
|
|
54
56
|
) => void;
|
|
55
57
|
|
|
56
58
|
export class DeviceManager extends EventEmitter<DeviceManagerEvents> {
|
|
59
|
+
readonly clientPresence: ClientPresence = {
|
|
60
|
+
nextReport: async (deviceId) => {
|
|
61
|
+
if (this.destroyed) return null;
|
|
62
|
+
const report = await this.client.clientPresence.nextReport(deviceId);
|
|
63
|
+
if (this.destroyed || !report || (report.platform !== 'ios' && report.platform !== 'android')) return null;
|
|
64
|
+
return {
|
|
65
|
+
schema_version: 1, session_id: report.sessionId, sequence: report.sequence,
|
|
66
|
+
platform: report.platform, sdk_package: report.sdkPackage, sdk_version: report.sdkVersion,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
57
70
|
private readonly client: BotaDeviceSDKClient;
|
|
58
71
|
private readonly discoveredDevices = new Map<string, DiscoveredDevice>();
|
|
59
72
|
private readonly connectedDevices = new Map<string, ConnectedDevice>();
|
|
@@ -485,6 +498,16 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> {
|
|
|
485
498
|
});
|
|
486
499
|
}
|
|
487
500
|
|
|
501
|
+
async readDiagnosticEvents(device: ConnectedDevice): Promise<DeviceDiagnosticsBatch> {
|
|
502
|
+
this.requireConnected(device.id);
|
|
503
|
+
return this.client.logs.readDiagnosticEvents(device);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async acknowledgeDiagnosticEvents(device: ConnectedDevice, acceptedEventIds: string[]): Promise<void> {
|
|
507
|
+
this.requireConnected(device.id);
|
|
508
|
+
await this.client.logs.acknowledgeDiagnosticEvents(device, acceptedEventIds);
|
|
509
|
+
}
|
|
510
|
+
|
|
488
511
|
async readConnectionSettings(
|
|
489
512
|
device: ConnectedDevice
|
|
490
513
|
): Promise<DeviceConnectionSettings> {
|