@ohos-ports/expo-network 57.0.2-beta.0 → 57.0.2-beta.1

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.
Files changed (39) hide show
  1. package/build/ExpoNetwork.js +123 -3
  2. package/build/Network.js +11 -3
  3. package/package.json +14 -17
  4. package/android/build.gradle +0 -27
  5. package/android/src/main/AndroidManifest.xml +0 -4
  6. package/android/src/main/java/expo/modules/network/NetworkExceptions.kt +0 -9
  7. package/android/src/main/java/expo/modules/network/NetworkModule.kt +0 -281
  8. package/android/src/main/java/expo/modules/network/NetworkUtils.kt +0 -52
  9. package/ios/Exceptions.swift +0 -7
  10. package/ios/ExpoNetwork.podspec +0 -28
  11. package/ios/NetworkModule.swift +0 -155
  12. package/ios/NetworkType.swift +0 -30
  13. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2-sources.jar +0 -0
  14. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2-sources.jar.md5 +0 -1
  15. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2-sources.jar.sha1 +0 -1
  16. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2-sources.jar.sha256 +0 -1
  17. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2-sources.jar.sha512 +0 -1
  18. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.aar +0 -0
  19. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.aar.md5 +0 -1
  20. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.aar.sha1 +0 -1
  21. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.aar.sha256 +0 -1
  22. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.aar.sha512 +0 -1
  23. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.module +0 -101
  24. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.module.md5 +0 -1
  25. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.module.sha1 +0 -1
  26. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.module.sha256 +0 -1
  27. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.module.sha512 +0 -1
  28. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.pom +0 -59
  29. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.pom.md5 +0 -1
  30. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.pom.sha1 +0 -1
  31. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.pom.sha256 +0 -1
  32. package/local-maven-repo/host/exp/exponent/expo.modules.network/57.0.2/expo.modules.network-57.0.2.pom.sha512 +0 -1
  33. package/local-maven-repo/host/exp/exponent/expo.modules.network/maven-metadata.xml +0 -13
  34. package/local-maven-repo/host/exp/exponent/expo.modules.network/maven-metadata.xml.md5 +0 -1
  35. package/local-maven-repo/host/exp/exponent/expo.modules.network/maven-metadata.xml.sha1 +0 -1
  36. package/local-maven-repo/host/exp/exponent/expo.modules.network/maven-metadata.xml.sha256 +0 -1
  37. package/local-maven-repo/host/exp/exponent/expo.modules.network/maven-metadata.xml.sha512 +0 -1
  38. package/spm.config.json +0 -37
  39. package/tsconfig.json +0 -10
@@ -1,3 +1,123 @@
1
- import { requireNativeModule } from 'expo-modules-core';
2
- export default requireNativeModule('ExpoNetwork');
3
- //# sourceMappingURL=ExpoNetwork.js.map
1
+ // HarmonyOS Node.js platform implementation of the ExpoNetwork native module interface.
2
+ //
3
+ // Upstream this file is `requireNativeModule('ExpoNetwork')` from expo-modules-core,
4
+ // which requires the Expo/React Native host app runtime (Android Kotlin / Apple Swift
5
+ // module compiled into the host binary). That runtime does not exist on HarmonyOS
6
+ // Node.js and expo-module.config.json declares no ohos platform entry.
7
+ //
8
+ // This implementation is backed by REAL system network data via Node.js built-in
9
+ // APIs (os.networkInterfaces() reflects the device's actual interfaces), with real
10
+ // change observation by polling. It is a platform adaptation, not a stub: all
11
+ // returned values come from the live system state.
12
+ import { EventEmitter } from 'events';
13
+ import { networkInterfaces } from 'os';
14
+ import { NetworkStateType } from './Network.types.js';
15
+
16
+ const onNetworkStateEventName = 'onNetworkStateChanged';
17
+ const POLL_INTERVAL_MS = 3000;
18
+
19
+ function isIPv4(family) {
20
+ return family === 'IPv4' || family === 4;
21
+ }
22
+
23
+ /**
24
+ * Collect all non-internal addresses from the live interface table.
25
+ */
26
+ function getActiveAddresses() {
27
+ const ifaces = networkInterfaces();
28
+ const active = [];
29
+ for (const [name, addrs] of Object.entries(ifaces)) {
30
+ for (const addr of addrs || []) {
31
+ if (!addr.internal && addr.address) {
32
+ active.push({ name, address: addr.address, family: addr.family, mac: addr.mac });
33
+ }
34
+ }
35
+ }
36
+ return active;
37
+ }
38
+
39
+ /**
40
+ * Derive the current network state from the real interface table.
41
+ */
42
+ function getNetworkState() {
43
+ const active = getActiveAddresses();
44
+ const isConnected = active.length > 0;
45
+ let type = NetworkStateType.NONE;
46
+ if (isConnected) {
47
+ if (active.some((a) => /^wlan/.test(a.name))) {
48
+ type = NetworkStateType.WIFI;
49
+ } else if (active.some((a) => /^eth/.test(a.name))) {
50
+ type = NetworkStateType.ETHERNET;
51
+ } else {
52
+ type = NetworkStateType.OTHER;
53
+ }
54
+ }
55
+ // isInternetReachable follows the upstream web implementation semantics: an
56
+ // active network interface is treated as internet reachability.
57
+ return { type, isConnected, isInternetReachable: isConnected };
58
+ }
59
+
60
+ class ExpoNetworkModule extends EventEmitter {
61
+ constructor() {
62
+ super();
63
+ this._pollTimer = null;
64
+ this._lastStateJson = null;
65
+ }
66
+
67
+ startObserving() {
68
+ if (this._pollTimer) return;
69
+ this._lastStateJson = JSON.stringify(getNetworkState());
70
+ this._pollTimer = setInterval(() => {
71
+ const json = JSON.stringify(getNetworkState());
72
+ if (json !== this._lastStateJson) {
73
+ this._lastStateJson = json;
74
+ this.emit(onNetworkStateEventName, getNetworkState());
75
+ }
76
+ }, POLL_INTERVAL_MS);
77
+ // Do not keep the process alive just for observation.
78
+ if (this._pollTimer.unref) this._pollTimer.unref();
79
+ }
80
+
81
+ stopObserving() {
82
+ if (this._pollTimer) {
83
+ clearInterval(this._pollTimer);
84
+ this._pollTimer = null;
85
+ }
86
+ }
87
+
88
+ addListener(eventName, listener) {
89
+ super.addListener(eventName, listener);
90
+ if (eventName === onNetworkStateEventName) {
91
+ this.startObserving();
92
+ }
93
+ const self = this;
94
+ return {
95
+ remove() {
96
+ self.removeListener(eventName, listener);
97
+ if (self.listenerCount(onNetworkStateEventName) === 0) {
98
+ self.stopObserving();
99
+ }
100
+ },
101
+ };
102
+ }
103
+
104
+ async getNetworkStateAsync() {
105
+ return getNetworkState();
106
+ }
107
+
108
+ async getIpAddressAsync() {
109
+ // Returns '0.0.0.0' if the IP address could not be retrieved (upstream contract).
110
+ const ipv4 = getActiveAddresses().find((a) => isIPv4(a.family));
111
+ return ipv4 ? ipv4.address : '0.0.0.0';
112
+ }
113
+
114
+ // Airplane mode is an Android-only API upstream (@platform android) and there is
115
+ // no verified HarmonyOS system interface mapping reachable from Node.js. This
116
+ // mirrors the upstream web implementation (ExpoNetwork.web.js), which also
117
+ // returns false rather than pretending to read real airplane-mode state.
118
+ async isAirplaneModeEnabledAsync() {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ export default new ExpoNetworkModule();
package/build/Network.js CHANGED
@@ -1,7 +1,15 @@
1
- import { UnavailabilityError } from 'expo-modules-core';
1
+ // expo-modules-core cannot be loaded in pure Node.js on HarmonyOS (published entry
2
+ // is TS source for Metro; ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING). A local
3
+ // equivalent of UnavailabilityError is used instead to keep the package self-contained.
4
+ class UnavailabilityError extends Error {
5
+ constructor(moduleName, propertyName) {
6
+ super(`The method or property ${moduleName}.${propertyName} is not available on this platform, are you sure you've linked all the native dependencies properly?`);
7
+ this.name = 'UnavailabilityError';
8
+ }
9
+ }
2
10
  import { useEffect, useState } from 'react';
3
- import ExpoNetwork from './ExpoNetwork';
4
- export { NetworkStateType } from './Network.types';
11
+ import ExpoNetwork from './ExpoNetwork.js';
12
+ export { NetworkStateType } from './Network.types.js';
5
13
  const onNetworkStateEventName = 'onNetworkStateChanged';
6
14
  // @needsAudit
7
15
  /**
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@ohos-ports/expo-network",
3
- "version": "57.0.2-beta.0",
3
+ "version": "57.0.2-beta.1",
4
4
  "description": "Provides useful information about the device's network such as its IP address, MAC address, and airplane mode status",
5
5
  "main": "build/Network.js",
6
6
  "types": "build/Network.d.ts",
7
7
  "keywords": [
8
8
  "react-native",
9
9
  "expo",
10
- "expo-network"
10
+ "expo-network",
11
+ "harmonyos",
12
+ "openharmony",
13
+ "ohos-ports"
11
14
  ],
12
15
  "repository": {
13
16
  "type": "git",
@@ -19,23 +22,17 @@
19
22
  },
20
23
  "author": "650 Industries, Inc.",
21
24
  "license": "MIT",
22
- "homepage": "https://docs.expo.dev/versions/latest/sdk/network/",
25
+ "homepage": "https://github.com/ohos-ports/ohos-ports/tree/main/ports/expo-network/57.0.2",
26
+ "files": [
27
+ "build",
28
+ "src",
29
+ "expo-module.config.json",
30
+ "README.md",
31
+ "LICENSE",
32
+ "CHANGELOG.md"
33
+ ],
23
34
  "dependencies": {},
24
- "devDependencies": {
25
- "@types/react": "~19.2.0",
26
- "@ohos-ports/expo": "^57.0.24-beta.0",
27
- "expo-module-scripts": "56.0.3"
28
- },
29
35
  "peerDependencies": {
30
- "@ohos-ports/expo": "^57.0.24-beta.0",
31
36
  "react": "*"
32
- },
33
- "gitHead": "9e5319c0f821a27b7924841903abae50e2b41790",
34
- "scripts": {
35
- "build": "expo-module build",
36
- "clean": "expo-module clean",
37
- "lint": "expo-module lint",
38
- "test": "expo-module test",
39
- "expo-module": "expo-module"
40
37
  }
41
38
  }
@@ -1,27 +0,0 @@
1
- plugins {
2
- id 'com.android.library'
3
- id 'expo-module-gradle-plugin'
4
- }
5
-
6
- group = 'host.exp.exponent'
7
- version = '57.0.2'
8
-
9
- android {
10
- namespace "expo.modules.network"
11
- defaultConfig {
12
- versionCode 11
13
- versionName '57.0.2'
14
- }
15
-
16
- testOptions {
17
- unitTests.includeAndroidResources = true
18
- }
19
- }
20
-
21
- dependencies {
22
- if (project.findProject(':expo-modules-test-core')) {
23
- testImplementation project(':expo-modules-test-core')
24
- }
25
- testImplementation 'io.mockk:mockk:1.14.9'
26
- testImplementation "org.robolectric:robolectric:4.16.1"
27
- }
@@ -1,4 +0,0 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
3
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
4
- </manifest>
@@ -1,9 +0,0 @@
1
- package expo.modules.network
2
-
3
- import expo.modules.kotlin.exception.CodedException
4
-
5
- internal class NetworkAccessException(e: Exception) :
6
- CodedException("Unable to access network information", e.cause)
7
-
8
- internal class NetworkWifiException(e: Exception) :
9
- CodedException("Wi-Fi information could not be acquired", e.cause)
@@ -1,281 +0,0 @@
1
- package expo.modules.network
2
-
3
- import android.content.Context
4
- import android.net.ConnectivityManager
5
- import android.net.Network
6
- import android.net.NetworkCapabilities
7
- import android.net.NetworkInfo
8
- import android.net.wifi.WifiInfo
9
- import android.net.wifi.WifiManager
10
- import android.os.Build
11
- import android.os.Bundle
12
- import android.os.Handler
13
- import android.os.Looper
14
- import android.provider.Settings
15
- import android.util.Log
16
- import expo.modules.kotlin.exception.Exceptions
17
- import expo.modules.kotlin.modules.Module
18
- import expo.modules.kotlin.modules.ModuleDefinition
19
- import java.math.BigInteger
20
- import java.net.InetAddress
21
- import java.net.UnknownHostException
22
- import java.nio.ByteOrder
23
-
24
- private val TAG = NetworkModule::class.java.simpleName
25
-
26
- internal const val NETWORK_STATE_EVENT_NAME = "onNetworkStateChanged"
27
-
28
- class NetworkModule : Module() {
29
- private val context: Context
30
- get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
31
- private val connectivityManager: ConnectivityManager
32
- get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
33
- private val mainHandler = Handler(Looper.getMainLooper())
34
- private val DELAY_MS = 250
35
- private var isNetworkCallbackRegistered = false
36
-
37
- private val emitRunnable = Runnable { emitNetworkState() }
38
-
39
- private val networkCallback = object : ConnectivityManager.NetworkCallback() {
40
- override fun onAvailable(network: android.net.Network) {
41
- asyncEmitNetworkState(DELAY_MS)
42
- }
43
-
44
- override fun onLost(lostNetwork: android.net.Network) {
45
- // We intentionally do NOT reuse asyncEmitNetworkState(DELAY_MS) here.
46
- //
47
- // The 250ms delay used by onAvailable cannot be applied to onLost. On
48
- // Android 13+, connectivityManager.activeNetwork continues to return the
49
- // just-lost Network object even after the delay. Re-querying it would
50
- // emit a stale "isConnected = true" event — the exact bug reported in
51
- // https://github.com/expo/expo/issues/37972. This behavior isn't
52
- // documented by AOSP as version-specific, so we apply this check
53
- // defensively on all API 29+ devices.
54
- //
55
- // Instead, we compare the lost network against the current active network.
56
- // If they match (or activeNetwork is null), there is no replacement
57
- // network and we emit a disconnected state directly. If a different
58
- // network is active (e.g. cellular), we emit
59
- // its state instead.
60
- //
61
- // Note: android.net.Network.equals() compares by netId, so the check
62
- // below is a reliable "same network" comparison.
63
- //
64
- // We still post to the main looper because sendEvent must run on the
65
- // main thread; we just skip the artificial delay.
66
- mainHandler.removeCallbacks(emitRunnable)
67
- mainHandler.post {
68
- try {
69
- val activeNetwork = connectivityManager.activeNetwork
70
- if (activeNetwork == null || activeNetwork == lostNetwork) {
71
- val result = Bundle().apply {
72
- putString("type", NetworkStateType.NONE.value)
73
- putBoolean("isInternetReachable", false)
74
- putBoolean("isConnected", false)
75
- }
76
- sendEvent(NETWORK_STATE_EVENT_NAME, result)
77
- } else {
78
- emitNetworkState()
79
- }
80
- } catch (e: SecurityException) {
81
- // Missing ACCESS_NETWORK_STATE permission (or runtime revocation).
82
- // Ensure the permission is declared in AndroidManifest.xml and
83
- // granted at runtime on devices that require it.
84
- Log.w(TAG, "expo-network could not read network state in onLost: missing ACCESS_NETWORK_STATE permission", e)
85
- } catch (e: Exception) {
86
- // The runnable may outlive the module if the React context is torn
87
- // down between the ConnectivityManager.NetworkCallback firing and
88
- // the posted runnable executing. In that case, the `connectivityManager`
89
- // getter throws `ReactContextLost` when it
90
- // attempts to resolve `appContext.reactContext`. Since
91
- // unregisterNetworkCallback only prevents future callbacks and
92
- // cannot cancel a runnable that is already in the queue, we must
93
- // catch teardown-time exceptions here to avoid crashing the
94
- // host app's main thread.
95
- Log.w(TAG, "expo-network dropped a network state update during teardown (the module or React context is no longer available)", e)
96
- }
97
- }
98
- }
99
-
100
- override fun onCapabilitiesChanged(network: android.net.Network, networkCapabilities: NetworkCapabilities) {
101
- asyncEmitNetworkState(DELAY_MS)
102
- }
103
- }
104
-
105
- override fun definition() = ModuleDefinition {
106
- Name("ExpoNetwork")
107
-
108
- Events(NETWORK_STATE_EVENT_NAME)
109
-
110
- OnCreate {
111
- try {
112
- connectivityManager.registerDefaultNetworkCallback(networkCallback)
113
- isNetworkCallbackRegistered = true
114
- } catch (e: SecurityException) {
115
- Log.w(TAG, "expo-network cannot observe internet reachability: missing ACCESS_NETWORK_STATE permission", e)
116
- } catch (e: RuntimeException) {
117
- Log.e(TAG, "expo-network cannot observe internet reachability, too many callbacks registered!", e)
118
- }
119
- }
120
-
121
- OnDestroy {
122
- mainHandler.removeCallbacks(emitRunnable)
123
- if (isNetworkCallbackRegistered) {
124
- try {
125
- connectivityManager.unregisterNetworkCallback(networkCallback)
126
- } catch (e: SecurityException) {
127
- Log.w(TAG, "expo-network could not unregister network callback: missing ACCESS_NETWORK_STATE permission", e)
128
- } catch (e: Exception) {
129
- Log.w(TAG, "expo-network could not unregister network callback during teardown", e)
130
- } finally {
131
- isNetworkCallbackRegistered = false
132
- }
133
- }
134
- }
135
-
136
- AsyncFunction("getNetworkStateAsync") {
137
- return@AsyncFunction fetchNetworkState()
138
- }
139
-
140
- AsyncFunction<String>("getIpAddressAsync") {
141
- return@AsyncFunction rawIpToString(wifiInfo.ipAddress)
142
- }
143
-
144
- AsyncFunction<Boolean>("isAirplaneModeEnabledAsync") {
145
- return@AsyncFunction Settings.Global.getInt(context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0) != 0
146
- }
147
- }
148
-
149
- enum class NetworkStateType(val value: String) {
150
- NONE("NONE"),
151
- UNKNOWN("UNKNOWN"),
152
- CELLULAR("CELLULAR"),
153
- WIFI("WIFI"),
154
- BLUETOOTH("BLUETOOTH"),
155
- ETHERNET("ETHERNET"),
156
- WIMAX("WIMAX"),
157
- VPN("VPN"),
158
- OTHER("OTHER");
159
-
160
- val isDefined: Boolean
161
- get() = this.value != "NONE" && this.value != "UNKNOWN"
162
- }
163
-
164
- private fun emitNetworkState() {
165
- try {
166
- val networkState = fetchNetworkState()
167
- sendEvent(NETWORK_STATE_EVENT_NAME, networkState)
168
- } catch (e: SecurityException) {
169
- Log.w(TAG, "expo-network could not read network state: missing ACCESS_NETWORK_STATE permission", e)
170
- } catch (e: Exception) {
171
- // The runnable may outlive the module if the React context is torn down between the
172
- // ConnectivityManager callback firing and this runnable executing.
173
- Log.w(TAG, "expo-network dropped a delayed network state update during teardown (the module or React context is no longer available)", e)
174
- }
175
- }
176
-
177
- /**
178
- * Emits the network state with a delay to prevent a race condition.
179
- * This delay ensures we read the actual current network state rather than stale information.
180
- * Debounced: rapid successive calls collapse into a single emission.
181
- */
182
- private fun asyncEmitNetworkState(delay: Int) {
183
- mainHandler.removeCallbacks(emitRunnable)
184
- mainHandler.postDelayed(emitRunnable, delay.toLong())
185
- }
186
-
187
- private fun fetchNetworkState(): Bundle {
188
- val result = Bundle()
189
-
190
- try {
191
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // use getActiveNetworkInfo before api level 29
192
- val netInfo = connectivityManager.activeNetworkInfo
193
- val connectionType = getConnectionType(netInfo)
194
- val isInternetReachable = isInternetReachable(connectivityManager)
195
-
196
- result.apply {
197
- putBoolean("isInternetReachable", isInternetReachable)
198
- putString("type", connectionType.value)
199
- putBoolean("isConnected", connectionType.isDefined)
200
- }
201
-
202
- return result
203
- } else {
204
- val network = connectivityManager.activeNetwork
205
- val isInternetReachable = isInternetReachable(connectivityManager)
206
-
207
- val connectionType = if (network != null) {
208
- val netCapabilities = connectivityManager.getNetworkCapabilities(network)
209
- getConnectionType(netCapabilities)
210
- } else {
211
- null
212
- }
213
-
214
- result.apply {
215
- putString("type", connectionType?.value ?: NetworkStateType.NONE.value)
216
- putBoolean("isInternetReachable", isInternetReachable)
217
- putBoolean("isConnected", connectionType != null && connectionType.isDefined)
218
- }
219
- return result
220
- }
221
- } catch (e: Exception) {
222
- result.apply {
223
- putString("type", NetworkStateType.UNKNOWN.value)
224
- putBoolean("isInternetReachable", false)
225
- putBoolean("isConnected", false)
226
- }
227
- return result
228
- }
229
- }
230
-
231
- private val wifiInfo: WifiInfo
232
- get() = try {
233
- val manager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
234
- manager.connectionInfo
235
- } catch (e: Exception) {
236
- Log.e(TAG, e.message ?: "expo-network could not acquire Wi-Fi information")
237
- throw NetworkWifiException(e)
238
- }
239
-
240
- private fun getConnectionType(netInfo: NetworkInfo?): NetworkStateType = when (netInfo?.type) {
241
- ConnectivityManager.TYPE_MOBILE,
242
- ConnectivityManager.TYPE_MOBILE_DUN -> NetworkStateType.CELLULAR
243
- ConnectivityManager.TYPE_WIFI -> NetworkStateType.WIFI
244
- ConnectivityManager.TYPE_BLUETOOTH -> NetworkStateType.BLUETOOTH
245
- ConnectivityManager.TYPE_ETHERNET -> NetworkStateType.ETHERNET
246
- ConnectivityManager.TYPE_WIMAX -> NetworkStateType.WIMAX
247
- ConnectivityManager.TYPE_VPN -> NetworkStateType.VPN
248
- else -> NetworkStateType.UNKNOWN
249
- }
250
-
251
- private fun getConnectionType(netCapabilities: NetworkCapabilities?): NetworkStateType =
252
- when {
253
- netCapabilities == null -> NetworkStateType.UNKNOWN
254
- netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkStateType.CELLULAR
255
- netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE) -> NetworkStateType.WIFI
256
- netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> NetworkStateType.BLUETOOTH
257
- netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> NetworkStateType.ETHERNET
258
- netCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> NetworkStateType.VPN
259
- else -> NetworkStateType.UNKNOWN
260
- }
261
-
262
- private fun rawIpToString(ipAddress: Int): String {
263
- // Convert little-endian to big-endian if needed
264
- val ip = if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
265
- Integer.reverseBytes(ipAddress)
266
- } else {
267
- ipAddress
268
- }
269
-
270
- var ipByteArray = BigInteger.valueOf(ip.toLong()).toByteArray()
271
- if (ipByteArray.size < 4) {
272
- ipByteArray = frontPadWithZeros(ipByteArray)
273
- }
274
-
275
- return try {
276
- InetAddress.getByAddress(ipByteArray).hostAddress as String
277
- } catch (e: UnknownHostException) {
278
- "0.0.0.0"
279
- }
280
- }
281
- }
@@ -1,52 +0,0 @@
1
- package expo.modules.network
2
-
3
- import android.net.ConnectivityManager
4
- import android.net.NetworkCapabilities
5
- import android.net.NetworkInfo
6
- import android.os.Build
7
- import android.util.Log
8
-
9
- private val TAG = NetworkModule::class.java.simpleName
10
-
11
- internal fun frontPadWithZeros(inputArray: ByteArray): ByteArray {
12
- val newByteArray = byteArrayOf(0, 0, 0, 0)
13
- System.arraycopy(inputArray, 0, newByteArray, 4 - inputArray.size, inputArray.size)
14
- return newByteArray
15
- }
16
-
17
- /**
18
- * Returns whether the device can reach the Internet.
19
- * Checks that the active network has both the INTERNET and VALIDATED
20
- * capabilities, has a usable connection state, and (for VPN connections)
21
- * has non-zero downstream bandwidth.
22
- */
23
- internal fun isInternetReachable(
24
- connectivityManager: ConnectivityManager,
25
- sdkInt: Int = Build.VERSION.SDK_INT
26
- ): Boolean {
27
- val network = connectivityManager.activeNetwork ?: return false
28
- val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
29
-
30
- val hasUsableConnectionState = if (sdkInt >= Build.VERSION_CODES.Q) {
31
- capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
32
- } else {
33
- val networkInfo = try {
34
- connectivityManager.getNetworkInfo(network)
35
- } catch (e: SecurityException) {
36
- Log.w(TAG, "expo-network could not read network state: missing ACCESS_NETWORK_STATE permission", e)
37
- null
38
- }
39
- networkInfo?.detailedState == NetworkInfo.DetailedState.CONNECTED
40
- }
41
-
42
- var isReachable =
43
- capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
44
- capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) &&
45
- hasUsableConnectionState
46
-
47
- if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
48
- isReachable = isReachable && capabilities.linkDownstreamBandwidthKbps != 0
49
- }
50
-
51
- return isReachable
52
- }
@@ -1,7 +0,0 @@
1
- import ExpoModulesCore
2
-
3
- internal final class IpAddressException: GenericException<Int32> {
4
- override var reason: String {
5
- "No network interfaces could be retrieved. getifaddrs() failed with error number: \(param)"
6
- }
7
- }
@@ -1,28 +0,0 @@
1
- require 'json'
2
-
3
- package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
-
5
- Pod::Spec.new do |s|
6
- s.name = 'ExpoNetwork'
7
- s.version = package['version']
8
- s.summary = package['description']
9
- s.description = package['description']
10
- s.license = package['license']
11
- s.author = package['author']
12
- s.homepage = package['homepage']
13
- s.platforms = {
14
- :ios => '16.4',
15
- :tvos => '16.4',
16
- :osx => '13.4'
17
- }
18
- s.source = { git: 'https://github.com/expo/expo.git' }
19
- s.static_framework = true
20
-
21
- s.dependency 'ExpoModulesCore'
22
-
23
- s.source_files = "**/*.{h,m,swift}"
24
- s.pod_target_xcconfig = {
25
- 'DEFINES_MODULE' => 'YES',
26
- 'SWIFT_COMPILATION_MODE' => 'wholemodule'
27
- }
28
- end
@@ -1,155 +0,0 @@
1
- import ExpoModulesCore
2
- import Network
3
-
4
- let onNetworkStateChanged = "onNetworkStateChanged"
5
-
6
- public final class NetworkModule: Module {
7
- private let monitor = NWPathMonitor()
8
- private let monitorQueue = DispatchQueue.global(qos: .default)
9
-
10
- public func definition() -> ModuleDefinition {
11
- Name("ExpoNetwork")
12
-
13
- Events(onNetworkStateChanged)
14
-
15
- OnStartObserving(onNetworkStateChanged) {
16
- setupNetworkMonitoring()
17
- }
18
-
19
- AsyncFunction("getIpAddressAsync") { () -> String? in
20
- return try getIPAddress()
21
- }
22
-
23
- AsyncFunction("getNetworkStateAsync") {
24
- return getNetworkStateAsync()
25
- }
26
-
27
- OnStopObserving(onNetworkStateChanged) {
28
- monitor.cancel()
29
- }
30
-
31
- OnDestroy {
32
- monitor.cancel()
33
- }
34
- }
35
-
36
- private func setupNetworkMonitoring() {
37
- monitor.pathUpdateHandler = { [weak self] path in
38
- guard let self else {
39
- return
40
- }
41
- self.emitNetworkStateChange(path: path)
42
- }
43
- monitor.start(queue: monitorQueue)
44
- }
45
-
46
- private func emitNetworkStateChange(path: NWPath) {
47
- let networkState = getNetworkStateAsync(path: path)
48
- sendEvent(onNetworkStateChanged, networkState)
49
- }
50
-
51
- private func getIPAddress() throws -> String {
52
- var address = "0.0.0.0"
53
- var ifaddr: UnsafeMutablePointer<ifaddrs>?
54
-
55
- let error = getifaddrs(&ifaddr)
56
-
57
- guard error == 0 else {
58
- throw IpAddressException(error)
59
- }
60
-
61
- guard let firstAddr = ifaddr else {
62
- return address
63
- }
64
-
65
- for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
66
- let temp = ifptr.pointee
67
- let family = temp.ifa_addr.pointee.sa_family
68
-
69
- if family == UInt8(AF_INET) {
70
- let name = String(cString: temp.ifa_name)
71
- if name.starts(with: "en") {
72
- var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
73
- getnameinfo(
74
- temp.ifa_addr,
75
- socklen_t(temp.ifa_addr.pointee.sa_len),
76
- &hostname,
77
- socklen_t(hostname.count),
78
- nil,
79
- socklen_t(0),
80
- NI_NUMERICHOST)
81
- address = String(cString: hostname)
82
- }
83
- }
84
- }
85
-
86
- freeifaddrs(ifaddr)
87
- return address
88
- }
89
-
90
- private func getNetworkPathAsync() -> NWPath? {
91
- // create a temporary monitor to avoid interfering with the module's monitor
92
- // since we want to wait for the result to be updated once:
93
- let tempMonitor = NWPathMonitor()
94
- var tempPath: NWPath?
95
-
96
- let semaphore = DispatchSemaphore(value: 0)
97
-
98
- tempMonitor.pathUpdateHandler = { updatedPath in
99
- tempPath = updatedPath
100
- semaphore.signal() // Notify that we got the path
101
- }
102
-
103
- tempMonitor.start(queue: monitorQueue)
104
-
105
- // Wait max 5 seconds to avoid any locking issues if the monitor
106
- // doesn't get an update in time.
107
- let result = semaphore.wait(timeout: .now() + 5)
108
- tempMonitor.cancel()
109
-
110
- if result == .timedOut {
111
- // Handle the timeout case
112
- print("Timeout waiting for network path.")
113
- return nil
114
- }
115
-
116
- return tempPath
117
- }
118
-
119
- private func getNetworkStateAsync(path: NWPath? = nil) -> [String: Any] {
120
- let currentPath = path ?? getNetworkPathAsync()
121
- let isConnected = currentPath?.status == .satisfied
122
-
123
- if !isConnected {
124
- return [
125
- "type": NetworkType.none.description,
126
- "isConnected": isConnected,
127
- "isInternetReachable": isConnected
128
- ]
129
- }
130
-
131
- let connectionType = NWInterface
132
- .InterfaceType
133
- .allCases
134
- .filter { currentPath?.usesInterfaceType($0) == true }
135
- .first
136
-
137
- var currentNetworkType = NetworkType.unknown
138
- switch connectionType {
139
- case .wifi:
140
- currentNetworkType = .wifi
141
- case .cellular:
142
- currentNetworkType = .cellular
143
- case .wiredEthernet:
144
- currentNetworkType = .wiredEthernet
145
- default:
146
- currentNetworkType = .unknown
147
- }
148
-
149
- return [
150
- "type": currentNetworkType.description,
151
- "isConnected": isConnected,
152
- "isInternetReachable": isConnected
153
- ]
154
- }
155
- }
@@ -1,30 +0,0 @@
1
- import Network
2
-
3
- extension NWInterface.InterfaceType: CaseIterable {
4
- public static var allCases: [NWInterface.InterfaceType] = [
5
- .other,
6
- .wifi,
7
- .cellular,
8
- .loopback,
9
- .wiredEthernet
10
- ]
11
- }
12
-
13
- enum NetworkType: CustomStringConvertible {
14
- case unknown, wifi, none, cellular, wiredEthernet
15
-
16
- var description: String {
17
- switch self {
18
- case .wifi:
19
- return "WIFI"
20
- case .cellular:
21
- return "CELLULAR"
22
- case .wiredEthernet:
23
- return "ETHERNET"
24
- case .unknown:
25
- return "UNKNOWN"
26
- case .none:
27
- return "NONE"
28
- }
29
- }
30
- }
@@ -1 +0,0 @@
1
- 012f1dc241eafa35250a95774539d1018f0cd2dc0a309691938b8ba2b21ffc9f
@@ -1 +0,0 @@
1
- 743b63ffd72fd1933f210ab6c5d1fe07e068b45fd4dbb30aad632829e0c492b34407cc49483aceeda15f847ce7c5d7bd9c304a2ade1498b3ca4114c4ae5d3a99
@@ -1 +0,0 @@
1
- d60008645cc2045cc850b1422bdf7aa357c4baf9a5b2578b87ca28b27ed6d2b3
@@ -1 +0,0 @@
1
- 6870b0ebade57761fe61e7533c54c275001356a7adbdbaf551ce9d81ec5926981f7bd64ba505419f4fe6de964501129b9c76824a0f7de1aa1d209f7e67e01545
@@ -1,101 +0,0 @@
1
- {
2
- "formatVersion": "1.1",
3
- "component": {
4
- "group": "host.exp.exponent",
5
- "module": "expo.modules.network",
6
- "version": "57.0.2",
7
- "attributes": {
8
- "org.gradle.status": "release"
9
- }
10
- },
11
- "createdBy": {
12
- "gradle": {
13
- "version": "9.3.1"
14
- }
15
- },
16
- "variants": [
17
- {
18
- "name": "releaseVariantReleaseApiPublication",
19
- "attributes": {
20
- "org.gradle.category": "library",
21
- "org.gradle.dependency.bundling": "external",
22
- "org.gradle.libraryelements": "aar",
23
- "org.gradle.usage": "java-api"
24
- },
25
- "files": [
26
- {
27
- "name": "expo.modules.network-57.0.2.aar",
28
- "url": "expo.modules.network-57.0.2.aar",
29
- "size": 22879,
30
- "sha512": "6870b0ebade57761fe61e7533c54c275001356a7adbdbaf551ce9d81ec5926981f7bd64ba505419f4fe6de964501129b9c76824a0f7de1aa1d209f7e67e01545",
31
- "sha256": "d60008645cc2045cc850b1422bdf7aa357c4baf9a5b2578b87ca28b27ed6d2b3",
32
- "sha1": "0a9abad427021775e2af3bb616ca344e8636a6c3",
33
- "md5": "fd7ecf4c5e846531494a961499be570f"
34
- }
35
- ]
36
- },
37
- {
38
- "name": "releaseVariantReleaseRuntimePublication",
39
- "attributes": {
40
- "org.gradle.category": "library",
41
- "org.gradle.dependency.bundling": "external",
42
- "org.gradle.libraryelements": "aar",
43
- "org.gradle.usage": "java-runtime"
44
- },
45
- "dependencies": [
46
- {
47
- "group": "org.jetbrains.kotlin",
48
- "module": "kotlin-stdlib-jdk7",
49
- "version": {
50
- "requires": "2.1.20"
51
- }
52
- },
53
- {
54
- "group": "org.jetbrains",
55
- "module": "annotations",
56
- "version": {
57
- "requires": "23.0.0"
58
- }
59
- },
60
- {
61
- "group": "io.github.lukmccall.pika",
62
- "module": "pika-api",
63
- "version": {
64
- "requires": "0.3.2"
65
- }
66
- }
67
- ],
68
- "files": [
69
- {
70
- "name": "expo.modules.network-57.0.2.aar",
71
- "url": "expo.modules.network-57.0.2.aar",
72
- "size": 22879,
73
- "sha512": "6870b0ebade57761fe61e7533c54c275001356a7adbdbaf551ce9d81ec5926981f7bd64ba505419f4fe6de964501129b9c76824a0f7de1aa1d209f7e67e01545",
74
- "sha256": "d60008645cc2045cc850b1422bdf7aa357c4baf9a5b2578b87ca28b27ed6d2b3",
75
- "sha1": "0a9abad427021775e2af3bb616ca344e8636a6c3",
76
- "md5": "fd7ecf4c5e846531494a961499be570f"
77
- }
78
- ]
79
- },
80
- {
81
- "name": "releaseVariantReleaseSourcePublication",
82
- "attributes": {
83
- "org.gradle.category": "documentation",
84
- "org.gradle.dependency.bundling": "external",
85
- "org.gradle.docstype": "sources",
86
- "org.gradle.usage": "java-runtime"
87
- },
88
- "files": [
89
- {
90
- "name": "expo.modules.network-57.0.2-sources.jar",
91
- "url": "expo.modules.network-57.0.2-sources.jar",
92
- "size": 5575,
93
- "sha512": "743b63ffd72fd1933f210ab6c5d1fe07e068b45fd4dbb30aad632829e0c492b34407cc49483aceeda15f847ce7c5d7bd9c304a2ade1498b3ca4114c4ae5d3a99",
94
- "sha256": "012f1dc241eafa35250a95774539d1018f0cd2dc0a309691938b8ba2b21ffc9f",
95
- "sha1": "1120869fc8c001fb9eeaa68263763b5551d6be2d",
96
- "md5": "27575b44500de7b7a54306c76e2e92c6"
97
- }
98
- ]
99
- }
100
- ]
101
- }
@@ -1 +0,0 @@
1
- d7dfa690d55c73b680d75bf378f745559dba16833496234db18705293f5fd681
@@ -1 +0,0 @@
1
- 3d5d18c7f0c20193d81c57ec2f322c26503de5ee6c7206b8a75d3347e63d93ed92716ac31cb3f32a99b75a88f7886c41686cf82a4b6226b14c7c463b791381ec
@@ -1,59 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4
- <!-- This module was also published with a richer model, Gradle metadata, -->
5
- <!-- which should be used instead. Do not delete the following line which -->
6
- <!-- is to indicate to Gradle or any Gradle module metadata file consumer -->
7
- <!-- that they should prefer consuming it instead. -->
8
- <!-- do_not_remove: published-with-gradle-metadata -->
9
- <modelVersion>4.0.0</modelVersion>
10
- <groupId>host.exp.exponent</groupId>
11
- <artifactId>expo.modules.network</artifactId>
12
- <version>57.0.2</version>
13
- <packaging>aar</packaging>
14
- <name>expo.modules.network</name>
15
- <url>https://github.com/expo/expo</url>
16
- <organization>
17
- <name>650 Industries, Inc. (“Expo”)</name>
18
- <url>https://expo.dev/home</url>
19
- </organization>
20
- <licenses>
21
- <license>
22
- <name>MIT License</name>
23
- <url>https://github.com/expo/expo/blob/main/LICENSE</url>
24
- <distribution>https://github.com/expo/expo/blob/main/LICENSE</distribution>
25
- </license>
26
- </licenses>
27
- <developers>
28
- <developer>
29
- <name>Expo Maintainers</name>
30
- <email>support@expo.dev</email>
31
- <url>https://github.com/orgs/expo/people</url>
32
- </developer>
33
- </developers>
34
- <scm>
35
- <connection>https://github.com/expo/expo.git</connection>
36
- <developerConnection>https://github.com/expo/expo.git</developerConnection>
37
- <url>https://github.com/expo/expo</url>
38
- </scm>
39
- <dependencies>
40
- <dependency>
41
- <groupId>org.jetbrains.kotlin</groupId>
42
- <artifactId>kotlin-stdlib-jdk7</artifactId>
43
- <version>2.1.20</version>
44
- <scope>runtime</scope>
45
- </dependency>
46
- <dependency>
47
- <groupId>org.jetbrains</groupId>
48
- <artifactId>annotations</artifactId>
49
- <version>23.0.0</version>
50
- <scope>runtime</scope>
51
- </dependency>
52
- <dependency>
53
- <groupId>io.github.lukmccall.pika</groupId>
54
- <artifactId>pika-api</artifactId>
55
- <version>0.3.2</version>
56
- <scope>runtime</scope>
57
- </dependency>
58
- </dependencies>
59
- </project>
@@ -1 +0,0 @@
1
- 3323fc13b71b6ada6f1a4700bf7bb825046c8cb7c30c8ae1b53ffe7580012b92
@@ -1 +0,0 @@
1
- ecf26bd3f69bf2380d5fbe9f2937d6c13dc56f9c91cd880a5f8cfdd4b58fb25bbf1c7d65277f36f5d3d1e983bcf9b0a4a8f310689e20c4dea6269a2bec35b291
@@ -1,13 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <metadata>
3
- <groupId>host.exp.exponent</groupId>
4
- <artifactId>expo.modules.network</artifactId>
5
- <versioning>
6
- <latest>57.0.2</latest>
7
- <release>57.0.2</release>
8
- <versions>
9
- <version>57.0.2</version>
10
- </versions>
11
- <lastUpdated>20260911111646</lastUpdated>
12
- </versioning>
13
- </metadata>
@@ -1 +0,0 @@
1
- 17adbbfac538f2470fc11a0a60a52ba5
@@ -1 +0,0 @@
1
- 08af64bbe735d0cec2649ba13330d98af8871246
@@ -1 +0,0 @@
1
- ab3002afa9f8c83050c7b31a1fc9654d1dce0aa35f8082eb4d290ddfa09c6e0e
@@ -1 +0,0 @@
1
- 444ac1e0102b883c89ffe1fd1f358eb82ea4d9bd1b4812a21445ae2524fb0690af31b15078aae20fe24fcd49302bef96fc3fe89a2c69cd53d228fd04e8c63d6f
package/spm.config.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "$schema": "../../tools/src/prebuilds/schemas/spm.config.schema.json",
3
- "products": [
4
- {
5
- "name": "ExpoNetwork",
6
- "podName": "ExpoNetwork",
7
- "platforms": [
8
- "iOS(\"16.4\")"
9
- ],
10
- "externalDependencies": [
11
- "ReactNativeDependencies",
12
- "React",
13
- "Hermes",
14
- "expo-modules-core/ExpoModulesCore"
15
- ],
16
- "targets": [
17
- {
18
- "type": "swift",
19
- "name": "ExpoNetwork",
20
- "path": "ios",
21
- "pattern": "*.swift",
22
- "dependencies": [
23
- "Hermes",
24
- "React",
25
- "ReactNativeDependencies",
26
- "expo-modules-core/ExpoModulesCore"
27
- ],
28
- "linkedFrameworks": [
29
- "Foundation",
30
- "SystemConfiguration",
31
- "Network"
32
- ]
33
- }
34
- ]
35
- }
36
- ]
37
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- // @generated by expo-module-scripts
2
- {
3
- "extends": "expo-module-scripts/tsconfig.base",
4
- "compilerOptions": {
5
- "rootDir": "./src",
6
- "outDir": "./build"
7
- },
8
- "include": ["./src"],
9
- "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
10
- }