@bota.dev/react-native-app-sdk 2.0.0-beta.1 → 2.0.0-beta.4
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 +75 -3
- package/android/build.gradle +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +4 -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 +83 -3
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +1 -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 +13 -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/index.js +15 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/managers/DeviceManager.js +8 -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/module/BotaClient.js +13 -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/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/managers/DeviceManager.js +8 -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/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/index.d.ts +6 -4
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/managers/DeviceManager.d.ts +3 -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/package.json +1 -1
- package/scripts/bota_device_sdk_spm_workaround.rb +26 -15
- package/src/BotaClient.ts +13 -3
- package/src/ble/constants.ts +5 -0
- package/src/ble/deviceDiagnostics.ts +238 -0
- package/src/client.ts +189 -18
- package/src/index.ts +14 -0
- package/src/managers/DeviceManager.ts +11 -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/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 SdkClientContext = NativeClientContext;
|
|
36
|
+
export type BotaClientPresence = {
|
|
37
|
+
nextReport(deviceId: string): Promise<SdkClientContext | 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
|
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export {
|
|
|
7
7
|
createBotaDeviceSDK,
|
|
8
8
|
} from './client';
|
|
9
9
|
export type {
|
|
10
|
+
SdkClientContext,
|
|
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,
|
|
@@ -62,6 +66,10 @@ export type {
|
|
|
62
66
|
DeviceConnectionSettings,
|
|
63
67
|
DeviceFlags,
|
|
64
68
|
DeviceLogEvent,
|
|
69
|
+
DeviceDiagnosticEvent,
|
|
70
|
+
DeviceDiagnosticEventType,
|
|
71
|
+
DeviceDiagnosticReasonCode,
|
|
72
|
+
DeviceDiagnosticsBatch,
|
|
65
73
|
DeviceState,
|
|
66
74
|
DeviceStatus,
|
|
67
75
|
DeviceType,
|
|
@@ -87,6 +95,9 @@ export type {
|
|
|
87
95
|
export type {
|
|
88
96
|
AudioCodec,
|
|
89
97
|
DeviceRecording,
|
|
98
|
+
RecordingDataStore,
|
|
99
|
+
UploadRecoveryContext,
|
|
100
|
+
UploadRecoveryProvider,
|
|
90
101
|
StreamingSessionEvents,
|
|
91
102
|
StreamingState,
|
|
92
103
|
StreamingSyncOptions,
|
|
@@ -118,13 +129,16 @@ export type {
|
|
|
118
129
|
FirmwareInfo,
|
|
119
130
|
OtaProgress,
|
|
120
131
|
OtaStage,
|
|
132
|
+
RecordingManagerOptions,
|
|
121
133
|
UploadInfoProvider,
|
|
122
134
|
} from './managers/types';
|
|
123
135
|
|
|
124
136
|
export { DeviceLogDecoder } from './ble/deviceLogs';
|
|
137
|
+
export { DeviceDiagnosticsDecoder, diagnosticEventIdCommand } from './ble/deviceDiagnostics';
|
|
125
138
|
export { DeviceManager } from './managers/DeviceManager';
|
|
126
139
|
export { OTAManager } from './managers/OTAManager';
|
|
127
140
|
export { RecordingManager } from './managers/RecordingManager';
|
|
141
|
+
export const UPLOAD_RECOVERY_VERSION = 1;
|
|
128
142
|
export { StreamingSession } from './managers/StreamingSession';
|
|
129
143
|
export {
|
|
130
144
|
BotaError,
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ConnectedDevice,
|
|
17
17
|
DeviceConnectionSettings,
|
|
18
18
|
DeviceLogEvent,
|
|
19
|
+
DeviceDiagnosticsBatch,
|
|
19
20
|
DeviceStatus,
|
|
20
21
|
DeviceWiFiScanResult,
|
|
21
22
|
DiscoveredDevice,
|
|
@@ -485,6 +486,16 @@ export class DeviceManager extends EventEmitter<DeviceManagerEvents> {
|
|
|
485
486
|
});
|
|
486
487
|
}
|
|
487
488
|
|
|
489
|
+
async readDiagnosticEvents(device: ConnectedDevice): Promise<DeviceDiagnosticsBatch> {
|
|
490
|
+
this.requireConnected(device.id);
|
|
491
|
+
return this.client.logs.readDiagnosticEvents(device);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async acknowledgeDiagnosticEvents(device: ConnectedDevice, acceptedEventIds: string[]): Promise<void> {
|
|
495
|
+
this.requireConnected(device.id);
|
|
496
|
+
await this.client.logs.acknowledgeDiagnosticEvents(device, acceptedEventIds);
|
|
497
|
+
}
|
|
498
|
+
|
|
488
499
|
async readConnectionSettings(
|
|
489
500
|
device: ConnectedDevice
|
|
490
501
|
): Promise<DeviceConnectionSettings> {
|