Skip to content

Commit

Permalink
fix(ios): avoid memory leak from ssid APIs by adding explicit config (#…
Browse files Browse the repository at this point in the history
…560)

* Fix #420 - Location Permissions Required

`CNCopyCurrentNetworkInfo` will leak without location permissions being enabled.
Added comments for depreciations & implementation notes that need future work.

* 420 - Pass config to native iOS & gate on new prop

Added new exported method for passing configuration to native iOS.  Storing it there on the instance as a property and gating based on that config.  It will not fetch SSIDs if config is nil.  Also cleaned up a bit of typing.

* 420 - Fix mock
* 420 Updated README

Co-authored-by: Mike Hardy <github@mikehardy.net>
  • Loading branch information
Adam Burdette and mikehardy committed Feb 9, 2022
1 parent 836332d commit fbf7c15
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 9 deletions.
8 changes: 6 additions & 2 deletions README.md
Expand Up @@ -177,7 +177,7 @@ The `details` value depends on the `type` value.
| Property | Platform | Type | Description |
| ----------------------- | --------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------- |
| `isConnectionExpensive` | Android, iOS, macOS, Windows, Web | `boolean` | If the network connection is considered "expensive". This could be in either energy or monetary terms. |
| `ssid` | Android, iOS (not tvOS), Windows | `string` | The SSID of the network. May not be present, `null`, or an empty string if it cannot be determined. **On iOS, make sure your app meets at least one of the [following requirements](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo?language=objc#discussion). On Android, you need to have the `ACCESS_FINE_LOCATION` permission in your `AndroidManifest.xml` and accepted by the user**. |
| `ssid` | Android, iOS (not tvOS), Windows | `string` | The SSID of the network. May not be present, `null`, or an empty string if it cannot be determined. **On iOS, your app must meet at least one of the [following requirements](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo?language=objc#discussion) and you must set the `shouldFetchWiFiSSID` configuration option or no attempt will be made to fetch the SSID. On Android, you need to have the `ACCESS_FINE_LOCATION` permission in your `AndroidManifest.xml` and accepted by the user**. |
| `bssid` | Android, iOS (not tvOS), Windows* | `string` | The BSSID of the network. May not be present, `null`, or an empty string if it cannot be determined. **On iOS, make sure your app meets at least one of the [following requirements](https://developer.apple.com/documentation/systemconfiguration/1614126-cncopycurrentnetworkinfo?language=objc#discussion). On Android, you need to have the `ACCESS_FINE_LOCATION` permission in your `AndroidManifest.xml` and accepted by the user**. |
| `strength` | Android, Windows | `number` | An integer number from `0` to `100` for the signal strength. May not be present if the signal strength cannot be determined. |
| `ipAddress` | Android, iOS, macOS | `string` | The external IP address. Can be in IPv4 or IPv6 format. May not be present if it cannot be determined. |
Expand Down Expand Up @@ -240,7 +240,9 @@ The configuration options for the library.
| `reachabilityShortTimeout` | `number` | 5 seconds | The number of milliseconds between internet reachability checks when the internet was not previously detected. Only used on platforms which do not supply internet reachability natively. |
| `reachabilityLongTimeout` | `number` | 60 seconds | The number of milliseconds between internet reachability checks when the internet was previously detected. Only used on platforms which do not supply internet reachability natively. |
| `reachabilityRequestTimeout` | `number` | 15 seconds | The number of milliseconds that a reachability check is allowed to take before failing. Only used on platforms which do not supply internet reachability natively. |
| `reachabilityShouldRun` | `() => boolean` | `() => true` | A function which returns a boolean to determine if checkInternetReachability should be run.
| `reachabilityShouldRun` | `() => boolean` | `() => true` | A function which returns a boolean to determine if checkInternetReachability should be run. |
| `shouldFetchWiFiSSID` | `boolean` | `false` | A flag indicating one of the requirements on iOS has been met to retrieve the network (B)SSID, and the native SSID retrieval APIs should be called. This has no effect on Android.


### Methods

Expand All @@ -259,6 +261,7 @@ NetInfo.configure({
reachabilityShortTimeout: 5 * 1000, // 5s
reachabilityRequestTimeout: 15 * 1000, // 15s
reachabilityShouldRun: () => true,
shouldFetchWiFiSSID: true // met iOS requirements to get SSID. Will leak memory if set to true without meeting requirements.
});
```

Expand Down Expand Up @@ -313,6 +316,7 @@ const YourComponent = () => {
reachabilityShortTimeout: 5 * 1000, // 5s
reachabilityRequestTimeout: 15 * 1000, // 15s
reachabilityShouldRun: () => true,
shouldFetchWiFiSSID: true // met iOS requirements to get SSID
});

// ...
Expand Down
20 changes: 17 additions & 3 deletions ios/RNCNetInfo.m
Expand Up @@ -25,6 +25,7 @@ @interface RNCNetInfo () <RNCConnectionStateWatcherDelegate>

@property (nonatomic, strong) RNCConnectionStateWatcher *connectionStateWatcher;
@property (nonatomic) BOOL isObserving;
@property (nonatomic) NSDictionary *config;

@end

Expand Down Expand Up @@ -96,6 +97,11 @@ - (void)connectionStateWatcher:(RNCConnectionStateWatcher *)connectionStateWatch
resolve([self currentDictionaryFromUpdateState:state withInterface:requestedInterface]);
}

RCT_EXPORT_METHOD(configure:(NSDictionary *)config)
{
self.config = config;
}

#pragma mark - Utilities

// Converts the state into a dictionary to send over the bridge
Expand Down Expand Up @@ -124,9 +130,15 @@ - (NSMutableDictionary *)detailsFromInterface:(nonnull NSString *)requestedInter
} else if ([requestedInterface isEqualToString: RNCConnectionTypeWifi] || [requestedInterface isEqualToString: RNCConnectionTypeEthernet]) {
details[@"ipAddress"] = [self ipAddress] ?: NSNull.null;
details[@"subnet"] = [self subnet] ?: NSNull.null;
#if !TARGET_OS_TV && !TARGET_OS_OSX
details[@"ssid"] = [self ssid] ?: NSNull.null;
details[@"bssid"] = [self bssid] ?: NSNull.null;
#if !TARGET_OS_TV && !TARGET_OS_OSX
/*
Without one of the conditions needed to use CNCopyCurrentNetworkInfo, it will leak memory.
Clients should only set the shouldFetchWiFiSSID to true after ensuring requirements are met to get (B)SSID.
*/
if (self.config && self.config[@"shouldFetchWiFiSSID"]) {
details[@"ssid"] = [self ssid] ?: NSNull.null;
details[@"bssid"] = [self bssid] ?: NSNull.null;
}
#endif
}
return details;
Expand Down Expand Up @@ -222,6 +234,7 @@ - (NSString *)ssid
NSDictionary *SSIDInfo;
NSString *SSID = NULL;
for (NSString *interfaceName in interfaceNames) {
// CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler
SSIDInfo = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName));
if (SSIDInfo.count > 0) {
SSID = SSIDInfo[@"SSID"];
Expand All @@ -240,6 +253,7 @@ - (NSString *)bssid
NSDictionary *networkDetails;
NSString *BSSID = NULL;
for (NSString *interfaceName in interfaceNames) {
// CNCopyCurrentNetworkInfo is deprecated for iOS 13+, need to override & use fetchCurrentWithCompletionHandler
networkDetails = CFBridgingRelease(CNCopyCurrentNetworkInfo((__bridge CFStringRef)interfaceName));
if (networkDetails.count > 0)
{
Expand Down
8 changes: 7 additions & 1 deletion src/index.ts
Expand Up @@ -8,12 +8,14 @@
*/

import {useState, useEffect} from 'react';
import {Platform} from 'react-native';
import DEFAULT_CONFIGURATION from './internal/defaultConfiguration';
import NativeInterface from './internal/nativeInterface';
import State from './internal/state';
import * as Types from './internal/types';

// Stores the currently used configuration
let _configuration: Types.NetInfoConfiguration = DEFAULT_CONFIGURATION;
let _configuration = DEFAULT_CONFIGURATION;

// Stores the singleton reference to the state manager
let _state: State | null = null;
Expand All @@ -40,6 +42,10 @@ export function configure(
_state.tearDown();
_state = createState();
}

if (Platform.OS === 'ios') {
NativeInterface.configure(configuration);
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/internal/__mocks__/nativeModule.ts
Expand Up @@ -4,6 +4,7 @@
/* eslint-env jest */

const RNCNetInfoMock = {
configure: jest.fn(),
getCurrentState: jest.fn(),
addListener: jest.fn(),
removeListeners: jest.fn(),
Expand Down
7 changes: 6 additions & 1 deletion src/internal/defaultConfiguration.ts
@@ -1,9 +1,14 @@
export default {
import * as Types from './types';

const DEFAULT_CONFIGURATION: Types.NetInfoConfiguration = {
reachabilityUrl: 'https://clients3.google.com/generate_204',
reachabilityTest: (response: Response): Promise<boolean> =>
Promise.resolve(response.status === 204),
reachabilityShortTimeout: 5 * 1000, // 5s
reachabilityLongTimeout: 60 * 1000, // 60s
reachabilityRequestTimeout: 15 * 1000, // 15s
reachabilityShouldRun: (): boolean => true,
shouldFetchWiFiSSID: false
};

export default DEFAULT_CONFIGURATION;
7 changes: 6 additions & 1 deletion src/internal/defaultConfiguration.web.ts
@@ -1,9 +1,14 @@
export default {
import * as Types from './types';

const DEFAULT_CONFIGURATION: Types.NetInfoConfiguration = {
reachabilityUrl: '/',
reachabilityTest: (response: Response): Promise<boolean> =>
Promise.resolve(response.status === 200),
reachabilityShortTimeout: 5 * 1000, // 5s
reachabilityLongTimeout: 60 * 1000, // 60s
reachabilityRequestTimeout: 15 * 1000, // 15s
reachabilityShouldRun: (): boolean => true,
shouldFetchWiFiSSID: true
};

export default DEFAULT_CONFIGURATION
4 changes: 4 additions & 0 deletions src/internal/nativeModule.web.ts
Expand Up @@ -285,6 +285,10 @@ const RNCNetInfo: NetInfoNativeModule = {
async getCurrentState(requestedInterface): Promise<NetInfoNativeModuleState> {
return getCurrentState(requestedInterface);
},

configure(): void {
return;
},
};

export default RNCNetInfo;
3 changes: 2 additions & 1 deletion src/internal/privateTypes.ts
Expand Up @@ -7,7 +7,7 @@
* @format
*/

import {NetInfoState} from './types';
import {NetInfoConfiguration, NetInfoState} from './types';

export const DEVICE_CONNECTIVITY_EVENT = 'netInfo.networkStatusDidChange';

Expand All @@ -22,6 +22,7 @@ export interface Events {
}

export interface NetInfoNativeModule {
configure: (config: Partial<NetInfoConfiguration>) => void;
getCurrentState: (
requestedInterface?: string,
) => Promise<NetInfoNativeModuleState>;
Expand Down
1 change: 1 addition & 0 deletions src/internal/types.ts
Expand Up @@ -114,4 +114,5 @@ export interface NetInfoConfiguration {
reachabilityShortTimeout: number;
reachabilityRequestTimeout: number;
reachabilityShouldRun: () => boolean;
shouldFetchWiFiSSID: boolean;
}

0 comments on commit fbf7c15

Please sign in to comment.