1/** 2 * Copyright (c) Facebook, Inc. and its affiliates. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8#import "RNCNetInfo.h" 9#import "RNCConnectionStateWatcher.h" 10 11#include <ifaddrs.h> 12#include <arpa/inet.h> 13 14#if !TARGET_OS_TV && !TARGET_OS_MACCATALYST 15#import <CoreTelephony/CTCarrier.h> 16#import <CoreTelephony/CTTelephonyNetworkInfo.h> 17#endif 18@import SystemConfiguration.CaptiveNetwork; 19 20#import <React/RCTAssert.h> 21#import <React/RCTBridge.h> 22#import <React/RCTEventDispatcher.h> 23 24@interface RNCNetInfo () <RNCConnectionStateWatcherDelegate> 25 26@property (nonatomic, strong) RNCConnectionStateWatcher *connectionStateWatcher; 27@property (nonatomic) BOOL isObserving; 28@property (nonatomic) NSDictionary *config; 29 30@end 31 32@implementation RNCNetInfo 33 34#pragma mark - Module setup 35 36RCT_EXPORT_MODULE() 37 38// We need RNCReachabilityCallback's and module methods to be called on the same thread so that we can have 39// guarantees about when we mess with the reachability callbacks. 40- (dispatch_queue_t)methodQueue 41{ 42 return dispatch_get_main_queue(); 43} 44 45+ (BOOL)requiresMainQueueSetup 46{ 47 return YES; 48} 49 50#pragma mark - Lifecycle 51 52- (NSArray *)supportedEvents 53{ 54 return @[@"netInfo.networkStatusDidChange"]; 55} 56 57- (void)startObserving 58{ 59 self.isObserving = YES; 60} 61 62- (void)stopObserving 63{ 64 self.isObserving = NO; 65} 66 67- (instancetype)init 68{ 69 self = [super init]; 70 if (self) { 71 _connectionStateWatcher = [[RNCConnectionStateWatcher alloc] initWithDelegate:self]; 72 } 73 return self; 74} 75 76- (void)dealloc 77{ 78 self.connectionStateWatcher = nil; 79} 80 81#pragma mark - RNCConnectionStateWatcherDelegate 82 83- (void)connectionStateWatcher:(RNCConnectionStateWatcher *)connectionStateWatcher didUpdateState:(RNCConnectionState *)state 84{ 85 if (self.isObserving) { 86 NSDictionary *dictionary = [self currentDictionaryFromUpdateState:state withInterface:NULL]; 87 [self sendEventWithName:@"netInfo.networkStatusDidChange" body:dictionary]; 88 } 89} 90 91#pragma mark - Public API 92 93RCT_EXPORT_METHOD(getCurrentState:(nullable NSString *)requestedInterface resolve:(RCTPromiseResolveBlock)resolve 94 reject:(__unused RCTPromiseRejectBlock)reject) 95{ 96 RNCConnectionState *state = [self.connectionStateWatcher currentState]; 97 resolve([self currentDictionaryFromUpdateState:state withInterface:requestedInterface]); 98} 99 100RCT_EXPORT_METHOD(configure:(NSDictionary *)config) 101{ 102 self.config = config; 103} 104 105#pragma mark - Utilities 106 107// Converts the state into a dictionary to send over the bridge 108- (NSDictionary *)currentDictionaryFromUpdateState:(RNCConnectionState *)state withInterface:(nullable NSString *)requestedInterface 109{ 110 NSString *selectedInterface = requestedInterface ?: state.type; 111 NSMutableDictionary *details = [self detailsFromInterface:selectedInterface withState:state]; 112 bool connected = [state.type isEqualToString:selectedInterface] && state.connected; 113 if (connected) { 114 details[@"isConnectionExpensive"] = @(state.expensive); 115 } 116 117 return @{ 118 @"type": selectedInterface, 119 @"isConnected": @(connected), 120 @"details": details ?: NSNull.null 121 }; 122} 123 124- (NSMutableDictionary *)detailsFromInterface:(nonnull NSString *)requestedInterface withState:(RNCConnectionState *)state 125{ 126 NSMutableDictionary *details = [NSMutableDictionary new]; 127 if ([requestedInterface isEqualToString: RNCConnectionTypeCellular]) { 128 details[@"cellularGeneration"] = state.cellularGeneration ?: NSNull.null; 129 details[@"carrier"] = [self carrier] ?: NSNull.null; 130 } else if ([requestedInterface isEqualToString: RNCConnectionTypeWifi] || [requestedInterface isEqualToString: RNCConnectionTypeEthernet]) { 131 details[@"ipAddress"] = [self ipAddress] ?: NSNull.null; 132 details[@"subnet"] = [self subnet] ?: NSNull.null; 133 #if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_MACCATALYST 134 /* 135 Without one of the conditions needed to use CNCopyCurrentNetworkInfo, it will leak memory. 136 Clients should only set the shouldFetchWiFiSSID to true after ensuring requirements are met to get (B)SSID. 137 */ 138 if (self.config && self.config[@"shouldFetchWiFiSSID"]) { 139 details[@"ssid"] = [self ssid] ?: NSNull.null; 140 details[@"bssid"] = [self bssid] ?: NSNull.null; 141 } 142 #endif 143 } 144 return details; 145} 146 147- (NSString *)carrier 148{ 149#if (TARGET_OS_TV || TARGET_OS_OSX || TARGET_OS_MACCATALYST) 150 return nil; 151#else 152 CTTelephonyNetworkInfo *netinfo = [[CTTelephonyNetworkInfo alloc] init]; 153 CTCarrier *carrier = [netinfo subscriberCellularProvider]; 154 return carrier.carrierName; 155#endif 156} 157 158- (NSString *)ipAddress 159{ 160 NSString *address = @"0.0.0.0"; 161 struct ifaddrs *interfaces = NULL; 162 struct ifaddrs *temp_addr = NULL; 163 int success = 0; 164 // retrieve the current interfaces - returns 0 on success 165 success = getifaddrs(&interfaces); 166 if (success == 0) { 167 // Loop through linked list of interfaces 168 temp_addr = interfaces; 169 while (temp_addr != NULL) { 170 if (temp_addr->ifa_addr->sa_family == AF_INET) { 171 NSString* ifname = [NSString stringWithUTF8String:temp_addr->ifa_name]; 172 if ( 173 // Check if interface is en0 which is the wifi connection on the iPhone 174 // and the ethernet connection on the Apple TV 175 [ifname isEqualToString:@"en0"] || 176 // Check if interface is en1 which is the wifi connection on the Apple TV 177 [ifname isEqualToString:@"en1"] 178 ) { 179 // Get NSString from C String 180 char str[INET_ADDRSTRLEN]; 181 inet_ntop(AF_INET, &((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr, str, INET_ADDRSTRLEN); 182 address = [NSString stringWithUTF8String:str]; 183 } 184 } 185 186 temp_addr = temp_addr->ifa_next; 187 } 188 } 189 // Free memory 190 freeifaddrs(interfaces); 191 return address; 192} 193 194- (NSString *)subnet 195{ 196 NSString *subnet = @"0.0.0.0"; 197 struct ifaddrs *interfaces = NULL; 198 struct ifaddrs *temp_addr = NULL; 199 int success = 0; 200 // retrieve the current interfaces - returns 0 on success 201 success = getifaddrs(&interfaces); 202 if (success == 0) { 203 // Loop through linked list of interfaces 204 temp_addr = interfaces; 205 while (temp_addr != NULL) { 206 if (temp_addr->ifa_addr->sa_family == AF_INET) { 207 NSString* ifname = [NSString stringWithUTF8String:temp_addr->ifa_name]; 208 if ( 209 // Check if interface is en0 which is the wifi connection on the iPhone 210 // and the ethernet connection on the Apple TV 211 [ifname isEqualToString:@"en0"] || 212 // Check if interface is en1 which is the wifi connection on the Apple TV 213 [ifname isEqualToString:@"en1"] 214 ) { 215 // Get NSString from C String 216 char str[INET_ADDRSTRLEN]; 217 inet_ntop(AF_INET, &((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr, str, INET_ADDRSTRLEN); 218 subnet = [NSString stringWithUTF8String:str]; 219 } 220 } 221 222 temp_addr = temp_addr->ifa_next; 223 } 224 } 225 // Free memory 226 freeifaddrs(interfaces); 227 return subnet; 228} 229 230#if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_MACCATALYST 231- (NSString *)ssid 232{ 233 NSArray *interfaceNames = CFBridgingRelease(CNCopySupportedInterfaces()); 234 NSDictionary *SSIDInfo; 235 NSString *SSID = NULL; 236 for (NSString *interfaceName in interfaceNames) { 237 // CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler 238 SSIDInfo = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName)); 239 if (SSIDInfo.count > 0) { 240 SSID = SSIDInfo[@"SSID"]; 241 if ([SSID isEqualToString:@"Wi-Fi"] || [SSID isEqualToString:@"WLAN"]){ 242 SSID = NULL; 243 } 244 break; 245 } 246 } 247 return SSID; 248} 249 250- (NSString *)bssid 251{ 252 NSArray *interfaceNames = CFBridgingRelease(CNCopySupportedInterfaces()); 253 NSDictionary *networkDetails; 254 NSString *BSSID = NULL; 255 for (NSString *interfaceName in interfaceNames) { 256 // CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler 257 networkDetails = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName)); 258 if (networkDetails.count > 0) 259 { 260 BSSID = networkDetails[(NSString *) kCNNetworkInfoKeyBSSID]; 261 break; 262 } 263 } 264 return BSSID; 265} 266#endif 267 268@end 269