@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,343 @@
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
+
8
+ import type { UploadTask, UploadTaskStatus } from '../models/Recording';
9
+ import { logger } from '../utils/logger';
10
+
11
+ const log = logger.tag('StorageManager');
12
+
13
+ // Storage keys
14
+ const STORAGE_PREFIX = '@bota_sdk:';
15
+ const UPLOAD_QUEUE_KEY = `${STORAGE_PREFIX}upload_queue`;
16
+ const SDK_STATE_KEY = `${STORAGE_PREFIX}sdk_state`;
17
+
18
+ /**
19
+ * SDK persistent state
20
+ */
21
+ interface SdkState {
22
+ lastSyncTimes: Record<string, number>; // deviceId -> timestamp
23
+ deviceInfo: Record<string, { serialNumber: string; firmwareVersion: string }>;
24
+ }
25
+
26
+ /**
27
+ * Storage Manager class
28
+ */
29
+ export class StorageManager {
30
+ private uploadQueue: UploadTask[] = [];
31
+ private sdkState: SdkState = {
32
+ lastSyncTimes: {},
33
+ deviceInfo: {},
34
+ };
35
+ private isInitialized = false;
36
+
37
+ /**
38
+ * Initialize storage manager
39
+ */
40
+ async initialize(): Promise<void> {
41
+ if (this.isInitialized) {
42
+ return;
43
+ }
44
+
45
+ log.debug('Initializing StorageManager');
46
+
47
+ try {
48
+ // Load upload queue
49
+ const queueData = await AsyncStorage.getItem(UPLOAD_QUEUE_KEY);
50
+ if (queueData) {
51
+ this.uploadQueue = JSON.parse(queueData);
52
+ // Restore dates
53
+ this.uploadQueue = this.uploadQueue.map((task) => ({
54
+ ...task,
55
+ createdAt: new Date(task.createdAt),
56
+ updatedAt: new Date(task.updatedAt),
57
+ }));
58
+ }
59
+
60
+ // Load SDK state
61
+ const stateData = await AsyncStorage.getItem(SDK_STATE_KEY);
62
+ if (stateData) {
63
+ this.sdkState = JSON.parse(stateData);
64
+ }
65
+
66
+ this.isInitialized = true;
67
+ log.info('StorageManager initialized', {
68
+ pendingUploads: this.uploadQueue.length,
69
+ });
70
+ } catch (error) {
71
+ log.error('Failed to initialize storage', error as Error);
72
+ // Continue with empty state
73
+ this.isInitialized = true;
74
+ }
75
+ }
76
+
77
+ // Upload Queue Methods
78
+
79
+ /**
80
+ * Get all upload tasks
81
+ */
82
+ getUploadQueue(): UploadTask[] {
83
+ return [...this.uploadQueue];
84
+ }
85
+
86
+ /**
87
+ * Get pending upload tasks
88
+ */
89
+ getPendingUploads(): UploadTask[] {
90
+ return this.uploadQueue.filter(
91
+ (task) => task.status === 'pending' || task.status === 'uploading'
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Get failed upload tasks
97
+ */
98
+ getFailedUploads(): UploadTask[] {
99
+ return this.uploadQueue.filter((task) => task.status === 'failed');
100
+ }
101
+
102
+ /**
103
+ * Add a task to the upload queue
104
+ */
105
+ async addUploadTask(task: UploadTask): Promise<void> {
106
+ log.debug('Adding upload task', { taskId: task.id, recordingId: task.recordingId });
107
+
108
+ this.uploadQueue.push(task);
109
+ await this.saveUploadQueue();
110
+ }
111
+
112
+ /**
113
+ * Update an upload task
114
+ */
115
+ async updateUploadTask(
116
+ taskId: string,
117
+ updates: Partial<UploadTask>
118
+ ): Promise<void> {
119
+ const index = this.uploadQueue.findIndex((t) => t.id === taskId);
120
+ if (index === -1) {
121
+ log.warn('Upload task not found', { taskId });
122
+ return;
123
+ }
124
+
125
+ this.uploadQueue[index] = {
126
+ ...this.uploadQueue[index],
127
+ ...updates,
128
+ updatedAt: new Date(),
129
+ };
130
+
131
+ await this.saveUploadQueue();
132
+ }
133
+
134
+ /**
135
+ * Update task status
136
+ */
137
+ async updateTaskStatus(
138
+ taskId: string,
139
+ status: UploadTaskStatus,
140
+ errorMessage?: string
141
+ ): Promise<void> {
142
+ await this.updateUploadTask(taskId, { status, errorMessage });
143
+ }
144
+
145
+ /**
146
+ * Increment retry count for a task
147
+ */
148
+ async incrementRetryCount(taskId: string): Promise<void> {
149
+ const task = this.uploadQueue.find((t) => t.id === taskId);
150
+ if (task) {
151
+ await this.updateUploadTask(taskId, { retryCount: task.retryCount + 1 });
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Remove a task from the queue
157
+ */
158
+ async removeUploadTask(taskId: string): Promise<void> {
159
+ log.debug('Removing upload task', { taskId });
160
+
161
+ this.uploadQueue = this.uploadQueue.filter((t) => t.id !== taskId);
162
+ await this.saveUploadQueue();
163
+ }
164
+
165
+ /**
166
+ * Clear all completed tasks
167
+ */
168
+ async clearCompletedTasks(): Promise<void> {
169
+ this.uploadQueue = this.uploadQueue.filter(
170
+ (t) => t.status !== 'completed'
171
+ );
172
+ await this.saveUploadQueue();
173
+ }
174
+
175
+ /**
176
+ * Clear all tasks
177
+ */
178
+ async clearAllTasks(): Promise<void> {
179
+ this.uploadQueue = [];
180
+ await this.saveUploadQueue();
181
+ }
182
+
183
+ /**
184
+ * Get a specific upload task
185
+ */
186
+ getUploadTask(taskId: string): UploadTask | undefined {
187
+ return this.uploadQueue.find((t) => t.id === taskId);
188
+ }
189
+
190
+ /**
191
+ * Save upload queue to storage
192
+ */
193
+ private async saveUploadQueue(): Promise<void> {
194
+ try {
195
+ await AsyncStorage.setItem(
196
+ UPLOAD_QUEUE_KEY,
197
+ JSON.stringify(this.uploadQueue)
198
+ );
199
+ } catch (error) {
200
+ log.error('Failed to save upload queue', error as Error);
201
+ }
202
+ }
203
+
204
+ // SDK State Methods
205
+
206
+ /**
207
+ * Get last sync time for a device
208
+ */
209
+ getLastSyncTime(deviceId: string): Date | null {
210
+ const timestamp = this.sdkState.lastSyncTimes[deviceId];
211
+ return timestamp ? new Date(timestamp) : null;
212
+ }
213
+
214
+ /**
215
+ * Set last sync time for a device
216
+ */
217
+ async setLastSyncTime(deviceId: string, time: Date = new Date()): Promise<void> {
218
+ this.sdkState.lastSyncTimes[deviceId] = time.getTime();
219
+ await this.saveSdkState();
220
+ }
221
+
222
+ /**
223
+ * Get cached device info
224
+ */
225
+ getDeviceInfo(
226
+ deviceId: string
227
+ ): { serialNumber: string; firmwareVersion: string } | undefined {
228
+ return this.sdkState.deviceInfo[deviceId];
229
+ }
230
+
231
+ /**
232
+ * Cache device info
233
+ */
234
+ async setDeviceInfo(
235
+ deviceId: string,
236
+ info: { serialNumber: string; firmwareVersion: string }
237
+ ): Promise<void> {
238
+ this.sdkState.deviceInfo[deviceId] = info;
239
+ await this.saveSdkState();
240
+ }
241
+
242
+ /**
243
+ * Save SDK state to storage
244
+ */
245
+ private async saveSdkState(): Promise<void> {
246
+ try {
247
+ await AsyncStorage.setItem(SDK_STATE_KEY, JSON.stringify(this.sdkState));
248
+ } catch (error) {
249
+ log.error('Failed to save SDK state', error as Error);
250
+ }
251
+ }
252
+
253
+ // Recording File Methods
254
+ // Note: For actual file storage, we'd need react-native-fs or similar
255
+ // For now, we'll store base64 encoded audio in AsyncStorage for small files
256
+
257
+ /**
258
+ * Save recording data locally
259
+ */
260
+ async saveRecordingData(
261
+ deviceId: string,
262
+ recordingUuid: string,
263
+ data: Buffer
264
+ ): Promise<string> {
265
+ const key = `${STORAGE_PREFIX}recording:${deviceId}:${recordingUuid}`;
266
+
267
+ try {
268
+ // Store as base64 (not ideal for large files, but works for demo)
269
+ await AsyncStorage.setItem(key, data.toString('base64'));
270
+ log.debug('Saved recording data', {
271
+ deviceId,
272
+ recordingUuid,
273
+ size: data.length,
274
+ });
275
+ return key;
276
+ } catch (error) {
277
+ log.error('Failed to save recording data', error as Error);
278
+ throw error;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Load recording data
284
+ */
285
+ async loadRecordingData(localPath: string): Promise<Buffer> {
286
+ try {
287
+ const data = await AsyncStorage.getItem(localPath);
288
+ if (!data) {
289
+ throw new Error(`Recording not found: ${localPath}`);
290
+ }
291
+ return Buffer.from(data, 'base64');
292
+ } catch (error) {
293
+ log.error('Failed to load recording data', error as Error);
294
+ throw error;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Delete recording data
300
+ */
301
+ async deleteRecordingData(localPath: string): Promise<void> {
302
+ try {
303
+ await AsyncStorage.removeItem(localPath);
304
+ log.debug('Deleted recording data', { localPath });
305
+ } catch (error) {
306
+ log.error('Failed to delete recording data', error as Error);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Clear all SDK storage
312
+ */
313
+ async clearAll(): Promise<void> {
314
+ log.info('Clearing all SDK storage');
315
+
316
+ try {
317
+ const keys = await AsyncStorage.getAllKeys();
318
+ const botaKeys = keys.filter((k) => k.startsWith(STORAGE_PREFIX));
319
+ await AsyncStorage.multiRemove(botaKeys);
320
+
321
+ this.uploadQueue = [];
322
+ this.sdkState = { lastSyncTimes: {}, deviceInfo: {} };
323
+ } catch (error) {
324
+ log.error('Failed to clear storage', error as Error);
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Clean up resources
330
+ */
331
+ destroy(): void {
332
+ this.uploadQueue = [];
333
+ this.sdkState = { lastSyncTimes: {}, deviceInfo: {} };
334
+ this.isInitialized = false;
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Generate a unique task ID
340
+ */
341
+ export function generateTaskId(): string {
342
+ return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
343
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Storage module exports
3
+ */
4
+
5
+ export { StorageManager, generateTaskId } from './StorageManager';
@@ -0,0 +1,164 @@
1
+ /**
2
+ * S3 Uploader - Handles uploading audio files to S3 using pre-signed URLs
3
+ */
4
+
5
+ import { Buffer } from 'buffer';
6
+
7
+ import { UploadError } from '../utils/errors';
8
+ import { logger } from '../utils/logger';
9
+
10
+ const log = logger.tag('S3Uploader');
11
+
12
+ /**
13
+ * Upload progress callback
14
+ */
15
+ export type UploadProgressCallback = (progress: number) => void;
16
+
17
+ /**
18
+ * Upload options
19
+ */
20
+ export interface UploadOptions {
21
+ /** Content type (default: audio/opus) */
22
+ contentType?: string;
23
+ /** Progress callback */
24
+ onProgress?: UploadProgressCallback;
25
+ /** Abort signal for cancellation */
26
+ abortSignal?: AbortSignal;
27
+ }
28
+
29
+ /**
30
+ * S3 Uploader class
31
+ */
32
+ export class S3Uploader {
33
+ /**
34
+ * Upload a file to S3 using a pre-signed URL
35
+ */
36
+ async upload(
37
+ data: Buffer,
38
+ uploadUrl: string,
39
+ options: UploadOptions = {}
40
+ ): Promise<void> {
41
+ const {
42
+ contentType = 'audio/opus',
43
+ onProgress,
44
+ abortSignal,
45
+ } = options;
46
+
47
+ log.info('Starting S3 upload', {
48
+ size: data.length,
49
+ contentType,
50
+ });
51
+
52
+ try {
53
+ // For React Native, we use fetch with the PUT method
54
+ const response = await fetch(uploadUrl, {
55
+ method: 'PUT',
56
+ headers: {
57
+ 'Content-Type': contentType,
58
+ 'Content-Length': data.length.toString(),
59
+ },
60
+ body: data,
61
+ signal: abortSignal,
62
+ });
63
+
64
+ if (!response.ok) {
65
+ const errorText = await response.text().catch(() => 'Unknown error');
66
+ log.error('S3 upload failed', undefined, {
67
+ status: response.status,
68
+ statusText: response.statusText,
69
+ error: errorText,
70
+ });
71
+
72
+ if (response.status === 403) {
73
+ throw UploadError.urlExpired('');
74
+ }
75
+
76
+ throw new UploadError(
77
+ `S3 upload failed: ${response.status} ${response.statusText}`,
78
+ 'S3_UPLOAD_FAILED'
79
+ );
80
+ }
81
+
82
+ // Report completion
83
+ onProgress?.(1.0);
84
+
85
+ log.info('S3 upload completed', { size: data.length });
86
+ } catch (error) {
87
+ if (error instanceof UploadError) {
88
+ throw error;
89
+ }
90
+
91
+ const err = error as Error;
92
+
93
+ // Check for abort
94
+ if (err.name === 'AbortError') {
95
+ throw new UploadError('Upload was cancelled', 'UPLOAD_CANCELLED');
96
+ }
97
+
98
+ // Check for network errors
99
+ if (err.message?.includes('Network request failed')) {
100
+ throw UploadError.networkUnavailable();
101
+ }
102
+
103
+ throw new UploadError(
104
+ `Upload failed: ${err.message}`,
105
+ 'UPLOAD_FAILED',
106
+ undefined,
107
+ err
108
+ );
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Upload a file in chunks (for larger files)
114
+ * Note: This requires multipart upload support from the pre-signed URL
115
+ */
116
+ async uploadChunked(
117
+ data: Buffer,
118
+ uploadUrl: string,
119
+ options: UploadOptions = {}
120
+ ): Promise<void> {
121
+ // For now, delegate to simple upload
122
+ // In production, you'd implement S3 multipart upload here
123
+ return this.upload(data, uploadUrl, options);
124
+ }
125
+
126
+ /**
127
+ * Notify the backend that upload is complete
128
+ */
129
+ async notifyCompletion(
130
+ completeUrl: string,
131
+ recordingId: string,
132
+ uploadToken: string
133
+ ): Promise<void> {
134
+ log.debug('Notifying upload completion', { recordingId });
135
+
136
+ try {
137
+ const response = await fetch(completeUrl, {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json',
141
+ Authorization: `Bearer ${uploadToken}`,
142
+ },
143
+ body: JSON.stringify({ recording_id: recordingId }),
144
+ });
145
+
146
+ if (!response.ok) {
147
+ const errorText = await response.text().catch(() => 'Unknown error');
148
+ throw new UploadError(
149
+ `Completion notification failed: ${response.status} - ${errorText}`,
150
+ 'COMPLETION_FAILED'
151
+ );
152
+ }
153
+
154
+ log.info('Upload completion notified', { recordingId });
155
+ } catch (error) {
156
+ if (error instanceof UploadError) {
157
+ throw error;
158
+ }
159
+
160
+ const err = error as Error;
161
+ throw UploadError.completionFailed('', err);
162
+ }
163
+ }
164
+ }