@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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload Queue - Manages persistent upload queue with retry logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import EventEmitter from 'eventemitter3';
|
|
6
|
+
|
|
7
|
+
import type { UploadTask } from '../models/Recording';
|
|
8
|
+
import { StorageManager, generateTaskId } from '../storage/StorageManager';
|
|
9
|
+
import { S3Uploader } from './S3Uploader';
|
|
10
|
+
import { shouldContinueRetrying } from '../utils/retry';
|
|
11
|
+
import { logger } from '../utils/logger';
|
|
12
|
+
|
|
13
|
+
const log = logger.tag('UploadQueue');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Events emitted by UploadQueue
|
|
17
|
+
*/
|
|
18
|
+
interface UploadQueueEvents {
|
|
19
|
+
taskAdded: (task: UploadTask) => void;
|
|
20
|
+
taskUpdated: (task: UploadTask) => void;
|
|
21
|
+
taskCompleted: (taskId: string, recordingId: string) => void;
|
|
22
|
+
taskFailed: (taskId: string, error: Error) => void;
|
|
23
|
+
uploadProgress: (taskId: string, progress: number) => void;
|
|
24
|
+
queueEmpty: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Upload Queue configuration
|
|
29
|
+
*/
|
|
30
|
+
export interface UploadQueueConfig {
|
|
31
|
+
/** Maximum concurrent uploads (default: 2) */
|
|
32
|
+
maxConcurrent?: number;
|
|
33
|
+
/** Maximum retry attempts (default: 6) */
|
|
34
|
+
maxRetries?: number;
|
|
35
|
+
/** Auto-start processing (default: true) */
|
|
36
|
+
autoStart?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Upload Queue class
|
|
41
|
+
*/
|
|
42
|
+
export class UploadQueue extends EventEmitter<UploadQueueEvents> {
|
|
43
|
+
private storage: StorageManager;
|
|
44
|
+
private uploader: S3Uploader;
|
|
45
|
+
private maxConcurrent: number;
|
|
46
|
+
private maxRetries: number;
|
|
47
|
+
private activeUploads: Set<string> = new Set();
|
|
48
|
+
private isPaused = false;
|
|
49
|
+
private isProcessing = false;
|
|
50
|
+
private abortControllers: Map<string, AbortController> = new Map();
|
|
51
|
+
|
|
52
|
+
constructor(storage: StorageManager, config: UploadQueueConfig = {}) {
|
|
53
|
+
super();
|
|
54
|
+
this.storage = storage;
|
|
55
|
+
this.uploader = new S3Uploader();
|
|
56
|
+
this.maxConcurrent = config.maxConcurrent ?? 2;
|
|
57
|
+
this.maxRetries = config.maxRetries ?? 6;
|
|
58
|
+
|
|
59
|
+
if (config.autoStart !== false) {
|
|
60
|
+
this.processQueue();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add a new upload task to the queue
|
|
66
|
+
*/
|
|
67
|
+
async enqueue(params: {
|
|
68
|
+
recordingId: string;
|
|
69
|
+
deviceId: string;
|
|
70
|
+
localPath: string;
|
|
71
|
+
uploadUrl: string;
|
|
72
|
+
uploadToken: string;
|
|
73
|
+
completeUrl: string;
|
|
74
|
+
}): Promise<UploadTask> {
|
|
75
|
+
const task: UploadTask = {
|
|
76
|
+
id: generateTaskId(),
|
|
77
|
+
recordingId: params.recordingId,
|
|
78
|
+
deviceId: params.deviceId,
|
|
79
|
+
localPath: params.localPath,
|
|
80
|
+
uploadUrl: params.uploadUrl,
|
|
81
|
+
uploadToken: params.uploadToken,
|
|
82
|
+
completeUrl: params.completeUrl,
|
|
83
|
+
status: 'pending',
|
|
84
|
+
retryCount: 0,
|
|
85
|
+
createdAt: new Date(),
|
|
86
|
+
updatedAt: new Date(),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await this.storage.addUploadTask(task);
|
|
90
|
+
this.emit('taskAdded', task);
|
|
91
|
+
|
|
92
|
+
log.info('Task enqueued', { taskId: task.id, recordingId: task.recordingId });
|
|
93
|
+
|
|
94
|
+
// Trigger queue processing
|
|
95
|
+
this.processQueue();
|
|
96
|
+
|
|
97
|
+
return task;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cancel a specific upload task
|
|
102
|
+
*/
|
|
103
|
+
async cancel(taskId: string): Promise<void> {
|
|
104
|
+
log.info('Cancelling task', { taskId });
|
|
105
|
+
|
|
106
|
+
// Abort if currently uploading
|
|
107
|
+
const controller = this.abortControllers.get(taskId);
|
|
108
|
+
if (controller) {
|
|
109
|
+
controller.abort();
|
|
110
|
+
this.abortControllers.delete(taskId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Remove from active uploads
|
|
114
|
+
this.activeUploads.delete(taskId);
|
|
115
|
+
|
|
116
|
+
// Remove from queue
|
|
117
|
+
await this.storage.removeUploadTask(taskId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Cancel all uploads
|
|
122
|
+
*/
|
|
123
|
+
async cancelAll(): Promise<void> {
|
|
124
|
+
log.info('Cancelling all uploads');
|
|
125
|
+
|
|
126
|
+
// Abort all active uploads
|
|
127
|
+
for (const controller of this.abortControllers.values()) {
|
|
128
|
+
controller.abort();
|
|
129
|
+
}
|
|
130
|
+
this.abortControllers.clear();
|
|
131
|
+
this.activeUploads.clear();
|
|
132
|
+
|
|
133
|
+
// Clear queue
|
|
134
|
+
await this.storage.clearAllTasks();
|
|
135
|
+
this.emit('queueEmpty');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Pause queue processing
|
|
140
|
+
*/
|
|
141
|
+
pause(): void {
|
|
142
|
+
log.info('Pausing upload queue');
|
|
143
|
+
this.isPaused = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Resume queue processing
|
|
148
|
+
*/
|
|
149
|
+
resume(): void {
|
|
150
|
+
log.info('Resuming upload queue');
|
|
151
|
+
this.isPaused = false;
|
|
152
|
+
this.processQueue();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Retry all failed uploads
|
|
157
|
+
*/
|
|
158
|
+
async retryFailed(): Promise<void> {
|
|
159
|
+
log.info('Retrying failed uploads');
|
|
160
|
+
|
|
161
|
+
const failedTasks = this.storage.getFailedUploads();
|
|
162
|
+
for (const task of failedTasks) {
|
|
163
|
+
if (task.retryCount < this.maxRetries) {
|
|
164
|
+
await this.storage.updateTaskStatus(task.id, 'pending');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
this.processQueue();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get all pending tasks
|
|
173
|
+
*/
|
|
174
|
+
getPendingTasks(): UploadTask[] {
|
|
175
|
+
return this.storage.getPendingUploads();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get all tasks
|
|
180
|
+
*/
|
|
181
|
+
getAllTasks(): UploadTask[] {
|
|
182
|
+
return this.storage.getUploadQueue();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Process the upload queue
|
|
187
|
+
*/
|
|
188
|
+
private async processQueue(): Promise<void> {
|
|
189
|
+
if (this.isPaused || this.isProcessing) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.isProcessing = true;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
while (!this.isPaused) {
|
|
197
|
+
// Check if we can start more uploads
|
|
198
|
+
if (this.activeUploads.size >= this.maxConcurrent) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Get next pending task
|
|
203
|
+
const pendingTasks = this.storage.getPendingUploads();
|
|
204
|
+
const nextTask = pendingTasks.find(
|
|
205
|
+
(t) => !this.activeUploads.has(t.id) && t.status === 'pending'
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
if (!nextTask) {
|
|
209
|
+
// No more tasks to process
|
|
210
|
+
if (this.activeUploads.size === 0) {
|
|
211
|
+
this.emit('queueEmpty');
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Start upload in background
|
|
217
|
+
this.processTask(nextTask);
|
|
218
|
+
}
|
|
219
|
+
} finally {
|
|
220
|
+
this.isProcessing = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Process a single upload task
|
|
226
|
+
*/
|
|
227
|
+
private async processTask(task: UploadTask): Promise<void> {
|
|
228
|
+
this.activeUploads.add(task.id);
|
|
229
|
+
|
|
230
|
+
const abortController = new AbortController();
|
|
231
|
+
this.abortControllers.set(task.id, abortController);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Update status to uploading
|
|
235
|
+
await this.storage.updateTaskStatus(task.id, 'uploading');
|
|
236
|
+
this.emit('taskUpdated', { ...task, status: 'uploading' });
|
|
237
|
+
|
|
238
|
+
log.info('Starting upload', { taskId: task.id, recordingId: task.recordingId });
|
|
239
|
+
|
|
240
|
+
// Load the audio data
|
|
241
|
+
const audioData = await this.storage.loadRecordingData(task.localPath);
|
|
242
|
+
|
|
243
|
+
// Upload to S3
|
|
244
|
+
await this.uploader.upload(audioData, task.uploadUrl, {
|
|
245
|
+
onProgress: (progress) => {
|
|
246
|
+
this.emit('uploadProgress', task.id, progress);
|
|
247
|
+
},
|
|
248
|
+
abortSignal: abortController.signal,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Notify completion
|
|
252
|
+
await this.uploader.notifyCompletion(
|
|
253
|
+
task.completeUrl,
|
|
254
|
+
task.recordingId,
|
|
255
|
+
task.uploadToken
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Mark as completed
|
|
259
|
+
await this.storage.updateTaskStatus(task.id, 'completed');
|
|
260
|
+
|
|
261
|
+
// Clean up local file
|
|
262
|
+
await this.storage.deleteRecordingData(task.localPath);
|
|
263
|
+
|
|
264
|
+
log.info('Upload completed', { taskId: task.id, recordingId: task.recordingId });
|
|
265
|
+
|
|
266
|
+
this.emit('taskCompleted', task.id, task.recordingId);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
const err = error as Error;
|
|
269
|
+
log.error('Upload failed', err, { taskId: task.id });
|
|
270
|
+
|
|
271
|
+
// Check if we should retry
|
|
272
|
+
if (
|
|
273
|
+
task.retryCount < this.maxRetries &&
|
|
274
|
+
shouldContinueRetrying(task.createdAt)
|
|
275
|
+
) {
|
|
276
|
+
await this.storage.incrementRetryCount(task.id);
|
|
277
|
+
await this.storage.updateTaskStatus(task.id, 'pending', err.message);
|
|
278
|
+
log.info('Task will be retried', {
|
|
279
|
+
taskId: task.id,
|
|
280
|
+
retryCount: task.retryCount + 1,
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
await this.storage.updateTaskStatus(task.id, 'failed', err.message);
|
|
284
|
+
this.emit('taskFailed', task.id, err);
|
|
285
|
+
}
|
|
286
|
+
} finally {
|
|
287
|
+
this.activeUploads.delete(task.id);
|
|
288
|
+
this.abortControllers.delete(task.id);
|
|
289
|
+
|
|
290
|
+
// Process next task
|
|
291
|
+
this.processQueue();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Clean up resources
|
|
297
|
+
*/
|
|
298
|
+
destroy(): void {
|
|
299
|
+
this.isPaused = true;
|
|
300
|
+
|
|
301
|
+
// Abort all active uploads
|
|
302
|
+
for (const controller of this.abortControllers.values()) {
|
|
303
|
+
controller.abort();
|
|
304
|
+
}
|
|
305
|
+
this.abortControllers.clear();
|
|
306
|
+
this.activeUploads.clear();
|
|
307
|
+
|
|
308
|
+
this.removeAllListeners();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error types for the Bota SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for all Bota SDK errors
|
|
7
|
+
*/
|
|
8
|
+
export class BotaError extends Error {
|
|
9
|
+
constructor(
|
|
10
|
+
message: string,
|
|
11
|
+
public readonly code: string,
|
|
12
|
+
public readonly cause?: Error
|
|
13
|
+
) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'BotaError';
|
|
16
|
+
Object.setPrototypeOf(this, BotaError.prototype);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Bluetooth-related errors
|
|
22
|
+
*/
|
|
23
|
+
export class BluetoothError extends BotaError {
|
|
24
|
+
constructor(message: string, code: string, cause?: Error) {
|
|
25
|
+
super(message, code, cause);
|
|
26
|
+
this.name = 'BluetoothError';
|
|
27
|
+
Object.setPrototypeOf(this, BluetoothError.prototype);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static unavailable(): BluetoothError {
|
|
31
|
+
return new BluetoothError(
|
|
32
|
+
'Bluetooth is not available on this device',
|
|
33
|
+
'BLUETOOTH_UNAVAILABLE'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static unauthorized(): BluetoothError {
|
|
38
|
+
return new BluetoothError(
|
|
39
|
+
'Bluetooth permission not granted',
|
|
40
|
+
'BLUETOOTH_UNAUTHORIZED'
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static poweredOff(): BluetoothError {
|
|
45
|
+
return new BluetoothError('Bluetooth is powered off', 'BLUETOOTH_OFF');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Device-related errors
|
|
51
|
+
*/
|
|
52
|
+
export class DeviceError extends BotaError {
|
|
53
|
+
constructor(
|
|
54
|
+
message: string,
|
|
55
|
+
code: string,
|
|
56
|
+
public readonly deviceId?: string,
|
|
57
|
+
cause?: Error
|
|
58
|
+
) {
|
|
59
|
+
super(message, code, cause);
|
|
60
|
+
this.name = 'DeviceError';
|
|
61
|
+
Object.setPrototypeOf(this, DeviceError.prototype);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static notFound(deviceId: string): DeviceError {
|
|
65
|
+
return new DeviceError(
|
|
66
|
+
`Device ${deviceId} not found`,
|
|
67
|
+
'DEVICE_NOT_FOUND',
|
|
68
|
+
deviceId
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static connectionFailed(deviceId: string, cause?: Error): DeviceError {
|
|
73
|
+
return new DeviceError(
|
|
74
|
+
`Failed to connect to device ${deviceId}`,
|
|
75
|
+
'CONNECTION_FAILED',
|
|
76
|
+
deviceId,
|
|
77
|
+
cause
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static connectionLost(deviceId: string, during?: string): DeviceError {
|
|
82
|
+
const message = during
|
|
83
|
+
? `Connection to device ${deviceId} lost during ${during}`
|
|
84
|
+
: `Connection to device ${deviceId} lost`;
|
|
85
|
+
return new DeviceError(message, 'CONNECTION_LOST', deviceId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static bondingFailed(deviceId: string, cause?: Error): DeviceError {
|
|
89
|
+
return new DeviceError(
|
|
90
|
+
`Bonding failed for device ${deviceId}`,
|
|
91
|
+
'BONDING_FAILED',
|
|
92
|
+
deviceId,
|
|
93
|
+
cause
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static notConnected(deviceId: string): DeviceError {
|
|
98
|
+
return new DeviceError(
|
|
99
|
+
`Device ${deviceId} is not connected`,
|
|
100
|
+
'NOT_CONNECTED',
|
|
101
|
+
deviceId
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static alreadyConnected(deviceId: string): DeviceError {
|
|
106
|
+
return new DeviceError(
|
|
107
|
+
`Device ${deviceId} is already connected`,
|
|
108
|
+
'ALREADY_CONNECTED',
|
|
109
|
+
deviceId
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Provisioning-related errors
|
|
116
|
+
*/
|
|
117
|
+
export class ProvisioningError extends BotaError {
|
|
118
|
+
constructor(
|
|
119
|
+
message: string,
|
|
120
|
+
code: string,
|
|
121
|
+
public readonly deviceId?: string,
|
|
122
|
+
cause?: Error
|
|
123
|
+
) {
|
|
124
|
+
super(message, code, cause);
|
|
125
|
+
this.name = 'ProvisioningError';
|
|
126
|
+
Object.setPrototypeOf(this, ProvisioningError.prototype);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static invalidToken(deviceId: string): ProvisioningError {
|
|
130
|
+
return new ProvisioningError(
|
|
131
|
+
'Device rejected the token as invalid',
|
|
132
|
+
'INVALID_TOKEN',
|
|
133
|
+
deviceId
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static storageError(deviceId: string): ProvisioningError {
|
|
138
|
+
return new ProvisioningError(
|
|
139
|
+
'Device failed to store the token',
|
|
140
|
+
'STORAGE_ERROR',
|
|
141
|
+
deviceId
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static chunkError(deviceId: string): ProvisioningError {
|
|
146
|
+
return new ProvisioningError(
|
|
147
|
+
'Token chunk transfer failed',
|
|
148
|
+
'CHUNK_ERROR',
|
|
149
|
+
deviceId
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
static alreadyProvisioned(deviceId: string): ProvisioningError {
|
|
154
|
+
return new ProvisioningError(
|
|
155
|
+
'Device is already provisioned',
|
|
156
|
+
'ALREADY_PROVISIONED',
|
|
157
|
+
deviceId
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
static timeout(deviceId: string): ProvisioningError {
|
|
162
|
+
return new ProvisioningError(
|
|
163
|
+
'Provisioning timed out waiting for device response',
|
|
164
|
+
'PROVISIONING_TIMEOUT',
|
|
165
|
+
deviceId
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Transfer-related errors
|
|
172
|
+
*/
|
|
173
|
+
export class TransferError extends BotaError {
|
|
174
|
+
constructor(
|
|
175
|
+
message: string,
|
|
176
|
+
code: string,
|
|
177
|
+
public readonly recordingUuid?: string,
|
|
178
|
+
cause?: Error
|
|
179
|
+
) {
|
|
180
|
+
super(message, code, cause);
|
|
181
|
+
this.name = 'TransferError';
|
|
182
|
+
Object.setPrototypeOf(this, TransferError.prototype);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static recordingNotFound(uuid: string): TransferError {
|
|
186
|
+
return new TransferError(
|
|
187
|
+
`Recording ${uuid} not found on device`,
|
|
188
|
+
'RECORDING_NOT_FOUND',
|
|
189
|
+
uuid
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static checksumMismatch(uuid: string): TransferError {
|
|
194
|
+
return new TransferError(
|
|
195
|
+
`Checksum mismatch for recording ${uuid}`,
|
|
196
|
+
'CHECKSUM_MISMATCH',
|
|
197
|
+
uuid
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static interrupted(uuid: string, cause?: Error): TransferError {
|
|
202
|
+
return new TransferError(
|
|
203
|
+
`Transfer interrupted for recording ${uuid}`,
|
|
204
|
+
'TRANSFER_INTERRUPTED',
|
|
205
|
+
uuid,
|
|
206
|
+
cause
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
static timeout(uuid: string): TransferError {
|
|
211
|
+
return new TransferError(
|
|
212
|
+
`Transfer timed out for recording ${uuid}`,
|
|
213
|
+
'TRANSFER_TIMEOUT',
|
|
214
|
+
uuid
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
static deviceError(uuid: string, errorCode: number): TransferError {
|
|
219
|
+
return new TransferError(
|
|
220
|
+
`Device error (code: ${errorCode}) during transfer of ${uuid}`,
|
|
221
|
+
'DEVICE_ERROR',
|
|
222
|
+
uuid
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Upload-related errors
|
|
229
|
+
*/
|
|
230
|
+
export class UploadError extends BotaError {
|
|
231
|
+
constructor(
|
|
232
|
+
message: string,
|
|
233
|
+
code: string,
|
|
234
|
+
public readonly taskId?: string,
|
|
235
|
+
cause?: Error
|
|
236
|
+
) {
|
|
237
|
+
super(message, code, cause);
|
|
238
|
+
this.name = 'UploadError';
|
|
239
|
+
Object.setPrototypeOf(this, UploadError.prototype);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static urlExpired(taskId: string): UploadError {
|
|
243
|
+
return new UploadError(
|
|
244
|
+
'Upload URL has expired',
|
|
245
|
+
'URL_EXPIRED',
|
|
246
|
+
taskId
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static networkUnavailable(): UploadError {
|
|
251
|
+
return new UploadError(
|
|
252
|
+
'Network is not available',
|
|
253
|
+
'NETWORK_UNAVAILABLE'
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static uploadFailed(taskId: string, cause?: Error): UploadError {
|
|
258
|
+
return new UploadError(
|
|
259
|
+
`Upload failed for task ${taskId}`,
|
|
260
|
+
'UPLOAD_FAILED',
|
|
261
|
+
taskId,
|
|
262
|
+
cause
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
static completionFailed(taskId: string, cause?: Error): UploadError {
|
|
267
|
+
return new UploadError(
|
|
268
|
+
`Failed to notify completion for task ${taskId}`,
|
|
269
|
+
'COMPLETION_FAILED',
|
|
270
|
+
taskId,
|
|
271
|
+
cause
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* SDK state errors
|
|
278
|
+
*/
|
|
279
|
+
export class SdkError extends BotaError {
|
|
280
|
+
constructor(message: string, code: string, cause?: Error) {
|
|
281
|
+
super(message, code, cause);
|
|
282
|
+
this.name = 'SdkError';
|
|
283
|
+
Object.setPrototypeOf(this, SdkError.prototype);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static notInitialized(): SdkError {
|
|
287
|
+
return new SdkError(
|
|
288
|
+
'SDK has not been initialized. Call BotaClient.configure() first.',
|
|
289
|
+
'NOT_INITIALIZED'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static invalidState(expected: string, actual: string): SdkError {
|
|
294
|
+
return new SdkError(
|
|
295
|
+
`Invalid state: expected ${expected}, got ${actual}`,
|
|
296
|
+
'INVALID_STATE'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
static timeout(operation: string): SdkError {
|
|
301
|
+
return new SdkError(`Operation timed out: ${operation}`, 'TIMEOUT');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Type guard to check if an error is a BotaError
|
|
307
|
+
*/
|
|
308
|
+
export function isBotaError(error: unknown): error is BotaError {
|
|
309
|
+
return error instanceof BotaError;
|
|
310
|
+
}
|