rn-file-toolkit 1.0.1

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 (34) hide show
  1. package/FileToolkit.podspec +22 -0
  2. package/LICENSE +20 -0
  3. package/README.md +522 -0
  4. package/android/build.gradle +61 -0
  5. package/android/src/main/AndroidManifest.xml +13 -0
  6. package/android/src/main/java/com/filetoolkit/FileToolkitModule.kt +1204 -0
  7. package/android/src/main/java/com/filetoolkit/FileToolkitPackage.kt +31 -0
  8. package/android/src/main/res/xml/file_provider_paths.xml +14 -0
  9. package/app.plugin.js +49 -0
  10. package/ios/FileToolkit.h +6 -0
  11. package/ios/FileToolkit.mm +1468 -0
  12. package/lib/commonjs/NativeFileToolkit.js +9 -0
  13. package/lib/commonjs/NativeFileToolkit.js.map +1 -0
  14. package/lib/commonjs/index.js +941 -0
  15. package/lib/commonjs/index.js.map +1 -0
  16. package/lib/commonjs/package.json +1 -0
  17. package/lib/module/NativeFileToolkit.js +5 -0
  18. package/lib/module/NativeFileToolkit.js.map +1 -0
  19. package/lib/module/index.js +905 -0
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/package.json +1 -0
  22. package/lib/typescript/commonjs/package.json +1 -0
  23. package/lib/typescript/commonjs/src/NativeFileToolkit.d.ts +29 -0
  24. package/lib/typescript/commonjs/src/NativeFileToolkit.d.ts.map +1 -0
  25. package/lib/typescript/commonjs/src/index.d.ts +635 -0
  26. package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
  27. package/lib/typescript/module/package.json +1 -0
  28. package/lib/typescript/module/src/NativeFileToolkit.d.ts +29 -0
  29. package/lib/typescript/module/src/NativeFileToolkit.d.ts.map +1 -0
  30. package/lib/typescript/module/src/index.d.ts +635 -0
  31. package/lib/typescript/module/src/index.d.ts.map +1 -0
  32. package/package.json +232 -0
  33. package/src/NativeFileToolkit.ts +29 -0
  34. package/src/index.tsx +1293 -0
@@ -0,0 +1,941 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.cancelDownload = cancelDownload;
7
+ exports.clearCache = clearCache;
8
+ exports.copyFile = copyFile;
9
+ exports.default = void 0;
10
+ exports.deleteFile = deleteFile;
11
+ exports.download = download;
12
+ exports.exists = exists;
13
+ exports.fs = void 0;
14
+ exports.getBackgroundDownloads = getBackgroundDownloads;
15
+ exports.getCachedFiles = getCachedFiles;
16
+ exports.getQueueStatus = getQueueStatus;
17
+ exports.ls = ls;
18
+ exports.mkdir = mkdir;
19
+ exports.moveFile = moveFile;
20
+ exports.onDownloadComplete = onDownloadComplete;
21
+ exports.onDownloadError = onDownloadError;
22
+ exports.onDownloadRetry = onDownloadRetry;
23
+ exports.onUploadProgress = onUploadProgress;
24
+ exports.openFile = openFile;
25
+ exports.pauseDownload = pauseDownload;
26
+ exports.readFile = readFile;
27
+ exports.resumeDownload = resumeDownload;
28
+ exports.saveBase64AsFile = saveBase64AsFile;
29
+ exports.setQueueOptions = setQueueOptions;
30
+ exports.shareFile = shareFile;
31
+ exports.stat = stat;
32
+ exports.unzip = unzip;
33
+ exports.upload = upload;
34
+ exports.urlToBase64 = urlToBase64;
35
+ exports.useDownload = useDownload;
36
+ exports.writeFile = writeFile;
37
+ exports.zip = zip;
38
+ var _react = require("react");
39
+ var _reactNative = require("react-native");
40
+ var _NativeFileToolkit = _interopRequireDefault(require("./NativeFileToolkit"));
41
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
42
+ const FileToolkitModule = _reactNative.NativeModules.FileToolkit || _NativeFileToolkit.default;
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
+ class DownloadQueue {
55
+ _maxConcurrent = 3;
56
+ _active = 0;
57
+ _queue = [];
58
+ setOptions(opts) {
59
+ if (opts.maxConcurrent != null && opts.maxConcurrent > 0) {
60
+ this._maxConcurrent = opts.maxConcurrent;
61
+ // Kick off any slots that just opened.
62
+ this._flush();
63
+ }
64
+ }
65
+ getStatus() {
66
+ return {
67
+ active: this._active,
68
+ pending: this._queue.length,
69
+ maxConcurrent: this._maxConcurrent
70
+ };
71
+ }
72
+ enqueue(options) {
73
+ return new Promise((resolve, reject) => {
74
+ const item = {
75
+ options,
76
+ resolve,
77
+ reject
78
+ };
79
+ if (options.priority === 'high') {
80
+ this._queue.unshift(item);
81
+ } else {
82
+ this._queue.push(item);
83
+ }
84
+ this._flush();
85
+ });
86
+ }
87
+ _flush() {
88
+ while (this._active < this._maxConcurrent && this._queue.length > 0) {
89
+ const item = this._queue.shift();
90
+ this._active++;
91
+ // Strip queue-specific fields before passing to the native layer.
92
+ const nativeOptions = {
93
+ ...item.options
94
+ };
95
+ delete nativeOptions.queue;
96
+ delete nativeOptions.priority;
97
+ _executeDownload(nativeOptions).then(result => {
98
+ item.resolve(result);
99
+ }).catch(err => {
100
+ item.reject(err);
101
+ }).finally(() => {
102
+ this._active--;
103
+ this._flush();
104
+ });
105
+ }
106
+ }
107
+ }
108
+ const _globalQueue = new DownloadQueue();
109
+
110
+ /**
111
+ * Configure the global managed download queue.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * setQueueOptions({ maxConcurrent: 3 });
116
+ * ```
117
+ */
118
+ function setQueueOptions(options) {
119
+ _globalQueue.setOptions(options);
120
+ }
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
+ function getQueueStatus() {
131
+ return _globalQueue.getStatus();
132
+ }
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
+ async function _executeDownload(options) {
141
+ let progressSubscription = null;
142
+ 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)
150
+ let _lastProgressTs = null;
151
+ let _lastProgressBytes = 0;
152
+ // Smoothed speed using exponential moving average (α = 0.3)
153
+ let _smoothedSpeedBps = 0;
154
+ if (options.onProgress) {
155
+ progressSubscription = eventEmitter.addListener('onDownloadProgress', event => {
156
+ const matchesId = knownDownloadId && event.downloadId === knownDownloadId;
157
+ const matchesUrl = !knownDownloadId && event.url === options.url;
158
+ if ((matchesId || matchesUrl) && options.onProgress) {
159
+ const now = Date.now();
160
+ const percent = event.progress ?? 0;
161
+ const bytesDownloaded = event.bytesDownloaded ?? 0;
162
+ const totalBytes = event.totalBytes ?? 0;
163
+ let speedBps = 0;
164
+ let etaSeconds = 0;
165
+ if (_lastProgressTs !== null) {
166
+ const dtSec = (now - _lastProgressTs) / 1000;
167
+ if (dtSec > 0) {
168
+ const bytesDelta = bytesDownloaded - _lastProgressBytes;
169
+ // Only update speed if we have byte-level data from native
170
+ if (bytesDelta > 0 && totalBytes > 0) {
171
+ const instantSpeed = bytesDelta / dtSec;
172
+ // Exponential moving average for smoother readings
173
+ _smoothedSpeedBps = _smoothedSpeedBps === 0 ? instantSpeed : 0.3 * instantSpeed + 0.7 * _smoothedSpeedBps;
174
+ 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
+ }
190
+ }
191
+ }
192
+ }
193
+ _lastProgressTs = now;
194
+ _lastProgressBytes = bytesDownloaded;
195
+ const info = {
196
+ percent,
197
+ bytesDownloaded,
198
+ totalBytes,
199
+ speedBps,
200
+ etaSeconds
201
+ };
202
+ options.onProgress(info);
203
+ }
204
+ });
205
+ }
206
+ if (options.retry?.onRetry) {
207
+ retrySubscription = eventEmitter.addListener('onDownloadRetry', event => {
208
+ const matchesId = knownDownloadId && event.downloadId === knownDownloadId;
209
+ const matchesUrl = !knownDownloadId && event.url === options.url;
210
+ if ((matchesId || matchesUrl) && options.retry?.onRetry) {
211
+ options.retry.onRetry(event.attempt, event.error ?? '');
212
+ }
213
+ });
214
+ }
215
+ const cleanup = () => {
216
+ progressSubscription?.remove();
217
+ retrySubscription?.remove();
218
+ };
219
+ try {
220
+ const resultPromise = _NativeFileToolkit.default.download({
221
+ url: options.url,
222
+ fileName: options.fileName,
223
+ background: options.background ?? false,
224
+ 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
233
+ });
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
+ cleanup();
247
+ return result;
248
+ } catch (error) {
249
+ cleanup();
250
+ return {
251
+ success: false,
252
+ error: error?.message || 'UNKNOWN_ERROR'
253
+ };
254
+ }
255
+ }
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
+ function download(options) {
284
+ if (options.queue) {
285
+ return _globalQueue.enqueue(options);
286
+ }
287
+ return _executeDownload(options);
288
+ }
289
+
290
+ // ─── Core upload ───────────────────────────────────────────────────────────────
291
+
292
+ /**
293
+ * Upload a file using multipart/form-data.
294
+ */
295
+ async function upload(options) {
296
+ let progressSubscription = null;
297
+ if (options.onProgress) {
298
+ progressSubscription = eventEmitter.addListener('onUploadProgress', event => {
299
+ if (event.url === options.url && options.onProgress) {
300
+ options.onProgress(event.progress);
301
+ }
302
+ });
303
+ }
304
+ try {
305
+ const result = await _NativeFileToolkit.default.upload({
306
+ url: options.url,
307
+ filePath: options.filePath,
308
+ fieldName: options.fieldName ?? 'file',
309
+ headers: options.headers ?? {},
310
+ parameters: options.parameters ?? {}
311
+ });
312
+ if (progressSubscription) {
313
+ progressSubscription.remove();
314
+ }
315
+ return result;
316
+ } catch (error) {
317
+ if (progressSubscription) {
318
+ progressSubscription.remove();
319
+ }
320
+ return {
321
+ success: false,
322
+ error: error?.message || 'UNKNOWN_ERROR'
323
+ };
324
+ }
325
+ }
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) {
335
+ try {
336
+ return await _NativeFileToolkit.default.pauseDownload(downloadId);
337
+ } catch (error) {
338
+ return {
339
+ success: false,
340
+ error: error?.message || 'UNKNOWN_ERROR'
341
+ };
342
+ }
343
+ }
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) {
350
+ try {
351
+ return await _NativeFileToolkit.default.resumeDownload(downloadId);
352
+ } catch (error) {
353
+ return {
354
+ success: false,
355
+ error: error?.message || 'UNKNOWN_ERROR'
356
+ };
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Cancel and discard a download (foreground or background).
362
+ * Any partially-downloaded file is deleted.
363
+ */
364
+ async function cancelDownload(downloadId) {
365
+ try {
366
+ return await _NativeFileToolkit.default.cancelDownload(downloadId);
367
+ } catch (error) {
368
+ return {
369
+ success: false,
370
+ error: error?.message || 'UNKNOWN_ERROR'
371
+ };
372
+ }
373
+ }
374
+
375
+ // ─── Cache management ─────────────────────────────────────────────────────────
376
+
377
+ /**
378
+ * List all files in the app's cache directory.
379
+ */
380
+ async function getCachedFiles() {
381
+ try {
382
+ return await _NativeFileToolkit.default.getCachedFiles();
383
+ } catch (error) {
384
+ return {
385
+ success: false,
386
+ error: error?.message || 'UNKNOWN_ERROR'
387
+ };
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Delete a single file by its absolute path.
393
+ */
394
+ async function deleteFile(filePath) {
395
+ try {
396
+ return await _NativeFileToolkit.default.deleteFile(filePath);
397
+ } catch (error) {
398
+ return {
399
+ success: false,
400
+ error: error?.message || 'UNKNOWN_ERROR'
401
+ };
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Delete all files in the app's cache directory.
407
+ */
408
+ async function clearCache() {
409
+ try {
410
+ return await _NativeFileToolkit.default.clearCache();
411
+ } catch (error) {
412
+ return {
413
+ success: false,
414
+ error: error?.message || 'UNKNOWN_ERROR'
415
+ };
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Get all active background downloads.
421
+ * Use this after app restart to "re-attach" to ongoing downloads.
422
+ */
423
+ async function getBackgroundDownloads() {
424
+ try {
425
+ return await _NativeFileToolkit.default.getBackgroundDownloads();
426
+ } catch (error) {
427
+ return {
428
+ success: false,
429
+ error: error?.message || 'UNKNOWN_ERROR'
430
+ };
431
+ }
432
+ }
433
+
434
+ // ─── File system helpers ─────────────────────────────────────────────────────
435
+
436
+ function _ensureFsSuccess(result, fallback) {
437
+ if (!result?.success) {
438
+ throw new Error(result?.error || fallback);
439
+ }
440
+ }
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;
447
+ }
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;
454
+ }
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 ?? '';
461
+ }
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');
467
+ }
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');
473
+ }
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');
479
+ }
480
+
481
+ /** Create a directory recursively. */
482
+ async function mkdir(dirPath) {
483
+ const result = await _NativeFileToolkit.default.mkdir(dirPath);
484
+ _ensureFsSuccess(result, 'MKDIR_ERROR');
485
+ }
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 || [];
492
+ }
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
+ const fs = exports.fs = {
507
+ exists,
508
+ stat,
509
+ readFile,
510
+ writeFile,
511
+ copyFile,
512
+ moveFile,
513
+ deleteFile: async filePath => {
514
+ const result = await deleteFile(filePath);
515
+ _ensureFsSuccess(result, 'DELETE_FILE_ERROR');
516
+ },
517
+ mkdir,
518
+ ls
519
+ };
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();
530
+ }
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();
539
+ }
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();
548
+ }
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();
557
+ }
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) {
583
+ try {
584
+ let {
585
+ base64Data,
586
+ fileName,
587
+ destination
588
+ } = options;
589
+
590
+ // Parse data URI if present (e.g., "data:image/png;base64,iVBORw0...")
591
+ 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
+ };
607
+ }
608
+ }
609
+ const result = await FileToolkitModule.saveBase64AsFile({
610
+ base64Data,
611
+ fileName,
612
+ destination
613
+ });
614
+ return result;
615
+ } catch (error) {
616
+ return {
617
+ success: false,
618
+ error: error?.message || 'UNKNOWN_ERROR'
619
+ };
620
+ }
621
+ }
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) {
645
+ try {
646
+ const result = await FileToolkitModule.urlToBase64({
647
+ url: options.url,
648
+ headers: options.headers
649
+ });
650
+ return result;
651
+ } catch (error) {
652
+ return {
653
+ success: false,
654
+ error: error?.message || 'UNKNOWN_ERROR'
655
+ };
656
+ }
657
+ }
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) {
679
+ try {
680
+ const result = await FileToolkitModule.shareFile(options.filePath, {
681
+ title: options.title,
682
+ subject: options.subject
683
+ });
684
+ return result;
685
+ } catch (error) {
686
+ return {
687
+ success: false,
688
+ error: error?.message || 'UNKNOWN_ERROR'
689
+ };
690
+ }
691
+ }
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) {
713
+ try {
714
+ const result = await FileToolkitModule.openFile(options.filePath, options.mimeType || '');
715
+ return result;
716
+ } catch (error) {
717
+ return {
718
+ success: false,
719
+ error: error?.message || 'UNKNOWN_ERROR'
720
+ };
721
+ }
722
+ }
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) {
749
+ try {
750
+ const result = await _NativeFileToolkit.default.unzip(sourcePath, destDir);
751
+ return result;
752
+ } catch (error) {
753
+ return {
754
+ success: false,
755
+ error: error?.message || 'UNZIP_ERROR'
756
+ };
757
+ }
758
+ }
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) {
790
+ try {
791
+ const result = await _NativeFileToolkit.default.zip(sourcePath, destPath);
792
+ return result;
793
+ } catch (error) {
794
+ return {
795
+ success: false,
796
+ error: error?.message || 'ZIP_ERROR'
797
+ };
798
+ }
799
+ }
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
+ function useDownload() {
848
+ const [status, setStatus] = (0, _react.useState)('idle');
849
+ const [progress, setProgress] = (0, _react.useState)(null);
850
+ const [result, setResult] = (0, _react.useState)(null);
851
+ const [downloadId, setDownloadId] = (0, _react.useState)(null);
852
+ const downloadIdRef = (0, _react.useRef)(null);
853
+ const start = (0, _react.useCallback)(async options => {
854
+ setStatus('downloading');
855
+ setProgress(null);
856
+ setResult(null);
857
+ setDownloadId(null);
858
+ downloadIdRef.current = null;
859
+ const res = await download({
860
+ ...options,
861
+ onProgress: info => {
862
+ setProgress(info);
863
+ options.onProgress?.(info);
864
+ }
865
+ });
866
+ if (res.downloadId) {
867
+ downloadIdRef.current = res.downloadId;
868
+ setDownloadId(res.downloadId);
869
+ }
870
+ setResult(res);
871
+ const isBackgroundPending = !!options.background && !!res.success && !!res.downloadId && !res.filePath;
872
+ setStatus(isBackgroundPending ? 'downloading' : res.success ? 'done' : 'error');
873
+ return res;
874
+ }, []);
875
+ const pause = (0, _react.useCallback)(async () => {
876
+ if (downloadIdRef.current) {
877
+ await pauseDownload(downloadIdRef.current);
878
+ setStatus('paused');
879
+ }
880
+ }, []);
881
+ const resume = (0, _react.useCallback)(async () => {
882
+ if (downloadIdRef.current) {
883
+ await resumeDownload(downloadIdRef.current);
884
+ setStatus('downloading');
885
+ }
886
+ }, []);
887
+ const cancel = (0, _react.useCallback)(async () => {
888
+ if (downloadIdRef.current) {
889
+ await cancelDownload(downloadIdRef.current);
890
+ setStatus('idle');
891
+ setProgress(null);
892
+ setResult(null);
893
+ downloadIdRef.current = null;
894
+ setDownloadId(null);
895
+ }
896
+ }, []);
897
+ return {
898
+ start,
899
+ pause,
900
+ resume,
901
+ cancel,
902
+ status,
903
+ progress,
904
+ result,
905
+ downloadId
906
+ };
907
+ }
908
+ var _default = exports.default = {
909
+ download,
910
+ upload,
911
+ pauseDownload,
912
+ resumeDownload,
913
+ cancelDownload,
914
+ getCachedFiles,
915
+ deleteFile,
916
+ clearCache,
917
+ getBackgroundDownloads,
918
+ saveBase64AsFile,
919
+ urlToBase64,
920
+ shareFile,
921
+ openFile,
922
+ onDownloadComplete,
923
+ onDownloadError,
924
+ onUploadProgress,
925
+ onDownloadRetry,
926
+ setQueueOptions,
927
+ getQueueStatus,
928
+ exists,
929
+ stat,
930
+ readFile,
931
+ writeFile,
932
+ copyFile,
933
+ moveFile,
934
+ mkdir,
935
+ ls,
936
+ fs,
937
+ useDownload,
938
+ unzip,
939
+ zip
940
+ };
941
+ //# sourceMappingURL=index.js.map