123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import <mutex>
- #import <FBReactNativeSpec/FBReactNativeSpec.h>
- #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/RCTUtils.h>
- #import <React/RCTHTTPRequestHandler.h>
- #import "RCTNetworkPlugins.h"
- typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSDictionary<NSString *, id> *result);
- NSString *const RCTNetworkingPHUploadHackScheme = @"ph-upload";
- @interface RCTNetworking () <NativeNetworkingIOSSpec>
- - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(NSDictionary<NSString *, id> *)data
- callback:(RCTHTTPQueryResult)callback;
- @end
- /**
- * Helper to convert FormData payloads into multipart/formdata requests.
- */
- @interface RCTHTTPFormDataHelper : NSObject
- @property (nonatomic, weak) RCTNetworking *networker;
- @end
- @implementation RCTHTTPFormDataHelper
- {
- NSMutableArray<NSDictionary<NSString *, id> *> *_parts;
- NSMutableData *_multipartBody;
- RCTHTTPQueryResult _callback;
- NSString *_boundary;
- }
- static NSString *RCTGenerateFormBoundary()
- {
- const size_t boundaryLength = 70;
- const char *boundaryChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.";
- char *bytes = (char*)malloc(boundaryLength);
- 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();
- }
- 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];
- }
- - (RCTURLRequestCancellationBlock)process:(NSArray<NSDictionary *> *)formData
- callback:(RCTHTTPQueryResult)callback
- {
- RCTAssertThread(_networker.methodQueue, @"process: must be called on method queue");
- if (formData.count == 0) {
- return callback(nil, nil);
- }
- _parts = [formData mutableCopy];
- _callback = callback;
- _multipartBody = [NSMutableData new];
- _boundary = RCTGenerateFormBoundary();
- for (NSUInteger i = 0; i < _parts.count; i++) {
- NSString *uri = _parts[i][@"uri"];
- if (uri && [[uri substringToIndex:@"ph:".length] caseInsensitiveCompare:@"ph:"] == NSOrderedSame) {
- uri = [RCTNetworkingPHUploadHackScheme stringByAppendingString:[uri substringFromIndex:@"ph".length]];
- NSMutableDictionary *mutableDict = [_parts[i] mutableCopy];
- mutableDict[@"uri"] = uri;
- _parts[i] = mutableDict;
- }
- }
- return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
- return [self handleResult:result error:error];
- }];
- }
- - (RCTURLRequestCancellationBlock)handleResult:(NSDictionary<NSString *, id> *)result
- error:(NSError *)error
- {
- RCTAssertThread(_networker.methodQueue, @"handleResult: must be called on method queue");
- if (error) {
- return _callback(error, nil);
- }
- // Start with boundary.
- [_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
- dataUsingEncoding:NSUTF8StringEncoding]];
- // Print headers.
- NSMutableDictionary<NSString *, NSString *> *headers = [_parts[0][@"headers"] mutableCopy];
- NSString *partContentType = result[@"contentType"];
- if (partContentType != nil) {
- headers[@"content-type"] = partContentType;
- }
- [headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
- [self->_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
- dataUsingEncoding:NSUTF8StringEncoding]];
- }];
- // Add the body.
- [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [_multipartBody appendData:result[@"body"]];
- [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [_parts removeObjectAtIndex:0];
- if (_parts.count) {
- return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary<NSString *, id> *res) {
- return [self handleResult:res error:err];
- }];
- }
- // We've processed the last item. Finish and return.
- [_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});
- }
- @end
- /**
- * Bridge module that provides the JS interface to the network stack.
- */
- @implementation RCTNetworking
- {
- NSMutableDictionary<NSNumber *, RCTNetworkTask *> *_tasksByRequestID;
- std::mutex _handlersLock;
- NSArray<id<RCTURLRequestHandler>> *_handlers;
- NSArray<id<RCTURLRequestHandler>> * (^_handlersProvider)(void);
- NSMutableArray<id<RCTNetworkingRequestHandler>> *_requestHandlers;
- NSMutableArray<id<RCTNetworkingResponseHandler>> *_responseHandlers;
- }
- @synthesize methodQueue = _methodQueue;
- RCT_EXPORT_MODULE()
- - (instancetype)initWithHandlersProvider:(NSArray<id<RCTURLRequestHandler>> * (^)(void))getHandlers
- {
- if (self = [super init]) {
- _handlersProvider = getHandlers;
- }
- return self;
- }
- - (void)invalidate
- {
- for (NSNumber *requestID in _tasksByRequestID) {
- [_tasksByRequestID[requestID] cancel];
- }
- [_tasksByRequestID removeAllObjects];
- _handlers = nil;
- _requestHandlers = nil;
- _responseHandlers = nil;
- }
- - (NSArray<NSString *> *)supportedEvents
- {
- return @[@"didCompleteNetworkResponse",
- @"didReceiveNetworkResponse",
- @"didSendNetworkData",
- @"didReceiveNetworkIncrementalData",
- @"didReceiveNetworkDataProgress",
- @"didReceiveNetworkData"];
- }
- - (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
- {
- if (!request.URL) {
- return nil;
- }
- {
- std::lock_guard<std::mutex> lock(_handlersLock);
- if (!_handlers) {
- if (_handlersProvider) {
- _handlers = _handlersProvider();
- } else {
- _handlers = [self.bridge modulesConformingToProtocol:@protocol(RCTURLRequestHandler)];
- }
- // Get handlers, sorted in reverse priority order (highest priority first)
- _handlers = [_handlers 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;
- }
- }];
- }
- }
- if (RCT_DEBUG) {
- // Check for handler conflicts
- float previousPriority = 0;
- id<RCTURLRequestHandler> previousHandler = nil;
- for (id<RCTURLRequestHandler> handler in _handlers) {
- float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
- if (previousHandler && priority < previousPriority) {
- return previousHandler;
- }
- 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;
- }
- }
- }
- return previousHandler;
- }
- // Normal code path
- for (id<RCTURLRequestHandler> handler in _handlers) {
- if ([handler canHandleRequest:request]) {
- return handler;
- }
- }
- return nil;
- }
- - (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;
- }
- - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary<NSString *, id> *)query
- completionBlock:(void (^)(NSURLRequest *request))block
- {
- RCTAssertThread(_methodQueue, @"buildRequest: must be called on method queue");
- NSURL *URL = [RCTConvert NSURL:query[@"url"]]; // this is marked as nullable in JS, but should not be null
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
- request.HTTPMethod = [RCTConvert NSString:RCTNilIfNull(query[@"method"])].uppercaseString ?: @"GET";
- request.HTTPShouldHandleCookies = [RCTConvert BOOL:query[@"withCredentials"]];
- if (request.HTTPShouldHandleCookies == YES) {
- // 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];
- }
- }];
- request.timeoutInterval = [RCTConvert NSTimeInterval:query[@"timeout"]];
- NSDictionary<NSString *, id> *data = [RCTConvert NSDictionary:RCTNilIfNull(query[@"data"])];
- NSString *trackingName = data[@"trackingName"];
- if (trackingName) {
- [NSURLProtocol setProperty:trackingName
- forKey:@"trackingName"
- inRequest:request];
- }
- return [self processDataForHTTPQuery:data callback:^(NSError *error, NSDictionary<NSString *, id> *result) {
- if (error) {
- RCTLogError(@"Error processing request body: %@", error);
- // Ideally we'd circle back to JS here and notify an error/abort on the request.
- return (RCTURLRequestCancellationBlock)nil;
- }
- request.HTTPBody = result[@"body"];
- NSString *dataContentType = result[@"contentType"];
- NSString *requestContentType = [request valueForHTTPHeaderField:@"Content-Type"];
- BOOL isMultipart = [dataContentType hasPrefix:@"multipart"];
- // 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"];
- }
- // Gzip the request body
- if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
- request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
- [request setValue:(@(request.HTTPBody.length)).description forHTTPHeaderField:@"Content-Length"];
- }
- dispatch_async(self->_methodQueue, ^{
- block(request);
- });
- return (RCTURLRequestCancellationBlock)nil;
- }];
- }
- - (BOOL)canHandleRequest:(NSURLRequest *)request
- {
- return [self handlerForRequest:request] != nil;
- }
- /**
- * 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
- *
- * - {"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
- *
- * - @"contentType" (NSString): the content type header of the request
- *
- */
- - (RCTURLRequestCancellationBlock)processDataForHTTPQuery:(nullable NSDictionary<NSString *, id> *)query callback:
- (RCTURLRequestCancellationBlock (^)(NSError *error, NSDictionary<NSString *, id> *result))callback
- {
- RCTAssertThread(_methodQueue, @"processDataForHTTPQuery: must be called on method queue");
- 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});
- }
- NSString *base64String = [RCTConvert NSString:query[@"base64"]];
- if (base64String) {
- NSData *data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
- return callback(nil, @{@"body": data});
- }
- NSURLRequest *request = [RCTConvert NSURLRequest:query[@"uri"]];
- if (request) {
- __block RCTURLRequestCancellationBlock cancellationBlock = nil;
- RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
- dispatch_async(self->_methodQueue, ^{
- cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
- });
- }];
- [task start];
- __weak RCTNetworkTask *weakTask = task;
- return ^{
- [weakTask cancel];
- if (cancellationBlock) {
- cancellationBlock();
- }
- };
- }
- NSArray<NSDictionary *> *formData = [RCTConvert NSDictionaryArray:query[@"formData"]];
- if (formData) {
- RCTHTTPFormDataHelper *formDataHelper = [RCTHTTPFormDataHelper new];
- formDataHelper.networker = self;
- return [formDataHelper process:formData callback:callback];
- }
- // Nothing in the data payload, at least nothing we could understand anyway.
- // Ignore and treat it as if it were null.
- return callback(nil, nil);
- }
- + (NSString *)decodeTextData:(NSData *)data fromResponse:(NSURLResponse *)response withCarryData:(NSMutableData *)inputCarryData
- {
- NSStringEncoding encoding = NSUTF8StringEncoding;
- if (response.textEncodingName) {
- CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
- encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
- }
- NSMutableData *currentCarryData = inputCarryData ?: [NSMutableData new];
- [currentCarryData appendData:data];
- // Attempt to decode text
- NSString *encodedResponse = [[NSString alloc] initWithData:currentCarryData encoding:encoding];
- 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).
- 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);
- NSUInteger removedBytes = 1;
- while (removedBytes < maxCharLength) {
- encodedResponse = [[NSString alloc] initWithData:[currentCarryData subdataWithRange:NSMakeRange(0, currentCarryData.length - removedBytes)]
- encoding:encoding];
- if (encodedResponse != nil) {
- break;
- }
- 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];
- }
- }
- if (inputCarryData) {
- NSUInteger encodedResponseLength = [encodedResponse dataUsingEncoding:encoding].length;
- // 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];
- }
- }
- return encodedResponse;
- }
- - (void)sendData:(NSData *)data
- responseType:(NSString *)responseType
- response:(NSURLResponse *)response
- forTask:(RCTNetworkTask *)task
- {
- RCTAssertThread(_methodQueue, @"sendData: must be called on method queue");
- id responseData = nil;
- for (id<RCTNetworkingResponseHandler> handler in _responseHandlers) {
- if ([handler canHandleNetworkingResponse:responseType]) {
- responseData = [handler handleNetworkingResponse:response data:data];
- break;
- }
- }
- if (!responseData) {
- if (data.length == 0) {
- return;
- }
- 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
- responseType:(NSString *)responseType
- incrementalUpdates:(BOOL)incrementalUpdates
- responseSender:(RCTResponseSenderBlock)responseSender
- {
- RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue");
- __weak __typeof(self) weakSelf = self;
- __block RCTNetworkTask *task;
- RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
- NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
- [weakSelf sendEventWithName:@"didSendNetworkData" body:responseJSON];
- };
- RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
- 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];
- [weakSelf sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON];
- };
- // 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"]) {
- // 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.
- NSMutableData *incrementalDataCarry = [NSMutableData new];
- incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
- NSUInteger initialCarryLength = incrementalDataCarry.length;
- NSString *responseString = [RCTNetworking decodeTextData:data
- fromResponse:task.response
- withCarryData:incrementalDataCarry];
- if (!responseString) {
- RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
- return;
- }
- // 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)];
- [weakSelf sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
- };
- } else {
- downloadProgressBlock = ^(int64_t progress, int64_t total) {
- NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)];
- [weakSelf sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
- };
- }
- }
- RCTURLRequestCompletionBlock completionBlock =
- ^(NSURLResponse *response, NSData *data, NSError *error) {
- __typeof(self) strongSelf = weakSelf;
- if (!strongSelf) {
- return;
- }
- // 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
- response:response
- forTask:task];
- }
- NSArray *responseJSON = @[task.requestID,
- RCTNullIfNil(error.localizedDescription),
- error.code == kCFURLErrorTimedOut ? @YES : @NO
- ];
- [strongSelf sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
- [strongSelf->_tasksByRequestID removeObjectForKey:task.requestID];
- };
- task = [self networkTaskWithRequest:request completionBlock:completionBlock];
- task.downloadProgressBlock = downloadProgressBlock;
- task.incrementalDataBlock = incrementalDataBlock;
- task.responseBlock = responseBlock;
- task.uploadProgressBlock = uploadProgressBlock;
- if (task.requestID) {
- if (!_tasksByRequestID) {
- _tasksByRequestID = [NSMutableDictionary new];
- }
- _tasksByRequestID[task.requestID] = task;
- responseSender(@[task.requestID]);
- }
- [task start];
- }
- #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];
- if (!handler) {
- RCTLogError(@"No suitable URL request handler found for %@", request.URL);
- return nil;
- }
- RCTNetworkTask *task = [[RCTNetworkTask alloc] initWithRequest:request
- handler:handler
- callbackQueue:_methodQueue];
- task.completionBlock = completionBlock;
- return task;
- }
- #pragma mark - JS API
- RCT_EXPORT_METHOD(sendRequest:(JS::NativeNetworkingIOS::SpecSendRequestQuery &)query
- callback:(RCTResponseSenderBlock)responseSender)
- {
- NSDictionary *queryDict = @{
- @"method": query.method(),
- @"url": query.url(),
- @"data": query.data(),
- @"headers": query.headers(),
- @"responseType": query.responseType(),
- @"incrementalUpdates": @(query.incrementalUpdates()),
- @"timeout": @(query.timeout()),
- @"withCredentials": @(query.withCredentials()),
- };
-
- // 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
- [self buildRequest:queryDict completionBlock:^(NSURLRequest *request) {
- NSString *responseType = [RCTConvert NSString:queryDict[@"responseType"]];
- BOOL incrementalUpdates = [RCTConvert BOOL:queryDict[@"incrementalUpdates"]];
- [self sendRequest:request
- responseType:responseType
- incrementalUpdates:incrementalUpdates
- responseSender:responseSender];
- }];
- }
- RCT_EXPORT_METHOD(abortRequest:(double)requestID)
- {
- [_tasksByRequestID[[NSNumber numberWithDouble:requestID]] cancel];
- [_tasksByRequestID removeObjectForKey:[NSNumber numberWithDouble:requestID]];
- }
- 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]);
- }
- - (std::shared_ptr<facebook::react::TurboModule>)
- getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
- nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
- perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
- {
- return std::make_shared<facebook::react::NativeNetworkingIOSSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
- }
- @end
- @implementation RCTBridge (RCTNetworking)
- - (RCTNetworking *)networking
- {
- return [self moduleForClass:[RCTNetworking class]];
- }
- @end
- Class RCTNetworkingCls(void) {
- return RCTNetworking.class;
- }
|