@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.
- package/README.md +279 -0
- package/lib/commonjs/BotaClient.js +223 -0
- package/lib/commonjs/BotaClient.js.map +1 -0
- package/lib/commonjs/ble/BleManager.js +494 -0
- package/lib/commonjs/ble/BleManager.js.map +1 -0
- package/lib/commonjs/ble/constants.js +166 -0
- package/lib/commonjs/ble/constants.js.map +1 -0
- package/lib/commonjs/ble/index.js +54 -0
- package/lib/commonjs/ble/index.js.map +1 -0
- package/lib/commonjs/ble/parsers.js +345 -0
- package/lib/commonjs/ble/parsers.js.map +1 -0
- package/lib/commonjs/index.js +81 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/managers/DeviceManager.js +437 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -0
- package/lib/commonjs/managers/OTAManager.js +227 -0
- package/lib/commonjs/managers/OTAManager.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +384 -0
- package/lib/commonjs/managers/RecordingManager.js.map +1 -0
- package/lib/commonjs/managers/index.js +27 -0
- package/lib/commonjs/managers/index.js.map +1 -0
- package/lib/commonjs/models/Device.js +2 -0
- package/lib/commonjs/models/Device.js.map +1 -0
- package/lib/commonjs/models/Recording.js +2 -0
- package/lib/commonjs/models/Recording.js.map +1 -0
- package/lib/commonjs/models/Status.js +6 -0
- package/lib/commonjs/models/Status.js.map +1 -0
- package/lib/commonjs/models/index.js +39 -0
- package/lib/commonjs/models/index.js.map +1 -0
- package/lib/commonjs/protocol/ProtocolHandler.js +343 -0
- package/lib/commonjs/protocol/ProtocolHandler.js.map +1 -0
- package/lib/commonjs/protocol/index.js +13 -0
- package/lib/commonjs/protocol/index.js.map +1 -0
- package/lib/commonjs/storage/StorageManager.js +333 -0
- package/lib/commonjs/storage/StorageManager.js.map +1 -0
- package/lib/commonjs/storage/index.js +19 -0
- package/lib/commonjs/storage/index.js.map +1 -0
- package/lib/commonjs/upload/S3Uploader.js +133 -0
- package/lib/commonjs/upload/S3Uploader.js.map +1 -0
- package/lib/commonjs/upload/UploadQueue.js +280 -0
- package/lib/commonjs/upload/UploadQueue.js.map +1 -0
- package/lib/commonjs/upload/index.js +20 -0
- package/lib/commonjs/upload/index.js.map +1 -0
- package/lib/commonjs/utils/errors.js +187 -0
- package/lib/commonjs/utils/errors.js.map +1 -0
- package/lib/commonjs/utils/index.js +40 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/commonjs/utils/logger.js +135 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/commonjs/utils/retry.js +160 -0
- package/lib/commonjs/utils/retry.js.map +1 -0
- package/lib/module/BotaClient.js +216 -0
- package/lib/module/BotaClient.js.map +1 -0
- package/lib/module/ble/BleManager.js +484 -0
- package/lib/module/ble/BleManager.js.map +1 -0
- package/lib/module/ble/constants.js +159 -0
- package/lib/module/ble/constants.js.map +1 -0
- package/lib/module/ble/index.js +8 -0
- package/lib/module/ble/index.js.map +1 -0
- package/lib/module/ble/parsers.js +328 -0
- package/lib/module/ble/parsers.js.map +1 -0
- package/lib/module/index.js +22 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/managers/DeviceManager.js +429 -0
- package/lib/module/managers/DeviceManager.js.map +1 -0
- package/lib/module/managers/OTAManager.js +219 -0
- package/lib/module/managers/OTAManager.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +376 -0
- package/lib/module/managers/RecordingManager.js.map +1 -0
- package/lib/module/managers/index.js +8 -0
- package/lib/module/managers/index.js.map +1 -0
- package/lib/module/models/Device.js +2 -0
- package/lib/module/models/Device.js.map +1 -0
- package/lib/module/models/Recording.js +2 -0
- package/lib/module/models/Recording.js.map +1 -0
- package/lib/module/models/Status.js +2 -0
- package/lib/module/models/Status.js.map +1 -0
- package/lib/module/models/index.js +8 -0
- package/lib/module/models/index.js.map +1 -0
- package/lib/module/protocol/ProtocolHandler.js +336 -0
- package/lib/module/protocol/ProtocolHandler.js.map +1 -0
- package/lib/module/protocol/index.js +6 -0
- package/lib/module/protocol/index.js.map +1 -0
- package/lib/module/storage/StorageManager.js +324 -0
- package/lib/module/storage/StorageManager.js.map +1 -0
- package/lib/module/storage/index.js +6 -0
- package/lib/module/storage/index.js.map +1 -0
- package/lib/module/upload/S3Uploader.js +126 -0
- package/lib/module/upload/S3Uploader.js.map +1 -0
- package/lib/module/upload/UploadQueue.js +272 -0
- package/lib/module/upload/UploadQueue.js.map +1 -0
- package/lib/module/upload/index.js +7 -0
- package/lib/module/upload/index.js.map +1 -0
- package/lib/module/utils/errors.js +173 -0
- package/lib/module/utils/errors.js.map +1 -0
- package/lib/module/utils/index.js +8 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/logger.js +129 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/module/utils/retry.js +149 -0
- package/lib/module/utils/retry.js.map +1 -0
- package/lib/typescript/src/BotaClient.d.ts +77 -0
- package/lib/typescript/src/BotaClient.d.ts.map +1 -0
- package/lib/typescript/src/ble/BleManager.d.ts +111 -0
- package/lib/typescript/src/ble/BleManager.d.ts.map +1 -0
- package/lib/typescript/src/ble/constants.d.ts +111 -0
- package/lib/typescript/src/ble/constants.d.ts.map +1 -0
- package/lib/typescript/src/ble/index.d.ts +7 -0
- package/lib/typescript/src/ble/index.d.ts.map +1 -0
- package/lib/typescript/src/ble/parsers.d.ts +100 -0
- package/lib/typescript/src/ble/parsers.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +16 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/managers/DeviceManager.d.ts +84 -0
- package/lib/typescript/src/managers/DeviceManager.d.ts.map +1 -0
- package/lib/typescript/src/managers/OTAManager.d.ts +78 -0
- package/lib/typescript/src/managers/OTAManager.d.ts.map +1 -0
- package/lib/typescript/src/managers/RecordingManager.d.ts +90 -0
- package/lib/typescript/src/managers/RecordingManager.d.ts.map +1 -0
- package/lib/typescript/src/managers/index.d.ts +7 -0
- package/lib/typescript/src/managers/index.d.ts.map +1 -0
- package/lib/typescript/src/models/Device.d.ts +139 -0
- package/lib/typescript/src/models/Device.d.ts.map +1 -0
- package/lib/typescript/src/models/Recording.d.ts +110 -0
- package/lib/typescript/src/models/Recording.d.ts.map +1 -0
- package/lib/typescript/src/models/Status.d.ts +104 -0
- package/lib/typescript/src/models/Status.d.ts.map +1 -0
- package/lib/typescript/src/models/index.d.ts +7 -0
- package/lib/typescript/src/models/index.d.ts.map +1 -0
- package/lib/typescript/src/protocol/ProtocolHandler.d.ts +69 -0
- package/lib/typescript/src/protocol/ProtocolHandler.d.ts.map +1 -0
- package/lib/typescript/src/protocol/index.d.ts +5 -0
- package/lib/typescript/src/protocol/index.d.ts.map +1 -0
- package/lib/typescript/src/storage/StorageManager.d.ts +116 -0
- package/lib/typescript/src/storage/StorageManager.d.ts.map +1 -0
- package/lib/typescript/src/storage/index.d.ts +5 -0
- package/lib/typescript/src/storage/index.d.ts.map +1 -0
- package/lib/typescript/src/upload/S3Uploader.d.ts +38 -0
- package/lib/typescript/src/upload/S3Uploader.d.ts.map +1 -0
- package/lib/typescript/src/upload/UploadQueue.d.ts +95 -0
- package/lib/typescript/src/upload/UploadQueue.d.ts.map +1 -0
- package/lib/typescript/src/upload/index.d.ts +6 -0
- package/lib/typescript/src/upload/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/errors.d.ts +82 -0
- package/lib/typescript/src/utils/errors.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +7 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/logger.d.ts +68 -0
- package/lib/typescript/src/utils/logger.d.ts.map +1 -0
- package/lib/typescript/src/utils/retry.d.ts +53 -0
- package/lib/typescript/src/utils/retry.d.ts.map +1 -0
- package/package.json +95 -0
- package/src/BotaClient.ts +238 -0
- package/src/ble/BleManager.ts +573 -0
- package/src/ble/constants.ts +158 -0
- package/src/ble/index.ts +7 -0
- package/src/ble/parsers.ts +395 -0
- package/src/index.ts +64 -0
- package/src/managers/DeviceManager.ts +545 -0
- package/src/managers/OTAManager.ts +263 -0
- package/src/managers/RecordingManager.ts +434 -0
- package/src/managers/index.ts +12 -0
- package/src/models/Device.ts +164 -0
- package/src/models/Recording.ts +123 -0
- package/src/models/Status.ts +126 -0
- package/src/models/index.ts +7 -0
- package/src/protocol/ProtocolHandler.ts +459 -0
- package/src/protocol/index.ts +5 -0
- package/src/storage/StorageManager.ts +343 -0
- package/src/storage/index.ts +5 -0
- package/src/upload/S3Uploader.ts +164 -0
- package/src/upload/UploadQueue.ts +310 -0
- package/src/upload/index.ts +6 -0
- package/src/utils/errors.ts +310 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/logger.ts +137 -0
- package/src/utils/retry.ts +177 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol Handler - Implements Device-App Protocol for recording transfer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
6
|
+
import { getBleManager } from '../ble/BleManager';
|
|
7
|
+
import { SERVICE_BOTA_STORAGE, CHAR_STORAGE_INFO, CHAR_RECORDING_LIST, CHAR_RECORDING_TRANSFER, CHAR_TRANSFER_CONTROL, TRANSFER_PACKET_TIMEOUT } from '../ble/constants';
|
|
8
|
+
import { parseStorageInfo, parseRecordingList, parseTransferPacket, createAckPacket, createTransferCommand } from '../ble/parsers';
|
|
9
|
+
import { TransferError, DeviceError } from '../utils/errors';
|
|
10
|
+
import { logger } from '../utils/logger';
|
|
11
|
+
const log = logger.tag('ProtocolHandler');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Transfer state for tracking ongoing transfers
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Transfer progress callback
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Protocol Handler class
|
|
23
|
+
*/
|
|
24
|
+
export class ProtocolHandler {
|
|
25
|
+
activeTransfers = new Map();
|
|
26
|
+
constructor() {
|
|
27
|
+
this.bleManager = getBleManager();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get storage info from device
|
|
32
|
+
*/
|
|
33
|
+
async getStorageInfo(deviceId) {
|
|
34
|
+
if (!this.bleManager.isConnected(deviceId)) {
|
|
35
|
+
throw DeviceError.notConnected(deviceId);
|
|
36
|
+
}
|
|
37
|
+
const data = await this.bleManager.readCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_STORAGE_INFO);
|
|
38
|
+
return parseStorageInfo(data);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* List recordings on device
|
|
43
|
+
*/
|
|
44
|
+
async listRecordings(deviceId) {
|
|
45
|
+
if (!this.bleManager.isConnected(deviceId)) {
|
|
46
|
+
throw DeviceError.notConnected(deviceId);
|
|
47
|
+
}
|
|
48
|
+
log.debug('Listing recordings', {
|
|
49
|
+
deviceId
|
|
50
|
+
});
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
let recordings = [];
|
|
53
|
+
let subscription;
|
|
54
|
+
let timeoutId;
|
|
55
|
+
const cleanup = () => {
|
|
56
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
57
|
+
subscription?.remove();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Set up timeout
|
|
61
|
+
timeoutId = setTimeout(() => {
|
|
62
|
+
cleanup();
|
|
63
|
+
// If we received some data, return it; otherwise error
|
|
64
|
+
if (recordings.length > 0) {
|
|
65
|
+
resolve(recordings);
|
|
66
|
+
} else {
|
|
67
|
+
reject(new TransferError('Timeout waiting for recording list', 'LIST_TIMEOUT'));
|
|
68
|
+
}
|
|
69
|
+
}, 5000);
|
|
70
|
+
|
|
71
|
+
// Subscribe to recording list notifications
|
|
72
|
+
subscription = this.bleManager.subscribeToCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_RECORDING_LIST, data => {
|
|
73
|
+
try {
|
|
74
|
+
const parsed = parseRecordingList(data);
|
|
75
|
+
recordings = recordings.concat(parsed);
|
|
76
|
+
|
|
77
|
+
// Check if this is the last packet (could check for end marker)
|
|
78
|
+
// For now, wait for timeout or explicit end
|
|
79
|
+
} catch (error) {
|
|
80
|
+
log.error('Failed to parse recording list', error);
|
|
81
|
+
}
|
|
82
|
+
}, error => {
|
|
83
|
+
cleanup();
|
|
84
|
+
reject(error);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Send list command
|
|
88
|
+
const command = createTransferCommand('list');
|
|
89
|
+
this.bleManager.writeCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_TRANSFER_CONTROL, command).catch(error => {
|
|
90
|
+
cleanup();
|
|
91
|
+
reject(error);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Transfer a recording from device
|
|
98
|
+
* Returns the audio data as a Buffer
|
|
99
|
+
*/
|
|
100
|
+
async transferRecording(deviceId, recordingUuid, onProgress) {
|
|
101
|
+
if (!this.bleManager.isConnected(deviceId)) {
|
|
102
|
+
throw DeviceError.notConnected(deviceId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check if transfer already in progress
|
|
106
|
+
if (this.activeTransfers.has(recordingUuid)) {
|
|
107
|
+
throw new TransferError('Transfer already in progress for this recording', 'TRANSFER_IN_PROGRESS', recordingUuid);
|
|
108
|
+
}
|
|
109
|
+
log.info('Starting recording transfer', {
|
|
110
|
+
deviceId,
|
|
111
|
+
recordingUuid
|
|
112
|
+
});
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const state = {
|
|
115
|
+
recordingUuid,
|
|
116
|
+
expectedSequence: 0,
|
|
117
|
+
receivedPackets: new Map(),
|
|
118
|
+
totalBytes: 0,
|
|
119
|
+
isComplete: false
|
|
120
|
+
};
|
|
121
|
+
this.activeTransfers.set(recordingUuid, state);
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
if (state.timeoutId) clearTimeout(state.timeoutId);
|
|
124
|
+
state.subscription?.remove();
|
|
125
|
+
this.activeTransfers.delete(recordingUuid);
|
|
126
|
+
};
|
|
127
|
+
const resetTimeout = () => {
|
|
128
|
+
if (state.timeoutId) clearTimeout(state.timeoutId);
|
|
129
|
+
state.timeoutId = setTimeout(() => {
|
|
130
|
+
cleanup();
|
|
131
|
+
reject(TransferError.timeout(recordingUuid));
|
|
132
|
+
}, TRANSFER_PACKET_TIMEOUT);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Subscribe to transfer data notifications
|
|
136
|
+
state.subscription = this.bleManager.subscribeToCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_RECORDING_TRANSFER, async data => {
|
|
137
|
+
try {
|
|
138
|
+
resetTimeout();
|
|
139
|
+
const packet = parseTransferPacket(data);
|
|
140
|
+
await this.handleTransferPacket(deviceId, state, packet, onProgress);
|
|
141
|
+
if (state.isComplete) {
|
|
142
|
+
cleanup();
|
|
143
|
+
|
|
144
|
+
// Assemble the audio data
|
|
145
|
+
const audioData = this.assembleAudioData(state);
|
|
146
|
+
|
|
147
|
+
// Verify checksum if available
|
|
148
|
+
if (state.checksum !== undefined) {
|
|
149
|
+
const calculatedChecksum = this.calculateCrc32(audioData);
|
|
150
|
+
if (calculatedChecksum !== state.checksum) {
|
|
151
|
+
reject(TransferError.checksumMismatch(recordingUuid));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
log.info('Transfer completed', {
|
|
156
|
+
recordingUuid,
|
|
157
|
+
size: audioData.length
|
|
158
|
+
});
|
|
159
|
+
resolve(audioData);
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
cleanup();
|
|
163
|
+
reject(error);
|
|
164
|
+
}
|
|
165
|
+
}, error => {
|
|
166
|
+
cleanup();
|
|
167
|
+
reject(TransferError.interrupted(recordingUuid, error));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Send start transfer command
|
|
171
|
+
const command = createTransferCommand('start', recordingUuid);
|
|
172
|
+
this.bleManager.writeCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_TRANSFER_CONTROL, command).then(() => {
|
|
173
|
+
resetTimeout();
|
|
174
|
+
}).catch(error => {
|
|
175
|
+
cleanup();
|
|
176
|
+
reject(error);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Handle a transfer packet from device
|
|
183
|
+
*/
|
|
184
|
+
async handleTransferPacket(deviceId, state, packet, onProgress) {
|
|
185
|
+
switch (packet.type) {
|
|
186
|
+
case 'data':
|
|
187
|
+
if (packet.data) {
|
|
188
|
+
// Store packet data
|
|
189
|
+
state.receivedPackets.set(packet.sequenceNumber, Buffer.from(packet.data));
|
|
190
|
+
state.totalBytes += packet.data.length;
|
|
191
|
+
|
|
192
|
+
// Send ACK
|
|
193
|
+
await this.sendAck(deviceId, 'ack', packet.sequenceNumber);
|
|
194
|
+
|
|
195
|
+
// Report progress
|
|
196
|
+
onProgress?.(state.totalBytes);
|
|
197
|
+
log.debug('Received data packet', {
|
|
198
|
+
seq: packet.sequenceNumber,
|
|
199
|
+
size: packet.data.length,
|
|
200
|
+
total: state.totalBytes
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
case 'eof':
|
|
205
|
+
state.checksum = packet.checksum;
|
|
206
|
+
state.isComplete = true;
|
|
207
|
+
|
|
208
|
+
// Send final ACK
|
|
209
|
+
await this.sendAck(deviceId, 'ack', packet.sequenceNumber);
|
|
210
|
+
log.debug('Received EOF packet', {
|
|
211
|
+
seq: packet.sequenceNumber,
|
|
212
|
+
checksum: packet.checksum
|
|
213
|
+
});
|
|
214
|
+
break;
|
|
215
|
+
case 'error':
|
|
216
|
+
throw TransferError.deviceError(state.recordingUuid, packet.errorCode ?? 0xff);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Send an ACK packet to device
|
|
222
|
+
*/
|
|
223
|
+
async sendAck(deviceId, type, sequenceNumber) {
|
|
224
|
+
const ackPacket = createAckPacket(type, sequenceNumber);
|
|
225
|
+
await this.bleManager.writeCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_RECORDING_TRANSFER, ackPacket, false // Write without response for speed
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Assemble audio data from received packets
|
|
231
|
+
*/
|
|
232
|
+
assembleAudioData(state) {
|
|
233
|
+
// Sort packets by sequence number and concatenate
|
|
234
|
+
const sortedSequences = Array.from(state.receivedPackets.keys()).sort((a, b) => a - b);
|
|
235
|
+
const chunks = [];
|
|
236
|
+
for (const seq of sortedSequences) {
|
|
237
|
+
const data = state.receivedPackets.get(seq);
|
|
238
|
+
if (data) {
|
|
239
|
+
chunks.push(data);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return Buffer.concat(chunks);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Calculate CRC32 checksum
|
|
247
|
+
*/
|
|
248
|
+
calculateCrc32(data) {
|
|
249
|
+
let crc = 0xffffffff;
|
|
250
|
+
const table = this.getCrc32Table();
|
|
251
|
+
for (let i = 0; i < data.length; i++) {
|
|
252
|
+
crc = crc >>> 8 ^ table[(crc ^ data[i]) & 0xff];
|
|
253
|
+
}
|
|
254
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get CRC32 lookup table (lazy initialized)
|
|
259
|
+
*/
|
|
260
|
+
crc32Table = null;
|
|
261
|
+
getCrc32Table() {
|
|
262
|
+
if (this.crc32Table) {
|
|
263
|
+
return this.crc32Table;
|
|
264
|
+
}
|
|
265
|
+
const table = [];
|
|
266
|
+
for (let i = 0; i < 256; i++) {
|
|
267
|
+
let crc = i;
|
|
268
|
+
for (let j = 0; j < 8; j++) {
|
|
269
|
+
crc = crc & 1 ? crc >>> 1 ^ 0xedb88320 : crc >>> 1;
|
|
270
|
+
}
|
|
271
|
+
table.push(crc >>> 0);
|
|
272
|
+
}
|
|
273
|
+
this.crc32Table = table;
|
|
274
|
+
return table;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Confirm sync to device (allows device to delete local copy)
|
|
279
|
+
*/
|
|
280
|
+
async confirmSync(deviceId, recordingUuid) {
|
|
281
|
+
if (!this.bleManager.isConnected(deviceId)) {
|
|
282
|
+
throw DeviceError.notConnected(deviceId);
|
|
283
|
+
}
|
|
284
|
+
log.debug('Confirming sync', {
|
|
285
|
+
deviceId,
|
|
286
|
+
recordingUuid
|
|
287
|
+
});
|
|
288
|
+
const command = createTransferCommand('confirm', recordingUuid);
|
|
289
|
+
await this.bleManager.writeCharacteristic(deviceId, SERVICE_BOTA_STORAGE, CHAR_TRANSFER_CONTROL, command);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Cancel an ongoing transfer
|
|
294
|
+
*/
|
|
295
|
+
async cancelTransfer(deviceId, recordingUuid) {
|
|
296
|
+
const state = this.activeTransfers.get(recordingUuid);
|
|
297
|
+
if (!state) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
log.info('Cancelling transfer', {
|
|
301
|
+
recordingUuid
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Send abort
|
|
305
|
+
try {
|
|
306
|
+
await this.sendAck(deviceId, 'abort', state.expectedSequence);
|
|
307
|
+
} catch {
|
|
308
|
+
// Ignore errors during cancellation
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Clean up
|
|
312
|
+
if (state.timeoutId) clearTimeout(state.timeoutId);
|
|
313
|
+
state.subscription?.remove();
|
|
314
|
+
this.activeTransfers.delete(recordingUuid);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Check if a transfer is in progress
|
|
319
|
+
*/
|
|
320
|
+
isTransferInProgress(recordingUuid) {
|
|
321
|
+
return this.activeTransfers.has(recordingUuid);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Clean up resources
|
|
326
|
+
*/
|
|
327
|
+
destroy() {
|
|
328
|
+
// Cancel all active transfers
|
|
329
|
+
for (const [, state] of this.activeTransfers) {
|
|
330
|
+
if (state.timeoutId) clearTimeout(state.timeoutId);
|
|
331
|
+
state.subscription?.remove();
|
|
332
|
+
}
|
|
333
|
+
this.activeTransfers.clear();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=ProtocolHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Buffer","getBleManager","SERVICE_BOTA_STORAGE","CHAR_STORAGE_INFO","CHAR_RECORDING_LIST","CHAR_RECORDING_TRANSFER","CHAR_TRANSFER_CONTROL","TRANSFER_PACKET_TIMEOUT","parseStorageInfo","parseRecordingList","parseTransferPacket","createAckPacket","createTransferCommand","TransferError","DeviceError","logger","log","tag","ProtocolHandler","activeTransfers","Map","constructor","bleManager","getStorageInfo","deviceId","isConnected","notConnected","data","readCharacteristic","listRecordings","debug","Promise","resolve","reject","recordings","subscription","timeoutId","cleanup","clearTimeout","remove","setTimeout","length","subscribeToCharacteristic","parsed","concat","error","command","writeCharacteristic","catch","transferRecording","recordingUuid","onProgress","has","info","state","expectedSequence","receivedPackets","totalBytes","isComplete","set","delete","resetTimeout","timeout","packet","handleTransferPacket","audioData","assembleAudioData","checksum","undefined","calculatedChecksum","calculateCrc32","checksumMismatch","size","interrupted","then","type","sequenceNumber","from","sendAck","seq","total","deviceError","errorCode","ackPacket","sortedSequences","Array","keys","sort","a","b","chunks","get","push","crc","table","getCrc32Table","i","crc32Table","j","confirmSync","cancelTransfer","isTransferInProgress","destroy","clear"],"sourceRoot":"../../../src","sources":["protocol/ProtocolHandler.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["ProtocolHandler"],"sourceRoot":"../../../src","sources":["protocol/index.ts"],"mappings":"AAAA;AACA;AACA;;AAEA,SAASA,eAAe,QAAuC,mBAAmB","ignoreList":[]}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Manager - Local persistence for recordings and upload queue
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
6
|
+
import { Buffer } from 'buffer';
|
|
7
|
+
import { logger } from '../utils/logger';
|
|
8
|
+
const log = logger.tag('StorageManager');
|
|
9
|
+
|
|
10
|
+
// Storage keys
|
|
11
|
+
const STORAGE_PREFIX = '@bota_sdk:';
|
|
12
|
+
const UPLOAD_QUEUE_KEY = `${STORAGE_PREFIX}upload_queue`;
|
|
13
|
+
const SDK_STATE_KEY = `${STORAGE_PREFIX}sdk_state`;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* SDK persistent state
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Storage Manager class
|
|
21
|
+
*/
|
|
22
|
+
export class StorageManager {
|
|
23
|
+
uploadQueue = [];
|
|
24
|
+
sdkState = {
|
|
25
|
+
lastSyncTimes: {},
|
|
26
|
+
deviceInfo: {}
|
|
27
|
+
};
|
|
28
|
+
isInitialized = false;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize storage manager
|
|
32
|
+
*/
|
|
33
|
+
async initialize() {
|
|
34
|
+
if (this.isInitialized) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
log.debug('Initializing StorageManager');
|
|
38
|
+
try {
|
|
39
|
+
// Load upload queue
|
|
40
|
+
const queueData = await AsyncStorage.getItem(UPLOAD_QUEUE_KEY);
|
|
41
|
+
if (queueData) {
|
|
42
|
+
this.uploadQueue = JSON.parse(queueData);
|
|
43
|
+
// Restore dates
|
|
44
|
+
this.uploadQueue = this.uploadQueue.map(task => ({
|
|
45
|
+
...task,
|
|
46
|
+
createdAt: new Date(task.createdAt),
|
|
47
|
+
updatedAt: new Date(task.updatedAt)
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Load SDK state
|
|
52
|
+
const stateData = await AsyncStorage.getItem(SDK_STATE_KEY);
|
|
53
|
+
if (stateData) {
|
|
54
|
+
this.sdkState = JSON.parse(stateData);
|
|
55
|
+
}
|
|
56
|
+
this.isInitialized = true;
|
|
57
|
+
log.info('StorageManager initialized', {
|
|
58
|
+
pendingUploads: this.uploadQueue.length
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
log.error('Failed to initialize storage', error);
|
|
62
|
+
// Continue with empty state
|
|
63
|
+
this.isInitialized = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Upload Queue Methods
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all upload tasks
|
|
71
|
+
*/
|
|
72
|
+
getUploadQueue() {
|
|
73
|
+
return [...this.uploadQueue];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get pending upload tasks
|
|
78
|
+
*/
|
|
79
|
+
getPendingUploads() {
|
|
80
|
+
return this.uploadQueue.filter(task => task.status === 'pending' || task.status === 'uploading');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get failed upload tasks
|
|
85
|
+
*/
|
|
86
|
+
getFailedUploads() {
|
|
87
|
+
return this.uploadQueue.filter(task => task.status === 'failed');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Add a task to the upload queue
|
|
92
|
+
*/
|
|
93
|
+
async addUploadTask(task) {
|
|
94
|
+
log.debug('Adding upload task', {
|
|
95
|
+
taskId: task.id,
|
|
96
|
+
recordingId: task.recordingId
|
|
97
|
+
});
|
|
98
|
+
this.uploadQueue.push(task);
|
|
99
|
+
await this.saveUploadQueue();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Update an upload task
|
|
104
|
+
*/
|
|
105
|
+
async updateUploadTask(taskId, updates) {
|
|
106
|
+
const index = this.uploadQueue.findIndex(t => t.id === taskId);
|
|
107
|
+
if (index === -1) {
|
|
108
|
+
log.warn('Upload task not found', {
|
|
109
|
+
taskId
|
|
110
|
+
});
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.uploadQueue[index] = {
|
|
114
|
+
...this.uploadQueue[index],
|
|
115
|
+
...updates,
|
|
116
|
+
updatedAt: new Date()
|
|
117
|
+
};
|
|
118
|
+
await this.saveUploadQueue();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Update task status
|
|
123
|
+
*/
|
|
124
|
+
async updateTaskStatus(taskId, status, errorMessage) {
|
|
125
|
+
await this.updateUploadTask(taskId, {
|
|
126
|
+
status,
|
|
127
|
+
errorMessage
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Increment retry count for a task
|
|
133
|
+
*/
|
|
134
|
+
async incrementRetryCount(taskId) {
|
|
135
|
+
const task = this.uploadQueue.find(t => t.id === taskId);
|
|
136
|
+
if (task) {
|
|
137
|
+
await this.updateUploadTask(taskId, {
|
|
138
|
+
retryCount: task.retryCount + 1
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove a task from the queue
|
|
145
|
+
*/
|
|
146
|
+
async removeUploadTask(taskId) {
|
|
147
|
+
log.debug('Removing upload task', {
|
|
148
|
+
taskId
|
|
149
|
+
});
|
|
150
|
+
this.uploadQueue = this.uploadQueue.filter(t => t.id !== taskId);
|
|
151
|
+
await this.saveUploadQueue();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clear all completed tasks
|
|
156
|
+
*/
|
|
157
|
+
async clearCompletedTasks() {
|
|
158
|
+
this.uploadQueue = this.uploadQueue.filter(t => t.status !== 'completed');
|
|
159
|
+
await this.saveUploadQueue();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Clear all tasks
|
|
164
|
+
*/
|
|
165
|
+
async clearAllTasks() {
|
|
166
|
+
this.uploadQueue = [];
|
|
167
|
+
await this.saveUploadQueue();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get a specific upload task
|
|
172
|
+
*/
|
|
173
|
+
getUploadTask(taskId) {
|
|
174
|
+
return this.uploadQueue.find(t => t.id === taskId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Save upload queue to storage
|
|
179
|
+
*/
|
|
180
|
+
async saveUploadQueue() {
|
|
181
|
+
try {
|
|
182
|
+
await AsyncStorage.setItem(UPLOAD_QUEUE_KEY, JSON.stringify(this.uploadQueue));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
log.error('Failed to save upload queue', error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// SDK State Methods
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get last sync time for a device
|
|
192
|
+
*/
|
|
193
|
+
getLastSyncTime(deviceId) {
|
|
194
|
+
const timestamp = this.sdkState.lastSyncTimes[deviceId];
|
|
195
|
+
return timestamp ? new Date(timestamp) : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Set last sync time for a device
|
|
200
|
+
*/
|
|
201
|
+
async setLastSyncTime(deviceId, time = new Date()) {
|
|
202
|
+
this.sdkState.lastSyncTimes[deviceId] = time.getTime();
|
|
203
|
+
await this.saveSdkState();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get cached device info
|
|
208
|
+
*/
|
|
209
|
+
getDeviceInfo(deviceId) {
|
|
210
|
+
return this.sdkState.deviceInfo[deviceId];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Cache device info
|
|
215
|
+
*/
|
|
216
|
+
async setDeviceInfo(deviceId, info) {
|
|
217
|
+
this.sdkState.deviceInfo[deviceId] = info;
|
|
218
|
+
await this.saveSdkState();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Save SDK state to storage
|
|
223
|
+
*/
|
|
224
|
+
async saveSdkState() {
|
|
225
|
+
try {
|
|
226
|
+
await AsyncStorage.setItem(SDK_STATE_KEY, JSON.stringify(this.sdkState));
|
|
227
|
+
} catch (error) {
|
|
228
|
+
log.error('Failed to save SDK state', error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Recording File Methods
|
|
233
|
+
// Note: For actual file storage, we'd need react-native-fs or similar
|
|
234
|
+
// For now, we'll store base64 encoded audio in AsyncStorage for small files
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Save recording data locally
|
|
238
|
+
*/
|
|
239
|
+
async saveRecordingData(deviceId, recordingUuid, data) {
|
|
240
|
+
const key = `${STORAGE_PREFIX}recording:${deviceId}:${recordingUuid}`;
|
|
241
|
+
try {
|
|
242
|
+
// Store as base64 (not ideal for large files, but works for demo)
|
|
243
|
+
await AsyncStorage.setItem(key, data.toString('base64'));
|
|
244
|
+
log.debug('Saved recording data', {
|
|
245
|
+
deviceId,
|
|
246
|
+
recordingUuid,
|
|
247
|
+
size: data.length
|
|
248
|
+
});
|
|
249
|
+
return key;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
log.error('Failed to save recording data', error);
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Load recording data
|
|
258
|
+
*/
|
|
259
|
+
async loadRecordingData(localPath) {
|
|
260
|
+
try {
|
|
261
|
+
const data = await AsyncStorage.getItem(localPath);
|
|
262
|
+
if (!data) {
|
|
263
|
+
throw new Error(`Recording not found: ${localPath}`);
|
|
264
|
+
}
|
|
265
|
+
return Buffer.from(data, 'base64');
|
|
266
|
+
} catch (error) {
|
|
267
|
+
log.error('Failed to load recording data', error);
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Delete recording data
|
|
274
|
+
*/
|
|
275
|
+
async deleteRecordingData(localPath) {
|
|
276
|
+
try {
|
|
277
|
+
await AsyncStorage.removeItem(localPath);
|
|
278
|
+
log.debug('Deleted recording data', {
|
|
279
|
+
localPath
|
|
280
|
+
});
|
|
281
|
+
} catch (error) {
|
|
282
|
+
log.error('Failed to delete recording data', error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Clear all SDK storage
|
|
288
|
+
*/
|
|
289
|
+
async clearAll() {
|
|
290
|
+
log.info('Clearing all SDK storage');
|
|
291
|
+
try {
|
|
292
|
+
const keys = await AsyncStorage.getAllKeys();
|
|
293
|
+
const botaKeys = keys.filter(k => k.startsWith(STORAGE_PREFIX));
|
|
294
|
+
await AsyncStorage.multiRemove(botaKeys);
|
|
295
|
+
this.uploadQueue = [];
|
|
296
|
+
this.sdkState = {
|
|
297
|
+
lastSyncTimes: {},
|
|
298
|
+
deviceInfo: {}
|
|
299
|
+
};
|
|
300
|
+
} catch (error) {
|
|
301
|
+
log.error('Failed to clear storage', error);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Clean up resources
|
|
307
|
+
*/
|
|
308
|
+
destroy() {
|
|
309
|
+
this.uploadQueue = [];
|
|
310
|
+
this.sdkState = {
|
|
311
|
+
lastSyncTimes: {},
|
|
312
|
+
deviceInfo: {}
|
|
313
|
+
};
|
|
314
|
+
this.isInitialized = false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Generate a unique task ID
|
|
320
|
+
*/
|
|
321
|
+
export function generateTaskId() {
|
|
322
|
+
return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=StorageManager.js.map
|