@bota-dev/react-native-sdk 0.0.2

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 (177) hide show
  1. package/README.md +279 -0
  2. package/lib/commonjs/BotaClient.js +223 -0
  3. package/lib/commonjs/BotaClient.js.map +1 -0
  4. package/lib/commonjs/ble/BleManager.js +494 -0
  5. package/lib/commonjs/ble/BleManager.js.map +1 -0
  6. package/lib/commonjs/ble/constants.js +166 -0
  7. package/lib/commonjs/ble/constants.js.map +1 -0
  8. package/lib/commonjs/ble/index.js +54 -0
  9. package/lib/commonjs/ble/index.js.map +1 -0
  10. package/lib/commonjs/ble/parsers.js +345 -0
  11. package/lib/commonjs/ble/parsers.js.map +1 -0
  12. package/lib/commonjs/index.js +81 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/managers/DeviceManager.js +437 -0
  15. package/lib/commonjs/managers/DeviceManager.js.map +1 -0
  16. package/lib/commonjs/managers/OTAManager.js +227 -0
  17. package/lib/commonjs/managers/OTAManager.js.map +1 -0
  18. package/lib/commonjs/managers/RecordingManager.js +384 -0
  19. package/lib/commonjs/managers/RecordingManager.js.map +1 -0
  20. package/lib/commonjs/managers/index.js +27 -0
  21. package/lib/commonjs/managers/index.js.map +1 -0
  22. package/lib/commonjs/models/Device.js +2 -0
  23. package/lib/commonjs/models/Device.js.map +1 -0
  24. package/lib/commonjs/models/Recording.js +2 -0
  25. package/lib/commonjs/models/Recording.js.map +1 -0
  26. package/lib/commonjs/models/Status.js +6 -0
  27. package/lib/commonjs/models/Status.js.map +1 -0
  28. package/lib/commonjs/models/index.js +39 -0
  29. package/lib/commonjs/models/index.js.map +1 -0
  30. package/lib/commonjs/protocol/ProtocolHandler.js +343 -0
  31. package/lib/commonjs/protocol/ProtocolHandler.js.map +1 -0
  32. package/lib/commonjs/protocol/index.js +13 -0
  33. package/lib/commonjs/protocol/index.js.map +1 -0
  34. package/lib/commonjs/storage/StorageManager.js +333 -0
  35. package/lib/commonjs/storage/StorageManager.js.map +1 -0
  36. package/lib/commonjs/storage/index.js +19 -0
  37. package/lib/commonjs/storage/index.js.map +1 -0
  38. package/lib/commonjs/upload/S3Uploader.js +133 -0
  39. package/lib/commonjs/upload/S3Uploader.js.map +1 -0
  40. package/lib/commonjs/upload/UploadQueue.js +280 -0
  41. package/lib/commonjs/upload/UploadQueue.js.map +1 -0
  42. package/lib/commonjs/upload/index.js +20 -0
  43. package/lib/commonjs/upload/index.js.map +1 -0
  44. package/lib/commonjs/utils/errors.js +187 -0
  45. package/lib/commonjs/utils/errors.js.map +1 -0
  46. package/lib/commonjs/utils/index.js +40 -0
  47. package/lib/commonjs/utils/index.js.map +1 -0
  48. package/lib/commonjs/utils/logger.js +135 -0
  49. package/lib/commonjs/utils/logger.js.map +1 -0
  50. package/lib/commonjs/utils/retry.js +160 -0
  51. package/lib/commonjs/utils/retry.js.map +1 -0
  52. package/lib/module/BotaClient.js +216 -0
  53. package/lib/module/BotaClient.js.map +1 -0
  54. package/lib/module/ble/BleManager.js +484 -0
  55. package/lib/module/ble/BleManager.js.map +1 -0
  56. package/lib/module/ble/constants.js +159 -0
  57. package/lib/module/ble/constants.js.map +1 -0
  58. package/lib/module/ble/index.js +8 -0
  59. package/lib/module/ble/index.js.map +1 -0
  60. package/lib/module/ble/parsers.js +328 -0
  61. package/lib/module/ble/parsers.js.map +1 -0
  62. package/lib/module/index.js +22 -0
  63. package/lib/module/index.js.map +1 -0
  64. package/lib/module/managers/DeviceManager.js +429 -0
  65. package/lib/module/managers/DeviceManager.js.map +1 -0
  66. package/lib/module/managers/OTAManager.js +219 -0
  67. package/lib/module/managers/OTAManager.js.map +1 -0
  68. package/lib/module/managers/RecordingManager.js +376 -0
  69. package/lib/module/managers/RecordingManager.js.map +1 -0
  70. package/lib/module/managers/index.js +8 -0
  71. package/lib/module/managers/index.js.map +1 -0
  72. package/lib/module/models/Device.js +2 -0
  73. package/lib/module/models/Device.js.map +1 -0
  74. package/lib/module/models/Recording.js +2 -0
  75. package/lib/module/models/Recording.js.map +1 -0
  76. package/lib/module/models/Status.js +2 -0
  77. package/lib/module/models/Status.js.map +1 -0
  78. package/lib/module/models/index.js +8 -0
  79. package/lib/module/models/index.js.map +1 -0
  80. package/lib/module/protocol/ProtocolHandler.js +336 -0
  81. package/lib/module/protocol/ProtocolHandler.js.map +1 -0
  82. package/lib/module/protocol/index.js +6 -0
  83. package/lib/module/protocol/index.js.map +1 -0
  84. package/lib/module/storage/StorageManager.js +324 -0
  85. package/lib/module/storage/StorageManager.js.map +1 -0
  86. package/lib/module/storage/index.js +6 -0
  87. package/lib/module/storage/index.js.map +1 -0
  88. package/lib/module/upload/S3Uploader.js +126 -0
  89. package/lib/module/upload/S3Uploader.js.map +1 -0
  90. package/lib/module/upload/UploadQueue.js +272 -0
  91. package/lib/module/upload/UploadQueue.js.map +1 -0
  92. package/lib/module/upload/index.js +7 -0
  93. package/lib/module/upload/index.js.map +1 -0
  94. package/lib/module/utils/errors.js +173 -0
  95. package/lib/module/utils/errors.js.map +1 -0
  96. package/lib/module/utils/index.js +8 -0
  97. package/lib/module/utils/index.js.map +1 -0
  98. package/lib/module/utils/logger.js +129 -0
  99. package/lib/module/utils/logger.js.map +1 -0
  100. package/lib/module/utils/retry.js +149 -0
  101. package/lib/module/utils/retry.js.map +1 -0
  102. package/lib/typescript/src/BotaClient.d.ts +77 -0
  103. package/lib/typescript/src/BotaClient.d.ts.map +1 -0
  104. package/lib/typescript/src/ble/BleManager.d.ts +111 -0
  105. package/lib/typescript/src/ble/BleManager.d.ts.map +1 -0
  106. package/lib/typescript/src/ble/constants.d.ts +111 -0
  107. package/lib/typescript/src/ble/constants.d.ts.map +1 -0
  108. package/lib/typescript/src/ble/index.d.ts +7 -0
  109. package/lib/typescript/src/ble/index.d.ts.map +1 -0
  110. package/lib/typescript/src/ble/parsers.d.ts +100 -0
  111. package/lib/typescript/src/ble/parsers.d.ts.map +1 -0
  112. package/lib/typescript/src/index.d.ts +16 -0
  113. package/lib/typescript/src/index.d.ts.map +1 -0
  114. package/lib/typescript/src/managers/DeviceManager.d.ts +84 -0
  115. package/lib/typescript/src/managers/DeviceManager.d.ts.map +1 -0
  116. package/lib/typescript/src/managers/OTAManager.d.ts +78 -0
  117. package/lib/typescript/src/managers/OTAManager.d.ts.map +1 -0
  118. package/lib/typescript/src/managers/RecordingManager.d.ts +90 -0
  119. package/lib/typescript/src/managers/RecordingManager.d.ts.map +1 -0
  120. package/lib/typescript/src/managers/index.d.ts +7 -0
  121. package/lib/typescript/src/managers/index.d.ts.map +1 -0
  122. package/lib/typescript/src/models/Device.d.ts +139 -0
  123. package/lib/typescript/src/models/Device.d.ts.map +1 -0
  124. package/lib/typescript/src/models/Recording.d.ts +110 -0
  125. package/lib/typescript/src/models/Recording.d.ts.map +1 -0
  126. package/lib/typescript/src/models/Status.d.ts +104 -0
  127. package/lib/typescript/src/models/Status.d.ts.map +1 -0
  128. package/lib/typescript/src/models/index.d.ts +7 -0
  129. package/lib/typescript/src/models/index.d.ts.map +1 -0
  130. package/lib/typescript/src/protocol/ProtocolHandler.d.ts +69 -0
  131. package/lib/typescript/src/protocol/ProtocolHandler.d.ts.map +1 -0
  132. package/lib/typescript/src/protocol/index.d.ts +5 -0
  133. package/lib/typescript/src/protocol/index.d.ts.map +1 -0
  134. package/lib/typescript/src/storage/StorageManager.d.ts +116 -0
  135. package/lib/typescript/src/storage/StorageManager.d.ts.map +1 -0
  136. package/lib/typescript/src/storage/index.d.ts +5 -0
  137. package/lib/typescript/src/storage/index.d.ts.map +1 -0
  138. package/lib/typescript/src/upload/S3Uploader.d.ts +38 -0
  139. package/lib/typescript/src/upload/S3Uploader.d.ts.map +1 -0
  140. package/lib/typescript/src/upload/UploadQueue.d.ts +95 -0
  141. package/lib/typescript/src/upload/UploadQueue.d.ts.map +1 -0
  142. package/lib/typescript/src/upload/index.d.ts +6 -0
  143. package/lib/typescript/src/upload/index.d.ts.map +1 -0
  144. package/lib/typescript/src/utils/errors.d.ts +82 -0
  145. package/lib/typescript/src/utils/errors.d.ts.map +1 -0
  146. package/lib/typescript/src/utils/index.d.ts +7 -0
  147. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  148. package/lib/typescript/src/utils/logger.d.ts +68 -0
  149. package/lib/typescript/src/utils/logger.d.ts.map +1 -0
  150. package/lib/typescript/src/utils/retry.d.ts +53 -0
  151. package/lib/typescript/src/utils/retry.d.ts.map +1 -0
  152. package/package.json +95 -0
  153. package/src/BotaClient.ts +238 -0
  154. package/src/ble/BleManager.ts +573 -0
  155. package/src/ble/constants.ts +158 -0
  156. package/src/ble/index.ts +7 -0
  157. package/src/ble/parsers.ts +395 -0
  158. package/src/index.ts +64 -0
  159. package/src/managers/DeviceManager.ts +545 -0
  160. package/src/managers/OTAManager.ts +263 -0
  161. package/src/managers/RecordingManager.ts +434 -0
  162. package/src/managers/index.ts +12 -0
  163. package/src/models/Device.ts +164 -0
  164. package/src/models/Recording.ts +123 -0
  165. package/src/models/Status.ts +126 -0
  166. package/src/models/index.ts +7 -0
  167. package/src/protocol/ProtocolHandler.ts +459 -0
  168. package/src/protocol/index.ts +5 -0
  169. package/src/storage/StorageManager.ts +343 -0
  170. package/src/storage/index.ts +5 -0
  171. package/src/upload/S3Uploader.ts +164 -0
  172. package/src/upload/UploadQueue.ts +310 -0
  173. package/src/upload/index.ts +6 -0
  174. package/src/utils/errors.ts +310 -0
  175. package/src/utils/index.ts +7 -0
  176. package/src/utils/logger.ts +137 -0
  177. package/src/utils/retry.ts +177 -0
@@ -0,0 +1,459 @@
1
+ /**
2
+ * Protocol Handler - Implements Device-App Protocol for recording transfer
3
+ */
4
+
5
+ import { Buffer } from 'buffer';
6
+ import { Subscription } from 'react-native-ble-plx';
7
+
8
+ import { getBleManager, BleManager } from '../ble/BleManager';
9
+ import {
10
+ SERVICE_BOTA_STORAGE,
11
+ CHAR_STORAGE_INFO,
12
+ CHAR_RECORDING_LIST,
13
+ CHAR_RECORDING_TRANSFER,
14
+ CHAR_TRANSFER_CONTROL,
15
+ TRANSFER_PACKET_TIMEOUT,
16
+ } from '../ble/constants';
17
+ import {
18
+ parseStorageInfo,
19
+ parseRecordingList,
20
+ parseTransferPacket,
21
+ createAckPacket,
22
+ createTransferCommand,
23
+ } from '../ble/parsers';
24
+ import type { StorageInfo } from '../models/Device';
25
+ import type { DeviceRecording, TransferPacket } from '../models/Recording';
26
+ import { TransferError, DeviceError } from '../utils/errors';
27
+ import { logger } from '../utils/logger';
28
+
29
+ const log = logger.tag('ProtocolHandler');
30
+
31
+ /**
32
+ * Transfer state for tracking ongoing transfers
33
+ */
34
+ interface TransferState {
35
+ recordingUuid: string;
36
+ expectedSequence: number;
37
+ receivedPackets: Map<number, Buffer>;
38
+ totalBytes: number;
39
+ isComplete: boolean;
40
+ checksum?: number;
41
+ subscription?: Subscription;
42
+ timeoutId?: NodeJS.Timeout;
43
+ }
44
+
45
+ /**
46
+ * Transfer progress callback
47
+ */
48
+ export type TransferProgressCallback = (
49
+ bytesReceived: number,
50
+ totalBytes?: number
51
+ ) => void;
52
+
53
+ /**
54
+ * Protocol Handler class
55
+ */
56
+ export class ProtocolHandler {
57
+ private bleManager: BleManager;
58
+ private activeTransfers: Map<string, TransferState> = new Map();
59
+
60
+ constructor() {
61
+ this.bleManager = getBleManager();
62
+ }
63
+
64
+ /**
65
+ * Get storage info from device
66
+ */
67
+ async getStorageInfo(deviceId: string): Promise<StorageInfo> {
68
+ if (!this.bleManager.isConnected(deviceId)) {
69
+ throw DeviceError.notConnected(deviceId);
70
+ }
71
+
72
+ const data = await this.bleManager.readCharacteristic(
73
+ deviceId,
74
+ SERVICE_BOTA_STORAGE,
75
+ CHAR_STORAGE_INFO
76
+ );
77
+
78
+ return parseStorageInfo(data);
79
+ }
80
+
81
+ /**
82
+ * List recordings on device
83
+ */
84
+ async listRecordings(deviceId: string): Promise<DeviceRecording[]> {
85
+ if (!this.bleManager.isConnected(deviceId)) {
86
+ throw DeviceError.notConnected(deviceId);
87
+ }
88
+
89
+ log.debug('Listing recordings', { deviceId });
90
+
91
+ return new Promise((resolve, reject) => {
92
+ let recordings: DeviceRecording[] = [];
93
+ let subscription: Subscription | undefined;
94
+ let timeoutId: NodeJS.Timeout | undefined;
95
+
96
+ const cleanup = () => {
97
+ if (timeoutId) clearTimeout(timeoutId);
98
+ subscription?.remove();
99
+ };
100
+
101
+ // Set up timeout
102
+ timeoutId = setTimeout(() => {
103
+ cleanup();
104
+ // If we received some data, return it; otherwise error
105
+ if (recordings.length > 0) {
106
+ resolve(recordings);
107
+ } else {
108
+ reject(new TransferError(
109
+ 'Timeout waiting for recording list',
110
+ 'LIST_TIMEOUT'
111
+ ));
112
+ }
113
+ }, 5000);
114
+
115
+ // Subscribe to recording list notifications
116
+ subscription = this.bleManager.subscribeToCharacteristic(
117
+ deviceId,
118
+ SERVICE_BOTA_STORAGE,
119
+ CHAR_RECORDING_LIST,
120
+ (data) => {
121
+ try {
122
+ const parsed = parseRecordingList(data);
123
+ recordings = recordings.concat(parsed);
124
+
125
+ // Check if this is the last packet (could check for end marker)
126
+ // For now, wait for timeout or explicit end
127
+ } catch (error) {
128
+ log.error('Failed to parse recording list', error as Error);
129
+ }
130
+ },
131
+ (error) => {
132
+ cleanup();
133
+ reject(error);
134
+ }
135
+ );
136
+
137
+ // Send list command
138
+ const command = createTransferCommand('list');
139
+ this.bleManager
140
+ .writeCharacteristic(
141
+ deviceId,
142
+ SERVICE_BOTA_STORAGE,
143
+ CHAR_TRANSFER_CONTROL,
144
+ command
145
+ )
146
+ .catch((error) => {
147
+ cleanup();
148
+ reject(error);
149
+ });
150
+ });
151
+ }
152
+
153
+ /**
154
+ * Transfer a recording from device
155
+ * Returns the audio data as a Buffer
156
+ */
157
+ async transferRecording(
158
+ deviceId: string,
159
+ recordingUuid: string,
160
+ onProgress?: TransferProgressCallback
161
+ ): Promise<Buffer> {
162
+ if (!this.bleManager.isConnected(deviceId)) {
163
+ throw DeviceError.notConnected(deviceId);
164
+ }
165
+
166
+ // Check if transfer already in progress
167
+ if (this.activeTransfers.has(recordingUuid)) {
168
+ throw new TransferError(
169
+ 'Transfer already in progress for this recording',
170
+ 'TRANSFER_IN_PROGRESS',
171
+ recordingUuid
172
+ );
173
+ }
174
+
175
+ log.info('Starting recording transfer', { deviceId, recordingUuid });
176
+
177
+ return new Promise((resolve, reject) => {
178
+ const state: TransferState = {
179
+ recordingUuid,
180
+ expectedSequence: 0,
181
+ receivedPackets: new Map(),
182
+ totalBytes: 0,
183
+ isComplete: false,
184
+ };
185
+
186
+ this.activeTransfers.set(recordingUuid, state);
187
+
188
+ const cleanup = () => {
189
+ if (state.timeoutId) clearTimeout(state.timeoutId);
190
+ state.subscription?.remove();
191
+ this.activeTransfers.delete(recordingUuid);
192
+ };
193
+
194
+ const resetTimeout = () => {
195
+ if (state.timeoutId) clearTimeout(state.timeoutId);
196
+ state.timeoutId = setTimeout(() => {
197
+ cleanup();
198
+ reject(TransferError.timeout(recordingUuid));
199
+ }, TRANSFER_PACKET_TIMEOUT);
200
+ };
201
+
202
+ // Subscribe to transfer data notifications
203
+ state.subscription = this.bleManager.subscribeToCharacteristic(
204
+ deviceId,
205
+ SERVICE_BOTA_STORAGE,
206
+ CHAR_RECORDING_TRANSFER,
207
+ async (data) => {
208
+ try {
209
+ resetTimeout();
210
+
211
+ const packet = parseTransferPacket(data);
212
+ await this.handleTransferPacket(
213
+ deviceId,
214
+ state,
215
+ packet,
216
+ onProgress
217
+ );
218
+
219
+ if (state.isComplete) {
220
+ cleanup();
221
+
222
+ // Assemble the audio data
223
+ const audioData = this.assembleAudioData(state);
224
+
225
+ // Verify checksum if available
226
+ if (state.checksum !== undefined) {
227
+ const calculatedChecksum = this.calculateCrc32(audioData);
228
+ if (calculatedChecksum !== state.checksum) {
229
+ reject(TransferError.checksumMismatch(recordingUuid));
230
+ return;
231
+ }
232
+ }
233
+
234
+ log.info('Transfer completed', {
235
+ recordingUuid,
236
+ size: audioData.length,
237
+ });
238
+
239
+ resolve(audioData);
240
+ }
241
+ } catch (error) {
242
+ cleanup();
243
+ reject(error);
244
+ }
245
+ },
246
+ (error) => {
247
+ cleanup();
248
+ reject(TransferError.interrupted(recordingUuid, error));
249
+ }
250
+ );
251
+
252
+ // Send start transfer command
253
+ const command = createTransferCommand('start', recordingUuid);
254
+ this.bleManager
255
+ .writeCharacteristic(
256
+ deviceId,
257
+ SERVICE_BOTA_STORAGE,
258
+ CHAR_TRANSFER_CONTROL,
259
+ command
260
+ )
261
+ .then(() => {
262
+ resetTimeout();
263
+ })
264
+ .catch((error) => {
265
+ cleanup();
266
+ reject(error);
267
+ });
268
+ });
269
+ }
270
+
271
+ /**
272
+ * Handle a transfer packet from device
273
+ */
274
+ private async handleTransferPacket(
275
+ deviceId: string,
276
+ state: TransferState,
277
+ packet: TransferPacket,
278
+ onProgress?: TransferProgressCallback
279
+ ): Promise<void> {
280
+ switch (packet.type) {
281
+ case 'data':
282
+ if (packet.data) {
283
+ // Store packet data
284
+ state.receivedPackets.set(packet.sequenceNumber, Buffer.from(packet.data));
285
+ state.totalBytes += packet.data.length;
286
+
287
+ // Send ACK
288
+ await this.sendAck(deviceId, 'ack', packet.sequenceNumber);
289
+
290
+ // Report progress
291
+ onProgress?.(state.totalBytes);
292
+
293
+ log.debug('Received data packet', {
294
+ seq: packet.sequenceNumber,
295
+ size: packet.data.length,
296
+ total: state.totalBytes,
297
+ });
298
+ }
299
+ break;
300
+
301
+ case 'eof':
302
+ state.checksum = packet.checksum;
303
+ state.isComplete = true;
304
+
305
+ // Send final ACK
306
+ await this.sendAck(deviceId, 'ack', packet.sequenceNumber);
307
+
308
+ log.debug('Received EOF packet', {
309
+ seq: packet.sequenceNumber,
310
+ checksum: packet.checksum,
311
+ });
312
+ break;
313
+
314
+ case 'error':
315
+ throw TransferError.deviceError(
316
+ state.recordingUuid,
317
+ packet.errorCode ?? 0xff
318
+ );
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Send an ACK packet to device
324
+ */
325
+ private async sendAck(
326
+ deviceId: string,
327
+ type: 'ack' | 'nack' | 'abort',
328
+ sequenceNumber: number
329
+ ): Promise<void> {
330
+ const ackPacket = createAckPacket(type, sequenceNumber);
331
+
332
+ await this.bleManager.writeCharacteristic(
333
+ deviceId,
334
+ SERVICE_BOTA_STORAGE,
335
+ CHAR_RECORDING_TRANSFER,
336
+ ackPacket,
337
+ false // Write without response for speed
338
+ );
339
+ }
340
+
341
+ /**
342
+ * Assemble audio data from received packets
343
+ */
344
+ private assembleAudioData(state: TransferState): Buffer {
345
+ // Sort packets by sequence number and concatenate
346
+ const sortedSequences = Array.from(state.receivedPackets.keys()).sort(
347
+ (a, b) => a - b
348
+ );
349
+
350
+ const chunks: Buffer[] = [];
351
+ for (const seq of sortedSequences) {
352
+ const data = state.receivedPackets.get(seq);
353
+ if (data) {
354
+ chunks.push(data);
355
+ }
356
+ }
357
+
358
+ return Buffer.concat(chunks);
359
+ }
360
+
361
+ /**
362
+ * Calculate CRC32 checksum
363
+ */
364
+ private calculateCrc32(data: Buffer): number {
365
+ let crc = 0xffffffff;
366
+ const table = this.getCrc32Table();
367
+
368
+ for (let i = 0; i < data.length; i++) {
369
+ crc = (crc >>> 8) ^ table[(crc ^ data[i]) & 0xff];
370
+ }
371
+
372
+ return (crc ^ 0xffffffff) >>> 0;
373
+ }
374
+
375
+ /**
376
+ * Get CRC32 lookup table (lazy initialized)
377
+ */
378
+ private crc32Table: number[] | null = null;
379
+ private getCrc32Table(): number[] {
380
+ if (this.crc32Table) {
381
+ return this.crc32Table;
382
+ }
383
+
384
+ const table: number[] = [];
385
+ for (let i = 0; i < 256; i++) {
386
+ let crc = i;
387
+ for (let j = 0; j < 8; j++) {
388
+ crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
389
+ }
390
+ table.push(crc >>> 0);
391
+ }
392
+
393
+ this.crc32Table = table;
394
+ return table;
395
+ }
396
+
397
+ /**
398
+ * Confirm sync to device (allows device to delete local copy)
399
+ */
400
+ async confirmSync(deviceId: string, recordingUuid: string): Promise<void> {
401
+ if (!this.bleManager.isConnected(deviceId)) {
402
+ throw DeviceError.notConnected(deviceId);
403
+ }
404
+
405
+ log.debug('Confirming sync', { deviceId, recordingUuid });
406
+
407
+ const command = createTransferCommand('confirm', recordingUuid);
408
+
409
+ await this.bleManager.writeCharacteristic(
410
+ deviceId,
411
+ SERVICE_BOTA_STORAGE,
412
+ CHAR_TRANSFER_CONTROL,
413
+ command
414
+ );
415
+ }
416
+
417
+ /**
418
+ * Cancel an ongoing transfer
419
+ */
420
+ async cancelTransfer(deviceId: string, recordingUuid: string): Promise<void> {
421
+ const state = this.activeTransfers.get(recordingUuid);
422
+ if (!state) {
423
+ return;
424
+ }
425
+
426
+ log.info('Cancelling transfer', { recordingUuid });
427
+
428
+ // Send abort
429
+ try {
430
+ await this.sendAck(deviceId, 'abort', state.expectedSequence);
431
+ } catch {
432
+ // Ignore errors during cancellation
433
+ }
434
+
435
+ // Clean up
436
+ if (state.timeoutId) clearTimeout(state.timeoutId);
437
+ state.subscription?.remove();
438
+ this.activeTransfers.delete(recordingUuid);
439
+ }
440
+
441
+ /**
442
+ * Check if a transfer is in progress
443
+ */
444
+ isTransferInProgress(recordingUuid: string): boolean {
445
+ return this.activeTransfers.has(recordingUuid);
446
+ }
447
+
448
+ /**
449
+ * Clean up resources
450
+ */
451
+ destroy(): void {
452
+ // Cancel all active transfers
453
+ for (const [, state] of this.activeTransfers) {
454
+ if (state.timeoutId) clearTimeout(state.timeoutId);
455
+ state.subscription?.remove();
456
+ }
457
+ this.activeTransfers.clear();
458
+ }
459
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Protocol module exports
3
+ */
4
+
5
+ export { ProtocolHandler, type TransferProgressCallback } from './ProtocolHandler';