@bota.dev/react-native-app-sdk 2.0.0-beta.1 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +66 -3
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDiagnostics.kt +53 -0
  4. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +16 -0
  5. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +144 -14
  6. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +150 -29
  7. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +27 -0
  8. package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +68 -3
  9. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +33 -0
  10. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +135 -6
  11. package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +236 -10
  12. package/generated/codegen-contract.json +495 -8
  13. package/ios/BotaDeviceSDK.mm +102 -0
  14. package/ios/BotaDeviceSDKAppleBridge.swift +126 -4
  15. package/ios/BotaDeviceSDKAppleDiagnostics.swift +40 -0
  16. package/ios/BotaDeviceSDKAppleLogs.swift +18 -0
  17. package/ios/BotaDeviceSDKAppleRecordingUploads.swift +138 -6
  18. package/ios/BotaDeviceSDKAppleRecordings.swift +113 -5
  19. package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +40 -0
  20. package/lib/commonjs/BotaClient.js +13 -2
  21. package/lib/commonjs/BotaClient.js.map +1 -1
  22. package/lib/commonjs/ble/constants.js +5 -1
  23. package/lib/commonjs/ble/constants.js.map +1 -1
  24. package/lib/commonjs/ble/deviceDiagnostics.js +240 -0
  25. package/lib/commonjs/ble/deviceDiagnostics.js.map +1 -0
  26. package/lib/commonjs/client.js +163 -28
  27. package/lib/commonjs/client.js.map +1 -1
  28. package/lib/commonjs/index.js +15 -0
  29. package/lib/commonjs/index.js.map +1 -1
  30. package/lib/commonjs/managers/DeviceManager.js +8 -0
  31. package/lib/commonjs/managers/DeviceManager.js.map +1 -1
  32. package/lib/commonjs/managers/NativeUploadQueue.js +386 -0
  33. package/lib/commonjs/managers/NativeUploadQueue.js.map +1 -0
  34. package/lib/commonjs/managers/RecordingManager.js +83 -102
  35. package/lib/commonjs/managers/RecordingManager.js.map +1 -1
  36. package/lib/commonjs/managers/uploadRecoveryMetadata.js +83 -0
  37. package/lib/commonjs/managers/uploadRecoveryMetadata.js.map +1 -0
  38. package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -1
  39. package/lib/module/BotaClient.js +13 -2
  40. package/lib/module/BotaClient.js.map +1 -1
  41. package/lib/module/ble/constants.js +4 -0
  42. package/lib/module/ble/constants.js.map +1 -1
  43. package/lib/module/ble/deviceDiagnostics.js +234 -0
  44. package/lib/module/ble/deviceDiagnostics.js.map +1 -0
  45. package/lib/module/client.js +163 -28
  46. package/lib/module/client.js.map +1 -1
  47. package/lib/module/index.js +2 -0
  48. package/lib/module/index.js.map +1 -1
  49. package/lib/module/managers/DeviceManager.js +8 -0
  50. package/lib/module/managers/DeviceManager.js.map +1 -1
  51. package/lib/module/managers/NativeUploadQueue.js +380 -0
  52. package/lib/module/managers/NativeUploadQueue.js.map +1 -0
  53. package/lib/module/managers/RecordingManager.js +83 -102
  54. package/lib/module/managers/RecordingManager.js.map +1 -1
  55. package/lib/module/managers/uploadRecoveryMetadata.js +77 -0
  56. package/lib/module/managers/uploadRecoveryMetadata.js.map +1 -0
  57. package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -1
  58. package/lib/typescript/BotaClient.d.ts.map +1 -1
  59. package/lib/typescript/ble/constants.d.ts +4 -0
  60. package/lib/typescript/ble/constants.d.ts.map +1 -1
  61. package/lib/typescript/ble/deviceDiagnostics.d.ts +10 -0
  62. package/lib/typescript/ble/deviceDiagnostics.d.ts.map +1 -0
  63. package/lib/typescript/client.d.ts +18 -2
  64. package/lib/typescript/client.d.ts.map +1 -1
  65. package/lib/typescript/index.d.ts +6 -4
  66. package/lib/typescript/index.d.ts.map +1 -1
  67. package/lib/typescript/managers/DeviceManager.d.ts +3 -1
  68. package/lib/typescript/managers/DeviceManager.d.ts.map +1 -1
  69. package/lib/typescript/managers/NativeUploadQueue.d.ts +41 -0
  70. package/lib/typescript/managers/NativeUploadQueue.d.ts.map +1 -0
  71. package/lib/typescript/managers/RecordingManager.d.ts +3 -8
  72. package/lib/typescript/managers/RecordingManager.d.ts.map +1 -1
  73. package/lib/typescript/managers/types.d.ts +6 -1
  74. package/lib/typescript/managers/types.d.ts.map +1 -1
  75. package/lib/typescript/managers/uploadRecoveryMetadata.d.ts +6 -0
  76. package/lib/typescript/managers/uploadRecoveryMetadata.d.ts.map +1 -0
  77. package/lib/typescript/models/Device.d.ts +42 -0
  78. package/lib/typescript/models/Device.d.ts.map +1 -1
  79. package/lib/typescript/models/Recording.d.ts +47 -0
  80. package/lib/typescript/models/Recording.d.ts.map +1 -1
  81. package/lib/typescript/models/Status.d.ts +5 -1
  82. package/lib/typescript/models/Status.d.ts.map +1 -1
  83. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +61 -0
  84. package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/scripts/bota_device_sdk_spm_workaround.rb +26 -15
  87. package/src/BotaClient.ts +13 -3
  88. package/src/ble/constants.ts +5 -0
  89. package/src/ble/deviceDiagnostics.ts +238 -0
  90. package/src/client.ts +166 -18
  91. package/src/index.ts +12 -0
  92. package/src/managers/DeviceManager.ts +11 -0
  93. package/src/managers/NativeUploadQueue.ts +330 -0
  94. package/src/managers/RecordingManager.ts +83 -116
  95. package/src/managers/types.ts +7 -1
  96. package/src/managers/uploadRecoveryMetadata.ts +59 -0
  97. package/src/models/Device.ts +43 -0
  98. package/src/models/Recording.ts +52 -0
  99. package/src/models/Status.ts +5 -1
  100. package/src/specs/NativeBotaDeviceSDK.ts +56 -0
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+
3
+ import { Buffer } from 'buffer';
4
+ import { DEVICE_DIAGNOSTICS_EVT_END, DEVICE_DIAGNOSTICS_EVT_DETAIL, DEVICE_DIAGNOSTICS_EVT_META, DEVICE_DIAGNOSTICS_EVT_SIGNATURE } from './constants';
5
+ const EVENT_TYPES = {
6
+ 1: 'reset',
7
+ 2: 'watchdog',
8
+ 3: 'hard_fault',
9
+ 4: 'storage',
10
+ 5: 'power',
11
+ 6: 'radio',
12
+ 7: 'security'
13
+ };
14
+ const REASONS = {
15
+ 1: 'watchdog_timeout',
16
+ 2: 'cpu_illegal_instruction',
17
+ 3: 'cpu_misaligned_access',
18
+ 4: 'cpu_stack_overflow',
19
+ 5: 'cpu_usage_fault',
20
+ 6: 'memory_protection_fault',
21
+ 7: 'invalid_register_read',
22
+ 8: 'invalid_register_write',
23
+ 9: 'audio_subsystem_fault',
24
+ 10: 'wireless_subsystem_fault',
25
+ 11: 'flash_mmu_fault',
26
+ 12: 'low_voltage_reset',
27
+ 255: 'unknown_cpu_exception'
28
+ };
29
+ const SUBSYSTEMS = {
30
+ 0: 'system',
31
+ 1: 'audio',
32
+ 2: 'wireless',
33
+ 3: 'storage',
34
+ 4: 'power',
35
+ 5: 'security'
36
+ };
37
+ const STATES = {
38
+ 0: 'boot',
39
+ 1: 'deep_standby',
40
+ 2: 'recording',
41
+ 3: 'upload_preparing',
42
+ 4: 'uploading',
43
+ 5: 'error_recovery',
44
+ 6: 'full_sleep'
45
+ };
46
+ const BREADCRUMBS = {
47
+ 1: 'boot',
48
+ 2: 'standby_entered',
49
+ 3: 'recording_started',
50
+ 4: 'upload_preparing',
51
+ 5: 'upload_started',
52
+ 6: 'error_recovery',
53
+ 7: 'full_sleep'
54
+ };
55
+ const DETAIL_WIRE_SIZE = 176;
56
+ function u64LeHex(bytes) {
57
+ return Buffer.from(bytes).reverse().toString('hex').padStart(16, '0');
58
+ }
59
+ export function diagnosticEventIdCommand(eventId) {
60
+ if (!/^[0-9a-f]{16}$/.test(eventId)) {
61
+ throw new Error('Diagnostic event_id must be 16 lowercase hex characters');
62
+ }
63
+ return Buffer.concat([Buffer.from([0x11]), Buffer.from(eventId, 'hex').reverse()]);
64
+ }
65
+ function normalizedAddress(value) {
66
+ if (value === 0) return undefined;
67
+ const region = (value & 0x80000000) !== 0 ? 'sdram' : (value & 0x40000000) !== 0 ? 'ram' : 'rom';
68
+ const offset = (value & 0x3fffffff).toString(16).padStart(8, '0');
69
+ return `${region}:${offset}`;
70
+ }
71
+ function fixedHex32(value) {
72
+ return value.toString(16).padStart(8, '0');
73
+ }
74
+ function decodeDetail(bytes) {
75
+ if (bytes.length !== DETAIL_WIRE_SIZE || bytes[0] !== 1) {
76
+ throw new Error('Unsupported diagnostic detail payload');
77
+ }
78
+ const hasReport = (bytes[1] & 1) !== 0;
79
+ const firmwareBuildId = Buffer.from(bytes.subarray(2, 14)).toString('ascii');
80
+ if (!/^[0-9a-f]{12}$/.test(firmwareBuildId)) {
81
+ throw new Error('Invalid diagnostic firmware build id');
82
+ }
83
+ const common = {
84
+ firmware_build_id: firmwareBuildId,
85
+ subsystem: SUBSYSTEMS[bytes[14]] ?? 'system',
86
+ state_before_event: STATES[bytes[15]] ?? 'unknown'
87
+ };
88
+ if (!hasReport) return common;
89
+ const pcCount = Math.min(bytes[17], 6);
90
+ const breadcrumbCount = Math.min(bytes[18], 8);
91
+ const runtimeMask = bytes[19];
92
+ const pcTrace = [];
93
+ for (let index = 0; index < pcCount; index++) {
94
+ const address = normalizedAddress(bytes.readUInt32LE(48 + index * 4));
95
+ if (address) pcTrace.push(address);
96
+ }
97
+ const task = Buffer.from(bytes.subarray(80, 96)).toString('ascii').replace(/\0.*$/, '');
98
+ const breadcrumbs = [];
99
+ for (let index = 0; index < breadcrumbCount; index++) {
100
+ const offset = 96 + index * 10;
101
+ breadcrumbs.push({
102
+ delta_ms: bytes.readInt32LE(offset),
103
+ code: BREADCRUMBS[bytes.readUInt16LE(offset + 4)] ?? 'state_changed',
104
+ arg0: bytes.readInt32LE(offset + 6)
105
+ });
106
+ }
107
+ const reti = normalizedAddress(bytes.readUInt32LE(40));
108
+ const rets = normalizedAddress(bytes.readUInt32LE(44));
109
+ return {
110
+ ...common,
111
+ report: {
112
+ fault: {
113
+ cpu_id: bytes[16],
114
+ cpu_emu: fixedHex32(bytes.readUInt32LE(20)),
115
+ core_emu: fixedHex32(bytes.readUInt32LE(24)),
116
+ hsb_emu: fixedHex32(bytes.readUInt32LE(28)),
117
+ audio_emu: fixedHex32(bytes.readUInt32LE(32)),
118
+ wireless_emu: fixedHex32(bytes.readUInt32LE(36))
119
+ },
120
+ execution: {
121
+ ...(task ? {
122
+ task
123
+ } : {}),
124
+ ...(reti ? {
125
+ reti
126
+ } : {}),
127
+ ...(rets ? {
128
+ rets
129
+ } : {}),
130
+ pc_trace: pcTrace
131
+ },
132
+ ...((runtimeMask & 3) !== 0 ? {
133
+ runtime: {
134
+ ...((runtimeMask & 1) !== 0 ? {
135
+ heap_free_bytes: bytes.readUInt32LE(72)
136
+ } : {}),
137
+ ...((runtimeMask & 2) !== 0 ? {
138
+ task_stack_remaining_bytes: bytes.readUInt32LE(76)
139
+ } : {})
140
+ }
141
+ } : {}),
142
+ ...(breadcrumbs.length ? {
143
+ breadcrumbs
144
+ } : {})
145
+ }
146
+ };
147
+ }
148
+ export class DeviceDiagnosticsDecoder {
149
+ events = new Map();
150
+ details = new Map();
151
+ push(packet) {
152
+ if (packet.length === 17 && packet[0] === DEVICE_DIAGNOSTICS_EVT_META) {
153
+ const type = EVENT_TYPES[packet[2]];
154
+ if (!type) return null;
155
+ this.events.set(packet[1], {
156
+ event_id: u64LeHex(packet.subarray(9, 17)),
157
+ event_type: type,
158
+ reason_code: REASONS[packet.readUInt16LE(3)] ?? 'unknown_cpu_exception',
159
+ uptime_ms: packet.readUInt32LE(5)
160
+ });
161
+ return null;
162
+ }
163
+ if (packet.length === 10 && packet[0] === DEVICE_DIAGNOSTICS_EVT_SIGNATURE) {
164
+ const event = this.events.get(packet[1]);
165
+ if (event) event.signature = u64LeHex(packet.subarray(2, 10));
166
+ return null;
167
+ }
168
+ if (packet.length >= 6 && packet.length <= 20 && packet[0] === DEVICE_DIAGNOSTICS_EVT_DETAIL) {
169
+ const index = packet[1];
170
+ const offset = packet.readUInt16LE(2);
171
+ const total = packet.readUInt16LE(4);
172
+ if (total !== DETAIL_WIRE_SIZE || offset + packet.length - 6 > total) {
173
+ this.reset();
174
+ throw new Error('Invalid diagnostic detail chunk');
175
+ }
176
+ let detail = this.details.get(index);
177
+ if (offset === 0) {
178
+ if (detail) {
179
+ this.reset();
180
+ throw new Error(`Duplicate diagnostic detail at index ${index}`);
181
+ }
182
+ detail = {
183
+ buffer: Buffer.alloc(total),
184
+ received: 0
185
+ };
186
+ this.details.set(index, detail);
187
+ }
188
+ if (!detail || detail.received !== offset) {
189
+ this.reset();
190
+ throw new Error(`Out-of-order diagnostic detail at index ${index}`);
191
+ }
192
+ packet.copy(detail.buffer, offset, 6);
193
+ detail.received += packet.length - 6;
194
+ if (detail.received === total) {
195
+ const event = this.events.get(index);
196
+ if (!event) {
197
+ this.reset();
198
+ throw new Error(`Diagnostic detail without metadata at index ${index}`);
199
+ }
200
+ try {
201
+ Object.assign(event, decodeDetail(detail.buffer));
202
+ } catch (error) {
203
+ this.reset();
204
+ throw error;
205
+ }
206
+ }
207
+ return null;
208
+ }
209
+ if (packet.length === 2 && packet[0] === DEVICE_DIAGNOSTICS_EVT_END) {
210
+ const expected = packet[1];
211
+ const events = [];
212
+ for (let index = 0; index < expected; index++) {
213
+ const event = this.events.get(index);
214
+ if (!event?.signature || !event.firmware_build_id || !event.subsystem || !event.state_before_event || this.details.get(index)?.received !== DETAIL_WIRE_SIZE) {
215
+ this.reset();
216
+ throw new Error(`Incomplete diagnostic event at index ${index}`);
217
+ }
218
+ events.push(event);
219
+ }
220
+ const batch = {
221
+ schema_version: 1,
222
+ events
223
+ };
224
+ this.reset();
225
+ return batch;
226
+ }
227
+ return null;
228
+ }
229
+ reset() {
230
+ this.events.clear();
231
+ this.details.clear();
232
+ }
233
+ }
234
+ //# sourceMappingURL=deviceDiagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["Buffer","DEVICE_DIAGNOSTICS_EVT_END","DEVICE_DIAGNOSTICS_EVT_DETAIL","DEVICE_DIAGNOSTICS_EVT_META","DEVICE_DIAGNOSTICS_EVT_SIGNATURE","EVENT_TYPES","REASONS","SUBSYSTEMS","STATES","BREADCRUMBS","DETAIL_WIRE_SIZE","u64LeHex","bytes","from","reverse","toString","padStart","diagnosticEventIdCommand","eventId","test","Error","concat","normalizedAddress","value","undefined","region","offset","fixedHex32","decodeDetail","length","hasReport","firmwareBuildId","subarray","common","firmware_build_id","subsystem","state_before_event","pcCount","Math","min","breadcrumbCount","runtimeMask","pcTrace","index","address","readUInt32LE","push","task","replace","breadcrumbs","delta_ms","readInt32LE","code","readUInt16LE","arg0","reti","rets","report","fault","cpu_id","cpu_emu","core_emu","hsb_emu","audio_emu","wireless_emu","execution","pc_trace","runtime","heap_free_bytes","task_stack_remaining_bytes","DeviceDiagnosticsDecoder","events","Map","details","packet","type","set","event_id","event_type","reason_code","uptime_ms","event","get","signature","total","reset","detail","buffer","alloc","received","copy","Object","assign","error","expected","batch","schema_version","clear"],"sourceRoot":"../../../src","sources":["ble/deviceDiagnostics.ts"],"mappings":"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","ignoreList":[]}
@@ -8,12 +8,18 @@ export class BotaNativeModuleError extends Error {
8
8
  this.name = 'BotaNativeModuleError';
9
9
  }
10
10
  }
11
+ const formatAdvertisedMac = value => {
12
+ if (!value || !/^(?:[0-9a-f]{12}|(?:[0-9a-f]{2}:){5}[0-9a-f]{2})$/i.test(value)) {
13
+ return null;
14
+ }
15
+ return value.replace(/:/g, '').toUpperCase().match(/.{2}/g).join(':');
16
+ };
11
17
  const mapDiscoveredDevice = device => ({
12
18
  id: device.id,
13
19
  name: device.name ?? '',
14
20
  deviceType: device.deviceType ?? 'bota_pin',
15
21
  firmwareVersion: device.firmwareVersion ?? '',
16
- macAddress: device.macAddress ?? null,
22
+ macAddress: formatAdvertisedMac(device.macAddress),
17
23
  pairingState: device.pairingState ?? 'unpaired',
18
24
  rssi: device.rssi,
19
25
  discoveredAt: new Date(device.discoveredAtMs)
@@ -53,6 +59,41 @@ const createOpaqueId = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy
53
59
  const nibble = value === 'x' ? random : random & 0x3 | 0x8;
54
60
  return nibble.toString(16);
55
61
  });
62
+ const catalogInteger = (value, maximum = BigInt(Number.MAX_SAFE_INTEGER)) => {
63
+ if (typeof value !== 'string' || !/^(0|[1-9][0-9]{0,19})$/.test(value) || BigInt(value) > maximum) {
64
+ throw new Error('invalid native recording catalog metadata');
65
+ }
66
+ return Number(value);
67
+ };
68
+ const mapPendingRecording = value => {
69
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
70
+ if (value.profile === 'legacy' && value.legacy && !value.encrypted) {
71
+ const recording = value.legacy;
72
+ if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) || !Number.isSafeInteger(recording.startedAtMs) || recording.startedAtMs < 0 || recording.startedAtMs > 8640000000000000 || !Number.isSafeInteger(recording.durationMs) || recording.durationMs < 0 || !Number.isSafeInteger(recording.fileSize) || recording.fileSize < 0 || typeof recording.codec !== 'string' || typeof recording.isEncrypted !== 'boolean') {
73
+ throw new Error('invalid native recording catalog metadata');
74
+ }
75
+ return mapRecording(recording);
76
+ }
77
+ if (value.profile !== 'encrypted_upload_v2' || !value.encrypted || value.legacy) {
78
+ throw new Error('invalid native recording catalog profile');
79
+ }
80
+ const recording = value.encrypted;
81
+ const plaintextLength = recording.plaintextLength;
82
+ if (typeof recording.uuid !== 'string' || !uuidPattern.test(recording.uuid) || recording.storageFormat !== 3 || typeof plaintextLength !== 'string' || !/^(0|[1-9][0-9]{0,19})$/.test(plaintextLength) || BigInt(plaintextLength) > 0xffffffffffffffffn || typeof recording.ciphertextLength !== 'string' || !/^[1-9][0-9]{0,19}$/.test(recording.ciphertextLength) || BigInt(recording.ciphertextLength) > 0xffffffffffffffffn || typeof recording.ciphertextSha256 !== 'string' || !/^[0-9a-f]{64}$/.test(recording.ciphertextSha256) || !Number.isInteger(recording.generation) || recording.generation < 0 || recording.generation > 0xffffffff) {
83
+ throw new Error('invalid native recording catalog identity');
84
+ }
85
+ return {
86
+ uuid: recording.uuid,
87
+ generation: recording.generation,
88
+ ciphertextLength: recording.ciphertextLength,
89
+ ciphertextSha256: recording.ciphertextSha256,
90
+ storageFormat: 3,
91
+ plaintextLength,
92
+ startedAtMs: recording.startedAtMs,
93
+ startedAt: new Date(catalogInteger(recording.startedAtMs, 8640000000000000n)),
94
+ durationMs: catalogInteger(recording.durationMs)
95
+ };
96
+ };
56
97
  const bytesToHex = value => Array.from(value, byte => byte.toString(16).padStart(2, '0')).join('');
57
98
  const toNativeConnectionSettings = settings => ({
58
99
  enabledConnections: settings.enabled_connections,
@@ -244,6 +285,9 @@ const toNativeRecordingUploadRequest = task => ({
244
285
  recordingId: task.recordingId,
245
286
  deviceId: task.deviceId,
246
287
  localPath: task.localPath,
288
+ ...(task.fileSizeBytes === undefined ? {} : {
289
+ fileSizeBytes: task.fileSizeBytes
290
+ }),
247
291
  uploadUrl: task.uploadUrl,
248
292
  ...(task.uploadToken ? {
249
293
  uploadToken: task.uploadToken
@@ -263,9 +307,12 @@ const toNativeRecordingUploadRequest = task => ({
263
307
  } : {})
264
308
  });
265
309
  const parseUploadQueue = serialized => {
266
- const value = JSON.parse(serialized || '[]');
267
- if (!Array.isArray(value)) return [];
310
+ const value = JSON.parse(serialized);
311
+ if (!Array.isArray(value)) throw new Error('Invalid upload recovery journal');
268
312
  return value.map(task => {
313
+ if (!task || typeof task !== 'object' || Array.isArray(task) || typeof task.createdAt !== 'string' || typeof task.updatedAt !== 'string') {
314
+ throw new Error('Invalid upload recovery journal');
315
+ }
269
316
  const candidate = task;
270
317
  return {
271
318
  ...candidate,
@@ -494,6 +541,10 @@ export const createBotaDeviceSDK = nativeModule => {
494
541
  const values = await requireNativeModule().listRecordings(toNativeConnectedDevice(device));
495
542
  return values.map(mapRecording);
496
543
  },
544
+ async listPendingRecordings(device) {
545
+ const values = await requireNativeModule().listPendingRecordings(toNativeConnectedDevice(device));
546
+ return values.map(mapPendingRecording);
547
+ },
497
548
  async syncRecording(device, recording, onProgress, sinkId) {
498
549
  const module = requireNativeModule();
499
550
  const subscription = module.onRecordingTransferProgress(progress => {
@@ -505,38 +556,83 @@ export const createBotaDeviceSDK = nativeModule => {
505
556
  subscription.remove();
506
557
  }
507
558
  },
508
- async syncEncryptedRecordingV2(device, recording, provider, onProgress) {
559
+ async syncEncryptedRecordingV2(device, recording, provider, onProgress, options = {}) {
560
+ if (options.signal?.aborted) throw new Error('encrypted upload v2 cancelled');
509
561
  const module = requireNativeModule();
510
- const operationId = createOpaqueId();
511
- const profileSubscription = module.onEncryptedUploadV2ProfileRequested(request => {
512
- if (request.operationId !== operationId) return;
513
- void (async () => {
562
+ const operationId = options.operationId ?? createOpaqueId();
563
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(operationId)) {
564
+ throw new Error('invalid encrypted upload v2 operation ID');
565
+ }
566
+ let active = true;
567
+ let nativeStarted = false;
568
+ let cancellation;
569
+ let cancellationFailed = false;
570
+ const cancel = () => {
571
+ if (nativeStarted && !cancellation) {
514
572
  try {
515
- const decision = await provider({
516
- recording: request.recording,
517
- capability: request.capability,
518
- ...(request.checkpoint === undefined ? {} : {
519
- checkpoint: request.checkpoint
520
- })
573
+ cancellation = module.cancelEncryptedRecordingV2(operationId).catch(() => {
574
+ cancellationFailed = true;
521
575
  });
522
- if (decision.profile !== 'encrypted_upload_v2') {
523
- throw new Error('encrypted upload v2 requires an explicit v2 profile');
524
- }
525
- await module.resolveEncryptedUploadV2Profile(request.requestId, decision);
526
- } catch (error) {
527
- await module.rejectEncryptedUploadV2Profile(request.requestId, 'application_material_rejected');
576
+ } catch {
577
+ cancellationFailed = true;
528
578
  }
529
- })().catch(() => {});
530
- });
531
- const progressSubscription = module.onEncryptedUploadV2Progress(progress => {
532
- if (progress.operationId !== operationId) return;
533
- onProgress?.(mapEncryptedUploadV2Progress(progress));
534
- });
579
+ }
580
+ };
581
+ let profileSubscription;
582
+ let progressSubscription;
535
583
  try {
584
+ options.signal?.addEventListener('abort', cancel, {
585
+ once: true
586
+ });
587
+ profileSubscription = module.onEncryptedUploadV2ProfileRequested(request => {
588
+ if (!active || options.signal?.aborted || request.operationId !== operationId) return;
589
+ void (async () => {
590
+ let decision;
591
+ try {
592
+ decision = await provider({
593
+ operationId,
594
+ recording: request.recording,
595
+ capability: request.capability,
596
+ ...(request.checkpoint === undefined ? {} : {
597
+ checkpoint: request.checkpoint
598
+ })
599
+ });
600
+ if (!active || options.signal?.aborted) {
601
+ await module.releaseEncryptedUploadV2Material(decision.materialRegistrationId);
602
+ return;
603
+ }
604
+ if (decision.profile !== 'encrypted_upload_v2') {
605
+ throw new Error('encrypted upload v2 requires an explicit v2 profile');
606
+ }
607
+ await module.resolveEncryptedUploadV2Profile(request.requestId, decision);
608
+ } catch (error) {
609
+ if (decision) await module.releaseEncryptedUploadV2Material(decision.materialRegistrationId).catch(() => {});
610
+ if (active && !options.signal?.aborted) {
611
+ await module.rejectEncryptedUploadV2Profile(request.requestId, 'application_material_rejected');
612
+ }
613
+ }
614
+ })().catch(() => {});
615
+ });
616
+ progressSubscription = module.onEncryptedUploadV2Progress(progress => {
617
+ if (!active || options.signal?.aborted || progress.operationId !== operationId) return;
618
+ onProgress?.(mapEncryptedUploadV2Progress(progress));
619
+ });
620
+ if (options.signal?.aborted) throw new Error('encrypted upload v2 cancelled');
621
+ nativeStarted = true;
536
622
  await module.syncEncryptedRecordingV2(toNativeConnectedDevice(device), recording, operationId);
537
623
  } finally {
538
- profileSubscription.remove();
539
- progressSubscription.remove();
624
+ active = false;
625
+ let cleanupFailed = false;
626
+ for (const remove of [() => options.signal?.removeEventListener('abort', cancel), () => profileSubscription?.remove(), () => progressSubscription?.remove()]) {
627
+ try {
628
+ remove();
629
+ } catch {
630
+ cleanupFailed = true;
631
+ }
632
+ }
633
+ await cancellation;
634
+ if (cancellationFailed) throw new Error('encrypted upload v2 cancellation failed');
635
+ if (cleanupFailed) throw new Error('encrypted upload v2 listener cleanup failed');
540
636
  }
541
637
  },
542
638
  async uploadRecordingFile(task, onProgress) {
@@ -560,6 +656,9 @@ export const createBotaDeviceSDK = nativeModule => {
560
656
  async cancelRecordingUpload(taskId) {
561
657
  await requireNativeModule().cancelRecordingUpload(taskId);
562
658
  },
659
+ async releaseRecordingFile(taskId, localPath) {
660
+ await requireNativeModule().releaseRecordingFile(taskId, localPath);
661
+ },
563
662
  async loadUploadQueue() {
564
663
  return parseUploadQueue(await requireNativeModule().loadCompatibilityUploadQueue());
565
664
  },
@@ -642,6 +741,42 @@ export const createBotaDeviceSDK = nativeModule => {
642
741
  }
643
742
  };
644
743
  const logs = {
744
+ async readDiagnosticEvents(device) {
745
+ const batch = await requireNativeModule().readDiagnosticEvents(toNativeConnectedDevice(device));
746
+ if (batch.schema_version !== 1) throw new Error('Unsupported diagnostic batch schema');
747
+ return {
748
+ schema_version: 1,
749
+ events: batch.events.map(({
750
+ report,
751
+ ...event
752
+ }) => ({
753
+ ...event,
754
+ event_type: event.event_type,
755
+ reason_code: event.reason_code,
756
+ ...(report ? {
757
+ report: {
758
+ fault: report.fault,
759
+ execution: {
760
+ ...report.execution,
761
+ pc_trace: [...report.execution.pc_trace]
762
+ },
763
+ ...(report.runtime ? {
764
+ runtime: report.runtime
765
+ } : {}),
766
+ ...(report.breadcrumbs ? {
767
+ breadcrumbs: [...report.breadcrumbs]
768
+ } : {})
769
+ }
770
+ } : {})
771
+ }))
772
+ };
773
+ },
774
+ async acknowledgeDiagnosticEvents(device, acceptedEventIds) {
775
+ if (acceptedEventIds.some(id => !/^[0-9a-f]{16}$/.test(id))) {
776
+ throw new Error('Diagnostic event_id must be 16 lowercase hex characters');
777
+ }
778
+ await requireNativeModule().acknowledgeDiagnosticEvents(toNativeConnectedDevice(device), [...acceptedEventIds]);
779
+ },
645
780
  async subscribe(device, onLine) {
646
781
  const module = requireNativeModule();
647
782
  const eventSubscription = module.onDeviceLog(line => {