@kesha-antonov/react-native-background-downloader 4.4.5 → 4.5.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 CHANGED
@@ -12,9 +12,13 @@
12
12
  <img src="https://img.shields.io/badge/New%20Architecture-supported-green.svg" alt="New Architecture">
13
13
  </p>
14
14
 
15
- # React Native Background Downloader
15
+ <h1 align="center">React Native Background Downloader</h1>
16
16
 
17
- Download and upload large files on iOS & Android — even when your app is in the background or terminated.
17
+ <p align="center">
18
+ Download and upload large files on iOS & Android — even when your app is in the background or terminated.
19
+ </p>
20
+
21
+ ---
18
22
 
19
23
  ## ✨ Features
20
24
 
@@ -28,7 +32,7 @@ Download and upload large files on iOS & Android — even when your app is in th
28
32
  - ⚡ **New Architecture** - Full TurboModules support for React Native
29
33
  - 📝 **TypeScript** - Complete TypeScript definitions included
30
34
 
31
- ## Why?
35
+ ## 💡 Why?
32
36
 
33
37
  **The Problem:** Standard network requests in React Native are tied to your app's lifecycle. When the user switches to another app or the OS terminates your app to free memory, your downloads stop. For small files this is fine, but for large files (videos, podcasts, documents) this creates a frustrating user experience.
34
38
 
@@ -40,35 +44,34 @@ Download and upload large files on iOS & Android — even when your app is in th
40
44
 
41
45
  **This Library:** `@kesha-antonov/react-native-background-downloader` wraps these native APIs in a simple, unified JavaScript interface. Start a download, close your app, reopen it hours later, and seamlessly reconnect to your ongoing downloads with a single function call.
42
46
 
43
- ## Table of Contents
44
-
45
- - [React Native Background Downloader](#react-native-background-downloader)
46
- - [ Features](#-features)
47
- - [Why?](#why)
48
- - [Table of Contents](#table-of-contents)
49
- - [Requirements](#requirements)
50
- - [Installation](#installation)
51
- - [Expo Projects](#expo-projects)
52
- - [Bare React Native Projects](#bare-react-native-projects)
53
- - [Usage](#usage)
54
- - [Downloading a file](#downloading-a-file)
55
- - [Re-Attaching to background tasks](#re-attaching-to-background-tasks)
56
- - [Advanced Configuration](#advanced-configuration)
57
- - [Max Parallel Downloads (iOS only)](#max-parallel-downloads-ios-only)
58
- - [Cellular/WiFi Restrictions](#cellularwifi-restrictions)
59
- - [API](#api)
60
- - [Quick Reference](#quick-reference)
61
- - [Platform Notes](#platform-notes)
62
- - [Troubleshooting](#troubleshooting)
63
- - [Example App](#example-app)
64
- - [Use Cases](#use-cases)
65
- - [Migration Guide](#migration-guide)
66
- - [Contributing](#contributing)
67
- - [Development Setup](#development-setup)
68
- - [Authors](#authors)
69
- - [License](#license)
70
-
71
- ## Requirements
47
+ ## 📖 Table of Contents
48
+
49
+ - [ Features](#-features)
50
+ - [💡 Why?](#-why)
51
+ - [📖 Table of Contents](#-table-of-contents)
52
+ - [📋 Requirements](#-requirements)
53
+ - [📦 Installation](#-installation)
54
+ - [Expo Projects](#expo-projects)
55
+ - [Bare React Native Projects](#bare-react-native-projects)
56
+ - [🚀 Usage](#-usage)
57
+ - [Downloading a file](#downloading-a-file)
58
+ - [Re-Attaching to background tasks](#re-attaching-to-background-tasks)
59
+ - [⚙️ Advanced Configuration](#️-advanced-configuration)
60
+ - [Max Parallel Downloads (iOS only)](#max-parallel-downloads-ios-only)
61
+ - [Cellular/WiFi Restrictions](#cellularwifi-restrictions)
62
+ - [📚 API](#-api)
63
+ - [Quick Reference](#quick-reference)
64
+ - [📱 Platform Notes](#-platform-notes)
65
+ - [ Troubleshooting](#-troubleshooting)
66
+ - [🧪 Example App](#-example-app)
67
+ - [💡 Use Cases](#-use-cases)
68
+ - [🔄 Migration Guide](#-migration-guide)
69
+ - [🤝 Contributing](#-contributing)
70
+ - [Development Setup](#development-setup)
71
+ - [👥 Authors](#-authors)
72
+ - [📄 License](#-license)
73
+
74
+ ## 📋 Requirements
72
75
 
73
76
  | Requirement | Version |
74
77
  |-------------|--------|
@@ -79,7 +82,7 @@ Download and upload large files on iOS & Android — even when your app is in th
79
82
 
80
83
  > **Note:** For older React Native versions (0.57.0 - 0.69.x), use version 2.x of this library.
81
84
 
82
- ## Installation
85
+ ## 📦 Installation
83
86
 
84
87
  ### Expo Projects
85
88
 
@@ -142,16 +145,24 @@ The plugin automatically handles:
142
145
 
143
146
  **Step 1:** Install the package
144
147
 
148
+ **yarn:**
145
149
  ```bash
146
- # Using yarn
147
150
  yarn add @kesha-antonov/react-native-background-downloader
151
+ ```
148
152
 
149
- # Using npm
153
+ **npm:**
154
+ ```bash
150
155
  npm install @kesha-antonov/react-native-background-downloader
151
156
  ```
152
157
 
153
158
  **Step 2:** Install iOS pods
154
159
 
160
+ **yarn:**
161
+ ```bash
162
+ cd ios && pod install && cd ..
163
+ ```
164
+
165
+ **npm:**
155
166
  ```bash
156
167
  cd ios && pod install && cd ..
157
168
  ```
@@ -209,7 +220,7 @@ dependencies {
209
220
 
210
221
  > **Note:** If you're already using [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv) in your project, skip this step — it already includes MMKV.
211
222
 
212
- ## Usage
223
+ ## 🚀 Usage
213
224
 
214
225
  ### Downloading a file
215
226
 
@@ -355,7 +366,45 @@ await task.stop()
355
366
 
356
367
  </details>
357
368
 
358
- ## Advanced Configuration
369
+ <details>
370
+ <summary><strong>Updating headers on paused downloads</strong></summary>
371
+
372
+ If your download uses authentication tokens that expire, you can update the headers of a paused download before resuming it. This is useful when auth tokens refresh while a download is paused:
373
+
374
+ ```javascript
375
+ import { getExistingDownloadTasks } from '@kesha-antonov/react-native-background-downloader'
376
+
377
+ // Get paused downloads
378
+ const tasks = await getExistingDownloadTasks()
379
+
380
+ for (const task of tasks) {
381
+ if (task.state === 'PAUSED') {
382
+ // Update headers with new auth token before resuming
383
+ await task.setDownloadParams({
384
+ ...task.downloadParams,
385
+ headers: {
386
+ ...task.downloadParams?.headers,
387
+ Authorization: 'Bearer new-refreshed-token'
388
+ }
389
+ })
390
+
391
+ // Now resume with the updated headers
392
+ await task.resume()
393
+ }
394
+ }
395
+ ```
396
+
397
+ **Notes:**
398
+ - `setDownloadParams()` is async and returns `true` if native headers were updated
399
+ - Headers are only updated in the native layer when the task is in `PAUSED` state
400
+ - On iOS, the download will resume using HTTP Range headers with the new headers
401
+ - On Android, both in-memory and persisted paused state are updated
402
+
403
+ > **Use case:** User pauses a large download, closes the app, and returns hours or days later. By then, the auth token has expired. This feature allows refreshing the token and updating headers before resuming, without restarting the download from scratch.
404
+
405
+ </details>
406
+
407
+ ## ⚙️ Advanced Configuration
359
408
 
360
409
  <details>
361
410
  <summary><strong>Using custom headers</strong></summary>
@@ -551,7 +600,106 @@ task.start()
551
600
 
552
601
  </details>
553
602
 
554
- ## API
603
+ <details>
604
+ <summary><strong>Notification Configuration (Android)</strong></summary>
605
+
606
+ On Android 14+ (API 34), downloads use User-Initiated Data Transfer (UIDT) jobs which **require** notifications. Due to Android system requirements, notifications cannot be completely disabled when using UIDT jobs. However, you can control their visibility:
607
+
608
+ - When `showNotificationsEnabled: true` - Full notifications with progress, title, and custom texts
609
+ - When `showNotificationsEnabled: false` (default) - Minimal silent notifications with lowest priority that are barely noticeable
610
+
611
+ **Basic configuration:**
612
+
613
+ ```javascript
614
+ import { setConfig } from '@kesha-antonov/react-native-background-downloader'
615
+
616
+ // Enable notifications and notification grouping with custom texts
617
+ setConfig({
618
+ showNotificationsEnabled: true, // Show full notifications (default: false - minimal silent notifications)
619
+ notificationsGrouping: {
620
+ enabled: true, // Enable grouping (default: false)
621
+ texts: {
622
+ downloadTitle: 'Download',
623
+ downloadStarting: 'Starting download...',
624
+ downloadProgress: 'Downloading... {progress}%',
625
+ downloadPaused: 'Paused',
626
+ downloadFinished: 'Download complete',
627
+ groupTitle: 'Downloads',
628
+ groupText: '{count} downloads in progress',
629
+ },
630
+ },
631
+ })
632
+
633
+ // Use minimal silent notifications (default behavior)
634
+ setConfig({
635
+ showNotificationsEnabled: false, // Minimal silent notifications (required by UIDT but barely visible)
636
+ })
637
+ ```
638
+
639
+ **Grouping downloads by category:**
640
+
641
+ When notification grouping is enabled, you can group related downloads (e.g., by album, playlist, podcast) by passing `groupId` and `groupName` in the task metadata:
642
+
643
+ ```javascript
644
+ import { createDownloadTask, directories } from '@kesha-antonov/react-native-background-downloader'
645
+
646
+ // Download album songs - they will be grouped under one notification
647
+ const task = createDownloadTask({
648
+ id: 'song-1',
649
+ url: 'https://example.com/albums/summer-hits/track01.mp3',
650
+ destination: `${directories.documents}/track01.mp3`,
651
+ metadata: {
652
+ groupId: 'album-summer-hits', // Unique identifier for the group
653
+ groupName: 'Summer Hits 2024', // Display name in notification
654
+ },
655
+ })
656
+
657
+ task.start()
658
+ ```
659
+
660
+ **Notification behavior during pause/resume:**
661
+
662
+ When a download is paused on Android 14+:
663
+ - The background UIDT job is cancelled (to prevent downloads continuing in background)
664
+ - A detached "Paused" notification remains visible showing current progress
665
+ - When resumed, a new UIDT job is created and the notification switches to "Downloading" state
666
+ - When stopped, the notification is removed
667
+ - **When app is closed, all download notifications are automatically removed**
668
+
669
+ This ensures users always see the download status without unexpected background activity.
670
+
671
+ **Configuration options:**
672
+
673
+ | Option | Type | Default | Description |
674
+ |--------|------|---------|-------------|
675
+ | `showNotificationsEnabled` | boolean | `false` | Show full download notifications. When `false`, creates minimal silent notifications (UIDT jobs require a notification, but it will be barely visible). This is a top-level config option. |
676
+ | `notificationsGrouping.enabled` | boolean | `false` | Enable notification grouping |
677
+ | `notificationsGrouping.texts` | object | See below | Customizable notification texts |
678
+
679
+ **Notification texts (`notificationsGrouping.texts`):**
680
+
681
+ | Key | Default | Placeholders | Description |
682
+ |-----|---------|--------------|-------------|
683
+ | `downloadTitle` | `'Download'` | — | Title for individual download notifications |
684
+ | `downloadStarting` | `'Starting download...'` | — | Text when download is starting |
685
+ | `downloadProgress` | `'Downloading... {progress}%'` | `{progress}` | Progress text with current percentage (0-100) |
686
+ | `downloadPaused` | `'Paused'` | — | Text when download is paused |
687
+ | `downloadFinished` | `'Download complete'` | — | Text when download is finished |
688
+ | `groupTitle` | `'Downloads'` | — | Title for group summary notification |
689
+ | `groupText` | `'{count} download(s) in progress'` | `{count}` | Group summary text with active downloads count |
690
+
691
+ **Notes:**
692
+ - Notifications cannot be completely disabled on Android 14+ due to UIDT requirements
693
+ - When `showNotificationsEnabled: false`, notifications are created with minimal visibility (lowest priority, empty content)
694
+ - Paused downloads show a non-ongoing notification (can be swiped away by user)
695
+ - Active downloads show an ongoing notification (cannot be swiped away)
696
+ - This feature only affects Android 14+ (API 34) where UIDT jobs are used
697
+ - On older Android versions, the standard DownloadManager notifications are shown
698
+ - iOS uses system download notifications and doesn't support custom grouping
699
+
700
+ </details>
701
+
702
+ ## 📚 API
555
703
 
556
704
  For complete API documentation, see the **[API Reference](./docs/API.md)**.
557
705
 
@@ -579,7 +727,7 @@ import {
579
727
  | `completeHandler(jobId)` | Signal download completion to OS |
580
728
  | `directories.documents` | Path to app's documents directory |
581
729
 
582
- ## Platform Notes
730
+ ## 📱 Platform Notes
583
731
 
584
732
  For detailed platform-specific information, see **[Platform Notes](./docs/PLATFORM_NOTES.md)**.
585
733
 
@@ -588,7 +736,7 @@ Key points:
588
736
  - **Android**: Uses `DownloadManager` + Foreground Services + MMKV
589
737
  - **Pause/Resume**: Works on both platforms (Android requires server Range header support)
590
738
 
591
- ## Troubleshooting
739
+ ## Troubleshooting
592
740
 
593
741
  <details>
594
742
  <summary><strong>Download stuck in "pending" state (Android)</strong></summary>
@@ -629,7 +777,7 @@ See the [Google Play Console Declaration](#google-play-console-declaration) sect
629
777
  Add the Proguard rules mentioned in the [Proguard Rules](#proguard-rules) section.
630
778
  </details>
631
779
 
632
- ## Example App
780
+ ## 🧪 Example App
633
781
 
634
782
  The repository includes a full example app demonstrating all features:
635
783
 
@@ -652,7 +800,7 @@ The example app shows:
652
800
  - Re-attaching to background tasks
653
801
  - File management
654
802
 
655
- ## Use Cases
803
+ ## 💡 Use Cases
656
804
 
657
805
  This library is perfect for apps that need reliable file transfers:
658
806
 
@@ -663,7 +811,7 @@ This library is perfect for apps that need reliable file transfers:
663
811
  - 🎮 **Games** - Download game assets and updates
664
812
  - 📱 **Enterprise Apps** - Sync large documents and media
665
813
 
666
- ## Migration Guide
814
+ ## 🔄 Migration Guide
667
815
 
668
816
  Upgrading from an older version? Check the [Migration Guide](./MIGRATION.md) for detailed instructions:
669
817
 
@@ -674,7 +822,7 @@ Upgrading from an older version? Check the [Migration Guide](./MIGRATION.md) for
674
822
 
675
823
  See the [Changelog](./CHANGELOG.md) for a complete list of changes in each version.
676
824
 
677
- ## Contributing
825
+ ## 🤝 Contributing
678
826
 
679
827
  Contributions are welcome! Please feel free to submit a Pull Request.
680
828
 
@@ -708,12 +856,14 @@ cd example && yarn install
708
856
  yarn ios # or yarn android
709
857
  ```
710
858
 
711
- ## Authors
859
+ ## 👥 Authors
712
860
 
713
861
  Maintained by [Kesha Antonov](https://github.com/kesha-antonov)
714
862
 
715
863
  Based on [react-native-background-downloader](https://github.com/ekolabs/react-native-background-downloader) by [Elad Gil](https://github.com/ptelad) (unmaintained since 2019)
716
864
 
717
- ## License
865
+ > Please note that this project is maintained in free time. If you find it helpful, please consider [becoming a sponsor](https://github.com/sponsors/kesha-antonov).
866
+
867
+ ## 📄 License
718
868
 
719
869
  [Apache 2.0](./LICENSE)
@@ -248,6 +248,19 @@ class Downloader(private val context: Context, private val storageManager: com.e
248
248
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Saved paused state for resumable download $configId at $bytesDownloaded/$bytesTotal bytes")
249
249
  }
250
250
 
251
+ /**
252
+ * Update headers for a paused download.
253
+ * This allows changing auth tokens before resuming.
254
+ */
255
+ fun updatePausedDownloadHeaders(configId: String, headers: Map<String, String>) {
256
+ val pausedInfo = pausedDownloads[configId] ?: return
257
+
258
+ pausedDownloads[configId] = pausedInfo.copy(headers = headers)
259
+ savePausedDownloads()
260
+
261
+ RNBackgroundDownloaderModuleImpl.logD(TAG, "Updated headers for paused download $configId")
262
+ }
263
+
251
264
  /**
252
265
  * Resume a paused download using the background service with HTTP Range headers.
253
266
  */
@@ -269,7 +282,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
269
282
  pausedInfo.headers,
270
283
  pausedInfo.bytesDownloaded,
271
284
  pausedInfo.bytesTotal,
272
- listener
285
+ listener,
286
+ pausedInfo.metadata
273
287
  )
274
288
 
275
289
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Resuming download $configId from ${pausedInfo.bytesDownloaded} bytes via service")
@@ -293,7 +307,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
293
307
  headers: Map<String, String>,
294
308
  startByte: Long,
295
309
  totalBytes: Long,
296
- listener: ResumableDownloader.DownloadListener
310
+ listener: ResumableDownloader.DownloadListener,
311
+ metadata: String = "{}"
297
312
  ) {
298
313
  // On Android 14+, use UIDT jobs for better background execution
299
314
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@@ -308,7 +323,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
308
323
  destination = destination,
309
324
  headers = headers,
310
325
  startByte = startByte,
311
- totalBytes = totalBytes
326
+ totalBytes = totalBytes,
327
+ metadata = metadata
312
328
  )
313
329
 
314
330
  if (scheduled) {
@@ -351,7 +367,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
351
367
  url: String,
352
368
  destination: String,
353
369
  headers: Map<String, String>,
354
- listener: ResumableDownloader.DownloadListener
370
+ listener: ResumableDownloader.DownloadListener,
371
+ metadata: String = "{}"
355
372
  ) {
356
373
  startDownloadService(
357
374
  configId,
@@ -360,7 +377,8 @@ class Downloader(private val context: Context, private val storageManager: com.e
360
377
  headers,
361
378
  0L, // Start from beginning
362
379
  -1L, // Total bytes unknown
363
- listener
380
+ listener,
381
+ metadata
364
382
  )
365
383
  RNBackgroundDownloaderModuleImpl.logD(TAG, "Started ResumableDownloader for $configId (DownloadManager fallback)")
366
384
  }
@@ -372,7 +390,7 @@ class Downloader(private val context: Context, private val storageManager: com.e
372
390
  // Check UIDT jobs on Android 14+
373
391
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
374
392
  if (UIDTDownloadJobService.isActiveJob(configId)) {
375
- return UIDTDownloadJobService.pauseJob(configId)
393
+ return UIDTDownloadJobService.pauseJob(context, configId)
376
394
  }
377
395
  }
378
396
  return downloadService?.pauseDownload(configId) ?: false
@@ -385,7 +403,7 @@ class Downloader(private val context: Context, private val storageManager: com.e
385
403
  // Check UIDT jobs on Android 14+
386
404
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
387
405
  if (UIDTDownloadJobService.isActiveJob(configId)) {
388
- return UIDTDownloadJobService.resumeJob(configId, listener)
406
+ return UIDTDownloadJobService.resumeJob(context, configId, listener)
389
407
  }
390
408
  }
391
409
  executeWhenServiceReady {
@@ -7,6 +7,5 @@ data class RNBGDTaskConfig(
7
7
  var url: String,
8
8
  var destination: String,
9
9
  var metadata: String = "{}",
10
- var notificationTitle: String?,
11
10
  var reportedBegin: Boolean = false
12
11
  ) : Serializable
@@ -14,7 +14,6 @@ data class RNBGDUploadTaskConfig(
14
14
  val fieldName: String? = null,
15
15
  val mimeType: String? = null,
16
16
  val parameters: Map<String, String>? = null,
17
- val notificationTitle: String? = null,
18
17
  var reportedBegin: Boolean = false,
19
18
  var bytesUploaded: Long = 0,
20
19
  var bytesTotal: Long = 0,