Implement Blob support for XMLHttpRequest

Summary:
This PR is a followup to https://github.com/facebook/react-native/pull/11417 and should be merged after that one is merged.

  1. Add support for creating blobs from strings, not just other blobs
  1. Add the `File` constructor which is a superset of `Blob`
  1. Add the `FileReader` API which can be used to read blobs as strings or data url (base64)
  1. Add support for uploading and downloading blobs via `XMLHttpRequest` and `fetch`
  1. Add ability to download local files on Android so you can do `fetch(uri).then(res => res.blob())` to get a blob for a local file (iOS already supported this)

  1. Clone the repo https://github.com/expo/react-native-blob-test
  1. Change the `package.json` and update `react-native` dependency to point to this branch, then run `npm install`
  1. Run the `server.js` file with `node server.js`
  1. Open the `index.common.js` file and replace `localhost` with your computer's IP address
  1. Start the packager with `yarn start` and run the app on your device

If everything went well, all tests should pass, and you should see a screen like this:

![screen shot 2017-06-08 at 7 53 08 pm](https://user-images.githubusercontent.com/1174278/26936407-435bbce2-4c8c-11e7-9ae3-eb104e46961e.png)!

Pull to rerun all tests or tap on specific test to re-run it

  [GENERAL] [FEATURE] [Blob] - Implement blob support for XMLHttpRequest
Closes https://github.com/facebook/react-native/pull/11573

Reviewed By: shergin

Differential Revision: D6082054

Pulled By: hramos

fbshipit-source-id: cc9c174fdefdfaf6e5d9fd7b300120a01a50e8c1
This commit is contained in:
Satyajit Sahoo
2018-01-26 09:06:14 -08:00
committed by Facebook Github Bot
parent 3fc33bb54f
commit be56a3efee
40 changed files with 2060 additions and 386 deletions

View File

@@ -131,12 +131,20 @@ static NSString *RCTGenerateFormBoundary()
NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
std::mutex _handlersLock;
NSArray<id<RCTURLRequestHandler>> *_handlers;
NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
}
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)invalidate
{
_requestHandlers = nil;
_responseHandlers = nil;
}
- (NSArray<NSString *> *)supportedEvents
{
return @[@"didCompleteNetworkResponse",
@@ -297,6 +305,8 @@ RCT_EXPORT_MODULE()
*
* - {"formData": [...]}: list of data payloads that will be combined into a multipart/form-data request
*
* - {"blob": {...}}: an object representing a blob
*
* If successful, the callback be called with a result dictionary containing the following (optional) keys:
*
* - @"body" (NSData): the body of the request
@@ -312,6 +322,15 @@ RCT_EXPORT_MODULE()
if (!query) {
return callback(nil, nil);
}
for (id<RCTNetworkingRequestHandler> handler in _requestHandlers) {
if ([handler canHandleNetworkingRequest:query]) {
// @lint-ignore FBOBJCUNTYPEDCOLLECTION1
NSDictionary *body = [handler handleNetworkingRequest:query];
if (body) {
return callback(nil, body);
}
}
}
NSData *body = [RCTConvert NSData:query[@"string"]];
if (body) {
return callback(nil, @{@"body": body});
@@ -417,6 +436,7 @@ RCT_EXPORT_MODULE()
- (void)sendData:(NSData *)data
responseType:(NSString *)responseType
response:(NSURLResponse *)response
forTask:(RCTNetworkTask *)task
{
RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
@@ -425,23 +445,31 @@ RCT_EXPORT_MODULE()
return;
}
NSString *responseString;
if ([responseType isEqualToString:@"text"]) {
// No carry storage is required here because the entire data has been loaded.
responseString = [RCTNetworking decodeTextData:data fromResponse:task.response withCarryData:nil];
if (!responseString) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
id responseData = nil;
for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
if ([handler canHandleNetworkingResponse:responseType]) {
responseData = [handler handleNetworkingResponse:response data:data];
break;
}
} else if ([responseType isEqualToString:@"base64"]) {
responseString = [data base64EncodedStringWithOptions:0];
} else {
RCTLogWarn(@"Invalid responseType: %@", responseType);
return;
}
NSArray<id> *responseJSON = @[task.requestID, responseString];
[self sendEventWithName:@"didReceiveNetworkData" body:responseJSON];
if (!responseData) {
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);
return;
}
}
[self sendEventWithName:@"didReceiveNetworkData" body:@[task.requestID, responseData]];
}
- (void)sendRequest:(NSURLRequest *)request
@@ -523,7 +551,10 @@ RCT_EXPORT_MODULE()
// 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"])) {
[strongSelf sendData:data responseType:responseType forTask:task];
[strongSelf sendData:data
responseType:responseType
response:response
forTask:task];
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
@@ -553,6 +584,32 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API
- (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];
}
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];