@kesha-antonov/react-native-background-downloader 4.5.4 → 4.5.6

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/lib/types.d.ts ADDED
@@ -0,0 +1,249 @@
1
+ export type UnsafeObject = {
2
+ [key: string]: string;
3
+ };
4
+ export type Headers = Record<string, string | null>;
5
+ export type LogCallback = (tag: string, message: string, ...args: unknown[]) => void;
6
+ /**
7
+ * Configuration for notification text with pluralization support.
8
+ * Use {count} placeholder for the number of downloads.
9
+ */
10
+ export interface NotificationTexts {
11
+ /** Title for individual download notification (default: "Download") */
12
+ downloadTitle?: string;
13
+ /** Text shown when download is starting (default: "Starting download...") */
14
+ downloadStarting?: string;
15
+ /** Text pattern for download progress. Use {progress} for percentage (default: "Downloading... {progress}%") */
16
+ downloadProgress?: string;
17
+ /** Text shown when download is paused (default: "Paused") */
18
+ downloadPaused?: string;
19
+ /** Text shown when download is finished (default: "Download complete") */
20
+ downloadFinished?: string;
21
+ /** Title for group summary notification (default: "Downloads") */
22
+ groupTitle?: string;
23
+ /** Text pattern for group summary. Use {count} for number of downloads.
24
+ * Can be a string or function for pluralization (default: "{count} download(s) in progress") */
25
+ groupText?: string | ((count: number) => string);
26
+ }
27
+ /**
28
+ * Mode for notification display when grouping is enabled.
29
+ * - 'individual': Show all notifications (default, current behavior)
30
+ * - 'summaryOnly': Show only summary notification, minimize individual ones
31
+ */
32
+ export type NotificationGroupingMode = 'individual' | 'summaryOnly';
33
+ /**
34
+ * Configuration for notifications grouping.
35
+ */
36
+ export interface NotificationsGroupingConfig {
37
+ /** Enable notification grouping (default: false) */
38
+ enabled: boolean;
39
+ /** Mode for notification display (default: 'individual') */
40
+ mode?: NotificationGroupingMode;
41
+ /** Custom notification texts with optional pluralization */
42
+ texts?: NotificationTexts;
43
+ }
44
+ /**
45
+ * iOS Data Protection level applied to the downloaded file (iOS only, ignored on Android).
46
+ *
47
+ * On iOS, files protected with `complete` become unreadable/unwritable while the device
48
+ * is locked, which can make a background download fail to save when it finishes on a
49
+ * locked device. The default `completeUntilFirstUserAuthentication` lets the file be
50
+ * written while locked (after the first unlock since boot), which is what you almost
51
+ * always want for background downloads.
52
+ *
53
+ * Maps to the NSFileProtection* constants.
54
+ */
55
+ export type IosDataProtection = 'complete' | 'completeUnlessOpen' | 'completeUntilFirstUserAuthentication' | 'none';
56
+ export interface Config {
57
+ headers?: Headers;
58
+ progressInterval?: number;
59
+ progressMinBytes?: number;
60
+ isLogsEnabled?: boolean;
61
+ logCallback?: LogCallback;
62
+ maxParallelDownloads?: number;
63
+ allowsCellularAccess?: boolean;
64
+ /** Show download notifications (Android only, default: false). When false, creates minimal silent notifications (UIDT requires a notification) */
65
+ showNotificationsEnabled?: boolean;
66
+ /** Configuration for notifications grouping on Android */
67
+ notificationsGrouping?: NotificationsGroupingConfig;
68
+ /**
69
+ * Default iOS Data Protection level for downloaded files (iOS only).
70
+ * Default: 'completeUntilFirstUserAuthentication'. Can be overridden per task.
71
+ */
72
+ iosDataProtection?: IosDataProtection;
73
+ }
74
+ export type SetConfig = (config: Partial<Config>) => void;
75
+ export type BeginHandlerParams = {
76
+ expectedBytes: number;
77
+ headers: Headers;
78
+ };
79
+ export type BeginHandler = (params: BeginHandlerParams) => void;
80
+ export type ProgressHandlerParams = {
81
+ bytesDownloaded: number;
82
+ bytesTotal: number;
83
+ };
84
+ export type ProgressHandler = (params: ProgressHandlerParams) => void;
85
+ export type DoneHandlerParams = {
86
+ location: string;
87
+ bytesDownloaded: number;
88
+ bytesTotal: number;
89
+ };
90
+ export type DoneHandler = (params: DoneHandlerParams) => void;
91
+ export interface ErrorHandlerParams {
92
+ error: string;
93
+ errorCode: number;
94
+ }
95
+ export type ErrorHandler = (params: ErrorHandlerParams) => void;
96
+ export type Metadata = Record<string, unknown>;
97
+ export interface TaskInfoNative {
98
+ id: string;
99
+ metadata: Metadata;
100
+ bytesDownloaded: number;
101
+ bytesTotal: number;
102
+ errorCode: number;
103
+ state: number;
104
+ destination?: string | null;
105
+ }
106
+ export interface TaskInfo {
107
+ id: string;
108
+ metadata?: object;
109
+ }
110
+ export type DownloadTaskState = 'PENDING' | 'DOWNLOADING' | 'PAUSED' | 'DONE' | 'FAILED' | 'STOPPED';
111
+ export type DownloadParams = {
112
+ url: string;
113
+ destination: string;
114
+ headers?: Headers;
115
+ isAllowedOverRoaming?: boolean;
116
+ isAllowedOverMetered?: boolean;
117
+ maxRedirects?: number;
118
+ /** Group ID for notification grouping (only used when grouping is enabled) */
119
+ groupId?: string;
120
+ /** Group name displayed in notification (only used when grouping is enabled) */
121
+ groupName?: string;
122
+ /**
123
+ * iOS Data Protection level for this download's file (iOS only).
124
+ * Overrides the global `iosDataProtection` from setConfig.
125
+ */
126
+ iosDataProtection?: IosDataProtection;
127
+ };
128
+ export interface DownloadTask {
129
+ id: string;
130
+ state: DownloadTaskState;
131
+ metadata: Metadata;
132
+ errorCode: number;
133
+ bytesDownloaded: number;
134
+ bytesTotal: number;
135
+ downloadParams?: DownloadParams;
136
+ begin: (handler: BeginHandler) => DownloadTask;
137
+ progress: (handler: ProgressHandler) => DownloadTask;
138
+ done: (handler: DoneHandler) => DownloadTask;
139
+ error: (handler: ErrorHandler) => DownloadTask;
140
+ beginHandler?: BeginHandler;
141
+ progressHandler?: ProgressHandler;
142
+ doneHandler?: DoneHandler;
143
+ errorHandler?: ErrorHandler;
144
+ setDownloadParams: (params: DownloadParams) => void;
145
+ start: () => void;
146
+ pause: () => Promise<void>;
147
+ resume: () => Promise<void>;
148
+ stop: () => Promise<void>;
149
+ tryParseJson: (metadata?: string | Metadata) => Metadata | null;
150
+ }
151
+ export type getExistingDownloadTasks = () => Promise<DownloadTask[]>;
152
+ export interface DownloadOption {
153
+ id: string;
154
+ url: string;
155
+ destination: string;
156
+ headers?: Headers | undefined;
157
+ metadata?: object;
158
+ isAllowedOverRoaming?: boolean;
159
+ isAllowedOverMetered?: boolean;
160
+ }
161
+ export type Download = (options: DownloadOption) => DownloadTask;
162
+ export type CompleteHandler = (id: string) => void;
163
+ export interface Directories {
164
+ documents: string;
165
+ }
166
+ export type UploadBeginHandlerParams = {
167
+ expectedBytes: number;
168
+ };
169
+ export type UploadBeginHandler = (params: UploadBeginHandlerParams) => void;
170
+ export type UploadProgressHandlerParams = {
171
+ bytesUploaded: number;
172
+ bytesTotal: number;
173
+ };
174
+ export type UploadProgressHandler = (params: UploadProgressHandlerParams) => void;
175
+ export type UploadDoneHandlerParams = {
176
+ responseCode: number;
177
+ responseBody: string;
178
+ bytesUploaded: number;
179
+ bytesTotal: number;
180
+ };
181
+ export type UploadDoneHandler = (params: UploadDoneHandlerParams) => void;
182
+ export interface UploadErrorHandlerParams {
183
+ error: string;
184
+ errorCode: number;
185
+ }
186
+ export type UploadErrorHandler = (params: UploadErrorHandlerParams) => void;
187
+ export type UploadTaskState = 'PENDING' | 'UPLOADING' | 'PAUSED' | 'DONE' | 'FAILED' | 'STOPPED';
188
+ export interface UploadTaskInfoNative {
189
+ id: string;
190
+ metadata: Metadata;
191
+ bytesUploaded: number;
192
+ bytesTotal: number;
193
+ errorCode: number;
194
+ state: number;
195
+ }
196
+ export interface UploadTaskInfo {
197
+ id: string;
198
+ metadata?: object;
199
+ }
200
+ export type UploadParams = {
201
+ url: string;
202
+ source: string;
203
+ method?: 'POST' | 'PUT' | 'PATCH';
204
+ headers?: Headers;
205
+ fieldName?: string;
206
+ mimeType?: string;
207
+ parameters?: Record<string, string>;
208
+ isAllowedOverRoaming?: boolean;
209
+ isAllowedOverMetered?: boolean;
210
+ };
211
+ export interface UploadTask {
212
+ id: string;
213
+ state: UploadTaskState;
214
+ metadata: Metadata;
215
+ errorCode: number;
216
+ bytesUploaded: number;
217
+ bytesTotal: number;
218
+ uploadParams?: UploadParams;
219
+ begin: (handler: UploadBeginHandler) => UploadTask;
220
+ progress: (handler: UploadProgressHandler) => UploadTask;
221
+ done: (handler: UploadDoneHandler) => UploadTask;
222
+ error: (handler: UploadErrorHandler) => UploadTask;
223
+ beginHandler?: UploadBeginHandler;
224
+ progressHandler?: UploadProgressHandler;
225
+ doneHandler?: UploadDoneHandler;
226
+ errorHandler?: UploadErrorHandler;
227
+ setUploadParams: (params: UploadParams) => void;
228
+ start: () => void;
229
+ pause: () => Promise<void>;
230
+ resume: () => Promise<void>;
231
+ stop: () => Promise<void>;
232
+ tryParseJson: (metadata?: string | Metadata) => Metadata | null;
233
+ }
234
+ export type getExistingUploadTasks = () => Promise<UploadTask[]>;
235
+ export interface UploadOption {
236
+ id: string;
237
+ url: string;
238
+ source: string;
239
+ method?: 'POST' | 'PUT' | 'PATCH';
240
+ headers?: Headers | undefined;
241
+ metadata?: object;
242
+ fieldName?: string;
243
+ mimeType?: string;
244
+ parameters?: Record<string, string>;
245
+ isAllowedOverRoaming?: boolean;
246
+ isAllowedOverMetered?: boolean;
247
+ }
248
+ export type Upload = (options: UploadOption) => UploadTask;
249
+ export type CompleteUploadHandler = (id: string) => void;
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kesha-antonov/react-native-background-downloader",
3
- "version": "4.5.4",
3
+ "version": "4.5.6",
4
4
  "description": "A library for React-Native to help you download large files on iOS and Android both in the foreground and most importantly in the background.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -23,9 +23,9 @@
23
23
  "name": "Kesha Antonov, Elad Gil"
24
24
  }
25
25
  ],
26
- "main": "src/index.ts",
26
+ "main": "lib/index.js",
27
27
  "react-native": "src/index.ts",
28
- "types": "src/index.d.ts",
28
+ "types": "lib/index.d.ts",
29
29
  "expo": {
30
30
  "plugin": "./app.plugin.js"
31
31
  },
@@ -44,6 +44,7 @@
44
44
  "package.json",
45
45
  "app.plugin.js",
46
46
  "src/",
47
+ "lib/",
47
48
  "ios/",
48
49
  "android/build.gradle",
49
50
  "android/src/",
@@ -53,9 +54,11 @@
53
54
  "bump": "npm version patch",
54
55
  "lint": "eslint .",
55
56
  "typecheck": "tsc src/*.ts --noEmit --strict --skipLibCheck",
56
- "prepublishOnly": "npm run build-plugin && npm run typecheck && jest && npm run lint",
57
+ "build": "rm -rf lib && tsc -p tsconfig.build.json",
58
+ "prepare": "npm run build && npm run build-plugin",
59
+ "prepublishOnly": "npm run build-plugin && npm run build && npm run typecheck && jest && npm run lint",
57
60
  "build-plugin": "tsc plugin/src/index.ts --outDir plugin/build --target ES2018 --module commonjs --declaration --esModuleInterop --strict --skipLibCheck",
58
- "publish": "npm publish",
61
+ "release": "npm publish",
59
62
  "test": "jest"
60
63
  },
61
64
  "lint-staged": {
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@kesha-antonov/react-native-background-downloader-expo-plugin",
3
+ "version": "1.0.0",
4
+ "main": "build/index.js",
5
+ "types": "build/index.d.ts"
6
+ }
@@ -20,8 +20,14 @@ Pod::Spec.new do |s|
20
20
  install_modules_dependencies(s)
21
21
 
22
22
  # MMKV is used for persistent download state storage on iOS
23
- # Using MMKV (Objective-C wrapper) which depends on MMKVCore
24
- s.dependency 'MMKV'
23
+ # Using MMKV (Objective-C wrapper) which depends on MMKVCore.
24
+ # We only use MMKV's basic key/value APIs (initializeMMKV:, mmkvWithID:,
25
+ # getDataForKey:/setData:forKey:, typed getters/setters) which have been stable
26
+ # since MMKV 1.x, so we require only that minimum. CocoaPods is still free to
27
+ # resolve a newer MMKV when another pod (e.g. react-native-mmkv, which pins an
28
+ # exact MMKVCore) requires one - we just don't impose our own higher floor.
29
+ # See https://github.com/kesha-antonov/react-native-background-downloader/issues/162
30
+ s.dependency 'MMKV', '>= 1.2.0'
25
31
 
26
32
  # Enable codegen for new architecture
27
33
  if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
@@ -192,6 +192,8 @@ export class DownloadTask {
192
192
  headers: this.headersToUnsafeObject(this.downloadParams.headers),
193
193
  isAllowedOverRoaming: this.downloadParams.isAllowedOverRoaming ?? false,
194
194
  isAllowedOverMetered: this.downloadParams.isAllowedOverMetered ?? false,
195
+ // iOS-only; ignored on Android. Falls back to the global setConfig default.
196
+ iosDataProtection: this.downloadParams.iosDataProtection ?? config.iosDataProtection,
195
197
  })
196
198
  }
197
199
 
@@ -78,6 +78,8 @@ export interface Spec extends TurboModule {
78
78
  isAllowedOverRoaming: boolean
79
79
  isAllowedOverMetered: boolean
80
80
  maxRedirects?: number
81
+ /** iOS only: NSFileProtection level for the saved file. Ignored on Android. */
82
+ iosDataProtection?: string
81
83
  }): void
82
84
 
83
85
  pauseTask(id: string): Promise<void>
package/src/config.ts CHANGED
@@ -1,9 +1,12 @@
1
- import { Headers, NotificationGroupingMode, NotificationsGroupingConfig, NotificationTexts } from './types'
1
+ import { Headers, IosDataProtection, NotificationGroupingMode, NotificationsGroupingConfig, NotificationTexts } from './types'
2
2
 
3
3
  export const DEFAULT_PROGRESS_INTERVAL = 1000
4
4
  export const DEFAULT_PROGRESS_MIN_BYTES = 1024 * 1024 // 1MB
5
5
  export const DEFAULT_MAX_PARALLEL_DOWNLOADS = 4
6
6
  export const DEFAULT_ALLOWS_CELLULAR_ACCESS = true
7
+ // Lets a background download save its file even while the device is locked
8
+ // (after the first unlock since boot). See the iOS background-download notes in the README.
9
+ export const DEFAULT_IOS_DATA_PROTECTION: IosDataProtection = 'completeUntilFirstUserAuthentication'
7
10
 
8
11
  // Default notification texts
9
12
  export const DEFAULT_NOTIFICATION_TEXTS: Required<NotificationTexts> = {
@@ -29,6 +32,7 @@ interface ConfigState {
29
32
  allowsCellularAccess: boolean
30
33
  showNotificationsEnabled: boolean
31
34
  notificationsGrouping: NotificationsGroupingConfig & { mode: NotificationGroupingMode }
35
+ iosDataProtection: IosDataProtection
32
36
  }
33
37
 
34
38
  export const config: ConfigState = {
@@ -40,6 +44,7 @@ export const config: ConfigState = {
40
44
  maxParallelDownloads: DEFAULT_MAX_PARALLEL_DOWNLOADS,
41
45
  allowsCellularAccess: DEFAULT_ALLOWS_CELLULAR_ACCESS,
42
46
  showNotificationsEnabled: false,
47
+ iosDataProtection: DEFAULT_IOS_DATA_PROTECTION,
43
48
  notificationsGrouping: {
44
49
  enabled: false,
45
50
  mode: 'individual',
package/src/index.ts CHANGED
@@ -372,9 +372,13 @@ export function setConfig ({
372
372
  allowsCellularAccess,
373
373
  showNotificationsEnabled,
374
374
  notificationsGrouping,
375
+ iosDataProtection,
375
376
  }: Config) {
376
377
  config.headers = headers
377
378
 
379
+ if (iosDataProtection !== undefined)
380
+ config.iosDataProtection = iosDataProtection
381
+
378
382
  if (progressInterval >= MIN_PROGRESS_INTERVAL)
379
383
  config.progressInterval = progressInterval
380
384
  else
package/src/types.ts CHANGED
@@ -47,6 +47,23 @@ export interface NotificationsGroupingConfig {
47
47
  texts?: NotificationTexts
48
48
  }
49
49
 
50
+ /**
51
+ * iOS Data Protection level applied to the downloaded file (iOS only, ignored on Android).
52
+ *
53
+ * On iOS, files protected with `complete` become unreadable/unwritable while the device
54
+ * is locked, which can make a background download fail to save when it finishes on a
55
+ * locked device. The default `completeUntilFirstUserAuthentication` lets the file be
56
+ * written while locked (after the first unlock since boot), which is what you almost
57
+ * always want for background downloads.
58
+ *
59
+ * Maps to the NSFileProtection* constants.
60
+ */
61
+ export type IosDataProtection =
62
+ | 'complete'
63
+ | 'completeUnlessOpen'
64
+ | 'completeUntilFirstUserAuthentication'
65
+ | 'none'
66
+
50
67
  export interface Config {
51
68
  headers?: Headers
52
69
  progressInterval?: number
@@ -59,6 +76,11 @@ export interface Config {
59
76
  showNotificationsEnabled?: boolean
60
77
  /** Configuration for notifications grouping on Android */
61
78
  notificationsGrouping?: NotificationsGroupingConfig
79
+ /**
80
+ * Default iOS Data Protection level for downloaded files (iOS only).
81
+ * Default: 'completeUntilFirstUserAuthentication'. Can be overridden per task.
82
+ */
83
+ iosDataProtection?: IosDataProtection
62
84
  }
63
85
 
64
86
  export type SetConfig = (config: Partial<Config>) => void
@@ -129,6 +151,11 @@ export type DownloadParams = {
129
151
  groupId?: string
130
152
  /** Group name displayed in notification (only used when grouping is enabled) */
131
153
  groupName?: string
154
+ /**
155
+ * iOS Data Protection level for this download's file (iOS only).
156
+ * Overrides the global `iosDataProtection` from setConfig.
157
+ */
158
+ iosDataProtection?: IosDataProtection
132
159
  }
133
160
 
134
161
  export interface DownloadTask {