Skip to content

Commit 14bea9b

Browse files
committed
feat(Android): Connect from "cellular" & "ethernet" interfaces
1 parent 98d4947 commit 14bea9b

File tree

3 files changed

+88
-57
lines changed

3 files changed

+88
-57
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ server.on('close', () => {
168168
| `host` | `<string>` ||| Host the socket should connect to. IP address in IPv4 format or `'localhost'`. **Default**: `'localhost'`. |
169169
| `localAddress` | `<string>` ||| Local address the socket should connect from. If not specified, the OS will decide. It is **highly recommended** to specify a `localAddress` to prevent overload errors and improve performance. |
170170
| `localPort` | `<number>` ||| Local port the socket should connect from. If not specified, the OS will decide. |
171-
| `interface`| `<string>` ||| Interface the socket should connect from. If not specified, it will use the current active connection. The options are: `'wifi'`. |
171+
| `interface`| `<string>` ||| Interface the socket should connect from. If not specified, it will use the current active connection. The options are: `'wifi', 'ethernet', 'cellular'`. |
172172
| `reuseAddress`| `<boolean>` ||| Enable/disable the reuseAddress socket option. **Default**: `true`. |
173173

174174
**Note**: The platforms marked as ❌ use the default value.

android/src/main/java/com/asterinet/react/tcpsocket/TcpSocketModule.java

Lines changed: 86 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,18 @@
2626
import java.net.InetSocketAddress;
2727
import java.util.concurrent.ConcurrentHashMap;
2828
import java.util.concurrent.CountDownLatch;
29+
import java.util.concurrent.ScheduledThreadPoolExecutor;
30+
import java.util.concurrent.TimeUnit;
2931

3032
import androidx.annotation.NonNull;
3133
import androidx.annotation.Nullable;
3234

3335
public class TcpSocketModule extends ReactContextBaseJavaModule implements TcpReceiverTask.OnDataReceivedListener {
34-
36+
private static final String TAG = "TcpSockets";
3537
private final ReactApplicationContext mReactContext;
3638
private final ConcurrentHashMap<Integer, TcpSocketClient> socketClients = new ConcurrentHashMap<>();
3739
private final ConcurrentHashMap<String, Network> mNetworkMap = new ConcurrentHashMap<>();
38-
@Nullable
39-
private Network mSelectedNetwork;
40-
41-
private static final String TAG = "TcpSockets";
40+
private final CurrentNetwork currentNetwork = new CurrentNetwork();
4241

4342
public TcpSocketModule(ReactApplicationContext reactContext) {
4443
super(reactContext);
@@ -57,53 +56,6 @@ private void sendEvent(String eventName, WritableMap params) {
5756
.emit(eventName, params);
5857
}
5958

60-
/**
61-
* Returns a network given its interface name:
62-
* "wifi" -> WIFI
63-
* "cellular" -> Cellular
64-
* etc...
65-
*/
66-
private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress) throws InterruptedException {
67-
if (iface == null) return;
68-
mSelectedNetwork = null;
69-
if (ipAddress != null) {
70-
Network cachedNetwork = mNetworkMap.get(ipAddress);
71-
if (cachedNetwork != null) {
72-
mSelectedNetwork = cachedNetwork;
73-
return;
74-
}
75-
}
76-
final CountDownLatch awaitingNetwork = new CountDownLatch(1); // only needs to be counted down once to release waiting threads
77-
final ConnectivityManager cm = (ConnectivityManager) mReactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
78-
NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
79-
switch (iface) {
80-
case "wifi":
81-
requestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
82-
cm.requestNetwork(requestBuilder.build(), new ConnectivityManager.NetworkCallback() {
83-
@Override
84-
public void onAvailable(Network network) {
85-
mSelectedNetwork = network;
86-
if (ipAddress != null && !ipAddress.equals("0.0.0.0"))
87-
mNetworkMap.put(ipAddress, mSelectedNetwork);
88-
awaitingNetwork.countDown(); // Stop waiting
89-
}
90-
91-
@Override
92-
public void onUnavailable() {
93-
awaitingNetwork.countDown(); // Stop waiting
94-
}
95-
});
96-
awaitingNetwork.await();
97-
break;
98-
case "cellular": // TODO
99-
default:
100-
mSelectedNetwork = null;
101-
break;
102-
}
103-
if (mSelectedNetwork != null && ipAddress != null && !ipAddress.equals("0.0.0.0"))
104-
mNetworkMap.put(ipAddress, mSelectedNetwork);
105-
}
106-
10759
/**
10860
* Creates a TCP Socket and establish a connection with the given host
10961
*
@@ -126,12 +78,12 @@ protected void doInBackgroundGuarded(Void... params) {
12678
}
12779
try {
12880
// Get the network interface
129-
String localAddress = options.hasKey("localAddress") ? options.getString("localAddress") : null;
130-
String iface = options.hasKey("interface") ? options.getString("interface") : null;
81+
final String localAddress = options.hasKey("localAddress") ? options.getString("localAddress") : null;
82+
final String iface = options.hasKey("interface") ? options.getString("interface") : null;
13183
selectNetwork(iface, localAddress);
13284
client = new TcpSocketClient(TcpSocketModule.this, cId, null);
13385
socketClients.put(cId, client);
134-
client.connect(host, port, options, mSelectedNetwork);
86+
client.connect(host, port, options, currentNetwork.getNetwork());
13587
onConnect(cId, host, port);
13688
} catch (Exception e) {
13789
onError(cId, e.getMessage());
@@ -209,6 +161,68 @@ protected void doInBackgroundGuarded(Void... params) {
209161
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
210162
}
211163

164+
private void requestNetwork(final int transportType) throws InterruptedException {
165+
final NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
166+
requestBuilder.addTransportType(transportType);
167+
final CountDownLatch awaitingNetwork = new CountDownLatch(1); // only needs to be counted down once to release waiting threads
168+
final ConnectivityManager cm = (ConnectivityManager) mReactContext.getSystemService(Context.CONNECTIVITY_SERVICE);
169+
cm.requestNetwork(requestBuilder.build(), new ConnectivityManager.NetworkCallback() {
170+
@Override
171+
public void onAvailable(Network network) {
172+
currentNetwork.setNetwork(network);
173+
awaitingNetwork.countDown(); // Stop waiting
174+
}
175+
176+
@Override
177+
public void onUnavailable() {
178+
awaitingNetwork.countDown(); // Stop waiting
179+
}
180+
});
181+
// Timeout if there the network is unreachable
182+
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
183+
exec.schedule(new Runnable() {
184+
public void run() {
185+
awaitingNetwork.countDown(); // Stop waiting
186+
}
187+
}, 5, TimeUnit.SECONDS);
188+
awaitingNetwork.await();
189+
}
190+
191+
// REQUEST NETWORK
192+
193+
/**
194+
* Returns a network given its interface name:
195+
* "wifi" -> WIFI
196+
* "cellular" -> Cellular
197+
* etc...
198+
*/
199+
private void selectNetwork(@Nullable final String iface, @Nullable final String ipAddress) throws InterruptedException, IOException {
200+
currentNetwork.setNetwork(null);
201+
if (iface == null) return;
202+
if (ipAddress != null) {
203+
final Network cachedNetwork = mNetworkMap.get(iface + ipAddress);
204+
if (cachedNetwork != null) {
205+
currentNetwork.setNetwork(cachedNetwork);
206+
return;
207+
}
208+
}
209+
switch (iface) {
210+
case "wifi":
211+
requestNetwork(NetworkCapabilities.TRANSPORT_WIFI);
212+
break;
213+
case "cellular":
214+
requestNetwork(NetworkCapabilities.TRANSPORT_CELLULAR);
215+
break;
216+
case "ethernet":
217+
requestNetwork(NetworkCapabilities.TRANSPORT_ETHERNET);
218+
break;
219+
}
220+
if (currentNetwork.getNetwork() == null) {
221+
throw new IOException("Interface " + iface + " unreachable");
222+
} else if (ipAddress != null && !ipAddress.equals("0.0.0.0"))
223+
mNetworkMap.put(iface + ipAddress, currentNetwork.getNetwork());
224+
}
225+
212226
// TcpReceiverTask.OnDataReceivedListener
213227

214228
@Override
@@ -273,4 +287,21 @@ public void onConnection(Integer serverId, Integer clientId, InetSocketAddress s
273287

274288
sendEvent("connection", eventParams);
275289
}
290+
291+
private class CurrentNetwork {
292+
@Nullable
293+
Network network = null;
294+
295+
private CurrentNetwork() {
296+
}
297+
298+
@Nullable
299+
private Network getNetwork() {
300+
return network;
301+
}
302+
303+
private void setNetwork(@Nullable final Network network) {
304+
this.network = network;
305+
}
306+
}
276307
}

src/TcpSocket.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class RemovableListener {
3232
}
3333

3434
/**
35-
* @typedef {{ port: number; host?: string; timeout?: number; localAddress?: string, localPort?: number, interface?: 'wifi', reuseAddress?: boolean}} ConnectionOptions
35+
* @typedef {{ port: number; host?: string; timeout?: number; localAddress?: string, localPort?: number, interface?: 'wifi' | 'cellular' | 'ethernet', reuseAddress?: boolean}} ConnectionOptions
3636
*/
3737
export default class TcpSocket {
3838
/**

0 commit comments

Comments
 (0)