1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
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
|
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 |
|
36 | RCT_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 |
|
93 | RCT_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 |
|
100 | RCT_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
|
134 | |
135 |
|
136 |
|
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)
|
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
|
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
|