mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
01c70f2fb9
Summary: On iOS in React Native, immediately after successfully connecting to a WiFi network, if 4G is also on, it is possible that network requests sent via fetch get sent over 4G instead of WiFi for several seconds (between 1 and 40+ seconds depending on the device and network in my experience). If the calls are meant to be communicating with a local Wireless Device (such as a wireless router, etc.), then API calls that get sent over 4G to the internet are lost. Note that Reachability/NetInfo are saying that we are connected to a WiFi network and are not sufficient to prevent this from happening. To prevent this, I would like to be able to set an instance property that iOS exposes for network requests allowsCellularAccess to NO to ensure a request is never sent over 4G. This code needs to be able to accept a flag to allow the user to override the default value of YES, but only when they explicitly opt in to this decision. I am not sure the best place to set and pass in this value, so I created [this issue](https://github.com/react-native-community/discussions-and-proposals/issues/113), where it was recommended that I make a PR and discuss how best to do this here. Before this PR could be merged we would need to allow the NO to be configurable in some way. Additional information about the [allowsCellularAccess](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409406-allowscellularaccess?language=objc) property can be found in the [Apple documentation.](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/Platform-SpecificNetworkingTechnologies/Platform-SpecificNetworkingTechnologies.html#//apple_ref/doc/uid/TP40010220-CH212-SW9) [iOS] [Added] - Ability to force network requests to use WiFi using the allowsCellularAccess property. This can ensure that network requests are sent over WiFi if communicating with a local hardware device and is accomplished by setting a flag. Default behavior of allowing network connections over cellular networks when available is unchanged. Pull Request resolved: https://github.com/facebook/react-native/pull/24242 Differential Revision: D15316760 Pulled By: cpojer fbshipit-source-id: 96df43b4eff6b4301c853df6b2e5c6e1bb1abda0
182 lines
6.0 KiB
Plaintext
182 lines
6.0 KiB
Plaintext
/**
|
|
* 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 "RCTHTTPRequestHandler.h"
|
|
|
|
#import <mutex>
|
|
|
|
#import "RCTNetworking.h"
|
|
|
|
@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate>
|
|
|
|
@end
|
|
|
|
@implementation RCTHTTPRequestHandler
|
|
{
|
|
NSMapTable *_delegates;
|
|
NSURLSession *_session;
|
|
std::mutex _mutex;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
@synthesize methodQueue = _methodQueue;
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (void)invalidate
|
|
{
|
|
dispatch_async(self->_methodQueue, ^{
|
|
[self->_session invalidateAndCancel];
|
|
self->_session = nil;
|
|
});
|
|
}
|
|
|
|
- (BOOL)isValid
|
|
{
|
|
// if session == nil and delegates != nil, we've been invalidated
|
|
return _session || !_delegates;
|
|
}
|
|
|
|
#pragma mark - NSURLRequestHandler
|
|
|
|
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
|
{
|
|
static NSSet<NSString *> *schemes = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
// technically, RCTHTTPRequestHandler can handle file:// as well,
|
|
// but it's less efficient than using RCTFileRequestHandler
|
|
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
|
|
});
|
|
return [schemes containsObject:request.URL.scheme.lowercaseString];
|
|
}
|
|
|
|
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
|
|
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
|
{
|
|
// Lazy setup
|
|
if (!_session && [self isValid]) {
|
|
// You can override default NSURLSession instance property allowsCellularAccess (default value YES)
|
|
// by providing the following key to your RN project (edit ios/project/Info.plist file in Xcode):
|
|
// <key>ReactNetworkForceWifiOnly</key> <string>YES</string>
|
|
// This will set allowsCellularAccess to NO and force Wifi only for all network calls on iOS
|
|
// If you do not want to override default behavior, do nothing or set key with value "NO"
|
|
NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
|
NSString *useWifiOnly = [infoDictionary objectForKey:@"ReactNetworkForceWifiOnly"];
|
|
|
|
NSOperationQueue *callbackQueue = [NSOperationQueue new];
|
|
callbackQueue.maxConcurrentOperationCount = 1;
|
|
callbackQueue.underlyingQueue = [[_bridge networking] methodQueue];
|
|
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
// set allowsCellularAccess to NO ONLY if key ReactNetworkForceWifiOnly exists AND string value is "YES"
|
|
NSString *compareKeyToForceWifiOnly = @"YES";
|
|
if (useWifiOnly) {
|
|
configuration.allowsCellularAccess = ![compareKeyToForceWifiOnly isEqualToString:useWifiOnly];
|
|
}
|
|
[configuration setHTTPShouldSetCookies:YES];
|
|
[configuration setHTTPCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
|
|
[configuration setHTTPCookieStorage:[NSHTTPCookieStorage sharedHTTPCookieStorage]];
|
|
_session = [NSURLSession sessionWithConfiguration:configuration
|
|
delegate:self
|
|
delegateQueue:callbackQueue];
|
|
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
_delegates = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
|
|
valueOptions:NSPointerFunctionsStrongMemory
|
|
capacity:0];
|
|
}
|
|
__block NSURLSessionDataTask *task = nil;
|
|
dispatch_sync(self->_methodQueue, ^{
|
|
task = [self->_session dataTaskWithRequest:request];
|
|
});
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
[_delegates setObject:delegate forKey:task];
|
|
}
|
|
[task resume];
|
|
return task;
|
|
}
|
|
|
|
- (void)cancelRequest:(NSURLSessionDataTask *)task
|
|
{
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
[_delegates removeObjectForKey:task];
|
|
}
|
|
[task cancel];
|
|
}
|
|
|
|
#pragma mark - NSURLSession delegate
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
task:(NSURLSessionTask *)task
|
|
didSendBodyData:(int64_t)bytesSent
|
|
totalBytesSent:(int64_t)totalBytesSent
|
|
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
|
|
{
|
|
id<RCTURLRequestDelegate> delegate;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
delegate = [_delegates objectForKey:task];
|
|
}
|
|
[delegate URLRequest:task didSendDataWithProgress:totalBytesSent];
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
task:(NSURLSessionTask *)task
|
|
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
|
newRequest:(NSURLRequest *)request
|
|
completionHandler:(void (^)(NSURLRequest *))completionHandler
|
|
{
|
|
// Reset the cookies on redirect.
|
|
// This is necessary because we're not letting iOS handle cookies by itself
|
|
NSMutableURLRequest *nextRequest = [request mutableCopy];
|
|
|
|
NSArray<NSHTTPCookie *> *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL];
|
|
nextRequest.allHTTPHeaderFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
|
|
completionHandler(nextRequest);
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
dataTask:(NSURLSessionDataTask *)task
|
|
didReceiveResponse:(NSURLResponse *)response
|
|
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
|
|
{
|
|
id<RCTURLRequestDelegate> delegate;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
delegate = [_delegates objectForKey:task];
|
|
}
|
|
[delegate URLRequest:task didReceiveResponse:response];
|
|
completionHandler(NSURLSessionResponseAllow);
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
dataTask:(NSURLSessionDataTask *)task
|
|
didReceiveData:(NSData *)data
|
|
{
|
|
id<RCTURLRequestDelegate> delegate;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
delegate = [_delegates objectForKey:task];
|
|
}
|
|
[delegate URLRequest:task didReceiveData:data];
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
|
|
{
|
|
id<RCTURLRequestDelegate> delegate;
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
delegate = [_delegates objectForKey:task];
|
|
[_delegates removeObjectForKey:task];
|
|
}
|
|
[delegate URLRequest:task didCompleteWithError:error];
|
|
}
|
|
|
|
@end
|