@ammarahmed/react-native-upload 6.31.0 → 6.32.0
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/ios/RNFileUploader.m +315 -202
- package/package.json +1 -1
package/ios/RNFileUploader.m
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#import <Foundation/Foundation.h>
|
|
2
2
|
#import <MobileCoreServices/MobileCoreServices.h>
|
|
3
|
-
#import <React/RCTEventEmitter.h>
|
|
4
|
-
#import <React/RCTBridgeModule.h>
|
|
5
3
|
#import <Photos/Photos.h>
|
|
4
|
+
#import <React/RCTBridgeModule.h>
|
|
5
|
+
#import <React/RCTEventEmitter.h>
|
|
6
6
|
|
|
7
7
|
#import "RNFileUploader.h"
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
RCT_EXPORT_MODULE();
|
|
12
12
|
|
|
13
13
|
@synthesize bridge = _bridge;
|
|
14
|
-
static RNFileUploader*
|
|
14
|
+
static RNFileUploader *staticInstance = nil;
|
|
15
15
|
static NSString *BACKGROUND_SESSION_ID = @"ReactNativeBackgroundUpload";
|
|
16
16
|
NSMutableDictionary *_responsesData;
|
|
17
17
|
NSURLSession *_urlSession = nil;
|
|
@@ -22,12 +22,11 @@ BOOL limitNetwork = NO;
|
|
|
22
22
|
return YES;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
- (dispatch_queue_t)methodQueue
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
25
|
+
- (dispatch_queue_t)methodQueue {
|
|
26
|
+
return dispatch_get_main_queue();
|
|
27
|
+
}
|
|
29
28
|
|
|
30
|
-
-(id)
|
|
29
|
+
- (id)init {
|
|
31
30
|
self = [super init];
|
|
32
31
|
if (self) {
|
|
33
32
|
staticInstance = self;
|
|
@@ -37,84 +36,98 @@ BOOL limitNetwork = NO;
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
- (void)_sendEventWithName:(NSString *)eventName body:(id)body {
|
|
40
|
-
if (staticInstance == nil)
|
|
39
|
+
if (staticInstance == nil)
|
|
40
|
+
return;
|
|
41
41
|
[staticInstance sendEventWithName:eventName body:body];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
- (NSArray<NSString *> *)supportedEvents {
|
|
45
45
|
return @[
|
|
46
|
-
@"RNFileUploader-progress",
|
|
47
|
-
@"RNFileUploader-
|
|
48
|
-
@"RNFileUploader-cancelled",
|
|
49
|
-
@"RNFileUploader-completed"
|
|
46
|
+
@"RNFileUploader-progress", @"RNFileUploader-error",
|
|
47
|
+
@"RNFileUploader-cancelled", @"RNFileUploader-completed"
|
|
50
48
|
];
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
- (void)startObserving {
|
|
54
|
-
// JS side is ready to receive events; create the background url session if
|
|
55
|
-
// iOS will then deliver the tasks completed while the app was
|
|
52
|
+
// JS side is ready to receive events; create the background url session if
|
|
53
|
+
// necessary iOS will then deliver the tasks completed while the app was
|
|
54
|
+
// dead (if any)
|
|
56
55
|
NSString *appGroup = nil;
|
|
57
56
|
double delayInSeconds = 5;
|
|
58
|
-
dispatch_time_t popTime =
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
dispatch_time_t popTime =
|
|
58
|
+
dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
|
|
59
|
+
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|
60
|
+
[self urlSession:appGroup];
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
+ (void)setCompletionHandlerWithIdentifier:
|
|
64
|
+
+ (void)setCompletionHandlerWithIdentifier:(NSString *)identifier
|
|
65
|
+
completionHandler:(void (^)())completionHandler {
|
|
65
66
|
if ([BACKGROUND_SESSION_ID isEqualToString:identifier]) {
|
|
66
67
|
backgroundSessionCompletionHandler = completionHandler;
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/*
|
|
71
|
-
Gets file information for the path specified. Example valid path is:
|
|
72
|
-
|
|
72
|
+
Gets file information for the path specified. Example valid path is:
|
|
73
|
+
file:///var/mobile/Containers/Data/Application/3C8A0EFB-A316-45C0-A30A-761BF8CCF2F8/tmp/trim.A5F76017-14E9-4890-907E-36A045AF9436.MOV
|
|
74
|
+
Returns an object such as: {mimeType: "video/quicktime", size: 2569900, exists:
|
|
75
|
+
true, name: "trim.AF9A9225-FC37-416B-A25B-4EDB8275A625.MOV", extension: "MOV"}
|
|
73
76
|
*/
|
|
74
|
-
RCT_EXPORT_METHOD(getFileInfo:(NSString *)path resolve:
|
|
75
|
-
{
|
|
77
|
+
RCT_EXPORT_METHOD(getFileInfo : (NSString *)path resolve : (
|
|
78
|
+
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
76
79
|
@try {
|
|
77
80
|
// Escape non latin characters in filename
|
|
78
|
-
NSString *escapedPath =
|
|
79
|
-
|
|
81
|
+
NSString *escapedPath =
|
|
82
|
+
[path stringByAddingPercentEncodingWithAllowedCharacters:
|
|
83
|
+
NSCharacterSet.URLQueryAllowedCharacterSet];
|
|
84
|
+
|
|
80
85
|
NSURL *fileUri = [NSURL URLWithString:escapedPath];
|
|
81
86
|
NSString *pathWithoutProtocol = [fileUri path];
|
|
82
87
|
NSString *name = [fileUri lastPathComponent];
|
|
83
88
|
NSString *extension = [name pathExtension];
|
|
84
|
-
bool exists = [[NSFileManager defaultManager]
|
|
85
|
-
|
|
89
|
+
bool exists = [[NSFileManager defaultManager]
|
|
90
|
+
fileExistsAtPath:pathWithoutProtocol];
|
|
91
|
+
NSMutableDictionary *params = [NSMutableDictionary
|
|
92
|
+
dictionaryWithObjectsAndKeys:name, @"name", nil];
|
|
86
93
|
[params setObject:extension forKey:@"extension"];
|
|
87
94
|
[params setObject:[NSNumber numberWithBool:exists] forKey:@"exists"];
|
|
88
95
|
|
|
89
|
-
if (exists)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
NSError*
|
|
93
|
-
NSDictionary<NSFileAttributeKey, id> *attributes =
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
if (exists) {
|
|
97
|
+
[params setObject:[self guessMIMETypeFromFileName:name]
|
|
98
|
+
forKey:@"mimeType"];
|
|
99
|
+
NSError *error;
|
|
100
|
+
NSDictionary<NSFileAttributeKey, id> *attributes =
|
|
101
|
+
[[NSFileManager defaultManager]
|
|
102
|
+
attributesOfItemAtPath:pathWithoutProtocol
|
|
103
|
+
error:&error];
|
|
104
|
+
if (error == nil) {
|
|
96
105
|
unsigned long long fileSize = [attributes fileSize];
|
|
97
|
-
[params setObject:[NSNumber numberWithLong:fileSize]
|
|
106
|
+
[params setObject:[NSNumber numberWithLong:fileSize]
|
|
107
|
+
forKey:@"size"];
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
resolve(params);
|
|
101
|
-
}
|
|
102
|
-
@catch (NSException *exception) {
|
|
111
|
+
} @catch (NSException *exception) {
|
|
103
112
|
reject(@"RN Uploader", exception.name, nil);
|
|
104
113
|
}
|
|
105
114
|
}
|
|
106
115
|
|
|
107
116
|
/*
|
|
108
|
-
Borrowed from
|
|
117
|
+
Borrowed from
|
|
118
|
+
http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
|
|
109
119
|
*/
|
|
110
|
-
- (NSString *)guessMIMETypeFromFileName:
|
|
111
|
-
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
- (NSString *)guessMIMETypeFromFileName:(NSString *)fileName {
|
|
121
|
+
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(
|
|
122
|
+
kUTTagClassFilenameExtension,
|
|
123
|
+
(__bridge CFStringRef)[fileName pathExtension], NULL);
|
|
124
|
+
CFStringRef MIMEType =
|
|
125
|
+
UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
|
|
126
|
+
|
|
114
127
|
if (UTI) {
|
|
115
128
|
CFRelease(UTI);
|
|
116
129
|
}
|
|
117
|
-
|
|
130
|
+
|
|
118
131
|
if (!MIMEType) {
|
|
119
132
|
return @"application/octet-stream";
|
|
120
133
|
}
|
|
@@ -122,33 +135,48 @@ RCT_EXPORT_METHOD(getFileInfo:(NSString *)path resolve:(RCTPromiseResolveBlock)r
|
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
/*
|
|
125
|
-
Utility method to copy a PHAsset file into a local temp file, which can then be
|
|
138
|
+
Utility method to copy a PHAsset file into a local temp file, which can then be
|
|
139
|
+
uploaded.
|
|
126
140
|
*/
|
|
127
|
-
- (void)copyAssetToFile:
|
|
141
|
+
- (void)copyAssetToFile:(NSString *)assetUrl
|
|
142
|
+
completionHandler:(void (^)(NSString *__nullable tempFileUrl,
|
|
143
|
+
NSError *__nullable error))completionHandler {
|
|
128
144
|
NSURL *url = [NSURL URLWithString:assetUrl];
|
|
129
|
-
PHAsset *asset =
|
|
145
|
+
PHAsset *asset =
|
|
146
|
+
[PHAsset fetchAssetsWithALAssetURLs:@[ url ] options:nil].lastObject;
|
|
130
147
|
if (!asset) {
|
|
131
|
-
NSMutableDictionary*
|
|
132
|
-
[details
|
|
133
|
-
|
|
148
|
+
NSMutableDictionary *details = [NSMutableDictionary dictionary];
|
|
149
|
+
[details
|
|
150
|
+
setValue:
|
|
151
|
+
@"Asset could not be fetched. Are you missing permissions?"
|
|
152
|
+
forKey:NSLocalizedDescriptionKey];
|
|
153
|
+
completionHandler(nil, [NSError errorWithDomain:@"RNUploader"
|
|
154
|
+
code:5
|
|
155
|
+
userInfo:details]);
|
|
134
156
|
return;
|
|
135
157
|
}
|
|
136
|
-
PHAssetResource *assetResource =
|
|
137
|
-
|
|
158
|
+
PHAssetResource *assetResource =
|
|
159
|
+
[[PHAssetResource assetResourcesForAsset:asset] firstObject];
|
|
160
|
+
NSString *pathToWrite = [NSTemporaryDirectory()
|
|
161
|
+
stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
|
|
138
162
|
NSURL *pathUrl = [NSURL fileURLWithPath:pathToWrite];
|
|
139
163
|
NSString *fileURI = pathUrl.absoluteString;
|
|
140
164
|
|
|
141
|
-
PHAssetResourceRequestOptions *options =
|
|
165
|
+
PHAssetResourceRequestOptions *options =
|
|
166
|
+
[PHAssetResourceRequestOptions new];
|
|
142
167
|
options.networkAccessAllowed = YES;
|
|
143
168
|
|
|
144
|
-
[[PHAssetResourceManager defaultManager]
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
169
|
+
[[PHAssetResourceManager defaultManager]
|
|
170
|
+
writeDataForAssetResource:assetResource
|
|
171
|
+
toFile:pathUrl
|
|
172
|
+
options:options
|
|
173
|
+
completionHandler:^(NSError *_Nullable e) {
|
|
174
|
+
if (e == nil) {
|
|
175
|
+
completionHandler(fileURI, nil);
|
|
176
|
+
} else {
|
|
177
|
+
completionHandler(nil, e);
|
|
178
|
+
}
|
|
179
|
+
}];
|
|
152
180
|
}
|
|
153
181
|
|
|
154
182
|
/*
|
|
@@ -162,8 +190,8 @@ RCT_EXPORT_METHOD(getFileInfo:(NSString *)path resolve:(RCTPromiseResolveBlock)r
|
|
|
162
190
|
*
|
|
163
191
|
* Returns a promise with the string ID of the upload.
|
|
164
192
|
*/
|
|
165
|
-
RCT_EXPORT_METHOD(startUpload:(NSDictionary *)options resolve:
|
|
166
|
-
{
|
|
193
|
+
RCT_EXPORT_METHOD(startUpload : (NSDictionary *)options resolve : (
|
|
194
|
+
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
167
195
|
NSString *uploadUrl = options[@"url"];
|
|
168
196
|
__block NSString *fileURI = options[@"path"];
|
|
169
197
|
NSString *method = options[@"method"] ?: @"POST";
|
|
@@ -175,68 +203,89 @@ RCT_EXPORT_METHOD(startUpload:(NSDictionary *)options resolve:(RCTPromiseResolve
|
|
|
175
203
|
NSDictionary *parameters = options[@"parameters"];
|
|
176
204
|
|
|
177
205
|
@try {
|
|
178
|
-
NSURL *requestUrl = [NSURL URLWithString:
|
|
206
|
+
NSURL *requestUrl = [NSURL URLWithString:uploadUrl];
|
|
179
207
|
if (requestUrl == nil) {
|
|
180
|
-
return reject(@"RN Uploader", @"URL not compliant with RFC 2396",
|
|
208
|
+
return reject(@"RN Uploader", @"URL not compliant with RFC 2396",
|
|
209
|
+
nil);
|
|
181
210
|
}
|
|
182
211
|
|
|
183
|
-
NSMutableURLRequest *request =
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
NSMutableURLRequest *request =
|
|
213
|
+
[NSMutableURLRequest requestWithURL:requestUrl];
|
|
214
|
+
[request setHTTPMethod:method];
|
|
215
|
+
|
|
216
|
+
[headers enumerateKeysAndObjectsUsingBlock:^(
|
|
217
|
+
id _Nonnull key, id _Nonnull val, BOOL *_Nonnull stop) {
|
|
218
|
+
if ([val respondsToSelector:@selector(stringValue)]) {
|
|
219
|
+
val = [val stringValue];
|
|
220
|
+
}
|
|
221
|
+
if ([val isKindOfClass:[NSString class]]) {
|
|
222
|
+
[request setValue:val forHTTPHeaderField:key];
|
|
223
|
+
}
|
|
193
224
|
}];
|
|
194
225
|
|
|
195
|
-
|
|
196
|
-
//
|
|
226
|
+
// asset library files have to be copied over to a temp file. they
|
|
227
|
+
// can't be uploaded directly
|
|
197
228
|
if ([fileURI hasPrefix:@"assets-library"]) {
|
|
198
229
|
dispatch_group_t group = dispatch_group_create();
|
|
199
230
|
dispatch_group_enter(group);
|
|
200
|
-
[self copyAssetToFile:fileURI
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
231
|
+
[self copyAssetToFile:fileURI
|
|
232
|
+
completionHandler:^(NSString *_Nullable tempFileUrl,
|
|
233
|
+
NSError *_Nullable error) {
|
|
234
|
+
if (error) {
|
|
235
|
+
dispatch_group_leave(group);
|
|
236
|
+
reject(@"RN Uploader",
|
|
237
|
+
@"Asset could not be copied to temp file.", nil);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
fileURI = tempFileUrl;
|
|
241
|
+
dispatch_group_leave(group);
|
|
242
|
+
}];
|
|
209
243
|
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
|
210
244
|
}
|
|
211
245
|
|
|
212
|
-
NSString *uploadId =
|
|
246
|
+
NSString *uploadId =
|
|
247
|
+
customUploadId ? customUploadId : [[NSUUID UUID] UUIDString];
|
|
213
248
|
NSURLSessionUploadTask *uploadTask;
|
|
214
249
|
|
|
215
250
|
if ([uploadType isEqualToString:@"multipart"]) {
|
|
216
251
|
NSString *uuidStr = [[NSUUID UUID] UUIDString];
|
|
217
|
-
[request setValue:[NSString stringWithFormat
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
252
|
+
[request setValue:[NSString stringWithFormat:
|
|
253
|
+
@"multipart/form-data; boundary=%@",
|
|
254
|
+
uuidStr]
|
|
255
|
+
forHTTPHeaderField:@"Content-Type"];
|
|
256
|
+
|
|
257
|
+
NSData *multipartData = [self createBodyWithBoundary:uuidStr
|
|
258
|
+
path:fileURI
|
|
259
|
+
parameters:parameters
|
|
260
|
+
fieldName:fieldName];
|
|
261
|
+
|
|
262
|
+
NSURL *multipartDataFileUrl = [NSURL
|
|
263
|
+
fileURLWithPath:[NSString
|
|
264
|
+
stringWithFormat:@"%@/%@",
|
|
265
|
+
[self getTmpDirectory],
|
|
266
|
+
uploadId]];
|
|
222
267
|
[multipartData writeToURL:multipartDataFileUrl atomically:YES];
|
|
223
268
|
|
|
224
|
-
uploadTask = [[self urlSession:
|
|
269
|
+
uploadTask = [[self urlSession:appGroup]
|
|
270
|
+
uploadTaskWithRequest:request
|
|
271
|
+
fromFile:multipartDataFileUrl];
|
|
225
272
|
} else {
|
|
226
273
|
if (parameters.count > 0) {
|
|
227
|
-
reject(@"RN Uploader",
|
|
274
|
+
reject(@"RN Uploader",
|
|
275
|
+
@"Parameters supported only in multipart type", nil);
|
|
228
276
|
return;
|
|
229
277
|
}
|
|
230
278
|
|
|
231
|
-
uploadTask = [[self urlSession:
|
|
279
|
+
uploadTask = [[self urlSession:appGroup]
|
|
280
|
+
uploadTaskWithRequest:request
|
|
281
|
+
fromFile:[NSURL URLWithString:fileURI]];
|
|
232
282
|
}
|
|
233
283
|
|
|
234
284
|
uploadTask.taskDescription = uploadId;
|
|
235
285
|
|
|
236
286
|
[uploadTask resume];
|
|
237
287
|
resolve(uploadTask.taskDescription);
|
|
238
|
-
}
|
|
239
|
-
@catch (NSException *exception) {
|
|
288
|
+
} @catch (NSException *exception) {
|
|
240
289
|
reject(@"RN Uploader", exception.name, nil);
|
|
241
290
|
}
|
|
242
291
|
}
|
|
@@ -246,14 +295,22 @@ RCT_EXPORT_METHOD(startUpload:(NSDictionary *)options resolve:(RCTPromiseResolve
|
|
|
246
295
|
* Accepts upload ID as a first argument, this upload will be cancelled
|
|
247
296
|
* Event "cancelled" will be fired when upload is cancelled.
|
|
248
297
|
*/
|
|
249
|
-
RCT_EXPORT_METHOD(cancelUpload: (NSString *)cancelUploadId resolve:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
298
|
+
RCT_EXPORT_METHOD(cancelUpload : (NSString *)cancelUploadId resolve : (
|
|
299
|
+
RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
300
|
+
if (_urlSession == nil) {
|
|
301
|
+
resolve([NSNumber numberWithBool:NO]);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
[_urlSession getTasksWithCompletionHandler:^(NSArray *dataTasks,
|
|
305
|
+
NSArray *uploadTasks,
|
|
306
|
+
NSArray *downloadTasks) {
|
|
307
|
+
for (NSURLSessionTask *uploadTask in uploadTasks) {
|
|
308
|
+
if ([uploadTask.taskDescription isEqualToString:cancelUploadId]) {
|
|
309
|
+
// == checks if references are equal, while isEqualToString checks
|
|
310
|
+
// the string value
|
|
311
|
+
[uploadTask cancel];
|
|
312
|
+
}
|
|
313
|
+
}
|
|
257
314
|
}];
|
|
258
315
|
resolve([NSNumber numberWithBool:YES]);
|
|
259
316
|
}
|
|
@@ -265,93 +322,125 @@ RCT_EXPORT_METHOD(canSuspendIfBackground) {
|
|
|
265
322
|
}
|
|
266
323
|
}
|
|
267
324
|
|
|
268
|
-
RCT_EXPORT_METHOD(shouldLimitNetwork: (BOOL)
|
|
269
|
-
limitNetwork = limit;
|
|
270
|
-
}
|
|
325
|
+
RCT_EXPORT_METHOD(shouldLimitNetwork : (BOOL)limit) { limitNetwork = limit; }
|
|
271
326
|
|
|
272
|
-
RCT_EXPORT_METHOD(getAllUploads:(RCTPromiseResolveBlock)
|
|
273
|
-
|
|
274
|
-
{
|
|
327
|
+
RCT_EXPORT_METHOD(getAllUploads : (RCTPromiseResolveBlock)
|
|
328
|
+
resolve reject : (RCTPromiseRejectBlock)reject) {
|
|
275
329
|
NSString *appGroup = nil;
|
|
276
|
-
[[self urlSession:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
330
|
+
[[self urlSession:appGroup]
|
|
331
|
+
getTasksWithCompletionHandler:^(
|
|
332
|
+
NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
|
|
333
|
+
NSMutableArray *uploads = [NSMutableArray new];
|
|
334
|
+
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
|
|
335
|
+
NSString *state = @"unknown"; // Default initialization
|
|
336
|
+
switch (uploadTask.state) {
|
|
337
|
+
case NSURLSessionTaskStateRunning:
|
|
338
|
+
state = @"running";
|
|
339
|
+
break;
|
|
340
|
+
case NSURLSessionTaskStateSuspended:
|
|
341
|
+
state = @"pending";
|
|
342
|
+
break;
|
|
343
|
+
case NSURLSessionTaskStateCanceling:
|
|
344
|
+
state = @"cancelled";
|
|
345
|
+
break;
|
|
346
|
+
case NSURLSessionTaskStateCompleted:
|
|
347
|
+
state = @"completed";
|
|
348
|
+
break;
|
|
349
|
+
default: // Handle unexpected states
|
|
350
|
+
state = @"unknown";
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
NSDictionary *upload = @{
|
|
355
|
+
@"id" : uploadTask.taskDescription ?: @"", // Null safety
|
|
356
|
+
@"state" : state
|
|
357
|
+
};
|
|
358
|
+
[uploads addObject:upload];
|
|
359
|
+
}
|
|
360
|
+
resolve(uploads);
|
|
361
|
+
}];
|
|
303
362
|
}
|
|
304
363
|
|
|
305
364
|
- (NSData *)createBodyWithBoundary:(NSString *)boundary
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
365
|
+
path:(NSString *)path
|
|
366
|
+
parameters:(NSDictionary *)parameters
|
|
367
|
+
fieldName:(NSString *)fieldName {
|
|
309
368
|
|
|
310
369
|
NSMutableData *httpBody = [NSMutableData data];
|
|
311
370
|
|
|
312
371
|
// Escape non latin characters in filename
|
|
313
|
-
NSString *escapedPath =
|
|
372
|
+
NSString *escapedPath =
|
|
373
|
+
[path stringByAddingPercentEncodingWithAllowedCharacters:
|
|
374
|
+
NSCharacterSet.URLQueryAllowedCharacterSet];
|
|
314
375
|
|
|
315
376
|
// resolve path
|
|
316
|
-
NSURL *fileUri = [NSURL URLWithString:
|
|
317
|
-
|
|
318
|
-
NSError*
|
|
319
|
-
NSData *data = [NSData dataWithContentsOfURL:fileUri
|
|
377
|
+
NSURL *fileUri = [NSURL URLWithString:escapedPath];
|
|
378
|
+
|
|
379
|
+
NSError *error = nil;
|
|
380
|
+
NSData *data = [NSData dataWithContentsOfURL:fileUri
|
|
381
|
+
options:NSDataReadingMappedAlways
|
|
382
|
+
error:&error];
|
|
320
383
|
|
|
321
384
|
if (data == nil) {
|
|
322
385
|
NSLog(@"Failed to read file %@", error);
|
|
323
386
|
}
|
|
324
387
|
|
|
325
|
-
NSString *filename
|
|
326
|
-
NSString *mimetype
|
|
327
|
-
|
|
328
|
-
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey,
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
388
|
+
NSString *filename = [path lastPathComponent];
|
|
389
|
+
NSString *mimetype = [self guessMIMETypeFromFileName:path];
|
|
390
|
+
|
|
391
|
+
[parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey,
|
|
392
|
+
NSString *parameterValue,
|
|
393
|
+
BOOL *stop) {
|
|
394
|
+
[httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
|
395
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
396
|
+
[httpBody
|
|
397
|
+
appendData:[[NSString
|
|
398
|
+
stringWithFormat:@"Content-Disposition: form-data; "
|
|
399
|
+
@"name=\"%@\"\r\n\r\n",
|
|
400
|
+
parameterKey]
|
|
401
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
402
|
+
[httpBody
|
|
403
|
+
appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue]
|
|
404
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
332
405
|
}];
|
|
333
406
|
|
|
334
|
-
[httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
|
335
|
-
|
|
336
|
-
[httpBody
|
|
407
|
+
[httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
|
|
408
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
409
|
+
[httpBody
|
|
410
|
+
appendData:[[NSString
|
|
411
|
+
stringWithFormat:@"Content-Disposition: form-data; "
|
|
412
|
+
@"name=\"%@\"; filename=\"%@\"\r\n",
|
|
413
|
+
fieldName, filename]
|
|
414
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
415
|
+
[httpBody
|
|
416
|
+
appendData:[[NSString
|
|
417
|
+
stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype]
|
|
418
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
337
419
|
[httpBody appendData:data];
|
|
338
420
|
[httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
339
421
|
|
|
340
|
-
[httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
|
|
422
|
+
[httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
|
|
423
|
+
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
341
424
|
|
|
342
425
|
return httpBody;
|
|
343
426
|
}
|
|
344
427
|
|
|
345
|
-
- (NSURLSession *)urlSession:
|
|
428
|
+
- (NSURLSession *)urlSession:(NSString *)groupId {
|
|
346
429
|
if (_urlSession == nil) {
|
|
347
|
-
NSURLSessionConfiguration *sessionConfiguration =
|
|
430
|
+
NSURLSessionConfiguration *sessionConfiguration =
|
|
431
|
+
[NSURLSessionConfiguration
|
|
432
|
+
backgroundSessionConfigurationWithIdentifier:
|
|
433
|
+
BACKGROUND_SESSION_ID];
|
|
348
434
|
if (groupId != nil && ![groupId isEqualToString:@""]) {
|
|
349
435
|
sessionConfiguration.sharedContainerIdentifier = groupId;
|
|
350
436
|
}
|
|
351
437
|
if (limitNetwork) {
|
|
352
438
|
sessionConfiguration.allowsCellularAccess = NO;
|
|
353
439
|
}
|
|
354
|
-
_urlSession =
|
|
440
|
+
_urlSession =
|
|
441
|
+
[NSURLSession sessionWithConfiguration:sessionConfiguration
|
|
442
|
+
delegate:self
|
|
443
|
+
delegateQueue:nil];
|
|
355
444
|
}
|
|
356
445
|
|
|
357
446
|
return _urlSession;
|
|
@@ -360,41 +449,45 @@ RCT_EXPORT_METHOD(getAllUploads:(RCTPromiseResolveBlock)resolve
|
|
|
360
449
|
- (NSString *)getTmpDirectory {
|
|
361
450
|
NSFileManager *manager = [NSFileManager defaultManager];
|
|
362
451
|
NSString *namespace = @"react-native-upload";
|
|
363
|
-
NSString *tmpPath =
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
452
|
+
NSString *tmpPath =
|
|
453
|
+
[NSTemporaryDirectory() stringByAppendingString:namespace];
|
|
454
|
+
|
|
455
|
+
[manager createDirectoryAtPath:tmpPath
|
|
456
|
+
withIntermediateDirectories:YES
|
|
457
|
+
attributes:nil
|
|
458
|
+
error:nil];
|
|
459
|
+
|
|
367
460
|
return tmpPath;
|
|
368
461
|
}
|
|
369
462
|
|
|
370
463
|
#pragma mark - NSURLSessionTaskDelegate
|
|
371
464
|
|
|
372
465
|
- (void)URLSession:(NSURLSession *)session
|
|
373
|
-
|
|
374
|
-
didCompleteWithError:(NSError *)error {
|
|
375
|
-
NSMutableDictionary *data = [NSMutableDictionary
|
|
466
|
+
task:(NSURLSessionTask *)task
|
|
467
|
+
didCompleteWithError:(NSError *)error {
|
|
468
|
+
NSMutableDictionary *data = [NSMutableDictionary
|
|
469
|
+
dictionaryWithObjectsAndKeys:task.taskDescription, @"id", nil];
|
|
376
470
|
NSURLSessionDataTask *uploadTask = (NSURLSessionDataTask *)task;
|
|
377
471
|
NSHTTPURLResponse *response = (NSHTTPURLResponse *)uploadTask.response;
|
|
378
|
-
if (response != nil)
|
|
379
|
-
|
|
380
|
-
|
|
472
|
+
if (response != nil) {
|
|
473
|
+
[data setObject:[NSNumber numberWithInteger:response.statusCode]
|
|
474
|
+
forKey:@"responseCode"];
|
|
381
475
|
}
|
|
382
|
-
//Add data that was collected earlier by the didReceiveData method
|
|
476
|
+
// Add data that was collected earlier by the didReceiveData method
|
|
383
477
|
NSMutableData *responseData = _responsesData[@(task.taskIdentifier)];
|
|
384
478
|
if (responseData) {
|
|
385
479
|
[_responsesData removeObjectForKey:@(task.taskIdentifier)];
|
|
386
|
-
NSString *response =
|
|
480
|
+
NSString *response =
|
|
481
|
+
[[NSString alloc] initWithData:responseData
|
|
482
|
+
encoding:NSUTF8StringEncoding];
|
|
387
483
|
[data setObject:response forKey:@"responseBody"];
|
|
388
484
|
} else {
|
|
389
485
|
[data setObject:[NSNull null] forKey:@"responseBody"];
|
|
390
486
|
}
|
|
391
487
|
|
|
392
|
-
if (error == nil)
|
|
393
|
-
{
|
|
488
|
+
if (error == nil) {
|
|
394
489
|
[self _sendEventWithName:@"RNFileUploader-completed" body:data];
|
|
395
|
-
}
|
|
396
|
-
else
|
|
397
|
-
{
|
|
490
|
+
} else {
|
|
398
491
|
[data setObject:error.localizedDescription forKey:@"error"];
|
|
399
492
|
if (error.code == NSURLErrorCancelled) {
|
|
400
493
|
[self _sendEventWithName:@"RNFileUploader-cancelled" body:data];
|
|
@@ -403,32 +496,46 @@ didCompleteWithError:(NSError *)error {
|
|
|
403
496
|
}
|
|
404
497
|
}
|
|
405
498
|
|
|
406
|
-
NSURL *multipartDataFileUrl = [NSURL
|
|
407
|
-
|
|
499
|
+
NSURL *multipartDataFileUrl = [NSURL
|
|
500
|
+
fileURLWithPath:[NSString stringWithFormat:@"%@/%@",
|
|
501
|
+
[self getTmpDirectory],
|
|
502
|
+
task.taskDescription]];
|
|
503
|
+
[[NSFileManager defaultManager] removeItemAtURL:multipartDataFileUrl
|
|
504
|
+
error:nil];
|
|
408
505
|
}
|
|
409
506
|
|
|
410
507
|
- (void)URLSession:(NSURLSession *)session
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
|
|
508
|
+
task:(NSURLSessionTask *)task
|
|
509
|
+
didSendBodyData:(int64_t)bytesSent
|
|
510
|
+
totalBytesSent:(int64_t)totalBytesSent
|
|
511
|
+
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
|
|
415
512
|
float progress = -1;
|
|
416
|
-
if (totalBytesExpectedToSend >
|
|
513
|
+
if (totalBytesExpectedToSend >
|
|
514
|
+
0) // see documentation. For unknown size it's -1
|
|
515
|
+
// (NSURLSessionTransferSizeUnknown)
|
|
417
516
|
{
|
|
418
|
-
progress =
|
|
517
|
+
progress =
|
|
518
|
+
100.0 * (float)totalBytesSent / (float)totalBytesExpectedToSend;
|
|
419
519
|
}
|
|
420
|
-
[self _sendEventWithName:@"RNFileUploader-progress"
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
520
|
+
[self _sendEventWithName:@"RNFileUploader-progress"
|
|
521
|
+
body:@{
|
|
522
|
+
@"id" : task.taskDescription,
|
|
523
|
+
@"progress" : [NSNumber numberWithFloat:progress],
|
|
524
|
+
@"totalBytes" : [NSNumber
|
|
525
|
+
numberWithInteger:totalBytesExpectedToSend],
|
|
526
|
+
@"uploadedBytes" :
|
|
527
|
+
[NSNumber numberWithInteger:totalBytesSent]
|
|
528
|
+
}];
|
|
425
529
|
}
|
|
426
530
|
|
|
427
|
-
- (void)URLSession:(NSURLSession *)session
|
|
531
|
+
- (void)URLSession:(NSURLSession *)session
|
|
532
|
+
dataTask:(NSURLSessionDataTask *)dataTask
|
|
533
|
+
didReceiveData:(NSData *)data {
|
|
428
534
|
if (!data.length) {
|
|
429
535
|
return;
|
|
430
536
|
}
|
|
431
|
-
//Hold returned data so it can be picked up by the didCompleteWithError
|
|
537
|
+
// Hold returned data so it can be picked up by the didCompleteWithError
|
|
538
|
+
// method later
|
|
432
539
|
NSMutableData *responseData = _responsesData[@(dataTask.taskIdentifier)];
|
|
433
540
|
if (!responseData) {
|
|
434
541
|
responseData = [NSMutableData dataWithData:data];
|
|
@@ -440,21 +547,27 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
|
|
|
440
547
|
|
|
441
548
|
#pragma mark - NSURLSessionDelegate
|
|
442
549
|
|
|
443
|
-
- (void)URLSessionDidFinishEventsForBackgroundURLSession:
|
|
550
|
+
- (void)URLSessionDidFinishEventsForBackgroundURLSession:
|
|
551
|
+
(NSURLSession *)session {
|
|
444
552
|
if (backgroundSessionCompletionHandler) {
|
|
445
|
-
NSLog(@"RNBU Did Finish Events For Background URLSession (has
|
|
446
|
-
|
|
553
|
+
NSLog(@"RNBU Did Finish Events For Background URLSession (has "
|
|
554
|
+
@"backgroundSessionCompletionHandler)");
|
|
555
|
+
// This long delay is set as a security if the JS side does not call
|
|
556
|
+
// :canSuspendIfBackground: promptly
|
|
447
557
|
double delayInSeconds = 45.0;
|
|
448
|
-
dispatch_time_t popTime =
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
558
|
+
dispatch_time_t popTime =
|
|
559
|
+
dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
|
|
560
|
+
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
|
|
561
|
+
if (backgroundSessionCompletionHandler) {
|
|
562
|
+
backgroundSessionCompletionHandler();
|
|
563
|
+
NSLog(@"RNBU did call backgroundSessionCompletionHandler "
|
|
564
|
+
@"(timeout)");
|
|
565
|
+
backgroundSessionCompletionHandler = nil;
|
|
566
|
+
}
|
|
455
567
|
});
|
|
456
568
|
} else {
|
|
457
|
-
NSLog(@"RNBU Did Finish Events For Background URLSession (no
|
|
569
|
+
NSLog(@"RNBU Did Finish Events For Background URLSession (no "
|
|
570
|
+
@"backgroundSessionCompletionHandler)");
|
|
458
571
|
}
|
|
459
572
|
}
|
|
460
573
|
|
package/package.json
CHANGED