@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,263 @@
1
+ /**
2
+ * OTA Manager - Handles firmware over-the-air updates
3
+ *
4
+ * Note: Full Nordic DFU implementation requires native modules.
5
+ * This is a placeholder that handles version checking and update preparation.
6
+ * Actual DFU transfer would need react-native-nordic-dfu or similar.
7
+ */
8
+
9
+ import EventEmitter from 'eventemitter3';
10
+
11
+ import { getBleManager } from '../ble/BleManager';
12
+ import {
13
+ SERVICE_BOTA_CONTROL,
14
+ CHAR_DEVICE_COMMAND,
15
+ DEVICE_CMD_ENTER_DFU,
16
+ } from '../ble/constants';
17
+ import type { ConnectedDevice } from '../models/Device';
18
+ import { DeviceError } from '../utils/errors';
19
+ import { logger } from '../utils/logger';
20
+ import { Buffer } from 'buffer';
21
+
22
+ const log = logger.tag('OTAManager');
23
+
24
+ /**
25
+ * Firmware info
26
+ */
27
+ export interface FirmwareInfo {
28
+ version: string;
29
+ url: string;
30
+ checksum: string;
31
+ releaseNotes?: string;
32
+ size: number;
33
+ }
34
+
35
+ /**
36
+ * OTA progress stages
37
+ */
38
+ export type OtaStage =
39
+ | 'checking'
40
+ | 'downloading'
41
+ | 'preparing'
42
+ | 'updating'
43
+ | 'verifying'
44
+ | 'completed'
45
+ | 'failed';
46
+
47
+ /**
48
+ * OTA progress
49
+ */
50
+ export interface OtaProgress {
51
+ stage: OtaStage;
52
+ progress: number;
53
+ error?: string;
54
+ }
55
+
56
+ /**
57
+ * Events emitted by OTAManager
58
+ */
59
+ interface OTAManagerEvents {
60
+ updateAvailable: (firmware: FirmwareInfo) => void;
61
+ progress: (deviceId: string, progress: OtaProgress) => void;
62
+ completed: (deviceId: string, version: string) => void;
63
+ failed: (deviceId: string, error: Error) => void;
64
+ }
65
+
66
+ /**
67
+ * OTA Manager class
68
+ */
69
+ export class OTAManager extends EventEmitter<OTAManagerEvents> {
70
+ private firmwareCdnUrl: string;
71
+
72
+ constructor(firmwareCdnUrl: string = 'https://cdn.bota.dev/firmware') {
73
+ super();
74
+ this.firmwareCdnUrl = firmwareCdnUrl;
75
+ }
76
+
77
+ /**
78
+ * Check for available firmware updates
79
+ */
80
+ async checkForUpdate(device: ConnectedDevice): Promise<FirmwareInfo | null> {
81
+ log.info('Checking for firmware update', {
82
+ deviceId: device.id,
83
+ currentVersion: device.firmwareVersion,
84
+ deviceType: device.deviceType,
85
+ });
86
+
87
+ this.emit('progress', device.id, { stage: 'checking', progress: 0 });
88
+
89
+ try {
90
+ // Fetch latest firmware info from CDN
91
+ const response = await fetch(
92
+ `${this.firmwareCdnUrl}/latest?device_type=${device.deviceType}&current=${device.firmwareVersion}`
93
+ );
94
+
95
+ if (!response.ok) {
96
+ if (response.status === 404) {
97
+ log.info('No update available', { deviceId: device.id });
98
+ return null;
99
+ }
100
+ throw new Error(`Failed to check for updates: ${response.status}`);
101
+ }
102
+
103
+ const firmware = (await response.json()) as FirmwareInfo;
104
+
105
+ // Compare versions
106
+ if (this.isNewerVersion(firmware.version, device.firmwareVersion)) {
107
+ log.info('Update available', {
108
+ deviceId: device.id,
109
+ currentVersion: device.firmwareVersion,
110
+ newVersion: firmware.version,
111
+ });
112
+
113
+ this.emit('updateAvailable', firmware);
114
+ return firmware;
115
+ }
116
+
117
+ log.info('Device is up to date', {
118
+ deviceId: device.id,
119
+ version: device.firmwareVersion,
120
+ });
121
+
122
+ return null;
123
+ } catch (error) {
124
+ log.error('Failed to check for update', error as Error, {
125
+ deviceId: device.id,
126
+ });
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Download firmware package
133
+ */
134
+ async downloadFirmware(firmware: FirmwareInfo): Promise<ArrayBuffer> {
135
+ log.info('Downloading firmware', {
136
+ version: firmware.version,
137
+ size: firmware.size,
138
+ });
139
+
140
+ const response = await fetch(firmware.url);
141
+
142
+ if (!response.ok) {
143
+ throw new Error(`Failed to download firmware: ${response.status}`);
144
+ }
145
+
146
+ const data = await response.arrayBuffer();
147
+
148
+ // Verify checksum
149
+ // Note: In production, implement proper checksum verification
150
+ log.info('Firmware downloaded', { size: data.byteLength });
151
+
152
+ return data;
153
+ }
154
+
155
+ /**
156
+ * Prepare device for DFU mode
157
+ */
158
+ async enterDfuMode(device: ConnectedDevice): Promise<void> {
159
+ if (!getBleManager().isConnected(device.id)) {
160
+ throw DeviceError.notConnected(device.id);
161
+ }
162
+
163
+ log.info('Entering DFU mode', { deviceId: device.id });
164
+
165
+ // Send DFU command to device
166
+ await getBleManager().writeCharacteristic(
167
+ device.id,
168
+ SERVICE_BOTA_CONTROL,
169
+ CHAR_DEVICE_COMMAND,
170
+ Buffer.from([DEVICE_CMD_ENTER_DFU])
171
+ );
172
+
173
+ // Device will disconnect and reboot into DFU mode
174
+ log.info('DFU command sent, device will reboot', { deviceId: device.id });
175
+ }
176
+
177
+ /**
178
+ * Perform firmware update
179
+ *
180
+ * Note: This is a placeholder. Full implementation requires:
181
+ * 1. react-native-nordic-dfu for actual DFU transfer
182
+ * 2. Native module integration
183
+ * 3. DFU package preparation
184
+ */
185
+ async performUpdate(
186
+ device: ConnectedDevice,
187
+ firmware: FirmwareInfo
188
+ ): Promise<void> {
189
+ log.info('Starting firmware update', {
190
+ deviceId: device.id,
191
+ version: firmware.version,
192
+ });
193
+
194
+ this.emit('progress', device.id, { stage: 'downloading', progress: 0 });
195
+
196
+ try {
197
+ // Download firmware
198
+ await this.downloadFirmware(firmware);
199
+
200
+ this.emit('progress', device.id, { stage: 'downloading', progress: 1 });
201
+ this.emit('progress', device.id, { stage: 'preparing', progress: 0 });
202
+
203
+ // Enter DFU mode
204
+ await this.enterDfuMode(device);
205
+
206
+ this.emit('progress', device.id, { stage: 'preparing', progress: 1 });
207
+
208
+ // Note: At this point, we would use react-native-nordic-dfu
209
+ // to perform the actual firmware transfer.
210
+ // For now, we throw an error indicating this needs native implementation.
211
+
212
+ throw new Error(
213
+ 'Full DFU implementation requires react-native-nordic-dfu native module. ' +
214
+ 'Device has entered DFU mode and is advertising as "BotaDFU-xxx".'
215
+ );
216
+
217
+ // After DFU completes:
218
+ // this.emit('progress', device.id, { stage: 'completed', progress: 1 });
219
+ // this.emit('completed', device.id, firmware.version);
220
+ } catch (error) {
221
+ const err = error as Error;
222
+ log.error('Firmware update failed', err, { deviceId: device.id });
223
+
224
+ this.emit('progress', device.id, {
225
+ stage: 'failed',
226
+ progress: 0,
227
+ error: err.message,
228
+ });
229
+ this.emit('failed', device.id, err);
230
+
231
+ throw error;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Compare semantic versions
237
+ */
238
+ private isNewerVersion(newVersion: string, currentVersion: string): boolean {
239
+ const parseVersion = (v: string): number[] => {
240
+ return v.split('.').map((n) => parseInt(n, 10) || 0);
241
+ };
242
+
243
+ const newParts = parseVersion(newVersion);
244
+ const currentParts = parseVersion(currentVersion);
245
+
246
+ for (let i = 0; i < Math.max(newParts.length, currentParts.length); i++) {
247
+ const newPart = newParts[i] || 0;
248
+ const currentPart = currentParts[i] || 0;
249
+
250
+ if (newPart > currentPart) return true;
251
+ if (newPart < currentPart) return false;
252
+ }
253
+
254
+ return false;
255
+ }
256
+
257
+ /**
258
+ * Clean up resources
259
+ */
260
+ destroy(): void {
261
+ this.removeAllListeners();
262
+ }
263
+ }
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Recording Manager - Handles recording sync and upload operations
3
+ */
4
+
5
+ import EventEmitter from 'eventemitter3';
6
+
7
+ import { ProtocolHandler } from '../protocol/ProtocolHandler';
8
+ import { StorageManager } from '../storage/StorageManager';
9
+ import { UploadQueue } from '../upload/UploadQueue';
10
+ import type { ConnectedDevice, StorageInfo } from '../models/Device';
11
+ import type {
12
+ DeviceRecording,
13
+ UploadInfo,
14
+ SyncProgress,
15
+ UploadTask,
16
+ } from '../models/Recording';
17
+ import type { RecordingManagerEvents } from '../models/Status';
18
+ import { DeviceError } from '../utils/errors';
19
+ import { logger } from '../utils/logger';
20
+ import { getBleManager } from '../ble/BleManager';
21
+
22
+ const log = logger.tag('RecordingManager');
23
+
24
+ /**
25
+ * Upload info provider callback type
26
+ */
27
+ export type UploadInfoProvider = (
28
+ recording: DeviceRecording
29
+ ) => Promise<UploadInfo>;
30
+
31
+ /**
32
+ * Recording Manager class
33
+ */
34
+ export class RecordingManager extends EventEmitter<RecordingManagerEvents> {
35
+ private protocolHandler: ProtocolHandler;
36
+ private storage: StorageManager;
37
+ private uploadQueue: UploadQueue;
38
+ private isInitialized = false;
39
+
40
+ constructor() {
41
+ super();
42
+ this.protocolHandler = new ProtocolHandler();
43
+ this.storage = new StorageManager();
44
+ this.uploadQueue = new UploadQueue(this.storage, { autoStart: false });
45
+
46
+ this.setupUploadQueueListeners();
47
+ }
48
+
49
+ /**
50
+ * Initialize the recording manager
51
+ */
52
+ async initialize(): Promise<void> {
53
+ if (this.isInitialized) {
54
+ return;
55
+ }
56
+
57
+ await this.storage.initialize();
58
+ this.isInitialized = true;
59
+
60
+ // Start processing any pending uploads
61
+ this.uploadQueue.resume();
62
+
63
+ log.info('RecordingManager initialized');
64
+ }
65
+
66
+ /**
67
+ * Set up upload queue event listeners
68
+ */
69
+ private setupUploadQueueListeners(): void {
70
+ this.uploadQueue.on('taskAdded', () => {
71
+ this.emit('queueUpdated', this.uploadQueue.getAllTasks());
72
+ });
73
+
74
+ this.uploadQueue.on('taskUpdated', () => {
75
+ this.emit('queueUpdated', this.uploadQueue.getAllTasks());
76
+ });
77
+
78
+ this.uploadQueue.on('taskCompleted', (taskId, recordingId) => {
79
+ this.emit('uploadCompleted', taskId, recordingId);
80
+ this.emit('queueUpdated', this.uploadQueue.getAllTasks());
81
+ });
82
+
83
+ this.uploadQueue.on('taskFailed', (taskId, error) => {
84
+ this.emit('uploadFailed', taskId, error);
85
+ this.emit('queueUpdated', this.uploadQueue.getAllTasks());
86
+ });
87
+
88
+ this.uploadQueue.on('uploadProgress', (taskId, progress) => {
89
+ this.emit('uploadProgress', taskId, progress);
90
+ });
91
+ }
92
+
93
+ /**
94
+ * Get storage info from device
95
+ */
96
+ async getStorageInfo(device: ConnectedDevice): Promise<StorageInfo> {
97
+ if (!getBleManager().isConnected(device.id)) {
98
+ throw DeviceError.notConnected(device.id);
99
+ }
100
+
101
+ return this.protocolHandler.getStorageInfo(device.id);
102
+ }
103
+
104
+ /**
105
+ * List recordings on a device
106
+ */
107
+ async listRecordings(device: ConnectedDevice): Promise<DeviceRecording[]> {
108
+ if (!getBleManager().isConnected(device.id)) {
109
+ throw DeviceError.notConnected(device.id);
110
+ }
111
+
112
+ log.info('Listing recordings', { deviceId: device.id });
113
+
114
+ const recordings = await this.protocolHandler.listRecordings(device.id);
115
+
116
+ log.info('Found recordings', {
117
+ deviceId: device.id,
118
+ count: recordings.length,
119
+ });
120
+
121
+ return recordings;
122
+ }
123
+
124
+ /**
125
+ * Sync a single recording from device
126
+ * Transfers from device, saves locally, and queues for upload
127
+ */
128
+ async *syncRecording(
129
+ device: ConnectedDevice,
130
+ recording: DeviceRecording,
131
+ uploadInfo: UploadInfo
132
+ ): AsyncGenerator<SyncProgress> {
133
+ if (!getBleManager().isConnected(device.id)) {
134
+ throw DeviceError.notConnected(device.id);
135
+ }
136
+
137
+ log.info('Starting recording sync', {
138
+ deviceId: device.id,
139
+ recordingUuid: recording.uuid,
140
+ });
141
+
142
+ this.emit('syncStarted', recording.uuid);
143
+
144
+ try {
145
+ // Stage: Preparing
146
+ yield {
147
+ stage: 'preparing',
148
+ progress: 0,
149
+ totalBytes: recording.fileSizeBytes,
150
+ };
151
+
152
+ // Stage: Transferring from device
153
+ const audioData = await this.protocolHandler.transferRecording(
154
+ device.id,
155
+ recording.uuid,
156
+ (_bytesReceived, _totalBytes) => {
157
+ // Progress callback - can be used for real-time progress updates
158
+ }
159
+ );
160
+
161
+ yield {
162
+ stage: 'transferring',
163
+ progress: 1,
164
+ bytesTransferred: audioData.length,
165
+ totalBytes: recording.fileSizeBytes,
166
+ };
167
+
168
+ // Save locally
169
+ const localPath = await this.storage.saveRecordingData(
170
+ device.id,
171
+ recording.uuid,
172
+ audioData
173
+ );
174
+
175
+ // Stage: Uploading
176
+ yield {
177
+ stage: 'uploading',
178
+ progress: 0,
179
+ bytesUploaded: 0,
180
+ totalBytes: audioData.length,
181
+ };
182
+
183
+ // Queue for upload (this will process in background)
184
+ const task = await this.uploadQueue.enqueue({
185
+ recordingId: uploadInfo.recordingId,
186
+ deviceId: device.id,
187
+ localPath,
188
+ uploadUrl: uploadInfo.uploadUrl,
189
+ uploadToken: uploadInfo.uploadToken,
190
+ completeUrl: uploadInfo.completeUrl,
191
+ });
192
+
193
+ // Wait for upload to complete
194
+ await this.waitForUpload(task.id);
195
+
196
+ // Stage: Completing - Confirm sync to device
197
+ yield {
198
+ stage: 'completing',
199
+ progress: 0.5,
200
+ };
201
+
202
+ await this.protocolHandler.confirmSync(device.id, recording.uuid);
203
+
204
+ // Update last sync time
205
+ await this.storage.setLastSyncTime(device.id);
206
+
207
+ // Stage: Completed
208
+ yield {
209
+ stage: 'completed',
210
+ progress: 1,
211
+ recordingId: uploadInfo.recordingId,
212
+ };
213
+
214
+ log.info('Recording sync completed', {
215
+ deviceId: device.id,
216
+ recordingUuid: recording.uuid,
217
+ recordingId: uploadInfo.recordingId,
218
+ });
219
+
220
+ this.emit('syncCompleted', recording.uuid, uploadInfo.recordingId);
221
+ } catch (error) {
222
+ const err = error as Error;
223
+ log.error('Recording sync failed', err, {
224
+ deviceId: device.id,
225
+ recordingUuid: recording.uuid,
226
+ });
227
+
228
+ yield {
229
+ stage: 'failed',
230
+ progress: 0,
231
+ error: err.message,
232
+ };
233
+
234
+ this.emit('syncFailed', recording.uuid, err);
235
+ throw error;
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Wait for an upload task to complete
241
+ */
242
+ private async waitForUpload(taskId: string): Promise<void> {
243
+ return new Promise<void>((resolve, reject) => {
244
+ const checkTask = () => {
245
+ const task = this.storage.getUploadTask(taskId);
246
+ if (!task) {
247
+ reject(new Error('Upload task not found'));
248
+ return true;
249
+ }
250
+
251
+ if (task.status === 'completed') {
252
+ resolve();
253
+ return true;
254
+ }
255
+
256
+ if (task.status === 'failed') {
257
+ reject(new Error(task.errorMessage || 'Upload failed'));
258
+ return true;
259
+ }
260
+
261
+ return false;
262
+ };
263
+
264
+ // Check immediately
265
+ if (checkTask()) return;
266
+
267
+ // Poll for completion
268
+ const interval = setInterval(() => {
269
+ if (checkTask()) {
270
+ clearInterval(interval);
271
+ }
272
+ }, 500);
273
+
274
+ // Also listen for events
275
+ const onComplete = (completedTaskId: string) => {
276
+ if (completedTaskId === taskId) {
277
+ clearInterval(interval);
278
+ this.uploadQueue.off('taskCompleted', onComplete);
279
+ this.uploadQueue.off('taskFailed', onFailed);
280
+ resolve();
281
+ }
282
+ };
283
+
284
+ const onFailed = (failedTaskId: string, error: Error) => {
285
+ if (failedTaskId === taskId) {
286
+ clearInterval(interval);
287
+ this.uploadQueue.off('taskCompleted', onComplete);
288
+ this.uploadQueue.off('taskFailed', onFailed);
289
+ reject(error);
290
+ }
291
+ };
292
+
293
+ this.uploadQueue.on('taskCompleted', onComplete);
294
+ this.uploadQueue.on('taskFailed', onFailed);
295
+ });
296
+ }
297
+
298
+ /**
299
+ * Sync all recordings from a device
300
+ */
301
+ async *syncAllRecordings(
302
+ device: ConnectedDevice,
303
+ uploadInfoProvider: UploadInfoProvider
304
+ ): AsyncGenerator<SyncProgress & { recordingIndex?: number; totalRecordings?: number }> {
305
+ if (!getBleManager().isConnected(device.id)) {
306
+ throw DeviceError.notConnected(device.id);
307
+ }
308
+
309
+ log.info('Syncing all recordings', { deviceId: device.id });
310
+
311
+ // List recordings
312
+ const recordings = await this.listRecordings(device);
313
+
314
+ if (recordings.length === 0) {
315
+ log.info('No recordings to sync', { deviceId: device.id });
316
+ return;
317
+ }
318
+
319
+ log.info('Starting sync of recordings', {
320
+ deviceId: device.id,
321
+ count: recordings.length,
322
+ });
323
+
324
+ // Sync each recording
325
+ for (let i = 0; i < recordings.length; i++) {
326
+ const recording = recordings[i];
327
+
328
+ try {
329
+ // Get upload info from customer backend
330
+ const uploadInfo = await uploadInfoProvider(recording);
331
+
332
+ // Sync the recording
333
+ for await (const progress of this.syncRecording(
334
+ device,
335
+ recording,
336
+ uploadInfo
337
+ )) {
338
+ yield {
339
+ ...progress,
340
+ recordingIndex: i,
341
+ totalRecordings: recordings.length,
342
+ };
343
+ }
344
+ } catch (error) {
345
+ const err = error as Error;
346
+ log.error('Failed to sync recording', err, {
347
+ deviceId: device.id,
348
+ recordingUuid: recording.uuid,
349
+ index: i,
350
+ });
351
+
352
+ yield {
353
+ stage: 'failed',
354
+ progress: 0,
355
+ error: err.message,
356
+ recordingIndex: i,
357
+ totalRecordings: recordings.length,
358
+ };
359
+
360
+ // Continue with next recording
361
+ }
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Get pending uploads
367
+ */
368
+ getPendingUploads(): UploadTask[] {
369
+ return this.uploadQueue.getPendingTasks();
370
+ }
371
+
372
+ /**
373
+ * Get all uploads
374
+ */
375
+ getAllUploads(): UploadTask[] {
376
+ return this.uploadQueue.getAllTasks();
377
+ }
378
+
379
+ /**
380
+ * Cancel a pending upload
381
+ */
382
+ async cancelUpload(taskId: string): Promise<void> {
383
+ await this.uploadQueue.cancel(taskId);
384
+ }
385
+
386
+ /**
387
+ * Retry failed uploads
388
+ */
389
+ async retryFailedUploads(): Promise<void> {
390
+ await this.uploadQueue.retryFailed();
391
+ }
392
+
393
+ /**
394
+ * Clear completed uploads from queue
395
+ */
396
+ async clearCompletedUploads(): Promise<void> {
397
+ await this.storage.clearCompletedTasks();
398
+ this.emit('queueUpdated', this.uploadQueue.getAllTasks());
399
+ }
400
+
401
+ /**
402
+ * Clear all uploads
403
+ */
404
+ async clearAllUploads(): Promise<void> {
405
+ await this.uploadQueue.cancelAll();
406
+ this.emit('queueUpdated', []);
407
+ }
408
+
409
+ /**
410
+ * Pause upload processing
411
+ */
412
+ pauseUploads(): void {
413
+ this.uploadQueue.pause();
414
+ }
415
+
416
+ /**
417
+ * Resume upload processing
418
+ */
419
+ resumeUploads(): void {
420
+ this.uploadQueue.resume();
421
+ }
422
+
423
+ /**
424
+ * Clean up resources
425
+ */
426
+ destroy(): void {
427
+ log.info('Destroying RecordingManager');
428
+ this.protocolHandler.destroy();
429
+ this.uploadQueue.destroy();
430
+ this.storage.destroy();
431
+ this.removeAllListeners();
432
+ this.isInitialized = false;
433
+ }
434
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Managers module exports
3
+ */
4
+
5
+ export { DeviceManager } from './DeviceManager';
6
+ export { RecordingManager, type UploadInfoProvider } from './RecordingManager';
7
+ export {
8
+ OTAManager,
9
+ type FirmwareInfo,
10
+ type OtaStage,
11
+ type OtaProgress,
12
+ } from './OTAManager';