2015-03-23 20:28:42 +00:00
/**
2018-09-11 22:27:47 +00:00
* Copyright (c) Facebook, Inc. and its affiliates.
2015-03-23 20:28:42 +00:00
*
2018-02-17 02:24:55 +00:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
2015-03-23 20:28:42 +00:00
*/
2015-02-20 04:10:52 +00:00
2016-11-23 15:47:52 +00:00
#import <mutex>
#import <React/RCTAssert.h>
#import <React/RCTConvert.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTLog.h>
#import <React/RCTNetworkTask.h>
#import <React/RCTNetworking.h>
#import <React/RCTURLRequestHandler.h>
#import <React/RCTUtils.h>
2016-09-02 02:55:29 +00:00
2015-06-09 19:25:33 +00:00
#import "RCTHTTPRequestHandler.h"
2015-02-20 04:10:52 +00:00
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;
2016-12-14 16:15:37 +00:00
const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.";
2015-07-23 10:55:12 +00:00
2016-09-02 02:55:29 +00:00
char *bytes = (char*)malloc(boundaryLength);
2018-08-09 01:24:47 +00:00
if (!bytes) {
// CWE - 391 : Unchecked error condition
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
abort();
}
2015-07-23 10:55:12 +00:00
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]];
2016-11-15 16:30:44 +00:00
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", _boundary];
2015-10-19 16:04:54 +00:00
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;
2016-09-02 02:55:29 +00:00
std::mutex _handlersLock;
2015-11-03 22:45:46 +00:00
NSArray<id<RCTURLRequestHandler>> *_handlers;
2018-01-26 17:06:14 +00:00
NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
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()
2018-01-26 17:06:14 +00:00
- (void)invalidate
{
Clear _handlers on RCTNetworking invalidation
Summary:
This PR fixes a bug where in `RCTNetworking` not all tasks/handlers were not being cleared when invalidating the class.
I came across this issue when writing some unit tests for my native plugins, sometimes a test would finish running (and the bridge invalidated), and only afterwards a callback from RCTNetworking would come, resulting in this exception:
```
2018-05-07 15:23:34.264494-0700 Guardian[73794:10710945] *** Assertion failure in -[RCTEventEmitter sendEventWithName:body:](), /Users/.../app/node_modules/react-native/React/Modules/RCTEventEmitter.m:41
2018-05-07 15:23:34.276505-0700 Guardian[73794:10710945] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error when sending event: didCompleteNetworkResponse with body: (
2,
cancelled,
0
). Bridge is not set. This is probably because you've explicitly synthesized the bridge in RCTNetworking, even though it's inherited from RCTEventEmitter.'
*** First throw call stack:
(
0 CoreFoundation 0x000000010d5b21e6 __exceptionPreprocess + 294
1 libobjc.A.dylib 0x000000010be6f031 objc_exception_throw + 48
2 CoreFoundation 0x000000010d5b7472 +[NSException raise:format:arguments:] + 98
3 Foundation 0x000000010b94864f -[NSAssertionHandler handleFailureInFunction:file:lineNumber:description:] + 165
4 Guardian 0x0000000106ff5227 -[RCTEventEmitter sendEventWithName:body:] + 567
5 Guardian 0x0000000106e9ebab __76-[RCTNetworking sendRequest:responseType:incrementalUpdates:responseSender:]_block_invoke.423 + 1115
6 Guardian 0x0000000106e8f48c __50-[RCTNetworkTask URLRequest:didCompleteWithError:]_block_invoke + 92
7 Guardian 0x0000000106e8ded1 -[RCTNetworkTask dispatchCallback:] + 113
8 Guardian 0x0000000106e8f37a -[RCTNetworkTask URLRequest:didCompleteWithError:] + 410
9 Guardian 0x0000000106ea1aa3 -[RCTHTTPRequestHandler URLSession:task:didCompleteWithError:] + 403
10 CFNetwork 0x000000010cf3a437 __51-[NSURLSession delegate_task:didCompleteWithError:]_block_invoke.207 + 80
11 Foundation 0x000000010b885363 __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 7
12 Foundation 0x000000010b8851ca -[NSBlockOperation main] + 68
13 Foundation 0x000000010b8836b2 -[__NSOperationInternal _start:] + 766
14 libdispatch.dylib 0x0000000112457779 _dispatch_client_callout + 8
15 libdispatch.dylib 0x000000011245c931 _dispatch_block_invoke_direct + 317
16 libdispatch.dylib 0x0000000112457779 _dispatch_client_callout + 8
17 libdispatch.dylib 0x000000011245c931 _dispatch_block_invoke_direct + 317
18 libdispatch.dylib 0x000000011245c7d4 dispatch_block_perform + 109
19 Foundation 0x000000010b87f75b __NSOQSchedule_f + 337
20 libdispatch.dylib 0x0000000112457779 _dispatch_client_callout + 8
21 libdispatch.dylib 0x000000011245f1b2 _dispatch_queue_serial_drain + 735
22 libdispatch.dylib 0x000000011245f9af _dispatch_queue_invoke + 321
23 libdispatch.dylib 0x0000000112461cf8 _dispatch_root_queue_drain + 473
24 libdispatch.dylib 0x0000000112461ac1 _dispatch_worker_thread3 + 119
25 libsystem_pthread.dylib 0x000000011297a169 _pthread_wqthread + 1387
26 libsystem_pthread.dylib 0x0000000112979be9 start_wqthread + 13
)
libc++abi.dylib: terminating with uncaught exception of type NSException
```
Bug can be reproduced by making a `XMLHttpRequest` (uses `RCTNetworking` internally) that takes a couple seconds to perform, and issuing a RCTBridge reload command in the meantime.
You can add the following code to the react-native template project,
```
componentDidMount() {
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", () => console.log('Finished'));
oReq.open("GET", "https://www.dropbox.com/s/o01hz0chqvjafhv/file.bin?dl=1");
oReq.send();
console.log('Request is being performed...')
}
```
In my case I download a 1MB file.
Run the project and reload the a couple times. Bug is triggered.
[INTERNAL] [BUGFIX] [RCTNetworking] - Clear handlers and tasks on RCTNetworking invalidation
Closes https://github.com/facebook/react-native/pull/19169
Differential Revision: D8053070
Pulled By: hramos
fbshipit-source-id: d8af54fecd99173905363f962ffc638ef8b85082
2018-05-18 01:51:03 +00:00
for (NSNumber *requestID in _tasksByRequestID) {
[_tasksByRequestID[requestID] cancel];
}
[_tasksByRequestID removeAllObjects];
_handlers = nil;
2018-01-26 17:06:14 +00:00
_requestHandlers = nil;
_responseHandlers = nil;
}
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;
}
2016-09-02 02:55:29 +00:00
{
std::lock_guard<std::mutex> lock(_handlersLock);
if (!_handlers) {
// Get handlers, sorted in reverse priority order (highest priority first)
_handlers = [[self.bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> b) {
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-11-25 11:09:00 +00:00
}
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";
2017-05-24 16:36:40 +00:00
// Load and set the cookie header.
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:URL];
request.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
// Set supplied headers.
NSDictionary *headers = [RCTConvert NSDictionary:query[@"headers"]];
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
if (value) {
[request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
}
}];
2016-01-18 15:47:04 +00:00
request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
2017-03-08 14:00:17 +00:00
request.HTTPShouldHandleCookies = [RCTConvert BOOL:query[@"withCredentials"]];
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"];
2016-09-05 21:54:38 +00:00
NSString *dataContentType = result[@"contentType"];
NSString *requestContentType = [request valueForHTTPHeaderField:@"Content-Type"];
BOOL isMultipart = [dataContentType hasPrefix:@"multipart"];
2016-11-23 15:47:52 +00:00
2016-09-05 21:54:38 +00:00
// For multipart requests we need to override caller-specified content type with one
// from the data object, because it contains the boundary string
if (dataContentType && ([requestContentType length] == 0 || isMultipart)) {
[request setValue:dataContentType 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
*
2018-01-26 17:06:14 +00:00
* - {"blob": {...}}: an object representing a blob
*
2015-06-09 19:25:33 +00:00
* 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
}
2018-01-26 17:06:14 +00:00
for (id<RCTNetworkingRequestHandler> handler in _requestHandlers) {
if ([handler canHandleNetworkingRequest:query]) {
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
NSDictionary *body = [handler handleNetworkingRequest:query];
if (body) {
return callback(nil, body);
}
}
}
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
}
2017-01-21 02:40:28 +00:00
NSString *base64String = [RCTConvert NSString:query[@"base64"]];
if (base64String) {
NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
return callback(nil, @{@"body": data});
}
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
2016-11-23 15:47:52 +00:00
+ (NSString *)decodeTextData:(NSData *)data fromResponse:(NSURLResponse *)response withCarryData:(NSMutableData *)inputCarryData
2015-06-09 19:25:33 +00:00
{
NSStringEncoding encoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
2016-11-23 15:47:52 +00:00
NSMutableData *currentCarryData = inputCarryData ?: [NSMutableData new];
2016-10-31 20:06:13 +00:00
[currentCarryData appendData:data];
2016-11-23 15:47:52 +00:00
2015-09-16 14:40:14 +00:00
// Attempt to decode text
2016-10-31 20:06:13 +00:00
NSString *encodedResponse = [[NSString alloc] initWithData:currentCarryData encoding:encoding];
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
if (!encodedResponse && data.length > 0) {
if (encoding == NSUTF8StringEncoding && inputCarryData) {
// If decode failed, we attempt to trim broken character bytes from the data.
// At this time, only UTF-8 support is enabled. Multibyte encodings, such as UTF-16 and UTF-32, require a lot of additional work
// to determine wether BOM was included in the first data packet. If so, save it, and attach it to each new data packet. If not,
// an encoding has to be selected with a suitable byte order (for ARM iOS, it would be little endianness).
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
CFStringEncoding cfEncoding = CFStringConvertNSStringEncodingToEncoding(encoding);
// Taking a single unichar is not good enough, due to Unicode combining character sequences or characters outside the BMP.
// See https://www.objc.io/issues/9-strings/unicode/#common-pitfalls
// We'll attempt with a sequence of two characters, the most common combining character sequence and characters outside the BMP (emojis).
CFIndex maxCharLength = CFStringGetMaximumSizeForEncoding(2, cfEncoding);
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
NSUInteger removedBytes = 1;
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
while (removedBytes < maxCharLength) {
encodedResponse = [[NSString alloc] initWithData:[currentCarryData subdataWithRange:NSMakeRange(0, currentCarryData.length - removedBytes)]
encoding:encoding];
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
if (encodedResponse != nil) {
break;
}
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
removedBytes += 1;
}
} else {
// We don't have an encoding, or the encoding is incorrect, so now we try to guess
[NSString stringEncodingForData:data
encodingOptions:@{ NSStringEncodingDetectionSuggestedEncodingsKey: @[ @(encoding) ] }
convertedString:&encodedResponse
usedLossyConversion:NULL];
}
}
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
if (inputCarryData) {
NSUInteger encodedResponseLength = [encodedResponse dataUsingEncoding:encoding].length;
2017-11-07 16:03:08 +00:00
// Ensure a valid subrange exists within currentCarryData
if (currentCarryData.length >= encodedResponseLength) {
NSData *newCarryData = [currentCarryData subdataWithRange:NSMakeRange(encodedResponseLength, currentCarryData.length - encodedResponseLength)];
[inputCarryData setData:newCarryData];
} else {
[inputCarryData setLength:0];
}
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
}
2016-11-23 15:47:52 +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
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
2018-01-26 17:06:14 +00:00
response:(NSURLResponse *)response
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
forTask:(RCTNetworkTask *)task
{
RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
2018-01-26 17:06:14 +00:00
id responseData = nil;
for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
if ([handler canHandleNetworkingResponse:responseType]) {
responseData = [handler handleNetworkingResponse:response data:data];
break;
}
}
if (!responseData) {
2018-03-27 17:34:09 +00:00
if (data.length == 0) {
return;
}
2018-01-26 17:06:14 +00:00
if ([responseType isEqualToString:@"text"]) {
// No carry storage is required here because the entire data has been loaded.
responseData = [RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
if (!responseData) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
} else if ([responseType isEqualToString:@"base64"]) {
responseData = [data base64EncodedStringWithOptions:0];
} else {
RCTLogWarn(@"Invalid responseType: %@", responseType);
2015-09-16 14:40:14 +00:00
return;
}
2015-06-09 19:25:33 +00:00
}
2018-01-26 17:06:14 +00:00
[self sendEventWithName:@"didReceiveNetworkData" body:@[task.requestID, responseData]];
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");
2017-02-13 20:21:38 +00:00
__weak __typeof(self) weakSelf = self;
2015-10-19 16:04:54 +00:00
__block RCTNetworkTask *task;
2015-07-27 20:46:59 +00:00
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
2016-08-17 17:34:15 +00:00
NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
2017-02-13 20:21:38 +00:00
[weakSelf 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-08-17 17:34:15 +00:00
NSDictionary<NSString *, NSString *> *headers;
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;
}
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
2017-02-13 20:21:38 +00:00
[weakSelf 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"]) {
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
// We need this to carry over bytes, which could not be decoded into text (such as broken UTF-8 characters).
// The incremental data block holds the ownership of this object, and will be released upon release of the block.
2016-11-23 15:47:52 +00:00
NSMutableData *incrementalDataCarry = [NSMutableData new];
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
incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
2016-10-31 20:06:13 +00:00
NSUInteger initialCarryLength = incrementalDataCarry.length;
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
NSString *responseString = [RCTNetworking decodeTextData:data
fromResponse:task.response
withCarryData:incrementalDataCarry];
2016-08-17 17:34:15 +00:00
if (!responseString) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
2016-11-23 15:47:52 +00:00
2016-10-31 20:06:13 +00:00
// Update progress to include the previous carry length and reduce the current carry length.
NSArray<id> *responseJSON = @[task.requestID,
responseString,
@(progress + initialCarryLength - incrementalDataCarry.length),
@(total)];
2016-11-23 15:47:52 +00:00
2017-02-13 20:21:38 +00:00
[weakSelf sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
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 {
downloadProgressBlock = ^(int64_t progress, int64_t total) {
2016-08-17 17:34:15 +00:00
NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)];
2017-02-13 20:21:38 +00:00
[weakSelf sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
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
};
}
}
2015-07-23 10:55:12 +00:00
RCTURLRequestCompletionBlock completionBlock =
^(NSURLResponse *response, NSData *data, NSError *error) {
2017-02-13 20:21:38 +00:00
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
2016-08-17 17:34:15 +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"])) {
2018-01-26 17:06:14 +00:00
[strongSelf sendData:data
responseType:responseType
response:response
forTask:task];
2016-08-17 17:34:15 +00:00
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
error.code == kCFURLErrorTimedOut ? @YES : @NO
];
2015-06-09 19:25:33 +00:00
2017-02-13 20:21:38 +00:00
[strongSelf sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
[strongSelf->_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
2018-01-26 17:06:14 +00:00
- (void)addRequestHandler:(id<RCTNetworkingRequestHandler>)handler
{
if (!_requestHandlers) {
_requestHandlers = [NSMutableArray new];
}
[_requestHandlers addObject:handler];
}
- (void)addResponseHandler:(id<RCTNetworkingResponseHandler>)handler
{
if (!_responseHandlers) {
_responseHandlers = [NSMutableArray new];
}
[_responseHandlers addObject:handler];
}
- (void)removeRequestHandler:(id<RCTNetworkingRequestHandler>)handler
{
[_requestHandlers removeObject:handler];
}
- (void)removeResponseHandler:(id<RCTNetworkingResponseHandler>)handler
{
[_responseHandlers removeObject:handler];
}
2016-08-17 17:34:15 +00:00
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request completionBlock:(RCTURLRequestCompletionBlock)completionBlock
2015-07-27 20:46:59 +00:00
{
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;
}
2016-08-17 17:34:15 +00:00
RCTNetworkTask *task = [[RCTNetworkTask alloc] initWithRequest:request
handler:handler
callbackQueue:_methodQueue];
task.completionBlock = completionBlock;
return task;
2015-07-27 20:46:59 +00:00
}
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
}
2016-08-26 12:34:52 +00:00
RCT_EXPORT_METHOD(clearCookies:(RCTResponseSenderBlock)responseSender)
{
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
if (!storage.cookies.count) {
responseSender(@[@NO]);
return;
}
for (NSHTTPCookie *cookie in storage.cookies) {
[storage deleteCookie:cookie];
}
responseSender(@[@YES]);
}
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