Skip to content

Commit 459d1ea

Browse files
committed
Merge branch 'master' of github.com:Vydia/react-native-background-upload
2 parents 3c44999 + 8576fbb commit 459d1ea

File tree

4 files changed

+161
-55
lines changed

4 files changed

+161
-55
lines changed

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Vydia, Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

android/src/main/java/com/vydia/UploaderModule.java

Lines changed: 84 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import com.facebook.react.modules.core.DeviceEventManagerModule;
1919

2020
import net.gotev.uploadservice.BinaryUploadRequest;
21+
import net.gotev.uploadservice.HttpUploadRequest;
22+
import net.gotev.uploadservice.MultipartUploadRequest;
2123
import net.gotev.uploadservice.ServerResponse;
2224
import net.gotev.uploadservice.UploadInfo;
2325
import net.gotev.uploadservice.UploadNotificationConfig;
@@ -83,18 +85,8 @@ public void getFileInfo(String path, final Promise promise) {
8385
}
8486

8587
/*
86-
* Starts a file upload.
87-
* Options are passed in as the first argument as a js hash:
88-
* {
89-
* url: string. url to post to.
90-
* path: string. path to the file on the device
91-
* headers: hash of name/value header pairs
92-
* method: HTTP method to use. Default is "POST"
93-
* notification: hash for customizing tray notifiaction
94-
* enabled: boolean to enable/disabled notifications, true by default.
95-
* }
96-
*
97-
* Returns a promise with the string ID of the upload.
88+
* Starts a file upload.
89+
* Returns a promise with the string ID of the upload.
9890
*/
9991
@ReactMethod
10092
public void startUpload(ReadableMap options, final Promise promise) {
@@ -108,66 +100,108 @@ public void startUpload(ReadableMap options, final Promise promise) {
108100
return;
109101
}
110102
}
103+
111104
if (options.hasKey("headers") && options.getType("headers") != ReadableType.Map) {
112105
promise.reject(new IllegalArgumentException("headers must be a hash."));
113106
return;
114107
}
108+
115109
if (options.hasKey("notification") && options.getType("notification") != ReadableType.Map) {
116110
promise.reject(new IllegalArgumentException("notification must be a hash."));
117111
return;
118112
}
119113

114+
String requestType = "raw";
115+
116+
if (options.hasKey("type")) {
117+
requestType = options.getString("type");
118+
if (requestType == null) {
119+
promise.reject(new IllegalArgumentException("type must be string."));
120+
return;
121+
}
122+
123+
if (!requestType.equals("raw") && !requestType.equals("multipart")) {
124+
promise.reject(new IllegalArgumentException("type should be string: raw or multipart."));
125+
return;
126+
}
127+
}
128+
120129
WritableMap notification = new WritableNativeMap();
121130
notification.putBoolean("enabled", true);
131+
122132
if (options.hasKey("notification")) {
123133
notification.merge(options.getMap("notification"));
124134
}
125135

126136
String url = options.getString("url");
127137
String filePath = options.getString("path");
128138
String method = options.hasKey("method") && options.getType("method") == ReadableType.String ? options.getString("method") : "POST";
139+
129140
final String customUploadId = options.hasKey("customUploadId") && options.getType("method") == ReadableType.String ? options.getString("customUploadId") : null;
141+
130142
try {
131-
final BinaryUploadRequest request = (BinaryUploadRequest) new BinaryUploadRequest(this.getReactApplicationContext(), url)
132-
.setMethod(method)
133-
.setFileToUpload(filePath)
134-
.setMaxRetries(2)
135-
.setDelegate(new UploadStatusDelegate() {
136-
@Override
137-
public void onProgress(Context context, UploadInfo uploadInfo) {
138-
WritableMap params = Arguments.createMap();
139-
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
140-
params.putInt("progress", uploadInfo.getProgressPercent()); //0-100
141-
sendEvent("progress", params);
142-
}
143-
144-
@Override
145-
public void onError(Context context, UploadInfo uploadInfo, Exception exception) {
146-
WritableMap params = Arguments.createMap();
147-
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
148-
params.putString("error", exception.getMessage());
149-
sendEvent("error", params);
150-
}
151-
152-
@Override
153-
public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
154-
WritableMap params = Arguments.createMap();
155-
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
156-
params.putInt("responseCode", serverResponse.getHttpCode());
157-
params.putString("responseBody", serverResponse.getBodyAsString());
158-
sendEvent("completed", params);
159-
}
160-
161-
@Override
162-
public void onCancelled(Context context, UploadInfo uploadInfo) {
163-
WritableMap params = Arguments.createMap();
164-
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
165-
sendEvent("cancelled", params);
166-
}
167-
});
143+
UploadStatusDelegate statusDelegate = new UploadStatusDelegate() {
144+
@Override
145+
public void onProgress(Context context, UploadInfo uploadInfo) {
146+
WritableMap params = Arguments.createMap();
147+
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
148+
params.putInt("progress", uploadInfo.getProgressPercent()); //0-100
149+
sendEvent("progress", params);
150+
}
151+
152+
@Override
153+
public void onError(Context context, UploadInfo uploadInfo, Exception exception) {
154+
WritableMap params = Arguments.createMap();
155+
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
156+
params.putString("error", exception.getMessage());
157+
sendEvent("error", params);
158+
}
159+
160+
@Override
161+
public void onCompleted(Context context, UploadInfo uploadInfo, ServerResponse serverResponse) {
162+
WritableMap params = Arguments.createMap();
163+
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
164+
params.putInt("responseCode", serverResponse.getHttpCode());
165+
params.putString("responseBody", serverResponse.getBodyAsString());
166+
sendEvent("completed", params);
167+
}
168+
169+
@Override
170+
public void onCancelled(Context context, UploadInfo uploadInfo) {
171+
WritableMap params = Arguments.createMap();
172+
params.putString("id", customUploadId != null ? customUploadId : uploadInfo.getUploadId());
173+
sendEvent("cancelled", params);
174+
}
175+
};
176+
177+
HttpUploadRequest<?> request;
178+
179+
if (requestType.equals("raw")) {
180+
request = new BinaryUploadRequest(this.getReactApplicationContext(), customUploadId, url)
181+
.setFileToUpload(filePath);
182+
} else {
183+
if (!options.hasKey("field")) {
184+
promise.reject(new IllegalArgumentException("field is required field for multipart type."));
185+
return;
186+
}
187+
188+
if (options.getType("field") != ReadableType.String) {
189+
promise.reject(new IllegalArgumentException("field must be string."));
190+
return;
191+
}
192+
193+
request = new MultipartUploadRequest(this.getReactApplicationContext(), customUploadId, url)
194+
.addFileToUpload(filePath, options.getString("field"));
195+
}
196+
197+
request.setMethod(method)
198+
.setMaxRetries(2)
199+
.setDelegate(statusDelegate);
200+
168201
if (notification.getBoolean("enabled")) {
169202
request.setNotificationConfig(new UploadNotificationConfig());
170203
}
204+
171205
if (options.hasKey("headers")) {
172206
ReadableMap headers = options.getMap("headers");
173207
ReadableMapKeySetIterator keys = headers.keySetIterator();
@@ -180,6 +214,7 @@ public void onCancelled(Context context, UploadInfo uploadInfo) {
180214
request.addHeader(key, headers.getString(key));
181215
}
182216
}
217+
183218
String uploadId = request.startUpload();
184219
promise.resolve(customUploadId != null ? customUploadId : uploadId);
185220
} catch (Exception exc) {

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ export type NotificationArgs = {
1313
export type StartUploadArgs = {
1414
url: string,
1515
path: string,
16+
// Optional, because raw is default
17+
type?: 'raw' | 'multipart',
18+
// This option is needed for multipart type
19+
field?: string,
20+
customUploadId?: string,
1621
headers?: Object,
1722
notification?: NotificationArgs
1823
}

ios/VydiaRNFileUploader.m

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ - (NSString *)guessMIMETypeFromFileName: (NSString *)fileName {
9494
return (__bridge NSString *)(MIMEType);
9595
}
9696

97-
9897
/*
9998
* Starts a file upload.
10099
* Options are passed in as the first argument as a js hash:
@@ -113,15 +112,19 @@ - (NSString *)guessMIMETypeFromFileName: (NSString *)fileName {
113112
{
114113
thisUploadId = uploadId++;
115114
}
115+
116116
NSString *uploadUrl = options[@"url"];
117117
NSString *fileURI = options[@"path"];
118-
NSString *method = options[@"method"];
119-
NSString *customUploadId = options[@"customUploadId"];
118+
NSString *method = options[@"method"] ?: @"POST";
119+
NSString *uploadType = options[@"type"] ?: @"raw";
120+
NSString *fieldName = options[@"field"];
121+
NSString *customUploadId = options[@"customUploadId"];
120122
NSDictionary *headers = options[@"headers"];
121-
123+
122124
@try {
123125
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: uploadUrl]];
124-
request.HTTPMethod = method ? method : @"POST";
126+
[request setHTTPMethod: method];
127+
125128
[headers enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull val, BOOL * _Nonnull stop) {
126129
if ([val respondsToSelector:@selector(stringValue)]) {
127130
val = [val stringValue];
@@ -130,8 +133,24 @@ - (NSString *)guessMIMETypeFromFileName: (NSString *)fileName {
130133
[request setValue:val forHTTPHeaderField:key];
131134
}
132135
}];
133-
NSURLSessionDataTask *uploadTask = [[self urlSession:thisUploadId] uploadTaskWithRequest:request fromFile:[NSURL URLWithString: fileURI]];
136+
137+
NSURLSessionDataTask *uploadTask;
138+
139+
if ([uploadType isEqualToString:@"multipart"]) {
140+
NSString *uuidStr = [[NSUUID UUID] UUIDString];
141+
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", uuidStr] forHTTPHeaderField:@"Content-Type"];
142+
143+
NSData *httpBody = [self createBodyWithBoundary:uuidStr path:fileURI fieldName:fieldName];
144+
[request setHTTPBody: httpBody];
145+
146+
// I am sorry about warning, but Upload tasks from NSData are not supported in background sessions.
147+
uploadTask = [[self urlSession:thisUploadId] uploadTaskWithRequest:request fromData: nil];
148+
} else {
149+
uploadTask = [[self urlSession:thisUploadId] uploadTaskWithRequest:request fromFile:[NSURL URLWithString: fileURI]];
150+
}
151+
134152
uploadTask.taskDescription = customUploadId ? customUploadId : [NSString stringWithFormat:@"%i", thisUploadId];
153+
135154
[uploadTask resume];
136155
resolve(uploadTask.taskDescription);
137156
}
@@ -140,6 +159,32 @@ - (NSString *)guessMIMETypeFromFileName: (NSString *)fileName {
140159
}
141160
}
142161

162+
- (NSData *)createBodyWithBoundary:(NSString *)boundary
163+
path:(NSString *)path
164+
fieldName:(NSString *)fieldName {
165+
166+
NSMutableData *httpBody = [NSMutableData data];
167+
168+
// resolve path
169+
NSURL *fileUri = [NSURL URLWithString: path];
170+
NSString *pathWithoutProtocol = [fileUri path];
171+
172+
NSData *data = [[NSFileManager defaultManager] contentsAtPath:pathWithoutProtocol];
173+
174+
NSString *filename = [path lastPathComponent];
175+
NSString *mimetype = [self guessMIMETypeFromFileName:path];
176+
177+
[httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
178+
[httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]];
179+
[httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]];
180+
[httpBody appendData:data];
181+
[httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
182+
183+
[httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
184+
185+
return httpBody;
186+
}
187+
143188
- (NSURLSession *)urlSession: (int) thisUploadId{
144189
if(_urlSession == nil) {
145190
NSURLSessionConfiguration *sessionConfigurationt = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:BACKGROUND_SESSION_ID];

0 commit comments

Comments
 (0)