@akbeniwal/react-native-smart-netinfo 1.0.3 β 1.0.5
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.
- package/README.md +13 -1
- package/ReactNativeSmartNetinfo.podspec +20 -0
- package/android/build.gradle +67 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/akbeniwal/reactnativesmartnetinfo/ReactNativeSmartNetinfoModule.kt +73 -0
- package/android/src/main/java/com/akbeniwal/reactnativesmartnetinfo/ReactNativeSmartNetinfoPackage.kt +31 -0
- package/dist/NativeReactNativeSmartNetinfo.d.ts +8 -0
- package/dist/NativeReactNativeSmartNetinfo.d.ts.map +1 -0
- package/dist/NativeReactNativeSmartNetinfo.js +3 -0
- package/dist/NativeReactNativeSmartNetinfo.js.map +1 -0
- package/dist/SmartNetInfo.d.ts +4 -0
- package/dist/SmartNetInfo.d.ts.map +1 -1
- package/dist/SmartNetInfo.js +55 -4
- package/dist/SmartNetInfo.js.map +1 -1
- package/dist/utils/getLatency.d.ts.map +1 -1
- package/dist/utils/getLatency.js +1 -0
- package/dist/utils/getLatency.js.map +1 -1
- package/ios/ReactNativeSmartNetinfo.h +6 -0
- package/ios/ReactNativeSmartNetinfo.mm +73 -0
- package/package.json +13 -2
- package/src/NativeReactNativeSmartNetinfo.ts +8 -0
- package/src/SmartNetInfo.ts +342 -0
- package/src/index.ts +2 -0
- package/src/types.ts +29 -0
- package/src/utils/getConnectionQuality.ts +20 -0
- package/src/utils/getLatency.ts +61 -0
- package/src/utils/runSpeedTest.ts +60 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A lightweight, zero-dependency, smart network connection monitor for React Nativ
|
|
|
11
11
|
- π **Auto Speed Test**: Automatically run speed test when transitioning from offline to online.
|
|
12
12
|
- π± **Foreground Check**: Recheck connectivity instantly when the app transitions back to the foreground.
|
|
13
13
|
- πΈοΈ **Web Compatibility**: Smooth fallback and support for browser online/offline events (perfect for React Native Web).
|
|
14
|
-
- π§© **
|
|
14
|
+
- π§© **Hybrid Architecture (Native + JS)**: Uses iOS (`NWPathMonitor`) and Android (`ConnectivityManager`) native modules for **Zero Battery Drain** when offline, and JS Polling for real-time latency when online.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -25,6 +25,18 @@ npm install @akbeniwal/react-native-smart-netinfo
|
|
|
25
25
|
yarn add @akbeniwal/react-native-smart-netinfo
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
### iOS Setup
|
|
29
|
+
|
|
30
|
+
Since this package uses native modules for optimal battery performance, you must run `pod install` for iOS:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd ios
|
|
34
|
+
pod install
|
|
35
|
+
cd ..
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
*(Android is auto-linked, no extra steps required).*
|
|
39
|
+
|
|
28
40
|
---
|
|
29
41
|
|
|
30
42
|
## Usage
|
|
@@ -0,0 +1,20 @@
|
|
|
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 = "ReactNativeSmartNetinfo"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/AKBeniwal1700/react-native-smart-netinfo.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.ReactNativeSmartNetinfo = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return ReactNativeSmartNetinfo[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.akbeniwal.reactnativesmartnetinfo"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
}
|
package/android/src/main/java/com/akbeniwal/reactnativesmartnetinfo/ReactNativeSmartNetinfoModule.kt
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.akbeniwal.reactnativesmartnetinfo
|
|
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.NetworkRequest
|
|
8
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
10
|
+
import com.facebook.react.bridge.Arguments
|
|
11
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
12
|
+
|
|
13
|
+
class ReactNativeSmartNetinfoModule(private val reactContext: ReactApplicationContext) :
|
|
14
|
+
NativeReactNativeSmartNetinfoSpec(reactContext) {
|
|
15
|
+
|
|
16
|
+
private var connectivityManager: ConnectivityManager? = null
|
|
17
|
+
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
|
18
|
+
private var isConnected = false
|
|
19
|
+
|
|
20
|
+
init {
|
|
21
|
+
connectivityManager = reactContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
|
22
|
+
registerNetworkCallback()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private fun registerNetworkCallback() {
|
|
26
|
+
try {
|
|
27
|
+
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
28
|
+
override fun onAvailable(network: Network) {
|
|
29
|
+
super.onAvailable(network)
|
|
30
|
+
isConnected = true
|
|
31
|
+
sendEvent("NetworkStatusChanged", true)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun onLost(network: Network) {
|
|
35
|
+
super.onLost(network)
|
|
36
|
+
isConnected = false
|
|
37
|
+
sendEvent("NetworkStatusChanged", false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
val builder = NetworkRequest.Builder()
|
|
42
|
+
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
43
|
+
|
|
44
|
+
connectivityManager?.registerNetworkCallback(builder.build(), networkCallback!!)
|
|
45
|
+
} catch (e: Exception) {
|
|
46
|
+
// Fallback or ignore
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private fun sendEvent(eventName: String, isConnected: Boolean) {
|
|
51
|
+
try {
|
|
52
|
+
val params: WritableMap = Arguments.createMap()
|
|
53
|
+
params.putBoolean("isConnected", isConnected)
|
|
54
|
+
reactContext
|
|
55
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
56
|
+
.emit(eventName, params)
|
|
57
|
+
} catch (e: Exception) {
|
|
58
|
+
// Ignored
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun addListener(eventName: String) {
|
|
63
|
+
// Required for RN built-in Event Emitter Calls
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun removeListeners(count: Double) {
|
|
67
|
+
// Required for RN built-in Event Emitter Calls
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
companion object {
|
|
71
|
+
const val NAME = NativeReactNativeSmartNetinfoSpec.NAME
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.akbeniwal.reactnativesmartnetinfo
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class ReactNativeSmartNetinfoPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == ReactNativeSmartNetinfoModule.NAME) {
|
|
13
|
+
ReactNativeSmartNetinfoModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
20
|
+
mapOf(
|
|
21
|
+
ReactNativeSmartNetinfoModule.NAME to ReactModuleInfo(
|
|
22
|
+
name = ReactNativeSmartNetinfoModule.NAME,
|
|
23
|
+
className = ReactNativeSmartNetinfoModule.NAME,
|
|
24
|
+
canOverrideExistingModule = false,
|
|
25
|
+
needsEagerInit = false,
|
|
26
|
+
isCxxModule = false,
|
|
27
|
+
isTurboModule = true
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type TurboModule } from 'react-native';
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
addListener(eventName: string): void;
|
|
4
|
+
removeListeners(count: number): void;
|
|
5
|
+
}
|
|
6
|
+
declare const _default: Spec;
|
|
7
|
+
export default _default;
|
|
8
|
+
//# sourceMappingURL=NativeReactNativeSmartNetinfo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeReactNativeSmartNetinfo.d.ts","sourceRoot":"","sources":["../src/NativeReactNativeSmartNetinfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;;AAED,wBAAiF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeReactNativeSmartNetinfo.js","sourceRoot":"","sources":["../src/NativeReactNativeSmartNetinfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAoB,MAAM,cAAc,CAAC;AAOrE,eAAe,mBAAmB,CAAC,YAAY,CAAO,yBAAyB,CAAC,CAAC"}
|
package/dist/SmartNetInfo.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { NetworkState, SmartNetInfoConfig, NetworkStateListener } from './types';
|
|
2
2
|
declare class SmartNetInfoManager {
|
|
3
|
+
private pingUrlIndex;
|
|
3
4
|
private config;
|
|
4
5
|
private state;
|
|
5
6
|
private listeners;
|
|
6
7
|
private timeoutId;
|
|
7
8
|
private appStateSubscription;
|
|
9
|
+
private nativeEventEmitter;
|
|
10
|
+
private nativeEventSubscription;
|
|
8
11
|
private isMonitoring;
|
|
9
12
|
private isChecking;
|
|
10
13
|
/**
|
|
@@ -45,6 +48,7 @@ declare class SmartNetInfoManager {
|
|
|
45
48
|
* Stop monitoring network status and clean up timers and listeners.
|
|
46
49
|
*/
|
|
47
50
|
stopMonitoring(): void;
|
|
51
|
+
private handleNativeNetworkChange;
|
|
48
52
|
private handleAppStateChange;
|
|
49
53
|
private handleWebOnline;
|
|
50
54
|
private handleWebOffline;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartNetInfo.d.ts","sourceRoot":"","sources":["../src/SmartNetInfo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"SmartNetInfo.d.ts","sourceRoot":"","sources":["../src/SmartNetInfo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAcjF,cAAM,mBAAmB;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,MAAM,CAKZ;IAEF,OAAO,CAAC,KAAK,CAOX;IAEF,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,oBAAoB,CAAuC;IACnE,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,OAAO,CAAC,uBAAuB,CAAuC;IACtE,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,UAAU,CAAS;IAE3B;;;OAGG;IACI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI;IAQ3D;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,YAAY,CAAC;IAK3C;;;;;;OAMG;IACI,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAanE;;;;;OAKG;IACI,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAOhE;;;;OAIG;IACU,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAkBnD;;OAEG;IACI,eAAe,IAAI,IAAI;IA6C9B;;OAEG;IACI,cAAc,IAAI,IAAI;IA+B7B,OAAO,CAAC,yBAAyB,CAkB/B;IAEF,OAAO,CAAC,oBAAoB,CAM1B;IAEF,OAAO,CAAC,eAAe,CAKrB;IAEF,OAAO,CAAC,gBAAgB,CAOtB;IAEF,OAAO,CAAC,iBAAiB;YA6BX,iBAAiB;IA0C/B,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,eAAe;CASxB;AAED,eAAO,MAAM,YAAY,qBAA4B,CAAC;AACtD,eAAe,YAAY,CAAC"}
|
package/dist/SmartNetInfo.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { AppState } from 'react-native';
|
|
1
|
+
import { AppState, NativeEventEmitter } from 'react-native';
|
|
2
|
+
import NativeReactNativeSmartNetinfo from './NativeReactNativeSmartNetinfo';
|
|
2
3
|
import { getConnectionQuality } from './utils/getConnectionQuality';
|
|
3
4
|
import { getLatency } from './utils/getLatency';
|
|
4
5
|
import { runSpeedTest as executeSpeedTest } from './utils/runSpeedTest';
|
|
5
|
-
const
|
|
6
|
+
const PING_URLS = [
|
|
7
|
+
'https://clients3.google.com/generate_204',
|
|
8
|
+
'https://www.apple.com/library/test/success.html',
|
|
9
|
+
'https://cloudflare-dns.com/dns-query',
|
|
10
|
+
'https://google.com/generate_204'
|
|
11
|
+
];
|
|
6
12
|
const SPEED_TEST_URL = 'https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js';
|
|
7
13
|
class SmartNetInfoManager {
|
|
8
14
|
constructor() {
|
|
15
|
+
this.pingUrlIndex = 0;
|
|
9
16
|
this.config = {
|
|
10
17
|
pingIntervalMs: 30000,
|
|
11
18
|
timeoutMs: 5000,
|
|
@@ -23,8 +30,30 @@ class SmartNetInfoManager {
|
|
|
23
30
|
this.listeners = new Set();
|
|
24
31
|
this.timeoutId = null;
|
|
25
32
|
this.appStateSubscription = null;
|
|
33
|
+
this.nativeEventEmitter = null;
|
|
34
|
+
this.nativeEventSubscription = null;
|
|
26
35
|
this.isMonitoring = false;
|
|
27
36
|
this.isChecking = false;
|
|
37
|
+
this.handleNativeNetworkChange = (event) => {
|
|
38
|
+
if (!event.isConnected) {
|
|
39
|
+
this.updateState({
|
|
40
|
+
isConnected: false,
|
|
41
|
+
isInternetReachable: false,
|
|
42
|
+
latencyMs: null,
|
|
43
|
+
connectionQuality: null,
|
|
44
|
+
});
|
|
45
|
+
if (this.timeoutId) {
|
|
46
|
+
clearTimeout(this.timeoutId);
|
|
47
|
+
this.timeoutId = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.updateState({ isConnected: true });
|
|
52
|
+
this.checkConnectivity().finally(() => {
|
|
53
|
+
this.scheduleNextCheck();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
28
57
|
this.handleAppStateChange = (nextAppState) => {
|
|
29
58
|
if (nextAppState === 'active') {
|
|
30
59
|
this.checkConnectivity().finally(() => {
|
|
@@ -118,6 +147,16 @@ class SmartNetInfoManager {
|
|
|
118
147
|
if (this.isMonitoring)
|
|
119
148
|
return;
|
|
120
149
|
this.isMonitoring = true;
|
|
150
|
+
// Set up Native Event Emitter if available
|
|
151
|
+
try {
|
|
152
|
+
if (NativeReactNativeSmartNetinfo) {
|
|
153
|
+
this.nativeEventEmitter = new NativeEventEmitter(NativeReactNativeSmartNetinfo);
|
|
154
|
+
this.nativeEventSubscription = this.nativeEventEmitter.addListener('NetworkStatusChanged', this.handleNativeNetworkChange);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
console.warn('SmartNetInfo native module not found, falling back to pure polling');
|
|
159
|
+
}
|
|
121
160
|
// Run initial check immediately and then schedule subsequent checks
|
|
122
161
|
this.checkConnectivity().then(() => {
|
|
123
162
|
// Auto run speed test on initial connect if online
|
|
@@ -153,6 +192,10 @@ class SmartNetInfoManager {
|
|
|
153
192
|
clearTimeout(this.timeoutId);
|
|
154
193
|
this.timeoutId = null;
|
|
155
194
|
}
|
|
195
|
+
if (this.nativeEventSubscription) {
|
|
196
|
+
this.nativeEventSubscription.remove();
|
|
197
|
+
this.nativeEventSubscription = null;
|
|
198
|
+
}
|
|
156
199
|
if (this.appStateSubscription) {
|
|
157
200
|
if (typeof this.appStateSubscription.remove === 'function') {
|
|
158
201
|
this.appStateSubscription.remove();
|
|
@@ -178,7 +221,12 @@ class SmartNetInfoManager {
|
|
|
178
221
|
}
|
|
179
222
|
if (this.config.pingIntervalMs <= 0)
|
|
180
223
|
return;
|
|
181
|
-
//
|
|
224
|
+
// If native module explicitly told us we are offline (isConnected: false),
|
|
225
|
+
// we DO NOT poll at all to save battery. We wait for native event.
|
|
226
|
+
if (this.state.isConnected === false && this.nativeEventEmitter) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// When offline (without native fallback), check more frequently (every 3 seconds) to detect recovery quickly.
|
|
182
230
|
// When online, use the configured pingIntervalMs.
|
|
183
231
|
const isOffline = this.state.isInternetReachable === false;
|
|
184
232
|
const interval = isOffline
|
|
@@ -199,7 +247,10 @@ class SmartNetInfoManager {
|
|
|
199
247
|
const actualTimeout = isCurrentlyOffline
|
|
200
248
|
? Math.max(this.config.timeoutMs, 4000)
|
|
201
249
|
: this.config.timeoutMs;
|
|
202
|
-
const
|
|
250
|
+
const currentPingUrl = PING_URLS[this.pingUrlIndex];
|
|
251
|
+
// Cycle to the next URL for the next check to avoid DNS caching issues if it fails
|
|
252
|
+
this.pingUrlIndex = (this.pingUrlIndex + 1) % PING_URLS.length;
|
|
253
|
+
const { isReachable, latencyMs } = await getLatency(currentPingUrl, actualTimeout);
|
|
203
254
|
const wasInternetReachable = this.state.isInternetReachable;
|
|
204
255
|
this.updateState({
|
|
205
256
|
isConnected: isReachable,
|
package/dist/SmartNetInfo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SmartNetInfo.js","sourceRoot":"","sources":["../src/SmartNetInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAkB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"SmartNetInfo.js","sourceRoot":"","sources":["../src/SmartNetInfo.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLatency.d.ts","sourceRoot":"","sources":["../../src/utils/getLatency.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"getLatency.d.ts","sourceRoot":"","sources":["../../src/utils/getLatency.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C3F"}
|
package/dist/utils/getLatency.js
CHANGED
|
@@ -41,6 +41,7 @@ export async function getLatency(pingUrl, timeoutMs) {
|
|
|
41
41
|
return { isReachable, latencyMs };
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
|
+
console.warn(`[SmartNetInfo] getLatency failed:`, error instanceof Error ? error.message : error);
|
|
44
45
|
// If it fails due to network/timeout, we are offline.
|
|
45
46
|
return { isReachable: false, latencyMs: null };
|
|
46
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getLatency.js","sourceRoot":"","sources":["../../src/utils/getLatency.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,SAAiB;IACjE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAyC,IAAI,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,oFAAoF;QACpF,MAAM,SAAS,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAEzH,8CAA8C;QAC9C,wFAAwF;QACxF,4EAA4E;QAC5E,4CAA4C;QAC5C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YACtC,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,eAAe,EAAE,qCAAqC;gBACtD,QAAQ,EAAE,UAAU;gBACpB,SAAS,EAAE,GAAG;aACf;SACF,CAAC,CAAC;QAEH,8EAA8E;QAC9E,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS;QACX,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;QACzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sDAAsD;QACtD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjD,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"getLatency.js","sourceRoot":"","sources":["../../src/utils/getLatency.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAe,EAAE,SAAiB;IACjE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,SAAS,GAAyC,IAAI,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAC1B,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,oFAAoF;QACpF,MAAM,SAAS,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAEzH,8CAA8C;QAC9C,wFAAwF;QACxF,4EAA4E;QAC5E,4CAA4C;QAC5C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;YACtC,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE;gBACP,eAAe,EAAE,qCAAqC;gBACtD,QAAQ,EAAE,UAAU;gBACpB,SAAS,EAAE,GAAG;aACf;SACF,CAAC,CAAC;QAEH,8EAA8E;QAC9E,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,SAAS;QACX,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;QACzD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClG,sDAAsD;QACtD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACjD,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#import "ReactNativeSmartNetinfo.h"
|
|
2
|
+
#import <Network/Network.h>
|
|
3
|
+
|
|
4
|
+
@implementation ReactNativeSmartNetinfo {
|
|
5
|
+
nw_path_monitor_t _monitor;
|
|
6
|
+
dispatch_queue_t _queue;
|
|
7
|
+
BOOL _hasListeners;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
RCT_EXPORT_MODULE()
|
|
11
|
+
|
|
12
|
+
- (instancetype)init {
|
|
13
|
+
if (self = [super init]) {
|
|
14
|
+
_queue = dispatch_queue_create("com.smartnetinfo.networkmonitor", NULL);
|
|
15
|
+
_monitor = nw_path_monitor_create();
|
|
16
|
+
|
|
17
|
+
__weak typeof(self) weakSelf = self;
|
|
18
|
+
nw_path_monitor_set_update_handler(_monitor, ^(nw_path_t path) {
|
|
19
|
+
nw_path_status_t status = nw_path_get_status(path);
|
|
20
|
+
BOOL isConnected = (status == nw_path_status_satisfied);
|
|
21
|
+
[weakSelf sendNetworkStatusEvent:isConnected];
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
nw_path_monitor_set_queue(_monitor, _queue);
|
|
25
|
+
nw_path_monitor_start(_monitor);
|
|
26
|
+
}
|
|
27
|
+
return self;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
- (void)dealloc {
|
|
31
|
+
if (_monitor) {
|
|
32
|
+
nw_path_monitor_cancel(_monitor);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
- (NSArray<NSString *> *)supportedEvents {
|
|
37
|
+
return @[@"NetworkStatusChanged"];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
- (void)startObserving {
|
|
41
|
+
_hasListeners = YES;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)stopObserving {
|
|
45
|
+
_hasListeners = NO;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)sendNetworkStatusEvent:(BOOL)isConnected {
|
|
49
|
+
if (_hasListeners) {
|
|
50
|
+
[self sendEventWithName:@"NetworkStatusChanged" body:@{@"isConnected": @(isConnected)}];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (void)addListener:(NSString *)eventName {
|
|
55
|
+
// Required by TurboModules spec
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)removeListeners:(double)count {
|
|
59
|
+
// Required by TurboModules spec
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
63
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
64
|
+
{
|
|
65
|
+
return std::make_shared<facebook::react::NativeReactNativeSmartNetinfoSpecJSI>(params);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
+ (NSString *)moduleName
|
|
69
|
+
{
|
|
70
|
+
return @"ReactNativeSmartNetinfo";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akbeniwal/react-native-smart-netinfo",
|
|
3
|
-
|
|
4
|
-
"version": "1.0.3",
|
|
3
|
+
"version": "1.0.5",
|
|
5
4
|
"description": "A lightweight smart network monitoring library for React Native with internet reachability, latency monitoring, connection quality detection and speed testing.",
|
|
6
5
|
"main": "dist/index.js",
|
|
7
6
|
"types": "dist/index.d.ts",
|
|
8
7
|
"files": [
|
|
8
|
+
"src",
|
|
9
9
|
"dist",
|
|
10
|
+
"android",
|
|
11
|
+
"ios",
|
|
12
|
+
"*.podspec",
|
|
10
13
|
"README.md",
|
|
11
14
|
"LICENSE"
|
|
12
15
|
],
|
|
@@ -52,5 +55,13 @@
|
|
|
52
55
|
},
|
|
53
56
|
"engines": {
|
|
54
57
|
"node": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"codegenConfig": {
|
|
60
|
+
"name": "ReactNativeSmartNetinfoSpec",
|
|
61
|
+
"type": "modules",
|
|
62
|
+
"jsSrcsDir": "src",
|
|
63
|
+
"android": {
|
|
64
|
+
"javaPackageName": "com.akbeniwal.reactnativesmartnetinfo"
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export interface Spec extends TurboModule {
|
|
4
|
+
addListener(eventName: string): void;
|
|
5
|
+
removeListeners(count: number): void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeSmartNetinfo');
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { AppState, AppStateStatus, NativeEventEmitter } from 'react-native';
|
|
2
|
+
import NativeReactNativeSmartNetinfo from './NativeReactNativeSmartNetinfo';
|
|
3
|
+
import { NetworkState, SmartNetInfoConfig, NetworkStateListener } from './types';
|
|
4
|
+
import { getConnectionQuality } from './utils/getConnectionQuality';
|
|
5
|
+
import { getLatency } from './utils/getLatency';
|
|
6
|
+
import { runSpeedTest as executeSpeedTest } from './utils/runSpeedTest';
|
|
7
|
+
|
|
8
|
+
const PING_URLS = [
|
|
9
|
+
'https://clients3.google.com/generate_204',
|
|
10
|
+
'https://www.apple.com/library/test/success.html',
|
|
11
|
+
'https://cloudflare-dns.com/dns-query',
|
|
12
|
+
'https://google.com/generate_204'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const SPEED_TEST_URL = 'https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js';
|
|
16
|
+
|
|
17
|
+
class SmartNetInfoManager {
|
|
18
|
+
private pingUrlIndex = 0;
|
|
19
|
+
private config: Required<SmartNetInfoConfig> = {
|
|
20
|
+
pingIntervalMs: 30000,
|
|
21
|
+
timeoutMs: 5000,
|
|
22
|
+
speedTestFileSizeInBytes: 90000,
|
|
23
|
+
disableAutoSpeedTest: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
private state: NetworkState = {
|
|
27
|
+
isConnected: null,
|
|
28
|
+
isInternetReachable: null,
|
|
29
|
+
latencyMs: null,
|
|
30
|
+
connectionQuality: null,
|
|
31
|
+
internetSpeed: null,
|
|
32
|
+
isTestingSpeed: false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
private listeners = new Set<NetworkStateListener>();
|
|
36
|
+
private timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
37
|
+
private appStateSubscription: { remove: () => void } | null = null;
|
|
38
|
+
private nativeEventEmitter: NativeEventEmitter | null = null;
|
|
39
|
+
private nativeEventSubscription: { remove: () => void } | null = null;
|
|
40
|
+
private isMonitoring = false;
|
|
41
|
+
private isChecking = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Configure the SmartNetInfo options.
|
|
45
|
+
* If already monitoring, it will restart the monitoring process to apply new intervals.
|
|
46
|
+
*/
|
|
47
|
+
public configure(config: Partial<SmartNetInfoConfig>): void {
|
|
48
|
+
this.config = { ...this.config, ...config };
|
|
49
|
+
if (this.isMonitoring) {
|
|
50
|
+
this.stopMonitoring();
|
|
51
|
+
this.startMonitoring();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Fetches the current network state immediately by performing a connectivity check.
|
|
57
|
+
*/
|
|
58
|
+
public async fetch(): Promise<NetworkState> {
|
|
59
|
+
await this.checkConnectivity();
|
|
60
|
+
return this.state;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to network state changes.
|
|
65
|
+
* Automatically starts monitoring if it was not already running.
|
|
66
|
+
*
|
|
67
|
+
* @param listener Callback function receiving the updated NetworkState
|
|
68
|
+
* @returns A cleanup function to unsubscribe
|
|
69
|
+
*/
|
|
70
|
+
public addEventListener(listener: NetworkStateListener): () => void {
|
|
71
|
+
this.listeners.add(listener);
|
|
72
|
+
|
|
73
|
+
// Provide the current state immediately to the new listener
|
|
74
|
+
listener(this.state);
|
|
75
|
+
|
|
76
|
+
if (!this.isMonitoring) {
|
|
77
|
+
this.startMonitoring();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return () => this.removeEventListener(listener);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Unsubscribe a listener from network state changes.
|
|
85
|
+
* Automatically stops monitoring if no listeners remain.
|
|
86
|
+
*
|
|
87
|
+
* @param listener Callback function to unsubscribe
|
|
88
|
+
*/
|
|
89
|
+
public removeEventListener(listener: NetworkStateListener): void {
|
|
90
|
+
this.listeners.delete(listener);
|
|
91
|
+
if (this.listeners.size === 0 && this.isMonitoring) {
|
|
92
|
+
this.stopMonitoring();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Triggers an internet speed test manually.
|
|
98
|
+
*
|
|
99
|
+
* @returns Estimated download speed in Mbps, or null if test fails
|
|
100
|
+
*/
|
|
101
|
+
public async runSpeedTest(): Promise<number | null> {
|
|
102
|
+
if (this.state.isTestingSpeed) {
|
|
103
|
+
return this.state.internetSpeed;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.updateState({ isTestingSpeed: true });
|
|
107
|
+
|
|
108
|
+
const timeout = Math.max(this.config.timeoutMs * 3, 15000);
|
|
109
|
+
const speed = await executeSpeedTest(SPEED_TEST_URL, this.config.speedTestFileSizeInBytes, timeout);
|
|
110
|
+
|
|
111
|
+
this.updateState({
|
|
112
|
+
internetSpeed: speed,
|
|
113
|
+
isTestingSpeed: false,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return speed;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Start monitoring network status (polling, AppState transitions, and browser online/offline events).
|
|
121
|
+
*/
|
|
122
|
+
public startMonitoring(): void {
|
|
123
|
+
if (this.isMonitoring) return;
|
|
124
|
+
this.isMonitoring = true;
|
|
125
|
+
|
|
126
|
+
// Set up Native Event Emitter if available
|
|
127
|
+
try {
|
|
128
|
+
if (NativeReactNativeSmartNetinfo) {
|
|
129
|
+
this.nativeEventEmitter = new NativeEventEmitter(NativeReactNativeSmartNetinfo as any);
|
|
130
|
+
this.nativeEventSubscription = this.nativeEventEmitter.addListener(
|
|
131
|
+
'NetworkStatusChanged',
|
|
132
|
+
this.handleNativeNetworkChange
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.warn('SmartNetInfo native module not found, falling back to pure polling');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Run initial check immediately and then schedule subsequent checks
|
|
140
|
+
this.checkConnectivity().then(() => {
|
|
141
|
+
// Auto run speed test on initial connect if online
|
|
142
|
+
if (
|
|
143
|
+
this.state.isInternetReachable &&
|
|
144
|
+
this.state.internetSpeed === null &&
|
|
145
|
+
!this.config.disableAutoSpeedTest
|
|
146
|
+
) {
|
|
147
|
+
this.runSpeedTest();
|
|
148
|
+
}
|
|
149
|
+
this.scheduleNextCheck();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// React Native AppState listener to detect foreground transitions
|
|
153
|
+
try {
|
|
154
|
+
this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.warn('SmartNetInfo failed to register AppState listener:', error);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Web browser window event listeners for react-native-web compatibility
|
|
160
|
+
const hasWindowListeners = typeof window !== 'undefined' && typeof window.addEventListener === 'function';
|
|
161
|
+
if (hasWindowListeners) {
|
|
162
|
+
window.addEventListener('online', this.handleWebOnline);
|
|
163
|
+
window.addEventListener('offline', this.handleWebOffline);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Stop monitoring network status and clean up timers and listeners.
|
|
169
|
+
*/
|
|
170
|
+
public stopMonitoring(): void {
|
|
171
|
+
if (!this.isMonitoring) return;
|
|
172
|
+
this.isMonitoring = false;
|
|
173
|
+
|
|
174
|
+
if (this.timeoutId) {
|
|
175
|
+
clearTimeout(this.timeoutId);
|
|
176
|
+
this.timeoutId = null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.nativeEventSubscription) {
|
|
180
|
+
this.nativeEventSubscription.remove();
|
|
181
|
+
this.nativeEventSubscription = null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.appStateSubscription) {
|
|
185
|
+
if (typeof this.appStateSubscription.remove === 'function') {
|
|
186
|
+
this.appStateSubscription.remove();
|
|
187
|
+
} else {
|
|
188
|
+
// Fallback for older React Native versions
|
|
189
|
+
(AppState as any).removeEventListener?.('change', this.handleAppStateChange);
|
|
190
|
+
}
|
|
191
|
+
this.appStateSubscription = null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hasWindowListeners = typeof window !== 'undefined' && typeof window.removeEventListener === 'function';
|
|
195
|
+
if (hasWindowListeners) {
|
|
196
|
+
window.removeEventListener('online', this.handleWebOnline);
|
|
197
|
+
window.removeEventListener('offline', this.handleWebOffline);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private handleNativeNetworkChange = (event: { isConnected: boolean }) => {
|
|
202
|
+
if (!event.isConnected) {
|
|
203
|
+
this.updateState({
|
|
204
|
+
isConnected: false,
|
|
205
|
+
isInternetReachable: false,
|
|
206
|
+
latencyMs: null,
|
|
207
|
+
connectionQuality: null,
|
|
208
|
+
});
|
|
209
|
+
if (this.timeoutId) {
|
|
210
|
+
clearTimeout(this.timeoutId);
|
|
211
|
+
this.timeoutId = null;
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
this.updateState({ isConnected: true });
|
|
215
|
+
this.checkConnectivity().finally(() => {
|
|
216
|
+
this.scheduleNextCheck();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
private handleAppStateChange = (nextAppState: AppStateStatus): void => {
|
|
222
|
+
if (nextAppState === 'active') {
|
|
223
|
+
this.checkConnectivity().finally(() => {
|
|
224
|
+
this.scheduleNextCheck();
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
private handleWebOnline = (): void => {
|
|
230
|
+
this.updateState({ isConnected: true, isInternetReachable: true });
|
|
231
|
+
this.checkConnectivity().finally(() => {
|
|
232
|
+
this.scheduleNextCheck();
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
private handleWebOffline = (): void => {
|
|
237
|
+
this.updateState({
|
|
238
|
+
isConnected: false,
|
|
239
|
+
isInternetReachable: false,
|
|
240
|
+
latencyMs: null,
|
|
241
|
+
connectionQuality: null,
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
private scheduleNextCheck(): void {
|
|
246
|
+
if (!this.isMonitoring) return;
|
|
247
|
+
|
|
248
|
+
if (this.timeoutId) {
|
|
249
|
+
clearTimeout(this.timeoutId);
|
|
250
|
+
this.timeoutId = null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (this.config.pingIntervalMs <= 0) return;
|
|
254
|
+
|
|
255
|
+
// If native module explicitly told us we are offline (isConnected: false),
|
|
256
|
+
// we DO NOT poll at all to save battery. We wait for native event.
|
|
257
|
+
if (this.state.isConnected === false && this.nativeEventEmitter) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// When offline (without native fallback), check more frequently (every 3 seconds) to detect recovery quickly.
|
|
262
|
+
// When online, use the configured pingIntervalMs.
|
|
263
|
+
const isOffline = this.state.isInternetReachable === false;
|
|
264
|
+
const interval = isOffline
|
|
265
|
+
? Math.min(this.config.pingIntervalMs, 3000)
|
|
266
|
+
: this.config.pingIntervalMs;
|
|
267
|
+
|
|
268
|
+
this.timeoutId = setTimeout(async () => {
|
|
269
|
+
await this.checkConnectivity();
|
|
270
|
+
this.scheduleNextCheck();
|
|
271
|
+
}, interval);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async checkConnectivity(): Promise<void> {
|
|
275
|
+
if (this.isChecking) return;
|
|
276
|
+
this.isChecking = true;
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
// Add a buffer to timeout when we are currently offline to allow the radio to wake up
|
|
280
|
+
const isCurrentlyOffline = this.state.isInternetReachable === false;
|
|
281
|
+
const actualTimeout = isCurrentlyOffline
|
|
282
|
+
? Math.max(this.config.timeoutMs, 4000)
|
|
283
|
+
: this.config.timeoutMs;
|
|
284
|
+
|
|
285
|
+
const currentPingUrl = PING_URLS[this.pingUrlIndex];
|
|
286
|
+
// Cycle to the next URL for the next check to avoid DNS caching issues if it fails
|
|
287
|
+
this.pingUrlIndex = (this.pingUrlIndex + 1) % PING_URLS.length;
|
|
288
|
+
|
|
289
|
+
const { isReachable, latencyMs } = await getLatency(currentPingUrl, actualTimeout);
|
|
290
|
+
|
|
291
|
+
const wasInternetReachable = this.state.isInternetReachable;
|
|
292
|
+
|
|
293
|
+
this.updateState({
|
|
294
|
+
isConnected: isReachable,
|
|
295
|
+
isInternetReachable: isReachable,
|
|
296
|
+
latencyMs,
|
|
297
|
+
connectionQuality: getConnectionQuality(latencyMs),
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// If internet just became reachable and we haven't run speed test yet, trigger auto speed test
|
|
301
|
+
if (
|
|
302
|
+
isReachable &&
|
|
303
|
+
wasInternetReachable === false &&
|
|
304
|
+
this.state.internetSpeed === null &&
|
|
305
|
+
!this.config.disableAutoSpeedTest
|
|
306
|
+
) {
|
|
307
|
+
this.runSpeedTest();
|
|
308
|
+
}
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.warn('SmartNetInfo failed during checkConnectivity:', error);
|
|
311
|
+
} finally {
|
|
312
|
+
this.isChecking = false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private updateState(partialState: Partial<NetworkState>): void {
|
|
317
|
+
const nextState = { ...this.state, ...partialState };
|
|
318
|
+
|
|
319
|
+
// Check if anything actually changed
|
|
320
|
+
const hasChanged = Object.keys(nextState).some(
|
|
321
|
+
(key) => (nextState as any)[key] !== (this.state as any)[key]
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (hasChanged) {
|
|
325
|
+
this.state = nextState;
|
|
326
|
+
this.notifyListeners();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private notifyListeners(): void {
|
|
331
|
+
this.listeners.forEach((listener) => {
|
|
332
|
+
try {
|
|
333
|
+
listener(this.state);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('Error in SmartNetInfo listener:', error);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export const SmartNetInfo = new SmartNetInfoManager();
|
|
342
|
+
export default SmartNetInfo;
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ConnectionQuality = 'poor' | 'good' | 'excellent';
|
|
2
|
+
|
|
3
|
+
export interface NetworkState {
|
|
4
|
+
/** True if the network check succeeded, false if it failed or timed out */
|
|
5
|
+
isConnected: boolean | null;
|
|
6
|
+
/** True if internet reachability check succeeded */
|
|
7
|
+
isInternetReachable: boolean | null;
|
|
8
|
+
/** Round-trip ping latency in milliseconds */
|
|
9
|
+
latencyMs: number | null;
|
|
10
|
+
/** Connection quality based on latency ('poor' | 'good' | 'excellent') */
|
|
11
|
+
connectionQuality: ConnectionQuality | null;
|
|
12
|
+
/** Estimated internet download speed in Mbps (automatically measured when online) */
|
|
13
|
+
internetSpeed: number | null;
|
|
14
|
+
/** True if a speed test is currently running */
|
|
15
|
+
isTestingSpeed: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SmartNetInfoConfig {
|
|
19
|
+
/** The interval in milliseconds to poll the connection (defaults to 30000ms, set to 0 to disable polling) */
|
|
20
|
+
pingIntervalMs?: number;
|
|
21
|
+
/** The fetch timeout in milliseconds (defaults to 5000ms) */
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
/** File size of the speed test URL in bytes (defaults to 90000 bytes for jQuery) */
|
|
24
|
+
speedTestFileSizeInBytes?: number;
|
|
25
|
+
/** If true, disables the automatic speed test on mount/online transitions (defaults to false) */
|
|
26
|
+
disableAutoSpeedTest?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type NetworkStateListener = (state: NetworkState) => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ConnectionQuality } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Categorizes a round-trip connection latency (in milliseconds) into a descriptive rating.
|
|
5
|
+
*
|
|
6
|
+
* @param latencyMs Latency in milliseconds or null if offline/unknown
|
|
7
|
+
* @returns ConnectionQuality rating or null
|
|
8
|
+
*/
|
|
9
|
+
export function getConnectionQuality(latencyMs: number | null): ConnectionQuality | null {
|
|
10
|
+
if (latencyMs === null || latencyMs < 0) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
if (latencyMs < 150) {
|
|
14
|
+
return 'excellent';
|
|
15
|
+
}
|
|
16
|
+
if (latencyMs < 400) {
|
|
17
|
+
return 'good';
|
|
18
|
+
}
|
|
19
|
+
return 'poor';
|
|
20
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface LatencyResult {
|
|
2
|
+
/** True if the ping request responded with an HTTP status < 400 */
|
|
3
|
+
isReachable: boolean;
|
|
4
|
+
/** Latency in milliseconds, or null if the check timed out or failed */
|
|
5
|
+
latencyMs: number | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Pings a URL to verify internet reachability and measure round-trip latency.
|
|
10
|
+
* Uses an AbortController to support timeouts.
|
|
11
|
+
*
|
|
12
|
+
* @param pingUrl The target URL to ping
|
|
13
|
+
* @param timeoutMs Request timeout in milliseconds
|
|
14
|
+
* @returns A promise resolving to LatencyResult
|
|
15
|
+
*/
|
|
16
|
+
export async function getLatency(pingUrl: string, timeoutMs: number): Promise<LatencyResult> {
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
19
|
+
try {
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
timeoutId = setTimeout(() => {
|
|
22
|
+
controller.abort();
|
|
23
|
+
}, timeoutMs);
|
|
24
|
+
|
|
25
|
+
// Append dynamic timestamp and random string to strictly bypass network/DNS caching
|
|
26
|
+
const uniqueUrl = `${pingUrl}${pingUrl.includes('?') ? '&' : '?'}t=${Date.now()}&r=${Math.random().toString().slice(2)}`;
|
|
27
|
+
|
|
28
|
+
// Perform GET request to verify connectivity.
|
|
29
|
+
// We use GET instead of HEAD for maximum compatibility across various CDNs and proxies.
|
|
30
|
+
// Since clients3.google.com/generate_204 returns a 204 No Content response,
|
|
31
|
+
// using GET has negligible bandwidth usage.
|
|
32
|
+
const response = await fetch(uniqueUrl, {
|
|
33
|
+
method: 'GET',
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
headers: {
|
|
36
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
37
|
+
'Pragma': 'no-cache',
|
|
38
|
+
'Expires': '0',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Fully consume the response body to prevent connection leaks in React Native
|
|
43
|
+
try {
|
|
44
|
+
await response.text();
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isReachable = response.ok || response.status < 400;
|
|
50
|
+
const latencyMs = Date.now() - startTime;
|
|
51
|
+
return { isReachable, latencyMs };
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn(`[SmartNetInfo] getLatency failed:`, error instanceof Error ? error.message : error);
|
|
54
|
+
// If it fails due to network/timeout, we are offline.
|
|
55
|
+
return { isReachable: false, latencyMs: null };
|
|
56
|
+
} finally {
|
|
57
|
+
if (timeoutId) {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs a download speed test by fetching a remote asset, reading the response
|
|
3
|
+
* completely, and calculating the throughput speed in Mbps.
|
|
4
|
+
*
|
|
5
|
+
* @param speedTestUrl URL of the remote asset to download
|
|
6
|
+
* @param speedTestFileSizeInBytes Known size of the remote asset in bytes
|
|
7
|
+
* @returns A promise resolving to the estimated download speed in Mbps, or null if the test fails
|
|
8
|
+
*/
|
|
9
|
+
export async function runSpeedTest(
|
|
10
|
+
speedTestUrl: string,
|
|
11
|
+
speedTestFileSizeInBytes: number,
|
|
12
|
+
timeoutMs: number = 15000
|
|
13
|
+
): Promise<number | null> {
|
|
14
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
15
|
+
try {
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
|
|
19
|
+
timeoutId = setTimeout(() => {
|
|
20
|
+
controller.abort();
|
|
21
|
+
}, timeoutMs);
|
|
22
|
+
|
|
23
|
+
const uniqueUrl = `${speedTestUrl}${speedTestUrl.includes('?') ? '&' : '?'}t=${Date.now()}&r=${Math.random().toString().slice(2)}`;
|
|
24
|
+
|
|
25
|
+
const response = await fetch(uniqueUrl, {
|
|
26
|
+
method: 'GET',
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
headers: {
|
|
29
|
+
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
30
|
+
'Pragma': 'no-cache',
|
|
31
|
+
'Expires': '0',
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`Speed test download failed with status: ${response.status}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Fully consume the response body so that we measure the complete download duration
|
|
40
|
+
await response.text();
|
|
41
|
+
|
|
42
|
+
const durationSec = (Date.now() - startTime) / 1000;
|
|
43
|
+
if (durationSec <= 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fileSizeBits = speedTestFileSizeInBytes * 8;
|
|
48
|
+
const speedMbps = fileSizeBits / durationSec / 1000000;
|
|
49
|
+
|
|
50
|
+
// Round to 2 decimal places (e.g. 15.45)
|
|
51
|
+
return Math.round(speedMbps * 100) / 100;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.warn('Network speed test failed:', error);
|
|
54
|
+
return null;
|
|
55
|
+
} finally {
|
|
56
|
+
if (timeoutId) {
|
|
57
|
+
clearTimeout(timeoutId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|