rn-file-toolkit 1.0.1 → 1.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.
@@ -41,16 +41,6 @@ var _NativeFileToolkit = _interopRequireDefault(require("./NativeFileToolkit"));
41
41
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
42
42
  const FileToolkitModule = _reactNative.NativeModules.FileToolkit || _NativeFileToolkit.default;
43
43
  const eventEmitter = new _reactNative.NativeEventEmitter(FileToolkitModule);
44
-
45
- // ─── Types ────────────────────────────────────────────────────────────────────
46
-
47
- /**
48
- * Rich progress information emitted during a download.
49
- * Replaces the plain `number` percent from earlier versions.
50
- */
51
-
52
- // ─── Queue ───────────────────────────────────────────────────────────────────
53
-
54
44
  class DownloadQueue {
55
45
  _maxConcurrent = 3;
56
46
  _active = 0;
@@ -58,7 +48,6 @@ class DownloadQueue {
58
48
  setOptions(opts) {
59
49
  if (opts.maxConcurrent != null && opts.maxConcurrent > 0) {
60
50
  this._maxConcurrent = opts.maxConcurrent;
61
- // Kick off any slots that just opened.
62
51
  this._flush();
63
52
  }
64
53
  }
@@ -88,7 +77,6 @@ class DownloadQueue {
88
77
  while (this._active < this._maxConcurrent && this._queue.length > 0) {
89
78
  const item = this._queue.shift();
90
79
  this._active++;
91
- // Strip queue-specific fields before passing to the native layer.
92
80
  const nativeOptions = {
93
81
  ...item.options
94
82
  };
@@ -106,55 +94,31 @@ class DownloadQueue {
106
94
  }
107
95
  }
108
96
  const _globalQueue = new DownloadQueue();
109
-
110
- /**
111
- * Configure the global managed download queue.
112
- *
113
- * @example
114
- * ```ts
115
- * setQueueOptions({ maxConcurrent: 3 });
116
- * ```
117
- */
97
+ function _generateId() {
98
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
99
+ const r = Math.random() * 16 | 0;
100
+ const v = c === 'x' ? r : r & 0x3 | 0x8;
101
+ return v.toString(16);
102
+ });
103
+ }
118
104
  function setQueueOptions(options) {
119
105
  _globalQueue.setOptions(options);
120
106
  }
121
-
122
- /**
123
- * Get the current state of the managed download queue.
124
- *
125
- * @example
126
- * ```ts
127
- * const { active, pending, maxConcurrent } = getQueueStatus();
128
- * ```
129
- */
130
107
  function getQueueStatus() {
131
108
  return _globalQueue.getStatus();
132
109
  }
133
-
134
- // ─── Core download ────────────────────────────────────────────────────────────
135
-
136
- /**
137
- * Core native download executor — used internally by `download()` and the queue.
138
- * Never routes through the queue.
139
- */
140
110
  async function _executeDownload(options) {
141
111
  let progressSubscription = null;
142
112
  let retrySubscription = null;
143
-
144
- // downloadId is resolved immediately on Android foreground, allowing
145
- // ID-based event filtering. On iOS it only arrives on completion, so we
146
- // fall back to URL-based filtering in that case.
147
- let knownDownloadId = null;
148
-
149
- // Speed / ETA tracking (JS-side, works regardless of native platform)
113
+ const downloadId = options.downloadId || _generateId();
114
+ const knownDownloadId = downloadId;
150
115
  let _lastProgressTs = null;
151
116
  let _lastProgressBytes = 0;
152
- // Smoothed speed using exponential moving average (α = 0.3)
153
117
  let _smoothedSpeedBps = 0;
154
118
  if (options.onProgress) {
155
119
  progressSubscription = eventEmitter.addListener('onDownloadProgress', event => {
156
- const matchesId = knownDownloadId && event.downloadId === knownDownloadId;
157
- const matchesUrl = !knownDownloadId && event.url === options.url;
120
+ const matchesId = event.downloadId === knownDownloadId;
121
+ const matchesUrl = !event.downloadId && event.url === options.url;
158
122
  if ((matchesId || matchesUrl) && options.onProgress) {
159
123
  const now = Date.now();
160
124
  const percent = event.progress ?? 0;
@@ -166,47 +130,30 @@ async function _executeDownload(options) {
166
130
  const dtSec = (now - _lastProgressTs) / 1000;
167
131
  if (dtSec > 0) {
168
132
  const bytesDelta = bytesDownloaded - _lastProgressBytes;
169
- // Only update speed if we have byte-level data from native
170
133
  if (bytesDelta > 0 && totalBytes > 0) {
171
134
  const instantSpeed = bytesDelta / dtSec;
172
- // Exponential moving average for smoother readings
173
135
  _smoothedSpeedBps = _smoothedSpeedBps === 0 ? instantSpeed : 0.3 * instantSpeed + 0.7 * _smoothedSpeedBps;
174
136
  speedBps = _smoothedSpeedBps;
175
- const remaining = totalBytes - bytesDownloaded;
176
- etaSeconds = speedBps > 0 ? remaining / speedBps : 0;
177
- } else if (percent > 0 && percent < 100) {
178
- // Fallback: estimate from percent when bytes aren't available
179
- const estimatedTotalBytes = totalBytes > 0 ? totalBytes : _lastProgressBytes / (percent / 100);
180
- const percentDelta = percent - _lastProgressBytes / estimatedTotalBytes * 100;
181
- if (percentDelta > 0) {
182
- const estimatedByteDelta = percentDelta / 100 * estimatedTotalBytes;
183
- const instantSpeed = estimatedByteDelta / dtSec;
184
- _smoothedSpeedBps = _smoothedSpeedBps === 0 ? instantSpeed : 0.3 * instantSpeed + 0.7 * _smoothedSpeedBps;
185
- speedBps = _smoothedSpeedBps;
186
- const remainingPct = 100 - percent;
187
- const estimatedRemaining = remainingPct / 100 * estimatedTotalBytes;
188
- etaSeconds = speedBps > 0 ? estimatedRemaining / speedBps : 0;
189
- }
137
+ etaSeconds = speedBps > 0 ? (totalBytes - bytesDownloaded) / speedBps : 0;
190
138
  }
191
139
  }
192
140
  }
193
141
  _lastProgressTs = now;
194
142
  _lastProgressBytes = bytesDownloaded;
195
- const info = {
143
+ options.onProgress({
196
144
  percent,
197
145
  bytesDownloaded,
198
146
  totalBytes,
199
147
  speedBps,
200
148
  etaSeconds
201
- };
202
- options.onProgress(info);
149
+ });
203
150
  }
204
151
  });
205
152
  }
206
153
  if (options.retry?.onRetry) {
207
154
  retrySubscription = eventEmitter.addListener('onDownloadRetry', event => {
208
- const matchesId = knownDownloadId && event.downloadId === knownDownloadId;
209
- const matchesUrl = !knownDownloadId && event.url === options.url;
155
+ const matchesId = event.downloadId === knownDownloadId;
156
+ const matchesUrl = !event.downloadId && event.url === options.url;
210
157
  if ((matchesId || matchesUrl) && options.retry?.onRetry) {
211
158
  options.retry.onRetry(event.attempt, event.error ?? '');
212
159
  }
@@ -217,32 +164,13 @@ async function _executeDownload(options) {
217
164
  retrySubscription?.remove();
218
165
  };
219
166
  try {
220
- const resultPromise = _NativeFileToolkit.default.download({
221
- url: options.url,
222
- fileName: options.fileName,
167
+ const result = await FileToolkitModule.download({
168
+ ...options,
169
+ downloadId,
223
170
  background: options.background ?? false,
224
171
  headers: options.headers ?? {},
225
- destination: options.destination ?? 'downloads',
226
- notificationTitle: options.notificationTitle,
227
- notificationDescription: options.notificationDescription,
228
- checksum: options.checksum,
229
- retry: options.retry ? {
230
- attempts: options.retry.attempts,
231
- delay: options.retry.delay ?? 1000
232
- } : undefined
172
+ destination: options.destination ?? 'downloads'
233
173
  });
234
-
235
- // On Android foreground the promise resolves immediately with downloadId,
236
- // giving us a precise ID to filter events by before download completes.
237
- // On iOS it only resolves on completion, so knownDownloadId stays null
238
- // and URL-based filtering is used as the fallback.
239
- resultPromise.then?.(partial => {
240
- if (partial?.downloadId && !partial?.filePath) {
241
- // Android early-resolve: has downloadId but no filePath yet
242
- knownDownloadId = partial.downloadId;
243
- }
244
- });
245
- const result = await resultPromise;
246
174
  cleanup();
247
175
  return result;
248
176
  } catch (error) {
@@ -253,256 +181,149 @@ async function _executeDownload(options) {
253
181
  };
254
182
  }
255
183
  }
256
-
257
- /**
258
- * Download a file.
259
- *
260
- * For **foreground** downloads the promise resolves when the file is saved.
261
- * For **background** downloads the promise resolves immediately with a
262
- * `downloadId`; listen to the `onDownloadComplete` / `onDownloadError`
263
- * events for the final result.
264
- *
265
- * Pass `queue: true` to join the managed queue and respect the `maxConcurrent`
266
- * limit configured via `setQueueOptions()`. Use `priority: 'high'` to jump
267
- * ahead of other pending items in the queue.
268
- *
269
- * @example
270
- * ```ts
271
- * // Direct (unqueued) download
272
- * const result = await download({ url: 'https://...' });
273
- *
274
- * // Queued download with high priority
275
- * setQueueOptions({ maxConcurrent: 3 });
276
- * const result = await download({
277
- * url: 'https://...',
278
- * queue: true,
279
- * priority: 'high',
280
- * });
281
- * ```
282
- */
283
184
  function download(options) {
284
- if (options.queue) {
285
- return _globalQueue.enqueue(options);
286
- }
185
+ if (options.queue) return _globalQueue.enqueue(options);
287
186
  return _executeDownload(options);
288
187
  }
289
-
290
- // ─── Core upload ───────────────────────────────────────────────────────────────
291
-
292
- /**
293
- * Upload a file using multipart/form-data.
294
- */
295
188
  async function upload(options) {
296
- let progressSubscription = null;
189
+ let sub = null;
190
+ const uploadId = options.uploadId || _generateId();
297
191
  if (options.onProgress) {
298
- progressSubscription = eventEmitter.addListener('onUploadProgress', event => {
299
- if (event.url === options.url && options.onProgress) {
300
- options.onProgress(event.progress);
301
- }
192
+ sub = eventEmitter.addListener('onUploadProgress', e => {
193
+ if (e.uploadId === uploadId) options.onProgress(e.progress);
302
194
  });
303
195
  }
304
196
  try {
305
- const result = await _NativeFileToolkit.default.upload({
306
- url: options.url,
307
- filePath: options.filePath,
197
+ const res = await FileToolkitModule.upload({
198
+ ...options,
199
+ uploadId,
308
200
  fieldName: options.fieldName ?? 'file',
309
201
  headers: options.headers ?? {},
310
202
  parameters: options.parameters ?? {}
311
203
  });
312
- if (progressSubscription) {
313
- progressSubscription.remove();
314
- }
315
- return result;
316
- } catch (error) {
317
- if (progressSubscription) {
318
- progressSubscription.remove();
319
- }
204
+ sub?.remove();
205
+ return {
206
+ ...res,
207
+ uploadId
208
+ };
209
+ } catch (err) {
210
+ sub?.remove();
320
211
  return {
321
212
  success: false,
322
- error: error?.message || 'UNKNOWN_ERROR'
213
+ error: err?.message || 'UNKNOWN_ERROR',
214
+ uploadId
323
215
  };
324
216
  }
325
217
  }
326
-
327
- // ─── Pause / Resume / Cancel ──────────────────────────────────────────────────
328
-
329
- /**
330
- * Pause an active foreground download.
331
- * On iOS the partial data is stored so it can be resumed.
332
- * On Android the download thread is suspended.
333
- */
334
- async function pauseDownload(downloadId) {
218
+ async function pauseDownload(id) {
335
219
  try {
336
- return await _NativeFileToolkit.default.pauseDownload(downloadId);
337
- } catch (error) {
220
+ return await FileToolkitModule.pauseDownload(id);
221
+ } catch (err) {
338
222
  return {
339
223
  success: false,
340
- error: error?.message || 'UNKNOWN_ERROR'
224
+ error: err.message || 'UNKNOWN_ERROR'
341
225
  };
342
226
  }
343
227
  }
344
-
345
- /**
346
- * Resume a previously paused download.
347
- * On iOS resumes from partial data (HTTP Range). On Android unblocks the thread.
348
- */
349
- async function resumeDownload(downloadId) {
228
+ async function resumeDownload(id) {
350
229
  try {
351
- return await _NativeFileToolkit.default.resumeDownload(downloadId);
352
- } catch (error) {
230
+ return await FileToolkitModule.resumeDownload(id);
231
+ } catch (err) {
353
232
  return {
354
233
  success: false,
355
- error: error?.message || 'UNKNOWN_ERROR'
234
+ error: err.message || 'UNKNOWN_ERROR'
356
235
  };
357
236
  }
358
237
  }
359
-
360
- /**
361
- * Cancel and discard a download (foreground or background).
362
- * Any partially-downloaded file is deleted.
363
- */
364
- async function cancelDownload(downloadId) {
238
+ async function cancelDownload(id) {
365
239
  try {
366
- return await _NativeFileToolkit.default.cancelDownload(downloadId);
367
- } catch (error) {
240
+ return await FileToolkitModule.cancelDownload(id);
241
+ } catch (err) {
368
242
  return {
369
243
  success: false,
370
- error: error?.message || 'UNKNOWN_ERROR'
244
+ error: err.message || 'UNKNOWN_ERROR'
371
245
  };
372
246
  }
373
247
  }
374
-
375
- // ─── Cache management ─────────────────────────────────────────────────────────
376
-
377
- /**
378
- * List all files in the app's cache directory.
379
- */
380
248
  async function getCachedFiles() {
381
249
  try {
382
- return await _NativeFileToolkit.default.getCachedFiles();
383
- } catch (error) {
250
+ return await FileToolkitModule.getCachedFiles();
251
+ } catch (err) {
384
252
  return {
385
253
  success: false,
386
- error: error?.message || 'UNKNOWN_ERROR'
254
+ error: err.message || 'UNKNOWN_ERROR'
387
255
  };
388
256
  }
389
257
  }
390
-
391
- /**
392
- * Delete a single file by its absolute path.
393
- */
394
- async function deleteFile(filePath) {
258
+ async function deleteFile(path) {
395
259
  try {
396
- return await _NativeFileToolkit.default.deleteFile(filePath);
397
- } catch (error) {
260
+ return await FileToolkitModule.deleteFile(path);
261
+ } catch (err) {
398
262
  return {
399
263
  success: false,
400
- error: error?.message || 'UNKNOWN_ERROR'
264
+ error: err.message || 'UNKNOWN_ERROR'
401
265
  };
402
266
  }
403
267
  }
404
-
405
- /**
406
- * Delete all files in the app's cache directory.
407
- */
408
268
  async function clearCache() {
409
269
  try {
410
- return await _NativeFileToolkit.default.clearCache();
411
- } catch (error) {
270
+ return await FileToolkitModule.clearCache();
271
+ } catch (err) {
412
272
  return {
413
273
  success: false,
414
- error: error?.message || 'UNKNOWN_ERROR'
274
+ error: err.message || 'UNKNOWN_ERROR'
415
275
  };
416
276
  }
417
277
  }
418
-
419
- /**
420
- * Get all active background downloads.
421
- * Use this after app restart to "re-attach" to ongoing downloads.
422
- */
423
278
  async function getBackgroundDownloads() {
424
279
  try {
425
- return await _NativeFileToolkit.default.getBackgroundDownloads();
426
- } catch (error) {
280
+ return await FileToolkitModule.getBackgroundDownloads();
281
+ } catch (err) {
427
282
  return {
428
283
  success: false,
429
- error: error?.message || 'UNKNOWN_ERROR'
284
+ error: err.message || 'UNKNOWN_ERROR'
430
285
  };
431
286
  }
432
287
  }
433
-
434
- // ─── File system helpers ─────────────────────────────────────────────────────
435
-
436
- function _ensureFsSuccess(result, fallback) {
437
- if (!result?.success) {
438
- throw new Error(result?.error || fallback);
439
- }
288
+ function _ensure(res, msg) {
289
+ if (!res?.success) throw new Error(res?.error || msg);
440
290
  }
441
-
442
- /** Check whether a file or directory exists. */
443
- async function exists(filePath) {
444
- const result = await _NativeFileToolkit.default.exists(filePath);
445
- _ensureFsSuccess(result, 'EXISTS_ERROR');
446
- return !!result.exists;
291
+ async function exists(path) {
292
+ const res = await FileToolkitModule.exists(path);
293
+ _ensure(res, 'EXISTS_ERROR');
294
+ return !!res.exists;
447
295
  }
448
-
449
- /** Get file or directory metadata. */
450
- async function stat(filePath) {
451
- const result = await _NativeFileToolkit.default.stat(filePath);
452
- _ensureFsSuccess(result, 'STAT_ERROR');
453
- return result.stat;
296
+ async function stat(path) {
297
+ const res = await FileToolkitModule.stat(path);
298
+ _ensure(res, 'STAT_ERROR');
299
+ return res.stat;
454
300
  }
455
-
456
- /** Read a file as utf8 (default) or base64 string. */
457
- async function readFile(filePath, encoding = 'utf8') {
458
- const result = await _NativeFileToolkit.default.readFile(filePath, encoding);
459
- _ensureFsSuccess(result, 'READ_FILE_ERROR');
460
- return result.data ?? '';
301
+ async function readFile(path, enc = 'utf8') {
302
+ const res = await FileToolkitModule.readFile(path, enc);
303
+ _ensure(res, 'READ_ERROR');
304
+ return res.data ?? '';
461
305
  }
462
-
463
- /** Write utf8 (default) or base64 data to a file. */
464
- async function writeFile(filePath, data, encoding = 'utf8') {
465
- const result = await _NativeFileToolkit.default.writeFile(filePath, data, encoding);
466
- _ensureFsSuccess(result, 'WRITE_FILE_ERROR');
306
+ async function writeFile(path, data, enc = 'utf8') {
307
+ const res = await FileToolkitModule.writeFile(path, data, enc);
308
+ _ensure(res, 'WRITE_ERROR');
467
309
  }
468
-
469
- /** Copy a file from source to destination. */
470
- async function copyFile(fromPath, toPath) {
471
- const result = await _NativeFileToolkit.default.copyFile(fromPath, toPath);
472
- _ensureFsSuccess(result, 'COPY_FILE_ERROR');
310
+ async function copyFile(from, to) {
311
+ const res = await FileToolkitModule.copyFile(from, to);
312
+ _ensure(res, 'COPY_ERROR');
473
313
  }
474
-
475
- /** Move a file from source to destination. */
476
- async function moveFile(fromPath, toPath) {
477
- const result = await _NativeFileToolkit.default.moveFile(fromPath, toPath);
478
- _ensureFsSuccess(result, 'MOVE_FILE_ERROR');
314
+ async function moveFile(from, to) {
315
+ const res = await FileToolkitModule.moveFile(from, to);
316
+ _ensure(res, 'MOVE_ERROR');
479
317
  }
480
-
481
- /** Create a directory recursively. */
482
- async function mkdir(dirPath) {
483
- const result = await _NativeFileToolkit.default.mkdir(dirPath);
484
- _ensureFsSuccess(result, 'MKDIR_ERROR');
318
+ async function mkdir(path) {
319
+ const res = await FileToolkitModule.mkdir(path);
320
+ _ensure(res, 'MKDIR_ERROR');
485
321
  }
486
-
487
- /** List direct entries (names) in a directory. */
488
- async function ls(dirPath) {
489
- const result = await _NativeFileToolkit.default.ls(dirPath);
490
- _ensureFsSuccess(result, 'LS_ERROR');
491
- return result.entries || [];
322
+ async function ls(path) {
323
+ const res = await FileToolkitModule.ls(path);
324
+ _ensure(res, 'LS_ERROR');
325
+ return res.entries || [];
492
326
  }
493
-
494
- /**
495
- * File system API namespace.
496
- *
497
- * @example
498
- * ```ts
499
- * import { fs } from 'rn-file-toolkit';
500
- *
501
- * const ok = await fs.exists('/path/to/file.pdf');
502
- * const meta = await fs.stat('/path/to/file.pdf');
503
- * const text = await fs.readFile('/path/to/file.txt');
504
- * ```
505
- */
506
327
  const fs = exports.fs = {
507
328
  exists,
508
329
  stat,
@@ -510,366 +331,130 @@ const fs = exports.fs = {
510
331
  writeFile,
511
332
  copyFile,
512
333
  moveFile,
513
- deleteFile: async filePath => {
514
- const result = await deleteFile(filePath);
515
- _ensureFsSuccess(result, 'DELETE_FILE_ERROR');
334
+ deleteFile: async p => {
335
+ _ensure(await deleteFile(p), 'DEL_ERROR');
516
336
  },
517
337
  mkdir,
518
338
  ls
519
339
  };
520
-
521
- // ─── Event helpers ────────────────────────────────────────────────────────────
522
-
523
- /**
524
- * Subscribe to background download completion events.
525
- * Returns an unsubscribe function.
526
- */
527
- function onDownloadComplete(callback) {
528
- const sub = eventEmitter.addListener('onDownloadComplete', callback);
529
- return () => sub.remove();
340
+ function onDownloadComplete(cb) {
341
+ const s = eventEmitter.addListener('onDownloadComplete', cb);
342
+ return () => s.remove();
530
343
  }
531
-
532
- /**
533
- * Subscribe to background download error events.
534
- * Returns an unsubscribe function.
535
- */
536
- function onDownloadError(callback) {
537
- const sub = eventEmitter.addListener('onDownloadError', callback);
538
- return () => sub.remove();
344
+ function onDownloadError(cb) {
345
+ const s = eventEmitter.addListener('onDownloadError', cb);
346
+ return () => s.remove();
539
347
  }
540
-
541
- /**
542
- * Subscribe to upload progress events.
543
- * Returns an unsubscribe function.
544
- */
545
- function onUploadProgress(callback) {
546
- const sub = eventEmitter.addListener('onUploadProgress', callback);
547
- return () => sub.remove();
348
+ function onUploadProgress(cb) {
349
+ const s = eventEmitter.addListener('onUploadProgress', cb);
350
+ return () => s.remove();
548
351
  }
549
-
550
- /**
551
- * Subscribe to download retry events (fired before each retry attempt).
552
- * Returns an unsubscribe function.
553
- */
554
- function onDownloadRetry(callback) {
555
- const sub = eventEmitter.addListener('onDownloadRetry', callback);
556
- return () => sub.remove();
352
+ function onDownloadRetry(cb) {
353
+ const s = eventEmitter.addListener('onDownloadRetry', cb);
354
+ return () => s.remove();
557
355
  }
558
-
559
- // ─── saveBase64AsFile ─────────────────────────────────────────────────────────
560
-
561
- /**
562
- * Save a base64 string or data URI as a file.
563
- * Supports data URIs (e.g., "data:image/png;base64,iVBORw0...") and plain base64 strings.
564
- *
565
- * @example
566
- * ```typescript
567
- * // From data URI
568
- * const result = await saveBase64AsFile({
569
- * base64Data: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...',
570
- * fileName: 'photo.png',
571
- * destination: 'documents'
572
- * });
573
- *
574
- * // From plain base64
575
- * const result = await saveBase64AsFile({
576
- * base64Data: 'SGVsbG8gV29ybGQ=',
577
- * fileName: 'hello.txt',
578
- * destination: 'cache'
579
- * });
580
- * ```
581
- */
582
- async function saveBase64AsFile(options) {
356
+ async function saveBase64AsFile(opts) {
583
357
  try {
584
358
  let {
585
359
  base64Data,
586
360
  fileName,
587
361
  destination
588
- } = options;
589
-
590
- // Parse data URI if present (e.g., "data:image/png;base64,iVBORw0...")
362
+ } = opts;
591
363
  if (base64Data.startsWith('data:')) {
592
- const matches = base64Data.match(/^data:([^;]+);base64,(.+)$/);
593
- if (matches && matches[2]) {
594
- base64Data = matches[2];
595
-
596
- // Auto-detect file extension from MIME type if fileName not provided
597
- if (!fileName && matches[1]) {
598
- const mimeType = matches[1];
599
- const ext = mimeType.split('/')[1] || 'bin';
600
- fileName = `file_${Date.now()}.${ext}`;
601
- }
602
- } else {
603
- return {
604
- success: false,
605
- error: 'Invalid data URI format. Expected: data:<mimetype>;base64,<data>'
606
- };
364
+ const m = base64Data.match(/^data:([^;]+);base64,(.+)$/);
365
+ if (m && m[2]) {
366
+ base64Data = m[2];
367
+ if (!fileName && m[1]) fileName = `file_${Date.now()}.${m[1].split('/')[1] || 'bin'}`;
607
368
  }
608
369
  }
609
- const result = await FileToolkitModule.saveBase64AsFile({
370
+ return await FileToolkitModule.saveBase64AsFile({
610
371
  base64Data,
611
372
  fileName,
612
373
  destination
613
374
  });
614
- return result;
615
- } catch (error) {
375
+ } catch (err) {
616
376
  return {
617
377
  success: false,
618
- error: error?.message || 'UNKNOWN_ERROR'
378
+ error: err.message || 'UNKNOWN_ERROR'
619
379
  };
620
380
  }
621
381
  }
622
-
623
- // ─── urlToBase64 ──────────────────────────────────────────────────────────────
624
-
625
- /**
626
- * Convert a web URL (image, video, gif, etc.) to base64 string.
627
- * Downloads the file and returns both the base64 string and data URI format.
628
- *
629
- * @example
630
- * ```typescript
631
- * // Convert image to base64
632
- * const result = await urlToBase64({
633
- * url: 'https://example.com/photo.jpg',
634
- * headers: { Authorization: 'Bearer token' }
635
- * });
636
- *
637
- * if (result.success) {
638
- * console.log('Base64:', result.base64);
639
- * console.log('Data URI:', result.dataUri);
640
- * console.log('MIME Type:', result.mimeType);
641
- * }
642
- * ```
643
- */
644
- async function urlToBase64(options) {
382
+ async function urlToBase64(opts) {
645
383
  try {
646
- const result = await FileToolkitModule.urlToBase64({
647
- url: options.url,
648
- headers: options.headers
649
- });
650
- return result;
651
- } catch (error) {
384
+ return await FileToolkitModule.urlToBase64(opts);
385
+ } catch (err) {
652
386
  return {
653
387
  success: false,
654
- error: error?.message || 'UNKNOWN_ERROR'
388
+ error: err.message || 'UNKNOWN_ERROR'
655
389
  };
656
390
  }
657
391
  }
658
-
659
- // ─── shareFile ────────────────────────────────────────────────────────────────
660
-
661
- /**
662
- * Share a file with other apps using the native share dialog.
663
- * Opens the system share sheet on iOS and share chooser on Android.
664
- *
665
- * @example
666
- * ```typescript
667
- * const result = await shareFile({
668
- * filePath: '/path/to/document.pdf',
669
- * title: 'Share Document',
670
- * subject: 'Check out this file'
671
- * });
672
- *
673
- * if (result.success) {
674
- * console.log('File shared successfully');
675
- * }
676
- * ```
677
- */
678
- async function shareFile(options) {
392
+ async function shareFile(opts) {
679
393
  try {
680
- const result = await FileToolkitModule.shareFile(options.filePath, {
681
- title: options.title,
682
- subject: options.subject
394
+ return await FileToolkitModule.shareFile(opts.filePath, {
395
+ title: opts.title,
396
+ subject: opts.subject
683
397
  });
684
- return result;
685
- } catch (error) {
398
+ } catch (err) {
686
399
  return {
687
400
  success: false,
688
- error: error?.message || 'UNKNOWN_ERROR'
401
+ error: err.message || 'UNKNOWN_ERROR'
689
402
  };
690
403
  }
691
404
  }
692
-
693
- // ─── openFile ─────────────────────────────────────────────────────────────────
694
-
695
- /**
696
- * Open a file with the default app or app chooser.
697
- * On iOS, displays a preview or shows apps that can handle the file.
698
- * On Android, opens with the default app or shows app chooser.
699
- *
700
- * @example
701
- * ```typescript
702
- * const result = await openFile({
703
- * filePath: '/path/to/document.pdf',
704
- * mimeType: 'application/pdf' // optional, auto-detected if not provided
705
- * });
706
- *
707
- * if (result.success) {
708
- * console.log('File opened successfully');
709
- * }
710
- * ```
711
- */
712
- async function openFile(options) {
405
+ async function openFile(opts) {
713
406
  try {
714
- const result = await FileToolkitModule.openFile(options.filePath, options.mimeType || '');
715
- return result;
716
- } catch (error) {
407
+ return await FileToolkitModule.openFile(opts.filePath, opts.mimeType || '');
408
+ } catch (err) {
717
409
  return {
718
410
  success: false,
719
- error: error?.message || 'UNKNOWN_ERROR'
411
+ error: err.message || 'UNKNOWN_ERROR'
720
412
  };
721
413
  }
722
414
  }
723
-
724
- // ─── Zip / Unzip ─────────────────────────────────────────────────────────────
725
-
726
- /**
727
- * Extract a ZIP archive to a destination directory.
728
- *
729
- * Uses `java.util.zip` on Android and zlib (system framework) on iOS.
730
- * No third-party dependency required.
731
- *
732
- * @param sourcePath Absolute path to the `.zip` file
733
- * @param destDir Absolute path to the directory where files will be extracted.
734
- * The directory is created automatically if it does not exist.
735
- *
736
- * @example
737
- * ```ts
738
- * const result = await unzip(
739
- * '/path/to/archive.zip',
740
- * '/path/to/output-folder'
741
- * );
742
- *
743
- * if (result.success) {
744
- * console.log('Extracted files:', result.files);
745
- * }
746
- * ```
747
- */
748
- async function unzip(sourcePath, destDir) {
415
+ async function unzip(s, d) {
749
416
  try {
750
- const result = await _NativeFileToolkit.default.unzip(sourcePath, destDir);
751
- return result;
752
- } catch (error) {
417
+ return await FileToolkitModule.unzip(s, d);
418
+ } catch {
753
419
  return {
754
420
  success: false,
755
- error: error?.message || 'UNZIP_ERROR'
421
+ error: 'UNZIP_ERROR'
756
422
  };
757
423
  }
758
424
  }
759
-
760
- /**
761
- * Create a ZIP archive from a file or directory.
762
- *
763
- * Uses `java.util.zip` on Android and zlib (system framework) on iOS.
764
- * No third-party dependency required.
765
- *
766
- * @param sourcePath Absolute path to the file or directory to compress
767
- * @param destPath Absolute path for the output `.zip` file.
768
- * Parent directory is created automatically if needed.
769
- *
770
- * @example
771
- * ```ts
772
- * // Zip a single file
773
- * const result = await zip(
774
- * '/path/to/document.pdf',
775
- * '/path/to/document.zip'
776
- * );
777
- *
778
- * // Zip an entire directory
779
- * const result = await zip(
780
- * '/path/to/my-folder',
781
- * '/path/to/my-folder.zip'
782
- * );
783
- *
784
- * if (result.success) {
785
- * console.log('Archive created at:', result.zipPath);
786
- * }
787
- * ```
788
- */
789
- async function zip(sourcePath, destPath) {
425
+ async function zip(s, d) {
790
426
  try {
791
- const result = await _NativeFileToolkit.default.zip(sourcePath, destPath);
792
- return result;
793
- } catch (error) {
427
+ return await FileToolkitModule.zip(s, d);
428
+ } catch {
794
429
  return {
795
430
  success: false,
796
- error: error?.message || 'ZIP_ERROR'
431
+ error: 'ZIP_ERROR'
797
432
  };
798
433
  }
799
434
  }
800
-
801
- // ─── useDownload hook ─────────────────────────────────────────────────────────
802
-
803
- /**
804
- * React hook for managing a single download with built-in state.
805
- *
806
- * Tracks status, rich progress (percent, speed, ETA), and the final result.
807
- * Exposes `pause`, `resume`, and `cancel` controls tied to the active download.
808
- *
809
- * @example
810
- * ```tsx
811
- * function DownloadButton() {
812
- * const { start, pause, resume, cancel, status, progress, result } = useDownload();
813
- *
814
- * return (
815
- * <View>
816
- * <Button
817
- * title="Download"
818
- * onPress={() => start({ url: 'https://example.com/file.zip' })}
819
- * />
820
- *
821
- * {status === 'downloading' && progress && (
822
- * <View>
823
- * <Text>{progress.percent.toFixed(1)}%</Text>
824
- * <Text>Speed: {(progress.speedBps / 1024).toFixed(1)} KB/s</Text>
825
- * <Text>ETA: {progress.etaSeconds.toFixed(0)}s</Text>
826
- * <ProgressBar value={progress.percent / 100} />
827
- * <Button title="Pause" onPress={pause} />
828
- * </View>
829
- * )}
830
- *
831
- * {status === 'paused' && (
832
- * <Button title="Resume" onPress={resume} />
833
- * )}
834
- *
835
- * {status === 'done' && result?.success && (
836
- * <Text>Saved to: {result.filePath}</Text>
837
- * )}
838
- *
839
- * {status === 'error' && (
840
- * <Text>Error: {result?.error}</Text>
841
- * )}
842
- * </View>
843
- * );
844
- * }
845
- * ```
846
- */
847
435
  function useDownload() {
848
436
  const [status, setStatus] = (0, _react.useState)('idle');
849
437
  const [progress, setProgress] = (0, _react.useState)(null);
850
438
  const [result, setResult] = (0, _react.useState)(null);
851
439
  const [downloadId, setDownloadId] = (0, _react.useState)(null);
852
440
  const downloadIdRef = (0, _react.useRef)(null);
853
- const start = (0, _react.useCallback)(async options => {
441
+ const start = (0, _react.useCallback)(async opts => {
442
+ const id = opts.downloadId || _generateId();
854
443
  setStatus('downloading');
855
444
  setProgress(null);
856
445
  setResult(null);
857
- setDownloadId(null);
858
- downloadIdRef.current = null;
446
+ setDownloadId(id);
447
+ downloadIdRef.current = id;
859
448
  const res = await download({
860
- ...options,
861
- onProgress: info => {
862
- setProgress(info);
863
- options.onProgress?.(info);
449
+ ...opts,
450
+ downloadId: id,
451
+ onProgress: p => {
452
+ setProgress(p);
453
+ opts.onProgress?.(p);
864
454
  }
865
455
  });
866
- if (res.downloadId) {
867
- downloadIdRef.current = res.downloadId;
868
- setDownloadId(res.downloadId);
869
- }
870
456
  setResult(res);
871
- const isBackgroundPending = !!options.background && !!res.success && !!res.downloadId && !res.filePath;
872
- setStatus(isBackgroundPending ? 'downloading' : res.success ? 'done' : 'error');
457
+ setStatus(!!opts.background && !!res.success && !!res.downloadId && !res.filePath ? 'downloading' : res.success ? 'done' : 'error');
873
458
  return res;
874
459
  }, []);
875
460
  const pause = (0, _react.useCallback)(async () => {