From 1e8a1307af6d364468852ff82eb454f63f1c0d2a Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 31 May 2019 13:33:56 -0400 Subject: [PATCH] Update netInfo to check for internet connectivity (#2850) * Update netInfo to check for internet connectivity instead of using apple.com as a reference * Fix network indicator position on tablets --- Makefile | 5 +- .../network_indicator/network_indicator.js | 9 +- app/utils/network.js | 15 +- native_modules/RNCNetInfo.m | 228 ++++++++++++++++++ package-lock.json | 87 ++----- package.json | 4 +- 6 files changed, 265 insertions(+), 83 deletions(-) create mode 100644 native_modules/RNCNetInfo.m diff --git a/Makefile b/Makefile index 626af86ab..9645fbd3a 100644 --- a/Makefile +++ b/Makefile @@ -77,9 +77,12 @@ post-install: @# Need to copy custom RNDocumentPicker.m that implements direct access to the document picker in iOS @cp ./native_modules/RNDocumentPicker.m node_modules/react-native-document-picker/ios/RNDocumentPicker/RNDocumentPicker.m - # Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set + @# Need to copy custom RNCookieManagerIOS.m that fixes a crash when cookies does not have expiration date set @cp ./native_modules/RNCookieManagerIOS.m node_modules/react-native-cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m + @# Need to copy custom RNCNetInfo.m that checks for internet connectivity instead of reaching a host by default + @cp ./native_modules/RNCNetInfo.m node_modules/@react-native-community/netinfo/ios/RNCNetInfo.m + @rm -f node_modules/intl/.babelrc @# Hack to get react-intl and its dependencies to work with react-native @# Based off of https://github.com/este/este/blob/master/gulp/native-fix.js diff --git a/app/components/network_indicator/network_indicator.js b/app/components/network_indicator/network_indicator.js index 62b340117..71a40b28a 100644 --- a/app/components/network_indicator/network_indicator.js +++ b/app/components/network_indicator/network_indicator.js @@ -19,7 +19,6 @@ import IonIcon from 'react-native-vector-icons/Ionicons'; import FormattedText from 'app/components/formatted_text'; import {DeviceTypes, ViewTypes} from 'app/constants'; import mattermostBucket from 'app/mattermost_bucket'; -import mattermostManaged from 'app/mattermost_managed'; import PushNotifications from 'app/push_notifications'; import networkConnectionListener, {checkConnection} from 'app/utils/network'; import {t} from 'app/utils/i18n'; @@ -36,7 +35,6 @@ const { IOS_TOP_LANDSCAPE, IOS_TOP_PORTRAIT, IOSX_TOP_PORTRAIT, - STATUS_BAR_HEIGHT, } = ViewTypes; export default class NetworkIndicator extends PureComponent { @@ -142,7 +140,7 @@ export default class NetworkIndicator extends PureComponent { const {connection} = this.props.actions; clearTimeout(this.connectionRetryTimeout); - NetInfo.isConnected.fetch().then(async (isConnected) => { + NetInfo.fetch().then(async ({isConnected}) => { const {hasInternet, serverReachable} = await checkConnection(isConnected); connection(hasInternet); @@ -204,9 +202,7 @@ export default class NetworkIndicator extends PureComponent { return IOS_TOP_LANDSCAPE; } else if (isX) { return IOSX_TOP_PORTRAIT; - } else if (isLandscape && DeviceTypes.IS_TABLET && mattermostManaged.hasSafeAreaInsets) { - return IOS_TOP_LANDSCAPE + STATUS_BAR_HEIGHT; - } else if (isLandscape) { + } else if (isLandscape && !DeviceTypes.IS_TABLET) { return IOS_TOP_LANDSCAPE; } @@ -233,7 +229,6 @@ export default class NetworkIndicator extends PureComponent { handleAppStateChange = async (appState) => { const {actions, currentChannelId} = this.props; const active = appState === 'active'; - if (active) { this.connect(true); diff --git a/app/utils/network.js b/app/utils/network.js index a441f12d9..0502c3087 100644 --- a/app/utils/network.js +++ b/app/utils/network.js @@ -13,6 +13,10 @@ import mattermostManaged from 'app/mattermost_managed'; let certificate = ''; let previousState; export async function checkConnection(isConnected) { + if (!isConnected) { + return {hasInternet: false, serverReachable: false}; + } + if (!Client4.getBaseRoute().startsWith('http')) { // If we don't have a connection or have a server yet, return the default implementation return {hasInternet: isConnected, serverReachable: false}; @@ -43,6 +47,7 @@ export async function checkConnection(isConnected) { auto: true, waitsForConnectivity, timeoutIntervalForResource, + timeout: 3000, }; if (Platform.OS === 'ios' && certificate === '') { @@ -59,7 +64,7 @@ export async function checkConnection(isConnected) { } function handleConnectionChange(onChange) { - return async (isConnected) => { + return async ({isConnected}) => { if (isConnected !== previousState) { previousState = isConnected; @@ -72,13 +77,7 @@ function handleConnectionChange(onChange) { export default function networkConnectionListener(onChange) { const connectionChanged = handleConnectionChange(onChange); - - NetInfo.isConnected.fetch().then((isConnected) => { - NetInfo.isConnected.addEventListener('connectionChange', connectionChanged); - connectionChanged(isConnected); - }); - - const removeEventListener = () => NetInfo.isConnected.removeEventListener('connectionChange', connectionChanged); + const removeEventListener = NetInfo.addEventListener(connectionChanged); return { removeEventListener, diff --git a/native_modules/RNCNetInfo.m b/native_modules/RNCNetInfo.m new file mode 100644 index 000000000..0985fec2b --- /dev/null +++ b/native_modules/RNCNetInfo.m @@ -0,0 +1,228 @@ +/** + * 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 "RNCNetInfo.h" + +#if !TARGET_OS_TV + #import +#endif +#import +#import +#import + +// Start of the Mattermost fix +#import +// End of the Mattermost fix + +// Based on the ConnectionType enum described in the W3C Network Information API spec +// (https://wicg.github.io/netinfo/). +static NSString *const RNCConnectionTypeUnknown = @"unknown"; +static NSString *const RNCConnectionTypeNone = @"none"; +static NSString *const RNCConnectionTypeWifi = @"wifi"; +static NSString *const RNCConnectionTypeCellular = @"cellular"; + +// Based on the EffectiveConnectionType enum described in the W3C Network Information API spec +// (https://wicg.github.io/netinfo/). +static NSString *const RNCCellularGeneration2g = @"2g"; +static NSString *const RNCCellularGeneration3g = @"3g"; +static NSString *const RNCCellularGeneration4g = @"4g"; + +@implementation RNCNetInfo +{ + SCNetworkReachabilityRef _firstTimeReachability; + SCNetworkReachabilityRef _reachability; + NSString *_connectionType; + BOOL _connectionExpensive; + NSString *_effectiveConnectionType; + BOOL _isObserving; + NSMutableSet *_firstTimeReachabilityResolvers; +} + +RCT_EXPORT_MODULE() + +static void RNCReachabilityCallback(__unused SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) +{ + RNCNetInfo *self = (__bridge id)info; + BOOL didSetReachabilityFlags = [self setReachabilityStatus:flags]; + + if (self->_firstTimeReachability) { + [self->_firstTimeReachabilityResolvers enumerateObjectsUsingBlock:^(RCTPromiseResolveBlock resolver, BOOL *stop) { + resolver([self currentState]); + }]; + + [self cleanUpFirstTimeReachability]; + [self->_firstTimeReachabilityResolvers removeAllObjects]; + } + + if (didSetReachabilityFlags && self->_isObserving) { + [self sendEventWithName:@"netInfo.networkStatusDidChange" body:[self currentState]]; + } +} + +// We need RNCReachabilityCallback's and module methods to be called on the same thread so that we can have +// guarantees about when we mess with the reachability callbacks. +- (dispatch_queue_t)methodQueue +{ + return dispatch_get_main_queue(); +} + +#pragma mark - Lifecycle + +- (NSArray *)supportedEvents +{ + return @[@"netInfo.networkStatusDidChange"]; +} + +- (void)startObserving +{ + _isObserving = YES; + _connectionType = RNCConnectionTypeUnknown; + _effectiveConnectionType = nil; + _reachability = [self getReachabilityRef]; +} + +- (void)stopObserving +{ + _isObserving = NO; + if (_reachability) { + SCNetworkReachabilityUnscheduleFromRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFRelease(_reachability); + } +} + +- (void)dealloc +{ + [self cleanUpFirstTimeReachability]; +} + +- (SCNetworkReachabilityRef)getReachabilityRef +{ + // Start of the Mattermost fix + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *) &zeroAddress); + // End of the Mattermost fix + SCNetworkReachabilityContext context = { 0, ( __bridge void *)self, NULL, NULL, NULL }; + SCNetworkReachabilitySetCallback(reachability, RNCReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); + + return reachability; +} + +- (BOOL)setReachabilityStatus:(SCNetworkReachabilityFlags)flags +{ + NSString *connectionType = RNCConnectionTypeUnknown; + bool connectionExpensive = false; + NSString *effectiveConnectionType = nil; + if ((flags & kSCNetworkReachabilityFlagsReachable) == 0 || + (flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0) { + connectionType = RNCConnectionTypeNone; + } + +#if !TARGET_OS_TV + + else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) { + connectionType = RNCConnectionTypeCellular; + connectionExpensive = true; + + CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init]; + if (netinfo) { + if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS] || + [netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || + [netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyCDMA1x]) { + effectiveConnectionType = RNCCellularGeneration2g; + } 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 = RNCCellularGeneration3g; + } else if ([netinfo.currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) { + effectiveConnectionType = RNCCellularGeneration4g; + } + } + } + +#endif + + else { + connectionType = RNCConnectionTypeWifi; + } + + if (![connectionType isEqualToString:self->_connectionType] || + ![effectiveConnectionType isEqualToString:self->_effectiveConnectionType]) { + self->_connectionType = connectionType; + self->_connectionExpensive = connectionExpensive; + self->_effectiveConnectionType = effectiveConnectionType; + return YES; + } + + return NO; +} + +- (void)cleanUpFirstTimeReachability +{ + if (_firstTimeReachability) { + SCNetworkReachabilityUnscheduleFromRunLoop(self->_firstTimeReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFRelease(self->_firstTimeReachability); + _firstTimeReachability = nil; + } +} + +- (id)currentState +{ + NSString *connectionType = self->_connectionType ?: RNCConnectionTypeUnknown; + NSString *effectiveConnectionType = self->_effectiveConnectionType; + + BOOL isConnected = ![connectionType isEqualToString:RNCConnectionTypeNone] && ![connectionType isEqualToString:RNCConnectionTypeUnknown]; + + NSMutableDictionary *details = nil; + if (isConnected) { + details = [NSMutableDictionary new]; + details[@"isConnectionExpensive"] = @(self->_connectionExpensive ?: false); + + if ([connectionType isEqualToString:RNCConnectionTypeCellular]) { + details[@"cellularGeneration"] = effectiveConnectionType ?: [NSNull null]; + } + } + + return @{ + @"type": connectionType, + @"isConnected": @(isConnected), + @"details": details ?: [NSNull null] + }; +} + +#pragma mark - Public API + +RCT_EXPORT_METHOD(getCurrentState:(RCTPromiseResolveBlock)resolve + reject:(__unused RCTPromiseRejectBlock)reject) +{ + // Setup the reacability listener if needed + if (!_firstTimeReachability) { + _firstTimeReachability = [self getReachabilityRef]; + } + + // Start of the Mattermost fix + if (_firstTimeReachability) { + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(_firstTimeReachability, &flags); + [self setReachabilityStatus:flags]; + + resolve([self currentState]); + + [self cleanUpFirstTimeReachability]; + } + // End of the Mattermost fix +} + +@end diff --git a/package-lock.json b/package-lock.json index 6c9b3a126..1411679e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3625,9 +3625,9 @@ } }, "@react-native-community/netinfo": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-2.0.10.tgz", - "integrity": "sha512-NrIzyLe0eSbhgMnHl2QdSEhaA7yXh6p9jzMomfUa//hoTXE+xbObGDdiWWSQm2bnXnZJg8XCU3AB9qzvqcuLnA==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-3.1.3.tgz", + "integrity": "sha512-T9fFuxnMSZdVtpygW3BdmoE2o70BXrryi95Xi7FnRYmdr7PtEjNxEziR8i+VvrXXNG8KkQ2Bngc0YUqrNxNMtA==" }, "@react-navigation/core": { "version": "3.4.1", @@ -4932,9 +4932,9 @@ "optional": true }, "bluebird": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", - "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", "dev": true }, "boolbase": { @@ -7365,27 +7365,6 @@ } } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -13718,7 +13697,7 @@ }, "mmjstool": { "version": "github:mattermost/mattermost-utilities#b55348242168df75da500fd813d4105c44eaea08", - "from": "github:mattermost/mattermost-utilities#b55348242168df75da500fd813d4105c44eaea08", + "from": "github:mattermost/mattermost-utilities", "dev": true, "requires": { "estree-walk": "2.2.0", @@ -18108,9 +18087,9 @@ } }, "tapable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.1.tgz", - "integrity": "sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, "temp": { @@ -18669,9 +18648,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "v8-compile-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", - "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, "validate-npm-package-license": { @@ -18732,9 +18711,9 @@ "dev": true }, "webpack-cli": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.0.tgz", - "integrity": "sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.2.tgz", + "integrity": "sha512-FLkobnaJJ+03j5eplxlI0TUxhGCOdfewspIGuvDVtpOlrAuKMFC57K42Ukxqs1tn8947/PM6tP95gQc0DCzRYA==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -18756,32 +18735,12 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", @@ -18804,14 +18763,6 @@ "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } } }, "find-up": { @@ -18900,6 +18851,12 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", diff --git a/package.json b/package.json index f15232eba..410e98ae3 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "dependencies": { "@babel/runtime": "7.4.4", "@react-native-community/async-storage": "1.4.0", - "@react-native-community/netinfo": "2.0.10", + "@react-native-community/netinfo": "3.1.3", "analytics-react-native": "1.2.0", "commonmark": "github:mattermost/commonmark.js#6c3136c18ae8bb7842f8491d160f370a334fd903", "commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#3a2ac19cab725ad28b170fdc1d397dddedcf87eb", @@ -97,7 +97,7 @@ "jest-enzyme": "7.0.2", "jsdom-global": "3.0.2", "metro-react-native-babel-preset": "0.54.0", - "mmjstool": "github:mattermost/mattermost-utilities#b55348242168df75da500fd813d4105c44eaea08", + "mmjstool": "github:mattermost/mattermost-utilities", "nyc": "14.1.1", "react-dom": "16.8.6", "react-test-renderer": "16.8.6",