Skip to content

Commit

Permalink
feat(iOS): Add new method to connect to a SSIDPrefix protected only o…
Browse files Browse the repository at this point in the history
…nce, Android: Try to address issue #303

iOS: Add new method to connect to a SSIDPrefix protected only once, Android: Try to address issue #303
  • Loading branch information
JuanSeBestia authored Aug 8, 2024
2 parents 81b8a8c + 75ac2fa commit d98ccfc
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ The following methods work only on iOS

### `connectToProtectedSSIDPrefix(SSIDPrefix: string, password: string, isWep: boolean): Promise`

### `connectToProtectedSSIDPrefixOnce(SSIDPrefix: string, password: string, isWep: boolean, joinOnce: boolean): Promise`

Use this function when you want to match a known SSID prefix, but don’t have a full SSID. If the system finds multiple Wi-Fi networks whose SSID string matches the given prefix, it selects the network with the greatest signal strength.

#### SSIDPrefix
Expand Down
81 changes: 67 additions & 14 deletions android/src/main/java/com/reactlibrary/rnwifi/RNWifiModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@
import java.util.List;

public class RNWifiModule extends ReactContextBaseJavaModule {
private Network joinedNetwork;
private final WifiManager wifi;
private final ReactApplicationContext context;
private static String TAG = "RNWifiModule";

private static final int TIMEOUT_MILLIS = 15000;
private static final int TIMEOUT_REMOVE_MILLIS = 10000;

RNWifiModule(ReactApplicationContext context) {
super(context);
Expand Down Expand Up @@ -138,6 +140,14 @@ public void forceWifiUsageWithOptions(final boolean useWifi, @Nullable final Rea
}

if (useWifi) {
// thanks to https://github.com/flutternetwork/WiFiFlutter/pull/309
// SDK-31 If not previously in a disconnected state, select the joinedNetwork to ensure
// the correct network is used for communications, else fallback to network manager network.
// https://developer.android.com/about/versions/12/behavior-changes-12#concurrent-connections
if (joinedNetwork != null) {
selectNetwork(joinedNetwork, connectivityManager);
promise.resolve(null);
} else {
final NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);

Expand All @@ -149,17 +159,14 @@ public void forceWifiUsageWithOptions(final boolean useWifi, @Nullable final Rea
@Override
public void onAvailable(@NonNull final Network network) {
super.onAvailable(network);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(network);
} else {
ConnectivityManager.setProcessDefaultNetwork(network);
}
selectNetwork(network, connectivityManager);

connectivityManager.unregisterNetworkCallback(this);

promise.resolve(null);
}
});
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(null);
Expand Down Expand Up @@ -237,7 +244,7 @@ public void connectToProtectedSSID(@NonNull final String SSID, @NonNull final St

this.removeWifiNetwork(SSID, promise, () -> {
connectToWifiDirectly(SSID, password, isHidden, TIMEOUT_MILLIS, promise);
});
}, TIMEOUT_REMOVE_MILLIS);
}


Expand Down Expand Up @@ -265,11 +272,10 @@ public void connectToProtectedWifiSSID(@NonNull ReadableMap options, final Promi
boolean isHidden = options.hasKey("isHidden") && options.getBoolean("isHidden");
int secondsTimeout = options.hasKey("timeout") ? options.getInt("timeout") * 1000 : TIMEOUT_MILLIS;


this.removeWifiNetwork(ssid, promise, () -> {
assert ssid != null;
connectToWifiDirectly(ssid, password, isHidden, secondsTimeout, promise);
});
}, TIMEOUT_REMOVE_MILLIS);
}


Expand Down Expand Up @@ -300,14 +306,28 @@ public void connectionStatus(final Promise promise) {
*/
@ReactMethod
public void disconnect(final Promise promise) {
final int timeout = TIMEOUT_REMOVE_MILLIS;
final Handler timeoutHandler = new Handler(Looper.getMainLooper());
final Runnable timeoutRunnable = () -> {
promise.reject(ConnectErrorCodes.timeoutOccurred.toString(), "Connection timeout");
if (isAndroidTenOrLater()) {
DisconnectCallbackHolder.getInstance().unbindProcessFromNetwork();
DisconnectCallbackHolder.getInstance().disconnect();
}
};

timeoutHandler.postDelayed(timeoutRunnable, timeout);

WifiUtils.withContext(this.context).disconnect(new DisconnectionSuccessListener() {
@Override
public void success() {
timeoutHandler.removeCallbacks(timeoutRunnable);
promise.resolve(true);
}

@Override
public void failed(@NonNull DisconnectionErrorCode errorCode) {
timeoutHandler.removeCallbacks(timeoutRunnable);
switch (errorCode) {
case COULD_NOT_GET_WIFI_MANAGER: {
promise.reject(DisconnectErrorCodes.couldNotGetWifiManager.toString(), "Could not get WifiManager.");
Expand Down Expand Up @@ -391,20 +411,33 @@ public void getIP(final Promise promise) {
*/
@ReactMethod
public void isRemoveWifiNetwork(final String SSID, final Promise promise) {
removeWifiNetwork(SSID, promise, null);
removeWifiNetwork(SSID, promise, null, TIMEOUT_REMOVE_MILLIS);
}

private void removeWifiNetwork(final String SSID, final Promise promise, final Runnable onSuccess) {
private void removeWifiNetwork(final String SSID, final Promise promise, final Runnable onSuccess, final int timeout) {
final boolean locationPermissionGranted = PermissionUtils.isLocationPermissionGranted(context);
if (!locationPermissionGranted) {
promise.reject(IsRemoveWifiNetworkErrorCodes.locationPermissionMissing.toString(), "Location permission (ACCESS_FINE_LOCATION) is not granted");
return;
}

final Handler timeoutHandler = new Handler(Looper.getMainLooper());
final Runnable timeoutRunnable = () -> {
promise.reject(ConnectErrorCodes.timeoutOccurred.toString(), "Connection timeout");
if (isAndroidTenOrLater()) {
DisconnectCallbackHolder.getInstance().unbindProcessFromNetwork();
DisconnectCallbackHolder.getInstance().disconnect();
}
};

timeoutHandler.postDelayed(timeoutRunnable, timeout);

WifiUtils.withContext(this.context)
.remove(SSID, new RemoveSuccessListener() {
@Override
public void success() {
timeoutHandler.removeCallbacks(timeoutRunnable);
joinedNetwork = null;
if (onSuccess != null) {
onSuccess.run();
return;
Expand All @@ -414,6 +447,7 @@ public void success() {

@Override
public void failed(@NonNull RemoveErrorCode errorCode) {
timeoutHandler.removeCallbacks(timeoutRunnable);
switch (errorCode) {
case COULD_NOT_GET_WIFI_MANAGER: {
promise.reject(IsRemoveWifiNetworkErrorCodes.couldNotGetWifiManager.toString(), "Could not get WifiManager.");
Expand Down Expand Up @@ -456,7 +490,7 @@ public void reScanAndLoadWifiList(final Promise promise) {

private void connectToWifiDirectly(@NonNull final String SSID, @NonNull final String password, final boolean isHidden, final int timeout, final Promise promise) {
if (isAndroidTenOrLater()) {
connectAndroidQ(SSID, password, isHidden,timeout, promise);
connectAndroidQ(SSID, password, isHidden, timeout, promise);
} else {
connectPreAndroidQ(SSID, password, promise);
}
Expand Down Expand Up @@ -492,6 +526,14 @@ private void connectPreAndroidQ(@NonNull final String SSID, @NonNull final Strin
promise.resolve("connected");
}

private boolean selectNetwork(final Network network, final ConnectivityManager manager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return manager.bindProcessToNetwork(network);
} else {
return ConnectivityManager.setProcessDefaultNetwork(network);
}
}

@RequiresApi(api = Build.VERSION_CODES.Q)
private void connectAndroidQ(@NonNull final String SSID, @NonNull final String password, final boolean isHidden, final int timeout, final Promise promise) {
WifiNetworkSpecifier.Builder wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
Expand All @@ -504,12 +546,19 @@ private void connectAndroidQ(@NonNull final String SSID, @NonNull final String p

NetworkRequest nr = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(wifiNetworkSpecifier.build())
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
//.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
.setNetworkSpecifier(wifiNetworkSpecifier.build())
.build();

// cleanup previous connections just in case
DisconnectCallbackHolder.getInstance().disconnect();

joinedNetwork = null;

ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

final Handler timeoutHandler = new Handler(Looper.getMainLooper());
final Runnable timeoutRunnable = () -> {
promise.reject(ConnectErrorCodes.timeoutOccurred.toString(), "Connection timeout");
Expand All @@ -524,8 +573,9 @@ private void connectAndroidQ(@NonNull final String SSID, @NonNull final String p
public void onAvailable(@NonNull Network network) {
super.onAvailable(network);
timeoutHandler.removeCallbacks(timeoutRunnable);
joinedNetwork = network;
DisconnectCallbackHolder.getInstance().bindProcessToNetwork(network);
connectivityManager.setNetworkPreference(ConnectivityManager.DEFAULT_NETWORK_PREFERENCE);
//connectivityManager.setNetworkPreference(ConnectivityManager.DEFAULT_NETWORK_PREFERENCE);
if (!pollForValidSSID(3, SSID)) {
promise.reject(ConnectErrorCodes.android10ImmediatelyDroppedConnection.toString(), "Firmware bugs on OnePlus prevent it from connecting on some firmware versions.");
return;
Expand All @@ -537,12 +587,15 @@ public void onAvailable(@NonNull Network network) {
public void onUnavailable() {
super.onUnavailable();
timeoutHandler.removeCallbacks(timeoutRunnable);
joinedNetwork = null;
promise.reject(ConnectErrorCodes.didNotFindNetwork.toString(), "Network not found or network request cannot be fulfilled.");
}

@Override
public void onLost(@NonNull Network network) {
super.onLost(network);
timeoutHandler.removeCallbacks(timeoutRunnable);
joinedNetwork = null;
DisconnectCallbackHolder.getInstance().unbindProcessFromNetwork();
DisconnectCallbackHolder.getInstance().disconnect();
}
Expand Down
20 changes: 15 additions & 5 deletions ios/RNWifi.m
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,36 @@ + (BOOL)requiresMainQueueSetup
}
}

RCT_EXPORT_METHOD(connectToProtectedSSIDPrefix:(NSString*)ssid
RCT_EXPORT_METHOD(connectToProtectedSSIDPrefix:(NSString*)ssidPrefix
withPassphrase:(NSString*)passphrase
isWEP:(BOOL)isWEP
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {

[self connectToProtectedSSIDPrefixOnce:ssidPrefix withPassphrase:passphrase isWEP:isWEP joinOnce:false resolver:resolve rejecter:reject];
}

RCT_EXPORT_METHOD(connectToProtectedSSIDPrefixOnce:(NSString*)ssidPrefix
withPassphrase:(NSString*)passphrase
isWEP:(BOOL)isWEP
joinOnce:(BOOL)joinOnce
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {

if (@available(iOS 13.0, *)) {
NEHotspotConfiguration* configuration = [[NEHotspotConfiguration alloc] initWithSSIDPrefix:ssid passphrase:passphrase isWEP:isWEP];
configuration.joinOnce = false;
NEHotspotConfiguration* configuration = [[NEHotspotConfiguration alloc] initWithSSIDPrefix:ssidPrefix passphrase:passphrase isWEP:isWEP];
configuration.joinOnce = joinOnce;

[[NEHotspotConfigurationManager sharedManager] applyConfiguration:configuration completionHandler:^(NSError * _Nullable error) {
if (error != nil) {
reject([self parseError:error], @"Error while configuring WiFi", error);
} else {
// Verify SSID connection
[self getWifiSSID:^(NSString* result) {
if ([result hasPrefix:ssid]){
if ([result hasPrefix:ssidPrefix]){
resolve(nil);
} else {
reject([ConnectError code:UnableToConnect], [NSString stringWithFormat:@"%@/%@", @"Unable to connect to Wi-Fi with prefix ", ssid], nil);
reject([ConnectError code:UnableToConnect], [NSString stringWithFormat:@"%@/%@", @"Unable to connect to Wi-Fi with prefix ", ssidPrefix], nil);
}
}];
}
Expand Down
14 changes: 14 additions & 0 deletions lib/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ declare module 'react-native-wifi-reborn' {
password: string,
isWEP: boolean
): Promise<void>;
/**
* Connects to a WiFi network that start with SSIDPrefix. Rejects with an error if it couldn't connect.
*
* @param SSIDPrefix Wifi name prefix.
* @param password `null` for open networks.
* @param isWep Used on iOS. If `true`, the network is WEP Wi-Fi; otherwise it is a WPA or WPA2 personal Wi-Fi network.
* @param joinOnce Used on iOS. If `true`, restricts the lifetime of a configuration to the operating status of the app that created it.
*/
export function connectToProtectedSSIDPrefixOnce(
SSIDPrefix: string,
password: string | null,
isWEP: boolean,
joinOnce: boolean
): Promise<void>;

//#endregion

Expand Down

0 comments on commit d98ccfc

Please sign in to comment.