@kesha-antonov/react-native-background-downloader 4.4.0 โ†’ 4.4.4

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
@@ -1,141 +1,99 @@
1
-
2
1
  <p align="center">
3
2
  <img width="300" src="https://github.com/user-attachments/assets/25e89808-9eb7-42b2-8031-b48d8c24796c" />
4
3
  </p>
5
4
 
6
- [![npm version](https://badge.fury.io/js/@kesha-antonov%2Freact-native-background-downloader.svg)](https://badge.fury.io/js/@kesha-antonov%2Freact-native-background-downloader)
7
-
8
- ## ๐ŸŽ‰ Version 4.0.0 Released!
9
-
10
- **v4.0.0** is now available with full **React Native New Architecture (TurboModules)** support!
11
-
12
- ### What's New
13
- - โœ… Full TurboModules support for iOS and Android
14
- - โœ… Expo Config Plugin for automatic iOS setup
15
- - โœ… Android code converted to Kotlin
16
- - โœ… Full TypeScript support
17
- - โœ… New `progressMinBytes` option for hybrid progress reporting
18
- - โœ… `maxRedirects` option for Android
19
-
20
- ### Upgrading from v3.x?
21
- ๐Ÿ“– See the [Migration Guide](./MIGRATION.md) for detailed upgrade instructions and breaking changes.
22
-
23
- ๐Ÿ“‹ See the [Changelog](./CHANGELOG.md) for the full list of changes.
24
-
25
- ### Looking for v3.2.6?
26
- If you need the previous stable version: [3.2.6 readme](https://github.com/kesha-antonov/react-native-background-downloader/blob/8f4b8a844a2d7f00d1558f6ea65bac94c8dd6fc9/README.md)
27
-
28
- # @kesha-antonov/react-native-background-downloader
29
-
30
- A library for React-Native to help you download and upload large files on iOS and Android both in the foreground and most importantly in the background.
31
-
32
- ### Why?
5
+ <p align="center">
6
+ <a href="https://badge.fury.io/js/@kesha-antonov%2Freact-native-background-downloader"><img src="https://badge.fury.io/js/@kesha-antonov%2Freact-native-background-downloader.svg" alt="npm version"></a>
7
+ <a href="https://www.npmjs.com/package/@kesha-antonov/react-native-background-downloader"><img src="https://img.shields.io/npm/dm/@kesha-antonov/react-native-background-downloader.svg" alt="npm downloads"></a>
8
+ <a href="https://github.com/kesha-antonov/react-native-background-downloader/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-blue.svg" alt="license"></a>
9
+ <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20Android-lightgrey.svg" alt="platforms">
10
+ <img src="https://img.shields.io/badge/TypeScript-supported-blue.svg" alt="TypeScript">
11
+ <img src="https://img.shields.io/badge/Expo-compatible-000020.svg" alt="Expo compatible">
12
+ <img src="https://img.shields.io/badge/New%20Architecture-supported-green.svg" alt="New Architecture">
13
+ </p>
33
14
 
34
- On iOS, if you want to download big files no matter the state of your app, wether it's in the background or terminated by the OS, you have to use a system API called `NSURLSession`.
15
+ # React Native Background Downloader
35
16
 
36
- This API handles your downloads separately from your app and only keeps it informed using delegates (Read: [Downloading Files in the Background](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background)).
17
+ Download and upload large files on iOS & Android โ€” even when your app is in the background or terminated.
37
18
 
38
- On Android we are using similar process with [DownloadManager](https://developer.android.com/reference/android/app/DownloadManager)
19
+ ## โœจ Features
39
20
 
40
- The real challenge of using this method is making sure the app's UI is always up-to-date with the downloads that are happening in another process because your app might startup from scratch while the downloads are still running.
21
+ - ๐Ÿ“ฅ **Background Downloads** - Downloads continue even when app is in background or terminated
22
+ - ๐Ÿ“ค **Background Uploads** - Upload files reliably in the background
23
+ - โธ๏ธ **Pause/Resume** - Full pause and resume support on both iOS and Android
24
+ - ๐Ÿ”„ **Re-attach to Downloads** - Reconnect to ongoing downloads after app restart
25
+ - ๐Ÿ“Š **Progress Tracking** - Real-time progress updates with customizable intervals
26
+ - ๐Ÿ”’ **Custom Headers** - Support for authentication and custom request headers
27
+ - ๐Ÿ“ฑ **Expo Support** - Config plugin for easy Expo integration
28
+ - โšก **New Architecture** - Full TurboModules support for React Native
29
+ - ๐Ÿ“ **TypeScript** - Complete TypeScript definitions included
41
30
 
42
- `@kesha-antonov/react-native-background-downloader` gives you an easy API to both downloading large files and re-attaching to those downloads once your app launches again.
31
+ ## Why?
43
32
 
44
- ## ToC
33
+ **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.
45
34
 
46
- - [Getting started](#getting-started)
47
- - [Usage](#usage)
48
- - [Downloading](#downloading-a-file)
49
- - [Uploading](#uploading-a-file)
50
- - [API](#api)
51
- - [Constants](#constants)
35
+ **The Solution:** Both iOS and Android provide system-level APIs for background file transfers:
36
+ - **iOS:** [`NSURLSession`](https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background) - handles downloads in a separate process, continuing even after your app is terminated
37
+ - **Android:** A combination of [`DownloadManager`](https://developer.android.com/reference/android/app/DownloadManager) for system-managed downloads, [Foreground Services](https://developer.android.com/develop/background-work/services/foreground-services) for pause/resume support, and [MMKV](https://github.com/Tencent/MMKV) for persistent state storage
52
38
 
53
- ## Getting started
39
+ **The Challenge:** These APIs are powerful but complex. Downloads run in a separate process, so your app might restart from scratch while downloads are still in progress. Keeping your UI in sync with background downloads requires careful state management.
54
40
 
55
- ### Installation
41
+ **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.
56
42
 
57
- ```Terminal
58
- yarn add @kesha-antonov/react-native-background-downloader
59
- ```
43
+ ## Table of Contents
60
44
 
61
- or
62
- ```Terminal
63
- npm i @kesha-antonov/react-native-background-downloader
64
- ```
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)
65
70
 
66
- Then:
71
+ ## Requirements
67
72
 
68
- ```Terminal
69
- cd ios && pod install
70
- ```
73
+ | Requirement | Version |
74
+ |-------------|--------|
75
+ | React Native | >= 0.70.0 |
76
+ | iOS | >= 15.1 |
77
+ | Android | API 24+ (Android 7.0) |
78
+ | Expo | SDK 50+ (with config plugin) |
71
79
 
72
- <details>
73
- <summary>Manual Setup (Advanced)</summary>
80
+ > **Note:** For older React Native versions (0.57.0 - 0.69.x), use version 2.x of this library.
74
81
 
75
- If you need to manually configure the package for New Architecture:
82
+ ## Installation
76
83
 
77
- **iOS**: The library automatically detects New Architecture via compile-time flags.
84
+ ### Expo Projects
78
85
 
79
- **Android**: For New Architecture, you can optionally use `RNBackgroundDownloaderTurboPackage` instead of the default package:
80
- ```java
81
- import com.eko.RNBackgroundDownloaderTurboPackage;
86
+ **Step 1:** Install the package
82
87
 
83
- // In your MainApplication.java
84
- @Override
85
- protected List<ReactPackage> getPackages() {
86
- return Arrays.<ReactPackage>asList(
87
- // ... other packages
88
- new RNBackgroundDownloaderTurboPackage() // For New Architecture
89
- );
90
- }
88
+ ```bash
89
+ npx expo install @kesha-antonov/react-native-background-downloader
91
90
  ```
92
- </details>
93
-
94
- ### Mostly automatic installation
95
- Any React Native version **`>= 0.60`** supports autolinking so nothing should be done.
96
-
97
- For anything **`< 0.60`** run the following link command
98
-
99
- `$ react-native link @kesha-antonov/react-native-background-downloader`
100
-
101
- ### Manual installation
102
-
103
- <details>
104
91
 
105
- #### iOS
106
-
107
- 1. In XCode, in the project navigator, right click `Libraries` โžœ `Add Files to [your project's name]`
108
- 2. Go to `node_modules` โžœ `@kesha-antonov/react-native-background-downloader` and add `RNBackgroundDownloader.xcodeproj`
109
- 3. In XCode, in the project navigator, select your project. Add `libRNBackgroundDownloader.a` to your project's `Build Phases` โžœ `Link Binary With Libraries`
110
- 4. Run your project (`Cmd+R`)
111
-
112
- #### Android
113
-
114
- 1. Open up `android/app/src/main/java/[...]/MainActivity.java`
115
- - Add `import com.eko.RNBackgroundDownloaderPackage;` to the imports at the top of the file
116
- - Add `new RNBackgroundDownloaderPackage()` to the list returned by the `getPackages()` method
117
- 2. Append the following lines to `android/settings.gradle`:
118
- ```
119
- include ':react-native-background-downloader'
120
- project(':react-native-background-downloader').projectDir = new File(rootProject.projectDir, '../node_modules/@kesha-antonov/react-native-background-downloader/android')
121
- ```
122
- 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
123
- ```
124
- compile project(':react-native-background-downloader')
125
- ```
126
- </details>
127
-
128
- ### iOS - Extra Mandatory Step
129
-
130
- #### Option 1: Using Expo Config Plugin (Recommended for Expo/EAS users)
131
-
132
- If you're using Expo or EAS Build, you can use the included Expo config plugin to automatically configure the native code:
92
+ **Step 2:** Add the config plugin to your `app.json` or `app.config.js`:
133
93
 
134
- **In your `app.json`:**
135
94
  ```json
136
95
  {
137
96
  "expo": {
138
- "name": "Your App",
139
97
  "plugins": [
140
98
  "@kesha-antonov/react-native-background-downloader"
141
99
  ]
@@ -143,33 +101,16 @@ If you're using Expo or EAS Build, you can use the included Expo config plugin t
143
101
  }
144
102
  ```
145
103
 
146
- **Or in your `app.config.js`:**
147
- ```js
148
- export default {
149
- expo: {
150
- name: "Your App",
151
- plugins: [
152
- "@kesha-antonov/react-native-background-downloader"
153
- ]
154
- }
155
- }
156
- ```
157
-
158
- **Plugin Options:**
159
-
160
- You can customize the plugin behavior with options:
104
+ <details>
105
+ <summary><strong>Plugin Options (optional)</strong></summary>
161
106
 
162
107
  ```js
163
108
  // app.config.js
164
109
  export default {
165
110
  expo: {
166
- name: "Your App",
167
111
  plugins: [
168
112
  ["@kesha-antonov/react-native-background-downloader", {
169
- // Set to false if you're already using react-native-mmkv
170
- addMmkvDependency: true,
171
- // Customize the MMKV version (default: '2.2.4')
172
- mmkvVersion: "2.2.4"
113
+ mmkvVersion: "2.2.4" // Customize MMKV version on Android
173
114
  }]
174
115
  ]
175
116
  }
@@ -178,75 +119,94 @@ export default {
178
119
 
179
120
  | Option | Type | Default | Description |
180
121
  |--------|------|---------|-------------|
181
- | `addMmkvDependency` | boolean | `true` | Whether to automatically add MMKV dependency on Android. Set to `false` if you're using `react-native-mmkv`. |
182
- | `mmkvVersion` | string | `'2.2.4'` | The version of MMKV to use on Android. |
122
+ | `mmkvVersion` | string | `'2.2.4'` | The version of [MMKV](https://github.com/Tencent/MMKV/releases) to use on Android. |
183
123
 
184
- The plugin will automatically:
185
- - **iOS:** Add the required import to your AppDelegate (Objective-C) or Bridging Header (Swift)
186
- - **iOS:** Add the `handleEventsForBackgroundURLSession` method to your AppDelegate
187
- - **iOS:** Handle both React Native < 0.77 (Objective-C) and >= 0.77 (Swift) projects
188
- - **Android:** Add the required MMKV dependency (unless `addMmkvDependency: false`)
124
+ </details>
125
+
126
+ **Step 3:** Rebuild your app
189
127
 
190
- After adding the plugin, run:
191
128
  ```bash
192
- expo prebuild --clean
129
+ npx expo prebuild --clean
130
+ npx expo run:ios # or npx expo run:android
193
131
  ```
194
132
 
195
- #### Option 2: Manual Setup
133
+ The plugin automatically handles:
134
+ - **iOS:** Adding the required `handleEventsForBackgroundURLSession` method to AppDelegate
135
+ - **Android:** Adding the required MMKV dependency
196
136
 
197
- <details>
198
- <summary>Manual setup for React Native 0.77+ (Click to expand)</summary>
137
+ ---
199
138
 
200
- In your project bridging header file (e.g. `ios/{projectName}-Bridging-Header.h`)
201
- add an import for RNBackgroundDownloader:
139
+ ### Bare React Native Projects
202
140
 
203
- ```objc
204
- ...
205
- #import <RNBackgroundDownloader.h>
206
- ```
141
+ **Step 1:** Install the package
207
142
 
208
- Then in your `AppDelegate.swift` add the following method inside of your `AppDelegate` class:
143
+ ```bash
144
+ # Using yarn
145
+ yarn add @kesha-antonov/react-native-background-downloader
209
146
 
210
- ```swift
211
- ...
147
+ # Using npm
148
+ npm install @kesha-antonov/react-native-background-downloader
149
+ ```
212
150
 
213
- @main
214
- class AppDelegate: UIResponder, UIApplicationDelegate
215
- ...
151
+ **Step 2:** Install iOS pods
152
+
153
+ ```bash
154
+ cd ios && pod install && cd ..
155
+ ```
156
+
157
+ **Step 3:** Configure iOS AppDelegate
158
+
159
+ <details>
160
+ <summary><strong>React Native 0.77+ (Swift)</strong></summary>
161
+
162
+ In your project bridging header file (e.g. `ios/{projectName}-Bridging-Header.h`):
163
+
164
+ ```objc
165
+ #import <RNBackgroundDownloader.h>
166
+ ```
167
+
168
+ In your `AppDelegate.swift`:
169
+
170
+ ```swift
171
+ func application(
172
+ _ application: UIApplication,
173
+ handleEventsForBackgroundURLSession identifier: String,
174
+ completionHandler: @escaping () -> Void
175
+ ) {
176
+ RNBackgroundDownloader.setCompletionHandlerWithIdentifier(identifier, completionHandler: completionHandler)
177
+ }
178
+ ```
216
179
 
217
- func application(
218
- _ application: UIApplication,
219
- handleEventsForBackgroundURLSession identifier: String,
220
- completionHandler: @escaping () -> Void
221
- ) {
222
- RNBackgroundDownloader.setCompletionHandlerWithIdentifier(identifier, completionHandler: completionHandler)
223
- }
224
- }
225
- ...
226
- ```
227
- Failing to add this code will result in canceled background downloads. If Xcode complains that RNBackgroundDownloader.h is missing, you might have forgotten to `pod install` first.
228
180
  </details>
229
181
 
230
182
  <details>
231
- <summary>Manual setup for React Native < 0.77 (Click to expand)</summary>
183
+ <summary><strong>React Native < 0.77 (Objective-C)</strong></summary>
232
184
 
233
- In your `AppDelegate.m` add the following code:
234
- ```objc
235
- ...
236
- #import <RNBackgroundDownloader.h>
185
+ In your `AppDelegate.m`:
237
186
 
238
- ...
187
+ ```objc
188
+ #import <RNBackgroundDownloader.h>
239
189
 
240
- - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
241
- {
242
- [RNBackgroundDownloader setCompletionHandlerWithIdentifier:identifier completionHandler:completionHandler];
243
- }
190
+ - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
191
+ {
192
+ [RNBackgroundDownloader setCompletionHandlerWithIdentifier:identifier completionHandler:completionHandler];
193
+ }
194
+ ```
244
195
 
245
- ...
246
- ```
247
- Failing to add this code will result in canceled background downloads. If Xcode complains that RNBackgroundDownloader.h is missing, you might have forgotten to `pod install` first.
248
196
  </details>
249
197
 
198
+ **Step 4:** Configure Android MMKV dependency
199
+
200
+ Add MMKV to your `android/app/build.gradle`:
201
+
202
+ ```gradle
203
+ dependencies {
204
+ implementation 'com.tencent:mmkv-shared:2.2.4'
205
+ }
206
+ ```
207
+
208
+ > **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.
209
+
250
210
  ## Usage
251
211
 
252
212
  ### Downloading a file
@@ -292,33 +252,56 @@ await task.resume()
292
252
  await task.stop()
293
253
  ```
294
254
 
295
- ### Re-Attaching to background downloads
255
+ ### Re-Attaching to background tasks
256
+
257
+ The killer feature of this library: **reconnect to downloads and uploads that continued running while your app was closed**, or **resume paused tasks from a previous session**.
296
258
 
297
- This is the main selling point of this library (but it's free!).
259
+ When the OS terminates your app to free memory, background transfers keep running. When your app restarts, call `getExistingDownloadTasks()` or `getExistingUploadTasks()` to get back in sync. Paused tasks are also preserved and can be resumed with `task.resume()`.
298
260
 
299
- What happens to your downloads after the OS stopped your app? Well, they are still running, we just need to re-attach to them.
261
+ > **๐Ÿ’ก Tip:** Use meaningful task IDs (not random UUIDs) so you can match tasks to your UI components after restart.
300
262
 
301
- Add this code to app's init stage, and you'll never lose a download again!
263
+ **Downloads:**
302
264
 
303
265
  ```javascript
304
266
  import { getExistingDownloadTasks } from '@kesha-antonov/react-native-background-downloader'
305
267
 
306
- let lostTasks = await getExistingDownloadTasks()
307
- for (let task of lostTasks) {
308
- console.log(`Task ${task.id} was found!`)
268
+ const lostTasks = await getExistingDownloadTasks()
269
+
270
+ for (const task of lostTasks) {
271
+ console.log(`Found download: ${task.id}`)
272
+
309
273
  task.progress(({ bytesDownloaded, bytesTotal }) => {
310
274
  console.log(`Downloaded: ${bytesDownloaded / bytesTotal * 100}%`)
311
275
  }).done(({ location, bytesDownloaded, bytesTotal }) => {
312
- console.log('Download is done!', { location, bytesDownloaded, bytesTotal })
276
+ console.log('Download complete!', { location, bytesDownloaded, bytesTotal })
313
277
  }).error(({ error, errorCode }) => {
314
- console.log('Download canceled due to error: ', { error, errorCode })
278
+ console.log('Download failed:', { error, errorCode })
315
279
  })
316
280
  }
317
281
  ```
318
282
 
319
- `task.id` is very important for re-attaching the download task with any UI component representing that task. This is why you need to make sure to give sensible IDs that you know what to do with, try to avoid using random IDs.
283
+ **Uploads:**
284
+
285
+ ```javascript
286
+ import { getExistingUploadTasks } from '@kesha-antonov/react-native-background-downloader'
287
+
288
+ const lostUploads = await getExistingUploadTasks()
289
+
290
+ for (const task of lostUploads) {
291
+ console.log(`Found upload: ${task.id}`)
320
292
 
321
- ### Uploading a file
293
+ task.progress(({ bytesUploaded, bytesTotal }) => {
294
+ console.log(`Uploaded: ${bytesUploaded / bytesTotal * 100}%`)
295
+ }).done(({ responseCode, responseBody }) => {
296
+ console.log('Upload complete!', { responseCode, responseBody })
297
+ }).error(({ error, errorCode }) => {
298
+ console.log('Upload failed:', { error, errorCode })
299
+ })
300
+ }
301
+ ```
302
+
303
+ <details>
304
+ <summary><strong>Uploading a file</strong></summary>
322
305
 
323
306
  ```javascript
324
307
  import { Platform } from 'react-native'
@@ -368,27 +351,12 @@ await task.resume()
368
351
  await task.stop()
369
352
  ```
370
353
 
371
- ### Re-Attaching to background uploads
372
-
373
- Similar to downloads, you can re-attach to uploads that were running when your app was terminated:
354
+ </details>
374
355
 
375
- ```javascript
376
- import { getExistingUploadTasks } from '@kesha-antonov/react-native-background-downloader'
356
+ ## Advanced Configuration
377
357
 
378
- let lostUploads = await getExistingUploadTasks()
379
- for (let task of lostUploads) {
380
- console.log(`Upload task ${task.id} was found!`)
381
- task.progress(({ bytesUploaded, bytesTotal }) => {
382
- console.log(`Uploaded: ${bytesUploaded / bytesTotal * 100}%`)
383
- }).done(({ responseCode, responseBody }) => {
384
- console.log('Upload is done!', { responseCode, responseBody })
385
- }).error(({ error, errorCode }) => {
386
- console.log('Upload canceled due to error: ', { error, errorCode })
387
- })
388
- }
389
- ```
390
-
391
- ### Using custom headers
358
+ <details>
359
+ <summary><strong>Using custom headers</strong></summary>
392
360
  If you need to send custom headers with your download request, you can do in it 2 ways:
393
361
 
394
362
  1) Globally using `setConfig()`:
@@ -428,7 +396,10 @@ task.start()
428
396
  ```
429
397
  Headers given in `createDownloadTask()` are **merged** with the ones given in `setConfig({ headers: { ... } })`.
430
398
 
431
- ### Configuring Parallel Downloads and Network Types
399
+ </details>
400
+
401
+ <details>
402
+ <summary><strong>Configuring parallel downloads and network types</strong></summary>
432
403
 
433
404
  You can configure global settings for download behavior using `setConfig()`:
434
405
 
@@ -480,7 +451,10 @@ const task = createDownloadTask({
480
451
  })
481
452
  ```
482
453
 
483
- ### Enabling Debug Logs
454
+ </details>
455
+
456
+ <details>
457
+ <summary><strong>Enabling debug logs</strong></summary>
484
458
 
485
459
  The library includes verbose debug logging that can help diagnose download issues. Logging is disabled by default but can be enabled at runtime using `setConfig()`. **Logging works in both debug and production/release builds.**
486
460
 
@@ -517,7 +491,10 @@ setConfig({
517
491
  - Logs include detailed information about download lifecycle, session management, and errors
518
492
  - In production builds, logs are only printed when explicitly enabled via `isLogsEnabled`
519
493
 
520
- ### Handling Slow-Responding URLs
494
+ </details>
495
+
496
+ <details>
497
+ <summary><strong>Handling slow-responding URLs</strong></summary>
521
498
 
522
499
  This library automatically includes connection timeout improvements for slow-responding URLs. By default, the following headers are added to all download requests on Android:
523
500
 
@@ -527,7 +504,10 @@ This library automatically includes connection timeout improvements for slow-res
527
504
 
528
505
  These headers help prevent downloads from getting stuck in "pending" state when servers take several minutes to respond initially. You can override these headers by providing your own in the `headers` option.
529
506
 
530
- ### Handling URLs with Many Redirects (Android)
507
+ </details>
508
+
509
+ <details>
510
+ <summary><strong>Handling URLs with many redirects (Android)</strong></summary>
531
511
 
532
512
  Android's DownloadManager has a built-in redirect limit that can cause `ERROR_TOO_MANY_REDIRECTS` for URLs with multiple redirects (common with podcast URLs, tracking services, CDNs, etc.).
533
513
 
@@ -567,11 +547,13 @@ task.start()
567
547
  - Falls back to original URL if redirect resolution fails
568
548
  - Respects the same headers and timeouts as the main download
569
549
 
550
+ </details>
551
+
570
552
  ## API
571
553
 
572
- ### Named Exports
554
+ For complete API documentation, see the **[API Reference](./docs/API.md)**.
573
555
 
574
- The library exports the following functions and objects:
556
+ ### Quick Reference
575
557
 
576
558
  ```typescript
577
559
  import {
@@ -585,318 +567,151 @@ import {
585
567
  } from '@kesha-antonov/react-native-background-downloader'
586
568
  ```
587
569
 
588
- ### `createDownloadTask(options)`
589
-
590
- Download a file to destination
591
-
592
- **options**
593
-
594
- An object containing options properties
595
-
596
- | Property | Type | Required | Platforms | Info |
597
- | ------------- | ------------------------------------------------ | :------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
598
- | `id` | String | โœ… | All | A unique ID to provide for this download. This ID will help to identify the download task when the app re-launches |
599
- | `url` | String | โœ… | All | URL to file you want to download |
600
- | `destination` | String | โœ… | All | Where to copy the file to once the download is done. The 'file://' prefix will be automatically removed if present |
601
- | `metadata` | Record<string, unknown> | | All | Custom data to be preserved across app restarts. Will be serialized to JSON |
602
- | `headers` | Record<string, string \| null> | | All | Custom headers to add to the download request. These are merged with the headers given in `setConfig({ headers: { ... } })`. Headers with null values will be removed |
603
- | `maxRedirects` | Number | | Android | Maximum number of redirects to follow before passing URL to DownloadManager. If not specified or 0, no redirect resolution is performed. Helps avoid ERROR_TOO_MANY_REDIRECTS for URLs with many redirects (e.g., podcast URLs) |
604
- | `isAllowedOverRoaming` | Boolean | | Android | whether this download may proceed over a roaming connection. By default, roaming is allowed |
605
- | `isAllowedOverMetered` | Boolean | | Android | Whether this download may proceed over a metered network connection. By default, metered networks are allowed |
606
- | `isNotificationVisible` | Boolean | | Android | Whether to show a download notification or not |
607
- | `notificationTitle` | String | | Android | Title of the download notification |
608
-
609
- **returns**
610
-
611
- `DownloadTask` - The download task to control and monitor this download. Call `task.start()` to begin the download.
612
-
613
- ### `getExistingDownloadTasks()`
614
-
615
- Checks for downloads that ran in background while your app was terminated.
616
-
617
- Recommended to run at the init stage of the app.
618
-
619
- **returns**
620
-
621
- `Promise<DownloadTask[]>` - A promise that resolves to an array of tasks that were running in the background so you can re-attach callbacks to them
622
-
623
- ### `createUploadTask(options)`
624
-
625
- Upload a file to a server
626
-
627
- **options**
628
-
629
- An object containing options properties
630
-
631
- | Property | Type | Required | Platforms | Info |
632
- | ------------- | ------------------------------------------------ | :------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
633
- | `id` | String | โœ… | All | A unique ID to provide for this upload. This ID will help to identify the upload task when the app re-launches |
634
- | `url` | String | โœ… | All | URL to upload the file to |
635
- | `source` | String | โœ… | All | Path to the local file to upload. The 'file://' prefix will be automatically removed if present |
636
- | `method` | 'POST' \| 'PUT' \| 'PATCH' | | All | HTTP method for upload. Default is 'POST' |
637
- | `metadata` | Record<string, unknown> | | All | Custom data to be preserved across app restarts. Will be serialized to JSON |
638
- | `headers` | Record<string, string \| null> | | All | Custom headers to add to the upload request. These are merged with the headers given in `setConfig({ headers: { ... } })`. Headers with null values will be removed |
639
- | `fieldName` | String | | All | Name of the multipart form field for the file. Default is 'file' |
640
- | `mimeType` | String | | All | MIME type of the file being uploaded. Default is inferred from file extension |
641
- | `parameters` | Record<string, string> | | All | Additional form parameters to send with the upload |
642
- | `isAllowedOverRoaming` | Boolean | | Android | Whether this upload may proceed over a roaming connection. By default, roaming is allowed |
643
- | `isAllowedOverMetered` | Boolean | | Android | Whether this upload may proceed over a metered network connection. By default, metered networks are allowed |
644
- | `isNotificationVisible` | Boolean | | Android | Whether to show an upload notification or not |
645
- | `notificationTitle` | String | | Android | Title of the upload notification |
646
-
647
- **returns**
648
-
649
- `UploadTask` - The upload task to control and monitor this upload. Call `task.start()` to begin the upload.
650
-
651
- ### `getExistingUploadTasks()`
652
-
653
- Checks for uploads that ran in background while your app was terminated.
654
-
655
- Recommended to run at the init stage of the app.
656
-
657
- **returns**
658
-
659
- `Promise<UploadTask[]>` - A promise that resolves to an array of upload tasks that were running in the background so you can re-attach callbacks to them
660
-
661
- ### `setConfig(config)`
662
-
663
- Sets global configuration for the downloader.
664
-
665
- **config**
666
-
667
- An object containing configuration properties
668
-
669
- | Name | Type | Info |
670
- | -------------- | ------ | ---------------------------------------------------------------------------------------------------- |
671
- | `headers` | Record<string, string \| null> | Optional headers to use in all future downloads. Headers with null values will be removed |
672
- | `progressInterval` | Number | Interval in milliseconds for download progress updates. Must be >= 250. Default is 1000 |
673
- | `progressMinBytes` | Number | Minimum number of bytes that must be downloaded before a progress event is emitted. When set to 0, only the percentage threshold (1% change) triggers progress updates. Default is 1048576 (1MB) |
674
- | `isLogsEnabled` | Boolean | Enables/disables verbose debug logs in native code (iOS and Android). Works in both debug and release builds. Default is false |
675
- | `logCallback` | (log: { message: string, taskId?: string }) => void | Optional callback function to receive native debug logs in JavaScript. Only called when `isLogsEnabled` is true |
676
- | `maxParallelDownloads` | Number | **iOS only**. Sets the maximum number of simultaneous connections per host for the download session. Must be >= 1. Default is 4. Note: Android's DownloadManager does not support this configuration |
677
- | `allowsCellularAccess` | Boolean | Controls whether downloads are allowed over cellular (metered) connections. When set to `false`, downloads will only occur over WiFi. Default is `true`. This is a cross-platform abstraction - on iOS it sets `allowsCellularAccess`, on Android it sets `isAllowedOverMetered` |
678
-
679
- **Example:**
680
-
681
- ```javascript
682
- import { setConfig } from '@kesha-antonov/react-native-background-downloader'
683
-
684
- // Configure parallel downloads (iOS only) and cellular access
685
- setConfig({
686
- maxParallelDownloads: 8, // iOS only - max simultaneous connections per host
687
- allowsCellularAccess: false, // Only download over WiFi
688
- })
689
-
690
- // Enable verbose logging with callback
691
- setConfig({
692
- isLogsEnabled: true,
693
- logCallback: (log) => {
694
- console.log('[BackgroundDownloader]', log.message, log.taskId ? `(${log.taskId})` : '')
695
- }
696
- })
697
-
698
- // Or just enable native console logging without JS callback
699
- setConfig({
700
- isLogsEnabled: true
701
- })
702
- ```
703
-
704
- ### DownloadTask
705
-
706
- A class representing a download task created by `createDownloadTask()`. Note: You must call `task.start()` to begin the download after setting up event handlers.
707
-
708
- ### UploadTask
709
-
710
- A class representing an upload task created by `createUploadTask()`. Note: You must call `task.start()` to begin the upload after setting up event handlers.
711
-
712
- **Members** (same structure as DownloadTask with upload-specific properties)
713
-
714
- | Name | Type | Info |
715
- | -------------- | ------ | ---------------------------------------------------------------------------------------------------- |
716
- | `id` | String | The id you gave the task when calling `createUploadTask` |
717
- | `metadata` | Record<string, unknown> | The metadata you gave the task when calling `createUploadTask` |
718
- | `state` | 'PENDING' \| 'UPLOADING' \| 'PAUSED' \| 'DONE' \| 'FAILED' \| 'STOPPED' | Current state of the upload task |
719
- | `bytesUploaded` | Number | The number of bytes currently uploaded by the task |
720
- | `bytesTotal` | Number | The total number bytes to be uploaded by this task |
721
- | `uploadParams` | UploadParams | The upload parameters set for this task |
722
-
723
- **Callback Methods**
724
-
725
- | Function | Callback Arguments | Info|
726
- | ---------- | --------------------------------- | ---- |
727
- | `begin` | `{ expectedBytes: number }` | Called when upload starts |
728
- | `progress` | `{ bytesUploaded: number, bytesTotal: number }` | Called based on progressInterval (default: every 1000ms) so you can update your progress bar accordingly |
729
- | `done` | `{ responseCode: number, responseBody: string, bytesUploaded: number, bytesTotal: number }` | Called when the upload is done. Includes server response code and body |
730
- | `error` | `{ error: string, errorCode: number }` | Called when the upload stops due to an error |
731
-
732
- **Methods**
733
-
734
- - `pause(): Promise<void>` - Pauses the upload (platform support may vary)
735
- - `resume(): Promise<void>` - Resumes a paused upload
736
- - `stop(): Promise<void>` - Stops the upload and removes temporary data
737
- - `start(): void` - Starts the upload
738
-
739
- ### `Members`
740
- | Name | Type | Info |
741
- | -------------- | ------ | ---------------------------------------------------------------------------------------------------- |
742
- | `id` | String | The id you gave the task when calling `createDownloadTask` |
743
- | `metadata` | Record<string, unknown> | The metadata you gave the task when calling `createDownloadTask` |
744
- | `state` | 'PENDING' \| 'DOWNLOADING' \| 'PAUSED' \| 'DONE' \| 'FAILED' \| 'STOPPED' | Current state of the download task |
745
- | `bytesDownloaded` | Number | The number of bytes currently written by the task |
746
- | `bytesTotal` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded. **Note:** This value will be `-1` if the server does not provide a `Content-Length` header |
747
- | `downloadParams` | DownloadParams | The download parameters set for this task |
748
-
749
- ### `completeHandler(jobId: string)`
750
-
751
- Finishes download job and informs OS that app can be closed in background if needed.
752
- After finishing download in background you have some time to process your JS logic and finish the job.
753
-
754
- **Parameters:**
755
- - `jobId` (String) - The ID of the download task to complete
756
-
757
- **Note:** This should be called after processing your download in the `done` callback to properly signal completion to the OS.
758
-
759
- ### `Callback Methods`
760
- Use these methods to stay updated on what's happening with the task.
761
-
762
- All callback methods return the current instance of the `DownloadTask` for chaining.
763
-
764
- | Function | Callback Arguments | Info|
765
- | ---------- | --------------------------------- | ---- |
766
- | `begin` | `{ expectedBytes: number, headers: Record<string, string \| null> }` | Called when the first byte is received. ๐Ÿ’ก: this is good place to check if the device has enough storage space for this download |
767
- | `progress` | `{ bytesDownloaded: number, bytesTotal: number }` | Called based on progressInterval (default: every 1000ms) so you can update your progress bar accordingly. **Note:** `bytesTotal` will be `-1` if the server does not provide a `Content-Length` header |
768
- | `done` | `{ location: string, bytesDownloaded: number, bytesTotal: number }` | Called when the download is done, the file is at the destination you've set. `location` is the final file path. **Note:** `bytesTotal` will be `-1` if the server did not provide a `Content-Length` header |
769
- | `error` | `{ error: string, errorCode: number }` | Called when the download stops due to an error |
570
+ | Function | Description |
571
+ |----------|-------------|
572
+ | `createDownloadTask(options)` | Create a new download task |
573
+ | `createUploadTask(options)` | Create a new upload task |
574
+ | `getExistingDownloadTasks()` | Get downloads running in background |
575
+ | `getExistingUploadTasks()` | Get uploads running in background |
576
+ | `setConfig(config)` | Set global configuration |
577
+ | `completeHandler(jobId)` | Signal download completion to OS |
578
+ | `directories.documents` | Path to app's documents directory |
770
579
 
771
- ### `pause(): Promise<void>`
772
- Pauses the download. Returns a promise that resolves when the pause operation is complete.
580
+ ## Platform Notes
773
581
 
774
- **Note:** On Android, pause/resume is implemented using HTTP Range headers, which requires server support. The download progress is saved and resumed from where it left off.
582
+ For detailed platform-specific information, see **[Platform Notes](./docs/PLATFORM_NOTES.md)**.
775
583
 
776
- ### `resume(): Promise<void>`
777
- Resumes a paused download. Returns a promise that resolves when the resume operation is complete.
584
+ Key points:
585
+ - **iOS**: Uses `NSURLSession` for true background downloads
586
+ - **Android**: Uses `DownloadManager` + Foreground Services + MMKV
587
+ - **Pause/Resume**: Works on both platforms (Android requires server Range header support)
778
588
 
779
- **Note:** On Android, this uses HTTP Range headers to resume from the last downloaded byte position. If the server doesn't support range requests, the download will restart from the beginning.
589
+ ## Troubleshooting
780
590
 
781
- ### `stop(): Promise<void>`
782
- Stops the download for good and removes the file that was written so far. Returns a promise that resolves when the stop operation is complete.
591
+ <details>
592
+ <summary><strong>Download stuck in "pending" state (Android)</strong></summary>
783
593
 
784
- ## Constants
594
+ This can happen with slow-responding servers. The library automatically adds keep-alive headers, but you can also try:
595
+ - Increase timeout by setting custom headers
596
+ - Check if the server supports the download URL
597
+ - Enable debug logs to see what's happening: `setConfig({ isLogsEnabled: true })`
598
+ </details>
785
599
 
786
- ### directories
600
+ <details>
601
+ <summary><strong>Duplicate class errors with react-native-mmkv (Android)</strong></summary>
787
602
 
788
- ### `documents`
603
+ If you're using `react-native-mmkv`, you don't need to add the MMKV dependency manually - it's already included. The library uses `compileOnly` to avoid conflicts.
604
+ </details>
789
605
 
790
- An absolute path to the app's documents directory. It is recommended that you use this path as the target of downloaded files.
606
+ <details>
607
+ <summary><strong>EXC_BAD_ACCESS crash on iOS with react-native-mmkv</strong></summary>
791
608
 
792
- ## Platform-Specific Limitations
609
+ This was fixed in v4.4.0. Update to the latest version. If you're not using `react-native-mmkv`, add `pod 'MMKV', '>= 1.0.0'` to your Podfile.
610
+ </details>
793
611
 
794
- ### iOS MMKV Dependency
612
+ <details>
613
+ <summary><strong>Downloads not resuming after app restart</strong></summary>
795
614
 
796
- This library uses MMKV for persistent storage of download state on iOS. The MMKV dependency is **not** declared in the podspec to avoid conflicts with `react-native-mmkv`.
615
+ Make sure to call `getExistingDownloadTasks()` at app startup and re-attach your callbacks. The task IDs you provide are used to identify downloads across restarts.
616
+ </details>
797
617
 
798
- **If you're using `react-native-mmkv`:** No additional setup needed - `react-native-mmkv` already provides the required MMKV dependency (via MMKVCore pod).
618
+ <details>
619
+ <summary><strong>Google Play Console asking about Foreground Service</strong></summary>
799
620
 
800
- **If you're NOT using `react-native-mmkv`:** Add the MMKV dependency to your `ios/Podfile`:
621
+ See the [Google Play Console Declaration](#google-play-console-declaration) section for the required steps.
622
+ </details>
801
623
 
802
- ```ruby
803
- pod 'MMKV', '>= 1.0.0'
804
- ```
624
+ <details>
625
+ <summary><strong>TypeToken errors in release builds (Android)</strong></summary>
805
626
 
806
- Then run `cd ios && pod install`.
627
+ Add the Proguard rules mentioned in the [Proguard Rules](#proguard-rules) section.
628
+ </details>
807
629
 
808
- ### Android MMKV Dependency
630
+ ## Example App
809
631
 
810
- This library uses MMKV for persistent storage of download state on Android. The MMKV dependency is declared as `compileOnly`, meaning your app must provide it.
632
+ The repository includes a full example app demonstrating all features:
811
633
 
812
- **If you're using `react-native-mmkv`:** No additional setup needed - `react-native-mmkv` already provides the required MMKV dependency.
634
+ ```bash
635
+ cd example
636
+ yarn install
813
637
 
814
- **If you're NOT using `react-native-mmkv`:** Add the MMKV dependency to your app's `android/app/build.gradle`:
638
+ # iOS
639
+ cd ios && pod install && cd ..
640
+ yarn ios
815
641
 
816
- ```gradle
817
- dependencies {
818
- // ... other dependencies
819
- implementation 'com.tencent:mmkv-shared:2.2.4' // or newer
820
- }
642
+ # Android
643
+ yarn android
821
644
  ```
822
645
 
823
- **Note:** MMKV 2.0.0+ is required for Android 15+ support (16KB memory page sizes).
646
+ The example app shows:
647
+ - Starting multiple downloads
648
+ - Pause/resume functionality
649
+ - Progress tracking with animations
650
+ - Re-attaching to background tasks
651
+ - File management
824
652
 
825
- ### Android DownloadManager Limitations
653
+ ## Use Cases
826
654
 
827
- The Android implementation uses the system's `DownloadManager` service for downloads, with custom pause/resume support:
655
+ This library is perfect for apps that need reliable file transfers:
828
656
 
829
- #### Android 16+ User-Initiated Data Transfer (UIDT) Support
830
- - **Android 16 Compatibility**: Downloads are automatically marked as user-initiated data transfers on Android 16+ (API 36)
831
- - **What this fixes**: Prevents background downloads from being killed due to thermal throttling or job quota restrictions
832
- - **Requirements**: The library automatically includes the `RUN_USER_INITIATED_JOBS` permission and marks downloads as user-initiated when running on Android 16+
833
- - **No action needed**: This is handled automatically by the library - your downloads will continue reliably even under moderate thermal conditions (~40ยฐC) on Android 16+
657
+ - ๐ŸŽต **Music/Podcast Apps** - Download episodes for offline listening
658
+ - ๐Ÿ“š **E-book Readers** - Download books in the background
659
+ - ๐ŸŽฌ **Video Streaming** - Offline video downloads
660
+ - ๐Ÿ“ **File Managers** - Large file transfers
661
+ - ๐ŸŽฎ **Games** - Download game assets and updates
662
+ - ๐Ÿ“ฑ **Enterprise Apps** - Sync large documents and media
834
663
 
835
- #### Pause/Resume Support
836
- - **Implementation**: Pause/resume on Android is implemented using HTTP Range headers
837
- - **How it works**: When you pause a download, the current progress is saved. When resumed, a new download starts from where it left off using the `Range` header
838
- - **Server requirement**: The server must support HTTP Range requests for resume to work correctly. If the server doesn't support range requests, the download will restart from the beginning
839
- - **Temp files**: During pause/resume, progress is stored in a `.tmp` file which is renamed to the final destination upon completion
664
+ ## Migration Guide
840
665
 
841
- ### Google Play Console Declaration
666
+ Upgrading from an older version? Check the [Migration Guide](./MIGRATION.md) for detailed instructions:
842
667
 
843
- The library uses Foreground Service permissions (`FOREGROUND_SERVICE` and `FOREGROUND_SERVICE_DATA_SYNC`) to enable reliable background downloads. **Google Play requires you to declare foreground service usage in the Play Console** when publishing your app.
668
+ - [v4.3.x โ†’ v4.4.0](./MIGRATION.md#migration-guide-v43x--v440) - iOS MMKV dependency change
669
+ - [v4.1.x โ†’ v4.2.0](./MIGRATION.md#migration-guide-v41x--v420) - Android pause/resume support
670
+ - [v4.0.x โ†’ v4.1.0](./MIGRATION.md#migration-guide-v40x--v410) - MMKV dependency change
671
+ - [v3.2.6 โ†’ v4.0.0](./MIGRATION.md#migration-guide-v326--v400) - Major API changes
844
672
 
845
- If you see this error when submitting to Google Play:
673
+ See the [Changelog](./CHANGELOG.md) for a complete list of changes in each version.
846
674
 
847
- > "You must let us know whether your app uses any Foreground Service permissions."
675
+ ## Contributing
848
676
 
849
- Complete these steps in the Google Play Console:
677
+ Contributions are welcome! Please feel free to submit a Pull Request.
850
678
 
851
- 1. Go to your app in the [Google Play Console](https://play.google.com/console)
852
- 2. Navigate to **App content** โ†’ **Foreground Service**
853
- 3. Select **Yes** when asked if your app uses Foreground Service permissions
854
- 4. Choose **Data sync** as the Foreground Service type
855
- 5. Select **Network processing** as the task
856
- 6. Provide a justification explaining that your app downloads files in the background with a user-visible notification
679
+ 1. Fork the repository
680
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
681
+ 3. Install dependencies (`yarn install`)
682
+ 4. Make your changes
683
+ 5. Run tests (`yarn test`)
684
+ 6. Run linting (`yarn lint`)
685
+ 7. Commit your changes (`git commit -m 'Add amazing feature'`)
686
+ 8. Push to the branch (`git push origin feature/amazing-feature`)
687
+ 9. Open a Pull Request
857
688
 
858
- Example justification:
859
- > "This app downloads files in the background using a foreground service with a user-visible notification. The foreground service ensures downloads continue reliably when the app is in the background or when the device is under memory pressure. Users initiate downloads and can see download progress via the notification."
689
+ ### Development Setup
860
690
 
861
- This is a Play Console compliance step onlyโ€”no additional code changes are required.
862
-
863
- ## Rules for proguard-rules.pro
691
+ ```bash
692
+ # Install dependencies
693
+ yarn install
864
694
 
865
- If you encounter `java.lang.IllegalStateException: TypeToken must be created with a type argument: new TypeToken<...>()` in Android release builds, add these rules to your `proguard-rules.pro`:
695
+ # Run tests
696
+ yarn test
866
697
 
867
- ```
868
- # react-native-background-downloader - Keep config class used by Gson
869
- -keep class com.eko.RNBGDTaskConfig { *; }
698
+ # Run linting
699
+ yarn lint
870
700
 
871
- # Gson TypeToken support
872
- -keepattributes Signature
873
- -keep class com.google.gson.reflect.TypeToken { *; }
874
- -keep class * extends com.google.gson.reflect.TypeToken
701
+ # Build the Expo plugin
702
+ yarn build-plugin
875
703
 
876
- # MMKV
877
- -keep class com.tencent.mmkv.** { *; }
878
- -dontwarn com.tencent.mmkv.**
704
+ # Run the example app
705
+ cd example && yarn install
706
+ yarn ios # or yarn android
879
707
  ```
880
708
 
881
- ## Known Issues with New Architecture
882
- When using larger files with the New Architecture, you may encounter `ERROR_CANNOT_RESUME` (error code 1008). This is a known limitation of Android's DownloadManager, not specific to this library or the New Architecture. The error includes enhanced messaging to help diagnose the issue.
883
-
884
- **Workaround:** If you encounter this error frequently with large files, consider:
885
- 1. Breaking large downloads into smaller chunks
886
- 2. Implementing retry logic in your app
887
- 3. Using alternative download strategies for very large files
888
-
889
- The library now provides enhanced error handling for this specific case with detailed logging and cleanup.
890
-
891
- ## TODO
892
-
893
- - [ ] Write better API for downloads - current kinda boilerplate
894
-
895
709
  ## Authors
896
710
 
897
- Re-written & maintained by [Kesha Antonov](https://github.com/kesha-antonov)
711
+ Maintained by [Kesha Antonov](https://github.com/kesha-antonov)
898
712
 
899
- Originally developed by [Elad Gil](https://github.com/ptelad) of [Eko](https://github.com/ekolabs/react-native-background-downloader)
713
+ Based on [react-native-background-downloader](https://github.com/ekolabs/react-native-background-downloader) by [Elad Gil](https://github.com/ptelad) (unmaintained since 2019)
900
714
 
901
715
  ## License
902
- Apache 2
716
+
717
+ [Apache 2.0](./LICENSE)