Adam Burdette dbafc29e60 Fix #8615: NetInfo.isConnected for iOS
Summary:
This is fixing #8615. The problem was that, on initialization, the `_connectionType` variable was still set to its default value of `RCTConnectionTypeUnknown`. The exposed API method did nothing to determine this unless a subscription had be established and then the method would simply return the last reported value. Instead, the exposed oneshot API call now actually checks the connection status through the same methods as the subscription and updates RCTNetInfo’s values before returning.

In order to avoid reporting events without a subscription, a flag is set and unset on calls to start/stopObserving.

- start app
- observe the (in)correct reporting of the manual status
- change network status to offline
- press refresh
- observe the manual fetch
- start subscription
- change network status to online
- press refresh to show that the manual refresh works (only now working for current RN version)
- change network status to offline
- stop subscription
- change network status to online
- press refresh to show manual refresh does(n't) work without subscription
- start subscription to show it updates to current

Current Behavior: https://drive.google.com/file/d/1Ods6HORgp_vfm1mQVjGwhtH1D7issxjo/view?usp=sharing
Fixed Behavior: https://drive.google.com/file/d/11H1UOF33LeMGvXEOoapU62ARDSb7qoYv/view?usp=sharing

[IOS] [BUGFIX] [Libraries\Network\RCTNetInfo.m] - Fixed #8615, `iOS: NetInfo.isConnected returns always false`, by decoupling the fetch from the status of the subscription.
Closes https://github.com/facebook/react-native/pull/17397

Differential Revision: D7102771

Pulled By: hramos

fbshipit-source-id: ea11eb0b1a7ca641fc43da2fe172cf7b2597de4a
2018-02-27 14:16:20 -08:00

174 lines
6.5 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNetInfo.h"
#if !TARGET_OS_TV
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
#import <React/RCTAssert.h>
#import <React/RCTBridge.h>
#import <React/RCTEventDispatcher.h>
// Based on the ConnectionType enum described in the W3C Network Information API spec
// (https://wicg.github.io/netinfo/).
static NSString *const RCTConnectionTypeUnknown = @"unknown";
static NSString *const RCTConnectionTypeNone = @"none";
static NSString *const RCTConnectionTypeWifi = @"wifi";
static NSString *const RCTConnectionTypeCellular = @"cellular";
// Based on the EffectiveConnectionType enum described in the W3C Network Information API spec
// (https://wicg.github.io/netinfo/).
static NSString *const RCTEffectiveConnectionTypeUnknown = @"unknown";
static NSString *const RCTEffectiveConnectionType2g = @"2g";
static NSString *const RCTEffectiveConnectionType3g = @"3g";
static NSString *const RCTEffectiveConnectionType4g = @"4g";
// The RCTReachabilityState* values are deprecated.
static NSString *const RCTReachabilityStateUnknown = @"unknown";
static NSString *const RCTReachabilityStateNone = @"none";
static NSString *const RCTReachabilityStateWifi = @"wifi";
static NSString *const RCTReachabilityStateCell = @"cell";
@implementation RCTNetInfo
{
SCNetworkReachabilityRef _reachability;
NSString *_connectionType;
NSString *_effectiveConnectionType;
NSString *_statusDeprecated;
NSString *_host;
BOOL _isObserving;
}
RCT_EXPORT_MODULE()
static void RCTReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
{
RCTNetInfo *self = (__bridge id)info;
if ([self setReachabilityStatus:flags] && self->_isObserving) {
[self sendEventWithName:@"networkStatusDidChange" body:@{@"connectionType": self->_connectionType,
@"effectiveConnectionType": self->_effectiveConnectionType,
@"network_info": self->_statusDeprecated}];
}
}
#pragma mark - Lifecycle
- (instancetype)initWithHost:(NSString *)host
{
RCTAssertParam(host);
RCTAssert(![host hasPrefix:@"http"], @"Host value should just contain the domain, not the URL scheme.");
if ((self = [self init])) {
_host = [host copy];
}
return self;
}
- (NSArray<NSString *> *)supportedEvents
{
return @[@"networkStatusDidChange"];
}
- (void)startObserving
{
_isObserving = YES;
_connectionType = RCTConnectionTypeUnknown;
_effectiveConnectionType = RCTEffectiveConnectionTypeUnknown;
_statusDeprecated = RCTReachabilityStateUnknown;
_reachability = [self getReachabilityRef];
}
- (void)stopObserving
{
_isObserving = NO;
if (_reachability) {
SCNetworkReachabilityUnscheduleFromRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFRelease(_reachability);
}
}
- (SCNetworkReachabilityRef)getReachabilityRef
{
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, _host.UTF8String ?: "apple.com");
SCNetworkReachabilityContext context = { 0, ( __bridge void *)self, NULL, NULL, NULL };
SCNetworkReachabilitySetCallback(reachability, RCTReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
return reachability;
}
- (BOOL)setReachabilityStatus:(SCNetworkReachabilityFlags)flags
{
NSString *connectionType = RCTConnectionTypeUnknown;
NSString *effectiveConnectionType = RCTEffectiveConnectionTypeUnknown;
NSString *status = RCTReachabilityStateUnknown;
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0 ||
(flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0) {
connectionType = RCTConnectionTypeNone;
status = RCTReachabilityStateNone;
}
#if !TARGET_OS_TV
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
connectionType = RCTConnectionTypeCellular;
status = RCTReachabilityStateCell;
CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init];
if (netinfo) {
if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) {
effectiveConnectionType = RCTEffectiveConnectionType2g;
} else if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyWCDMA] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSDPA] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyHSUPA] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORev0] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevA] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMAEVDORevB] ||
[netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyeHRPD]) {
effectiveConnectionType = RCTEffectiveConnectionType3g;
} else if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
effectiveConnectionType = RCTEffectiveConnectionType4g;
}
}
}
#endif
else {
connectionType = RCTConnectionTypeWifi;
status = RCTReachabilityStateWifi;
}
if (![connectionType isEqualToString:self->_connectionType] ||
![effectiveConnectionType isEqualToString:self->_effectiveConnectionType] ||
![status isEqualToString:self->_statusDeprecated]) {
self->_connectionType = connectionType;
self->_effectiveConnectionType = effectiveConnectionType;
self->_statusDeprecated = status;
return YES;
}
return NO;
}
#pragma mark - Public API
RCT_EXPORT_METHOD(getCurrentConnectivity:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject)
{
SCNetworkReachabilityRef reachability = [self getReachabilityRef];
CFRelease(reachability);
resolve(@{@"connectionType": _connectionType ?: RCTConnectionTypeUnknown,
@"effectiveConnectionType": _effectiveConnectionType ?: RCTEffectiveConnectionTypeUnknown,
@"network_info": _statusDeprecated ?: RCTReachabilityStateUnknown});
}
@end