2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-06-18 16:41:38 +00:00
|
|
|
#import "RCTNetworking.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
#import "RCTAssert.h"
|
2015-04-10 03:18:31 +00:00
|
|
|
#import "RCTConvert.h"
|
2015-10-19 16:04:54 +00:00
|
|
|
#import "RCTNetworkTask.h"
|
2015-06-09 19:25:33 +00:00
|
|
|
#import "RCTURLRequestHandler.h"
|
2015-06-05 22:23:30 +00:00
|
|
|
#import "RCTEventDispatcher.h"
|
2015-06-09 19:25:33 +00:00
|
|
|
#import "RCTHTTPRequestHandler.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTLog.h"
|
|
|
|
#import "RCTUtils.h"
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSDictionary<NSString *, id> *result);
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
@interface RCTNetworking ()
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary<NSString *, id> *)data
|
2015-07-23 10:55:12 +00:00
|
|
|
callback:(RCTHTTPQueryResult)callback;
|
2015-06-09 19:25:33 +00:00
|
|
|
@end
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper to convert FormData payloads into multipart/formdata requests.
|
|
|
|
*/
|
|
|
|
@interface RCTHTTPFormDataHelper : NSObject
|
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
@property (nonatomic, weak) RCTNetworking *networker;
|
2015-06-09 19:25:33 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTHTTPFormDataHelper
|
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableArray<NSDictionary<NSString *, id> *> *_parts;
|
2015-10-19 16:04:54 +00:00
|
|
|
NSMutableData *_multipartBody;
|
2015-06-09 19:25:33 +00:00
|
|
|
RCTHTTPQueryResult _callback;
|
2015-10-19 16:04:54 +00:00
|
|
|
NSString *_boundary;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
static NSString *RCTGenerateFormBoundary()
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-07-23 10:55:12 +00:00
|
|
|
const size_t boundaryLength = 70;
|
|
|
|
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_./";
|
|
|
|
|
|
|
|
char *bytes = malloc(boundaryLength);
|
|
|
|
size_t charCount = strlen(boundaryChars);
|
|
|
|
for (int i = 0; i < boundaryLength; i++) {
|
|
|
|
bytes[i] = boundaryChars[arc4random_uniform((u_int32_t)charCount)];
|
|
|
|
}
|
|
|
|
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
|
|
|
|
}
|
|
|
|
|
2015-12-10 18:09:04 +00:00
|
|
|
- (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
|
2015-07-23 10:55:12 +00:00
|
|
|
callback:(RCTHTTPQueryResult)callback
|
|
|
|
{
|
2015-12-02 09:27:56 +00:00
|
|
|
RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue");
|
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
if (formData.count == 0) {
|
|
|
|
return callback(nil, nil);
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-07-23 10:55:12 +00:00
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
_parts = [formData mutableCopy];
|
2015-06-09 19:25:33 +00:00
|
|
|
_callback = callback;
|
2015-10-19 16:04:54 +00:00
|
|
|
_multipartBody = [NSMutableData new];
|
|
|
|
_boundary = RCTGenerateFormBoundary();
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
|
2015-07-23 10:55:12 +00:00
|
|
|
return [self handleResult:result error:error];
|
2015-06-09 19:25:33 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result
|
2015-07-23 10:55:12 +00:00
|
|
|
error:(NSError *)error
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-12-02 09:27:56 +00:00
|
|
|
RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on method queue");
|
|
|
|
|
2015-06-09 19:25:33 +00:00
|
|
|
if (error) {
|
2015-07-23 10:55:12 +00:00
|
|
|
return _callback(error, nil);
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start with boundary.
|
2015-10-19 16:04:54 +00:00
|
|
|
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
|
|
|
|
dataUsingEncoding:NSUTF8StringEncoding]];
|
2015-06-09 19:25:33 +00:00
|
|
|
|
|
|
|
// Print headers.
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSString *, NSString *> *headers = [_parts[0][@"headers"] mutableCopy];
|
2015-06-09 19:25:33 +00:00
|
|
|
NSString *partContentType = result[@"contentType"];
|
|
|
|
if (partContentType != nil) {
|
2015-08-24 10:14:33 +00:00
|
|
|
headers[@"content-type"] = partContentType;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
|
2016-07-07 19:36:56 +00:00
|
|
|
[self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
|
2015-10-19 16:04:54 +00:00
|
|
|
dataUsingEncoding:NSUTF8StringEncoding]];
|
2015-06-09 19:25:33 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
// Add the body.
|
2015-10-19 16:04:54 +00:00
|
|
|
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
[_multipartBody appendData:result[@"body"]];
|
|
|
|
[_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
[_parts removeObjectAtIndex:0];
|
|
|
|
if (_parts.count) {
|
2015-11-14 18:25:00 +00:00
|
|
|
return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary<NSString *, id> *res) {
|
2015-07-23 10:55:12 +00:00
|
|
|
return [self handleResult:res error:err];
|
2015-06-09 19:25:33 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
// We've processed the last item. Finish and return.
|
2015-10-19 16:04:54 +00:00
|
|
|
[_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary]
|
|
|
|
dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", _boundary];
|
|
|
|
return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType});
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Bridge module that provides the JS interface to the network stack.
|
|
|
|
*/
|
2015-06-18 16:41:38 +00:00
|
|
|
@implementation RCTNetworking
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
|
2015-11-03 22:45:46 +00:00
|
|
|
NSArray<id<RCTURLRequestHandler>> *_handlers;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-06-05 22:23:30 +00:00
|
|
|
|
2015-06-19 11:18:54 +00:00
|
|
|
@synthesize methodQueue = _methodQueue;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-04-08 12:42:43 +00:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2016-05-25 11:17:35 +00:00
|
|
|
- (NSArray<NSString *> *)supportedEvents
|
|
|
|
{
|
|
|
|
return @[@"didCompleteNetworkResponse",
|
|
|
|
@"didReceiveNetworkResponse",
|
|
|
|
@"didSendNetworkData",
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
@"didReceiveNetworkIncrementalData",
|
|
|
|
@"didReceiveNetworkDataProgress",
|
2016-05-25 11:17:35 +00:00
|
|
|
@"didReceiveNetworkData"];
|
|
|
|
}
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2016-01-07 12:00:15 +00:00
|
|
|
if (!request.URL) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_handlers) {
|
2016-03-03 10:20:20 +00:00
|
|
|
// Get handlers, sorted in reverse priority order (highest priority first)
|
2016-05-25 11:17:35 +00:00
|
|
|
_handlers = [[self.bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
|
2015-11-25 11:09:00 +00:00
|
|
|
float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
|
|
|
|
float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
|
|
|
|
if (priorityA > priorityB) {
|
|
|
|
return NSOrderedAscending;
|
|
|
|
} else if (priorityA < priorityB) {
|
|
|
|
return NSOrderedDescending;
|
|
|
|
} else {
|
|
|
|
return NSOrderedSame;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
|
|
|
if (RCT_DEBUG) {
|
|
|
|
// Check for handler conflicts
|
|
|
|
float previousPriority = 0;
|
|
|
|
id<RCTURLRequestHandler> previousHandler = nil;
|
|
|
|
for (id<RCTURLRequestHandler> handler in _handlers) {
|
2015-11-17 15:18:55 +00:00
|
|
|
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
|
|
|
|
if (previousHandler && priority < previousPriority) {
|
|
|
|
return previousHandler;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
if ([handler canHandleRequest:request]) {
|
|
|
|
if (previousHandler) {
|
|
|
|
if (priority == previousPriority) {
|
|
|
|
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
|
|
|
|
" they can handle the request %@, and have equal priority"
|
|
|
|
" (%g). This could result in non-deterministic behavior.",
|
|
|
|
handler, previousHandler, request, priority);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
previousHandler = handler;
|
|
|
|
previousPriority = priority;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-11-17 15:18:55 +00:00
|
|
|
return previousHandler;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
|
|
|
// Normal code path
|
|
|
|
for (id<RCTURLRequestHandler> handler in _handlers) {
|
|
|
|
if ([handler canHandleRequest:request]) {
|
|
|
|
return handler;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2016-05-04 06:41:26 +00:00
|
|
|
- (NSDictionary<NSString *, id> *)stripNullsInRequestHeaders:(NSDictionary<NSString *, id> *)headers
|
|
|
|
{
|
|
|
|
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:headers.count];
|
|
|
|
for (NSString *key in headers.allKeys) {
|
|
|
|
id val = headers[key];
|
|
|
|
if (val != [NSNull null]) {
|
|
|
|
result[key] = val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query
|
2015-07-23 10:55:12 +00:00
|
|
|
completionBlock:(void (^)(NSURLRequest *request))block
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue");
|
|
|
|
|
2015-08-14 12:13:40 +00:00
|
|
|
NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null
|
2015-06-09 19:25:33 +00:00
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
|
2015-08-24 10:14:33 +00:00
|
|
|
request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
|
2016-05-04 06:41:26 +00:00
|
|
|
request.allHTTPHeaderFields = [self stripNullsInRequestHeaders:[RCTConvert NSDictionary:query[@"headers"]]];
|
2016-01-18 15:47:04 +00:00
|
|
|
request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
|
2016-07-22 18:14:10 +00:00
|
|
|
NSString *trackingName = data[@"trackingName"];
|
|
|
|
if (trackingName) {
|
|
|
|
[NSURLProtocol setProperty:trackingName
|
|
|
|
forKey:@"trackingName"
|
|
|
|
inRequest:request];
|
|
|
|
}
|
2015-11-14 18:25:00 +00:00
|
|
|
return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
|
2015-06-09 19:25:33 +00:00
|
|
|
if (error) {
|
|
|
|
RCTLogError(@"Error processing request body: %@", error);
|
|
|
|
// Ideally we'd circle back to JS here and notify an error/abort on the request.
|
2015-07-23 10:55:12 +00:00
|
|
|
return (RCTURLRequestCancellationBlock)nil;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
request.HTTPBody = result[@"body"];
|
|
|
|
NSString *contentType = result[@"contentType"];
|
|
|
|
if (contentType) {
|
2015-07-16 16:34:28 +00:00
|
|
|
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-07-16 16:34:28 +00:00
|
|
|
|
|
|
|
// Gzip the request body
|
|
|
|
if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
|
|
|
|
request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
|
2015-08-24 10:14:33 +00:00
|
|
|
[request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"];
|
2015-07-16 16:34:28 +00:00
|
|
|
}
|
|
|
|
|
2016-07-07 19:36:56 +00:00
|
|
|
dispatch_async(self->_methodQueue, ^{
|
2015-11-25 11:09:00 +00:00
|
|
|
block(request);
|
|
|
|
});
|
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
return (RCTURLRequestCancellationBlock)nil;
|
2015-06-09 19:25:33 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-10-19 16:04:54 +00:00
|
|
|
return [self handlerForRequest:request] != nil;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
2015-06-09 19:25:33 +00:00
|
|
|
* Process the 'data' part of an HTTP query.
|
|
|
|
*
|
|
|
|
* 'data' can be a JSON value of the following forms:
|
|
|
|
*
|
|
|
|
* - {"string": "..."}: a simple JS string that will be UTF-8 encoded and sent as the body
|
|
|
|
*
|
|
|
|
* - {"uri": "some-uri://..."}: reference to a system resource, e.g. an image in the asset library
|
|
|
|
*
|
|
|
|
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
|
|
|
|
*
|
|
|
|
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
|
|
|
|
*
|
|
|
|
* - @"body" (NSData): the body of the request
|
|
|
|
*
|
|
|
|
* - @"contentType" (NSString): the content type header of the request
|
|
|
|
*
|
2015-02-20 04:10:52 +00:00
|
|
|
*/
|
2015-11-14 18:25:00 +00:00
|
|
|
- (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query callback:
|
|
|
|
(RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))callback
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-12-02 09:27:56 +00:00
|
|
|
RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on method queue");
|
2015-11-25 11:09:00 +00:00
|
|
|
|
2015-06-09 19:25:33 +00:00
|
|
|
if (!query) {
|
2015-07-23 10:55:12 +00:00
|
|
|
return callback(nil, nil);
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
NSData *body = [RCTConvert NSData:query[@"string"]];
|
|
|
|
if (body) {
|
2015-07-23 10:55:12 +00:00
|
|
|
return callback(nil, @{@"body": body});
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
|
|
|
|
if (request) {
|
2015-07-23 10:55:12 +00:00
|
|
|
|
|
|
|
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
|
2015-10-19 16:04:54 +00:00
|
|
|
RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
|
2016-07-07 19:36:56 +00:00
|
|
|
dispatch_async(self->_methodQueue, ^{
|
2015-12-02 09:27:56 +00:00
|
|
|
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
|
|
|
|
});
|
2015-06-09 19:25:33 +00:00
|
|
|
}];
|
2015-07-23 10:55:12 +00:00
|
|
|
|
2015-10-17 16:33:09 +00:00
|
|
|
[task start];
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
__weak RCTNetworkTask *weakTask = task;
|
2015-07-23 10:55:12 +00:00
|
|
|
return ^{
|
|
|
|
[weakTask cancel];
|
|
|
|
if (cancellationBlock) {
|
|
|
|
cancellationBlock();
|
|
|
|
}
|
|
|
|
};
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-12-10 18:09:04 +00:00
|
|
|
NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
|
2015-07-23 10:55:12 +00:00
|
|
|
if (formData) {
|
2015-08-17 14:35:34 +00:00
|
|
|
RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new];
|
2015-07-23 10:55:12 +00:00
|
|
|
formDataHelper.networker = self;
|
|
|
|
return [formDataHelper process:formData callback:callback];
|
2015-05-26 20:29:41 +00:00
|
|
|
}
|
2015-06-09 19:25:33 +00:00
|
|
|
// Nothing in the data payload, at least nothing we could understand anyway.
|
|
|
|
// Ignore and treat it as if it were null.
|
2015-07-23 10:55:12 +00:00
|
|
|
return callback(nil, nil);
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
2015-06-05 22:23:30 +00:00
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
+ (NSString *)decodeTextData:(NSData *)data fromResponse:(NSURLResponse *)response
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
|
|
|
NSStringEncoding encoding = NSUTF8StringEncoding;
|
|
|
|
if (response.textEncodingName) {
|
|
|
|
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
|
|
|
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
|
|
|
}
|
2015-09-16 14:40:14 +00:00
|
|
|
// Attempt to decode text
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
NSString *encodedResponse = [[NSString alloc] initWithData:data encoding:encoding];
|
|
|
|
if (!encodedResponse && data.length) {
|
2015-09-16 14:40:14 +00:00
|
|
|
// We don't have an encoding, or the encoding is incorrect, so now we
|
2015-10-19 16:04:54 +00:00
|
|
|
// try to guess (unfortunately, this feature is available in iOS 8+ only)
|
2015-09-16 14:40:14 +00:00
|
|
|
if ([NSString respondsToSelector:@selector(stringEncodingForData:
|
|
|
|
encodingOptions:
|
|
|
|
convertedString:
|
|
|
|
usedLossyConversion:)]) {
|
|
|
|
[NSString stringEncodingForData:data
|
|
|
|
encodingOptions:nil
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
convertedString:&encodedResponse
|
2015-09-16 14:40:14 +00:00
|
|
|
usedLossyConversion:NULL];
|
|
|
|
}
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
}
|
|
|
|
return encodedResponse;
|
|
|
|
}
|
2015-09-16 14:40:14 +00:00
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
- (void)sendData:(NSData *)data
|
|
|
|
responseType:(NSString *)responseType
|
|
|
|
forTask:(RCTNetworkTask *)task
|
|
|
|
{
|
|
|
|
RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
|
|
|
|
|
|
|
|
if (data.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *responseString;
|
|
|
|
if ([responseType isEqualToString:@"text"]) {
|
|
|
|
responseString = [RCTNetworking decodeTextData:data fromResponse:task.response];
|
|
|
|
if (!responseString) {
|
2015-09-16 14:40:14 +00:00
|
|
|
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
|
|
|
return;
|
|
|
|
}
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
} else if ([responseType isEqualToString:@"base64"]) {
|
|
|
|
responseString = [data base64EncodedStringWithOptions:0];
|
|
|
|
} else {
|
|
|
|
RCTLogWarn(@"Invalid responseType: %@", responseType);
|
|
|
|
return;
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
NSArray<id> *responseJSON = @[task.requestID, responseString];
|
2016-05-25 11:17:35 +00:00
|
|
|
[self sendEventWithName:@"didReceiveNetworkData" body:responseJSON];
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
- (void)sendRequest:(NSURLRequest *)request
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
responseType:(NSString *)responseType
|
2015-07-23 10:55:12 +00:00
|
|
|
incrementalUpdates:(BOOL)incrementalUpdates
|
|
|
|
responseSender:(RCTResponseSenderBlock)responseSender
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue");
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
__block RCTNetworkTask *task;
|
2015-07-23 10:55:12 +00:00
|
|
|
|
2015-07-27 20:46:59 +00:00
|
|
|
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
|
2016-07-07 19:36:56 +00:00
|
|
|
dispatch_async(self->_methodQueue, ^{
|
2015-07-27 20:46:59 +00:00
|
|
|
NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
|
2016-05-25 11:17:35 +00:00
|
|
|
[self sendEventWithName:@"didSendNetworkData" body:responseJSON];
|
2015-07-23 10:55:12 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
|
2016-07-07 19:36:56 +00:00
|
|
|
dispatch_async(self->_methodQueue, ^{
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, NSString *> *headers;
|
2015-10-13 15:12:54 +00:00
|
|
|
NSInteger status;
|
|
|
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
|
|
|
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
|
|
headers = httpResponse.allHeaderFields ?: @{};
|
|
|
|
status = httpResponse.statusCode;
|
|
|
|
} else {
|
|
|
|
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
|
|
|
|
status = 200;
|
2015-07-23 10:55:12 +00:00
|
|
|
}
|
2016-01-07 18:25:56 +00:00
|
|
|
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
|
|
|
|
NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
|
2016-05-25 11:17:35 +00:00
|
|
|
[self sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON];
|
2015-07-23 10:55:12 +00:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
// XHR does not allow you to peek at xhr.response before the response is
|
|
|
|
// finished. Only when xhr.responseType is set to ''/'text', consumers may
|
|
|
|
// peek at xhr.responseText. So unless the requested responseType is 'text',
|
|
|
|
// we only send progress updates and not incremental data updates to JS here.
|
|
|
|
RCTURLRequestIncrementalDataBlock incrementalDataBlock = nil;
|
|
|
|
RCTURLRequestProgressBlock downloadProgressBlock = nil;
|
|
|
|
if (incrementalUpdates) {
|
|
|
|
if ([responseType isEqualToString:@"text"]) {
|
|
|
|
incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
|
|
|
|
dispatch_async(self->_methodQueue, ^{
|
|
|
|
NSString *responseString = [RCTNetworking decodeTextData:data fromResponse:task.response];
|
|
|
|
if (!responseString) {
|
|
|
|
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
NSArray<id> *responseJSON = @[task.requestID, responseString, @(progress), @(total)];
|
|
|
|
[self sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
downloadProgressBlock = ^(int64_t progress, int64_t total) {
|
|
|
|
dispatch_async(self->_methodQueue, ^{
|
|
|
|
NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)];
|
|
|
|
[self sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2015-07-23 10:55:12 +00:00
|
|
|
|
|
|
|
RCTURLRequestCompletionBlock completionBlock =
|
|
|
|
^(NSURLResponse *response, NSData *data, NSError *error) {
|
2016-07-07 19:36:56 +00:00
|
|
|
dispatch_async(self->_methodQueue, ^{
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
// Unless we were sending incremental (text) chunks to JS, all along, now
|
|
|
|
// is the time to send the request body to JS.
|
|
|
|
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
|
|
|
|
[self sendData:data
|
|
|
|
responseType:responseType
|
|
|
|
forTask:task];
|
2015-07-23 10:55:12 +00:00
|
|
|
}
|
|
|
|
NSArray *responseJSON = @[task.requestID,
|
|
|
|
RCTNullIfNil(error.localizedDescription),
|
2016-04-15 12:16:53 +00:00
|
|
|
error.code == kCFURLErrorTimedOut ? @YES : @NO
|
2015-07-23 10:55:12 +00:00
|
|
|
];
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2016-05-25 11:17:35 +00:00
|
|
|
[self sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
|
2016-07-07 19:36:56 +00:00
|
|
|
[self->_tasksByRequestID removeObjectForKey:task.requestID];
|
2015-07-23 10:55:12 +00:00
|
|
|
});
|
|
|
|
};
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
task = [self networkTaskWithRequest:request completionBlock:completionBlock];
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
task.downloadProgressBlock = downloadProgressBlock;
|
2015-07-23 10:55:12 +00:00
|
|
|
task.incrementalDataBlock = incrementalDataBlock;
|
|
|
|
task.responseBlock = responseBlock;
|
|
|
|
task.uploadProgressBlock = uploadProgressBlock;
|
2015-06-09 19:25:33 +00:00
|
|
|
|
2015-07-23 10:55:12 +00:00
|
|
|
if (task.requestID) {
|
2016-02-05 23:07:31 +00:00
|
|
|
if (!_tasksByRequestID) {
|
2015-11-25 11:09:00 +00:00
|
|
|
_tasksByRequestID = [NSMutableDictionary new];
|
|
|
|
}
|
2015-07-23 10:55:12 +00:00
|
|
|
_tasksByRequestID[task.requestID] = task;
|
|
|
|
responseSender(@[task.requestID]);
|
|
|
|
}
|
2015-10-17 16:33:09 +00:00
|
|
|
|
|
|
|
[task start];
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2015-07-27 20:46:59 +00:00
|
|
|
#pragma mark - Public API
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
|
2015-07-27 20:46:59 +00:00
|
|
|
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
|
|
|
|
{
|
|
|
|
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
|
|
|
|
if (!handler) {
|
2015-10-19 16:04:54 +00:00
|
|
|
RCTLogError(@"No suitable URL request handler found for %@", request.URL);
|
2015-07-27 20:46:59 +00:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
return [[RCTNetworkTask alloc] initWithRequest:request
|
2015-07-27 20:46:59 +00:00
|
|
|
handler:handler
|
|
|
|
completionBlock:completionBlock];
|
|
|
|
}
|
|
|
|
|
2015-06-09 19:25:33 +00:00
|
|
|
#pragma mark - JS API
|
|
|
|
|
|
|
|
RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
|
|
|
|
responseSender:(RCTResponseSenderBlock)responseSender)
|
|
|
|
{
|
2015-07-23 10:55:12 +00:00
|
|
|
// TODO: buildRequest returns a cancellation block, but there's currently
|
|
|
|
// no way to invoke it, if, for example the request is cancelled while
|
|
|
|
// loading a large file to build the request body
|
2015-07-17 11:50:42 +00:00
|
|
|
[self buildRequest:query completionBlock:^(NSURLRequest *request) {
|
|
|
|
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
NSString *responseType = [RCTConvert NSString:query[@"responseType"]];
|
2015-07-17 11:50:42 +00:00
|
|
|
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
|
2015-07-23 10:55:12 +00:00
|
|
|
[self sendRequest:request
|
Add responseType as a concept to RCTNetworking, send binary data as base64
Summary:
In preparation for Blob support (wherein binary XHR and WebSocket responses can be retained as native data blobs on the native side and JS receives a web-like opaque Blob object), this change makes RCTNetworking aware of the responseType that JS requests. A `xhr.responseType` of `''` or `'text'` translates to a native response type of `'text'`. A `xhr.responseType` of `arraybuffer` translates to a native response type of `base64`, as we currently lack an API to transmit TypedArrays directly to JS. This is analogous to how the WebSocket module already works, and it's a lot more versatile and much less brittle than converting a JS *string* back to a TypedArray, which is what's currently going on.
Now that we don't always send text down to JS, JS consumers might still want to get progress updates about a binary download. This is what the `'progress'` event is designed for, so this change also implements that. This change also follows the XHR spec with regards to `xhr.response` and `xhr.responseText`:
- if the response type is `'text'`, `xhr.responseText` can be peeked at by the JS consumer. It will be updated periodically as the download progresses, so long as there's either an `onreadystatechange` or `onprogress` handler on the XHR.
- if the response type is not `'text'`, `xhr.responseText` can't be accessed and `xhr.response` remains `null` until the response is fully received. `'progress'` events containing response details (total bytes, downloaded so far) are dispatched if there's an `onprogress` handler.
Once Blobs are landed, `xhr.responseType` of `'blob'` will correspond to the same native response type, which will cause RCTNetworking to only send a blob ID down to JS, which can then create a `Blob` object from that for consumers.
Closes https://github.com/facebook/react-native/pull/8324
Reviewed By: javache
Differential Revision: D3508822
Pulled By: davidaurelio
fbshipit-source-id: 441b2d4d40265b6036559c3ccb9fa962999fa5df
2016-07-13 11:53:54 +00:00
|
|
|
responseType:responseType
|
2015-07-23 10:55:12 +00:00
|
|
|
incrementalUpdates:incrementalUpdates
|
2015-07-17 11:50:42 +00:00
|
|
|
responseSender:responseSender];
|
|
|
|
}];
|
2015-06-09 19:25:33 +00:00
|
|
|
}
|
|
|
|
|
2016-05-25 11:17:35 +00:00
|
|
|
RCT_EXPORT_METHOD(abortRequest:(nonnull NSNumber *)requestID)
|
2015-06-09 19:25:33 +00:00
|
|
|
{
|
2015-07-23 10:55:12 +00:00
|
|
|
[_tasksByRequestID[requestID] cancel];
|
|
|
|
[_tasksByRequestID removeObjectForKey:requestID];
|
2015-06-05 22:23:30 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@end
|
2015-07-27 20:46:59 +00:00
|
|
|
|
|
|
|
@implementation RCTBridge (RCTNetworking)
|
|
|
|
|
|
|
|
- (RCTNetworking *)networking
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
return [self moduleForClass:[RCTNetworking class]];
|
2015-07-27 20:46:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|