react-native-local-network-info 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,15 +4,15 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'com.localnetworkinfo'
7
- version = '1.0.0'
7
+ version = '1.0.2'
8
8
 
9
9
  // compileSdk (36), minSdk (24), targetSdk (36), Kotlin (2.0.21) and the
10
10
  // JVM toolchain (17) are supplied by the `expo-module-gradle-plugin`.
11
11
  android {
12
12
  namespace 'com.localnetworkinfo'
13
13
  defaultConfig {
14
- versionCode 1
15
- versionName '1.0.0'
14
+ versionCode 2
15
+ versionName '1.0.2'
16
16
  }
17
17
  lintOptions {
18
18
  abortOnError false
@@ -204,6 +204,19 @@ class LocalNetworkInfoModule : Module() {
204
204
  for (network in networks) {
205
205
  val capabilities = connectivityManager.getNetworkCapabilities(network) ?: continue
206
206
  if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) continue
207
+ // A real WiFi *station* provides the device's internet, so it advertises
208
+ // NET_CAPABILITY_INTERNET as soon as it connects — independent of
209
+ // NET_CAPABILITY_VALIDATED, so captive-portal / LAN-only stations still
210
+ // count and never flap to "hotspot". The device's OWN tethered SoftAP can
211
+ // surface as a TRANSPORT_WIFI network on some OEM builds, but it does NOT
212
+ // provide internet, so excluding no-INTERNET networks keeps the hotspot
213
+ // interface classified as HOTSPOT rather than WIFI.
214
+ //
215
+ // Trade-off: app-scoped local-only WiFi clients (WifiNetworkSpecifier,
216
+ // WiFi Direct/P2P, WiFi Aware) deliberately drop INTERNET too, so they are
217
+ // treated as non-stations here. Ordinary stations joined via Settings keep
218
+ // INTERNET even on an internet-less router, so they are unaffected.
219
+ if (!capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) continue
207
220
  connectivityManager.getLinkProperties(network)?.interfaceName?.let { names.add(it) }
208
221
  }
209
222
  } catch (_: Exception) {
@@ -269,7 +282,7 @@ class LocalNetworkInfoModule : Module() {
269
282
  interfaceName = station.name
270
283
  netmask = station.netmask
271
284
  // Prefer the real default-route gateway; fall back to a subnet guess.
272
- gateway = stationGateway() ?: deriveGateway(station.ip, station.prefixLength)
285
+ gateway = stationGateway(station.name) ?: deriveGateway(station.ip, station.prefixLength)
273
286
  } else if (host != null) {
274
287
  ip = host.ip
275
288
  role = "hotspot"
@@ -293,15 +306,34 @@ class LocalNetworkInfoModule : Module() {
293
306
  )
294
307
  }
295
308
 
296
- /** Real station gateway via LinkProperties (default route), with a DHCP fallback. */
297
- private fun stationGateway(): String? {
309
+ /**
310
+ * Gateway for the WiFi station on [interfaceName], read from THAT network's own
311
+ * LinkProperties — NOT `activeNetwork`. `activeNetwork` is the default network,
312
+ * which is the cellular network whenever WiFi is present but not the default
313
+ * (captive portal, LAN-only / IoT AP, or "avoid bad WiFi"); reading its route
314
+ * there would report the cellular gateway. Falls back to the DHCP server
315
+ * address, then to null (the caller then derives a subnet-guess gateway).
316
+ */
317
+ private fun stationGateway(interfaceName: String?): String? {
298
318
  return try {
299
- val network = connectivityManager.activeNetwork ?: return null
319
+ @Suppress("DEPRECATION")
320
+ val network = connectivityManager.allNetworks.firstOrNull { n ->
321
+ val caps = connectivityManager.getNetworkCapabilities(n)
322
+ caps != null &&
323
+ caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
324
+ caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
325
+ connectivityManager.getLinkProperties(n)?.interfaceName == interfaceName
326
+ } ?: return null
300
327
  val linkProperties = connectivityManager.getLinkProperties(network) ?: return null
301
- // Ignore the 0.0.0.0 wildcard (appears as the "gateway" of a directly-connected
302
- // default route, e.g. when the device is tethering rather than a real station).
328
+ // Require an IPv4 default route: without this a dual-stack station can return
329
+ // its IPv6 link-local next-hop (fe80::…%wlan0) depending on route ordering.
330
+ // Ignore the 0.0.0.0 wildcard (a directly-connected default route).
303
331
  val routeGateway = linkProperties.routes
304
- .firstOrNull { it.isDefaultRoute && it.gateway?.isAnyLocalAddress == false }
332
+ .firstOrNull {
333
+ it.isDefaultRoute &&
334
+ it.gateway is Inet4Address &&
335
+ it.gateway?.isAnyLocalAddress == false
336
+ }
305
337
  ?.gateway
306
338
  ?.hostAddress
307
339
  if (routeGateway != null) return routeGateway
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-local-network-info",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Read a React Native device's own local IPv4 address and whether it is connected to WiFi or acting as a hotspot host, with live network-change events. Built with the Expo Modules API; works in bare React Native and Expo.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",