@tenkam/react-native-wifi-scanner 1.0.0
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/LICENSE +21 -0
- package/README.md +133 -0
- package/android/build.gradle +48 -0
- package/android/src/main/AndroidManifest.xml +33 -0
- package/android/src/main/java/com/wifiscanner/RNWifiScannerModule.kt +203 -0
- package/android/src/main/java/com/wifiscanner/RNWifiScannerPackage.kt +40 -0
- package/package.json +34 -0
- package/src/WifiScanner.js +124 -0
- package/src/index.js +7 -0
- package/src/useWifiScanner.js +202 -0
- package/src/utils.js +159 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Benito Tenkam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# react-native-wifi-scanner
|
|
2
|
+
|
|
3
|
+
Bibliothèque React Native pour scanner les réseaux Wi-Fi environnants sur Android.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-native-wifi-scanner
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration Android (étape obligatoire)
|
|
12
|
+
|
|
13
|
+
Ouvrez `android/app/src/main/java/com/VOTRE_APP/MainApplication.kt` et ajoutez une ligne :
|
|
14
|
+
|
|
15
|
+
```kotlin
|
|
16
|
+
import com.wifiscanner.RNWifiScannerPackage // ← Ajouter cet import
|
|
17
|
+
|
|
18
|
+
override fun getPackages(): List<ReactPackage> =
|
|
19
|
+
PackageList(this).packages.apply {
|
|
20
|
+
add(RNWifiScannerPackage()) // ← Ajouter cette ligne
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Puis recompilez :
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd android && ./gradlew clean && cd ..
|
|
28
|
+
npx react-native run-android
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Utilisation — Hook (recommandé)
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import { useWifiScanner } from 'react-native-wifi-scanner';
|
|
37
|
+
|
|
38
|
+
const MyComponent = () => {
|
|
39
|
+
const {
|
|
40
|
+
networks, // tableau des réseaux détectés
|
|
41
|
+
isScanning, // true pendant le scan
|
|
42
|
+
error, // message d'erreur ou null
|
|
43
|
+
scan, // lancer un scan manuel
|
|
44
|
+
stopAutoScan, // arrêter l'auto-scan
|
|
45
|
+
startAutoScan, // reprendre l'auto-scan
|
|
46
|
+
} = useWifiScanner({
|
|
47
|
+
autoScan: true, // scan automatique toutes les 10s
|
|
48
|
+
interval: 10000, // intervalle en millisecondes
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<View>
|
|
53
|
+
{isScanning && <Text>Scan en cours...</Text>}
|
|
54
|
+
{networks.map(net => (
|
|
55
|
+
<Text key={net.bssid}>{net.ssid} — {net.rssi} dBm</Text>
|
|
56
|
+
))}
|
|
57
|
+
<Button title="Scanner" onPress={scan} />
|
|
58
|
+
</View>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Utilisation — API directe
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import { WifiScanner } from 'react-native-wifi-scanner';
|
|
69
|
+
|
|
70
|
+
// Vérifier si le Wi-Fi est activé
|
|
71
|
+
const enabled = await WifiScanner.isWifiEnabled();
|
|
72
|
+
|
|
73
|
+
// Lancer un scan et récupérer les résultats
|
|
74
|
+
const networks = await WifiScanner.scanNetworks();
|
|
75
|
+
|
|
76
|
+
// Chaque réseau contient :
|
|
77
|
+
// {
|
|
78
|
+
// ssid: string — Nom du réseau
|
|
79
|
+
// bssid: string — Adresse MAC du routeur
|
|
80
|
+
// rssi: number — Signal en dBm (ex: -65)
|
|
81
|
+
// frequency: number — Fréquence en MHz (ex: 2437)
|
|
82
|
+
// capabilities: string — Sécurité (ex: "[WPA2-PSK-CCMP][ESS]")
|
|
83
|
+
// }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Fonctions utilitaires incluses
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import {
|
|
92
|
+
rssiToPercent, // -65 dBm → 70%
|
|
93
|
+
rssiToLabel, // -65 dBm → "Bon"
|
|
94
|
+
frequencyToBand, // 5180 MHz → "5 GHz"
|
|
95
|
+
frequencyToChannel, // 2437 MHz → canal 6
|
|
96
|
+
rssiToDistance, // -65 dBm → "~8 m"
|
|
97
|
+
parseSecurityType, // "[WPA2-PSK]" → "WPA2"
|
|
98
|
+
sortNetworks, // trier par signal/nom/fréquence/sécurité
|
|
99
|
+
filterNetworks, // filtrer par SSID ou BSSID
|
|
100
|
+
computeStats, // statistiques globales
|
|
101
|
+
} from 'react-native-wifi-scanner';
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Permissions
|
|
107
|
+
|
|
108
|
+
Les permissions suivantes sont automatiquement ajoutées au Manifest de
|
|
109
|
+
l'application lors du build (fusion automatique Android) :
|
|
110
|
+
|
|
111
|
+
- `ACCESS_WIFI_STATE`
|
|
112
|
+
- `CHANGE_WIFI_STATE`
|
|
113
|
+
- `ACCESS_FINE_LOCATION` ← obligatoire pour voir les SSID sur Android 6+
|
|
114
|
+
- `ACCESS_COARSE_LOCATION`
|
|
115
|
+
- `ACCESS_NETWORK_STATE`
|
|
116
|
+
|
|
117
|
+
La bibliothèque gère automatiquement la demande de permission runtime
|
|
118
|
+
(dialog système) via le hook `useWifiScanner`.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Compatibilité
|
|
123
|
+
|
|
124
|
+
| Plateforme | Support |
|
|
125
|
+
|-----------|---------|
|
|
126
|
+
| Android | ✅ API 23+ (Android 6.0+) |
|
|
127
|
+
| iOS | ❌ Non supporté (retourne des données de test) |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Auteur
|
|
132
|
+
|
|
133
|
+
Benito Tenkam — Licence MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// android/build.gradle
|
|
2
|
+
// Configuration Gradle du module Android de la bibliothèque.
|
|
3
|
+
// Ce fichier indique à Gradle comment compiler le code Kotlin de la lib.
|
|
4
|
+
|
|
5
|
+
buildscript {
|
|
6
|
+
ext.kotlin_version = '1.8.0'
|
|
7
|
+
repositories {
|
|
8
|
+
google()
|
|
9
|
+
mavenCentral()
|
|
10
|
+
}
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath 'com.android.tools.build:gradle:7.4.2'
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: 'com.android.library' // C'est une BIBLIOTHÈQUE Android, pas une app
|
|
18
|
+
apply plugin: 'kotlin-android'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
compileSdkVersion 33
|
|
22
|
+
buildToolsVersion "33.0.0"
|
|
23
|
+
|
|
24
|
+
defaultConfig {
|
|
25
|
+
minSdkVersion 23 // Android 6.0 minimum (requis pour les permissions runtime)
|
|
26
|
+
targetSdkVersion 33
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
compileOptions {
|
|
30
|
+
sourceCompatibility JavaVersion.VERSION_11
|
|
31
|
+
targetCompatibility JavaVersion.VERSION_11
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
kotlinOptions {
|
|
35
|
+
jvmTarget = '11'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
repositories {
|
|
40
|
+
google()
|
|
41
|
+
mavenCentral()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
dependencies {
|
|
45
|
+
// React Native est fourni par l'application hôte (peerDependency)
|
|
46
|
+
// On l'importe ici comme "provided" pour la compilation seulement
|
|
47
|
+
implementation 'com.facebook.react:react-android:+'
|
|
48
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
android/src/main/AndroidManifest.xml de la BIBLIOTHÈQUE
|
|
4
|
+
|
|
5
|
+
Ce Manifest déclare les permissions requises par la bibliothèque.
|
|
6
|
+
Lors du build de l'application consommatrice, Android Studio
|
|
7
|
+
FUSIONNE automatiquement ce Manifest avec celui de l'application.
|
|
8
|
+
|
|
9
|
+
L'application consommatrice n'a donc PAS BESOIN de re-déclarer
|
|
10
|
+
ces permissions dans son propre AndroidManifest.xml.
|
|
11
|
+
C'est l'un des avantages des bibliothèques Android modulaires.
|
|
12
|
+
-->
|
|
13
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
14
|
+
package="com.wifiscanner">
|
|
15
|
+
|
|
16
|
+
<!-- Permission de lire l'état Wi-Fi et les résultats de scan -->
|
|
17
|
+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
18
|
+
|
|
19
|
+
<!-- Permission de déclencher un scan Wi-Fi (startScan) -->
|
|
20
|
+
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
|
21
|
+
|
|
22
|
+
<!--
|
|
23
|
+
Permission OBLIGATOIRE depuis Android 6.0 pour accéder aux SSID/BSSID.
|
|
24
|
+
Google considère les réseaux Wi-Fi comme des données de localisation.
|
|
25
|
+
Sans cette permission, tous les SSID apparaissent comme "<unknown ssid>".
|
|
26
|
+
-->
|
|
27
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
28
|
+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
29
|
+
|
|
30
|
+
<!-- Permission pour lire l'état de la connexion réseau -->
|
|
31
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
32
|
+
|
|
33
|
+
</manifest>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// android/src/main/java/com/wifiscanner/RNWifiScannerModule.kt
|
|
2
|
+
// Module natif Kotlin de la bibliothèque react-native-wifi-scanner
|
|
3
|
+
// Ce fichier est packagé DANS la bibliothèque et copié dans le projet Android
|
|
4
|
+
// lors de l'installation via npm install.
|
|
5
|
+
|
|
6
|
+
package com.wifiscanner
|
|
7
|
+
|
|
8
|
+
import android.content.BroadcastReceiver
|
|
9
|
+
import android.content.Context
|
|
10
|
+
import android.content.Intent
|
|
11
|
+
import android.content.IntentFilter
|
|
12
|
+
import android.net.wifi.WifiManager
|
|
13
|
+
import android.os.Build
|
|
14
|
+
import android.provider.Settings
|
|
15
|
+
|
|
16
|
+
import com.facebook.react.bridge.*
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* RNWifiScannerModule
|
|
20
|
+
*
|
|
21
|
+
* Module natif Android exposé à React Native sous le nom "RNWifiScanner".
|
|
22
|
+
* Ce nom correspond à celui utilisé dans WifiScanner.js :
|
|
23
|
+
* const { RNWifiScanner } = NativeModules;
|
|
24
|
+
*
|
|
25
|
+
* Ce module est fourni par la bibliothèque react-native-wifi-scanner.
|
|
26
|
+
* Il sera automatiquement copié dans le projet Android de l'application
|
|
27
|
+
* consommatrice lors de l'installation via npm.
|
|
28
|
+
*/
|
|
29
|
+
class RNWifiScannerModule(private val reactContext: ReactApplicationContext) :
|
|
30
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
31
|
+
|
|
32
|
+
// Accès au service Wi-Fi Android via lazy initialization
|
|
33
|
+
private val wifiManager: WifiManager? by lazy {
|
|
34
|
+
reactContext
|
|
35
|
+
.applicationContext
|
|
36
|
+
.getSystemService(Context.WIFI_SERVICE) as? WifiManager
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Nom du module tel qu'il sera accessible côté JavaScript :
|
|
41
|
+
* NativeModules.RNWifiScanner
|
|
42
|
+
*/
|
|
43
|
+
override fun getName(): String = "RNWifiScanner"
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// MÉTHODES EXPOSÉES À JAVASCRIPT
|
|
47
|
+
// Toutes annotées @ReactMethod pour être accessibles via le bridge RN
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Vérifie si le Wi-Fi est activé sur l'appareil.
|
|
52
|
+
* @param promise Résout avec true (activé) ou false (désactivé)
|
|
53
|
+
*/
|
|
54
|
+
@ReactMethod
|
|
55
|
+
fun isWifiEnabled(promise: Promise) {
|
|
56
|
+
try {
|
|
57
|
+
promise.resolve(wifiManager?.isWifiEnabled == true)
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
promise.reject("WIFI_STATE_ERROR", "Impossible de lire l'état Wi-Fi: ${e.message}")
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Demande l'activation du Wi-Fi.
|
|
65
|
+
* Android 10+ : ouvre le panneau Paramètres Wi-Fi (Google bloque setWifiEnabled)
|
|
66
|
+
* Android 9- : active directement via setWifiEnabled(true)
|
|
67
|
+
*/
|
|
68
|
+
@ReactMethod
|
|
69
|
+
fun requestEnableWifi(promise: Promise) {
|
|
70
|
+
try {
|
|
71
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
72
|
+
// Android 10+ : redirection vers les paramètres système
|
|
73
|
+
val intent = Intent(Settings.Panel.ACTION_WIFI).apply {
|
|
74
|
+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
75
|
+
}
|
|
76
|
+
reactContext.startActivity(intent)
|
|
77
|
+
} else {
|
|
78
|
+
// Android 9 et inférieur : activation directe (dépréciée mais fonctionnelle)
|
|
79
|
+
@Suppress("DEPRECATION")
|
|
80
|
+
wifiManager?.isWifiEnabled = true
|
|
81
|
+
}
|
|
82
|
+
promise.resolve(true)
|
|
83
|
+
} catch (e: Exception) {
|
|
84
|
+
promise.reject("WIFI_ENABLE_ERROR", "Impossible d'activer le Wi-Fi: ${e.message}")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Lance un scan Wi-Fi et retourne les réseaux détectés.
|
|
90
|
+
*
|
|
91
|
+
* Fonctionnement asynchrone via BroadcastReceiver :
|
|
92
|
+
* 1. Enregistre un receiver sur SCAN_RESULTS_AVAILABLE_ACTION
|
|
93
|
+
* 2. Lance startScan() pour déclencher le scan matériel
|
|
94
|
+
* 3. Quand Android envoie l'événement → récupère getScanResults()
|
|
95
|
+
* 4. Convertit chaque ScanResult en objet JavaScript (WritableMap)
|
|
96
|
+
* 5. Résout la Promise avec le tableau de réseaux
|
|
97
|
+
* 6. Désenregistre le receiver pour éviter les fuites mémoire
|
|
98
|
+
*
|
|
99
|
+
* @param promise Résout avec Array<WifiNetwork> ou rejette en cas d'erreur
|
|
100
|
+
*/
|
|
101
|
+
@ReactMethod
|
|
102
|
+
fun scanNetworks(promise: Promise) {
|
|
103
|
+
val wifi = wifiManager
|
|
104
|
+
|
|
105
|
+
// Vérifications préalables
|
|
106
|
+
if (wifi == null) {
|
|
107
|
+
promise.reject("WIFI_UNAVAILABLE", "WifiManager non disponible sur cet appareil.")
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
if (!wifi.isWifiEnabled) {
|
|
111
|
+
promise.reject("WIFI_DISABLED", "Le Wi-Fi est désactivé. Activez-le d'abord.")
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Récepteur temporaire ──────────────────────────────────────────────
|
|
116
|
+
// Android envoie SCAN_RESULTS_AVAILABLE_ACTION quand le scan est terminé.
|
|
117
|
+
// Ce receiver est créé une seule fois, utilisé une seule fois, puis supprimé.
|
|
118
|
+
val scanReceiver = object : BroadcastReceiver() {
|
|
119
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
120
|
+
|
|
121
|
+
// Désenregistrement IMMÉDIAT pour éviter les fuites mémoire
|
|
122
|
+
// et ne pas recevoir de futurs scans déclenchés par d'autres apps
|
|
123
|
+
try {
|
|
124
|
+
reactContext.unregisterReceiver(this)
|
|
125
|
+
} catch (_: IllegalArgumentException) {
|
|
126
|
+
// Déjà désenregistré, on ignore
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── Conversion des résultats en objets JavaScript ─────────────
|
|
130
|
+
try {
|
|
131
|
+
val results = wifi.scanResults // Liste de ScanResult Android
|
|
132
|
+
val networksArray = Arguments.createArray()
|
|
133
|
+
|
|
134
|
+
for (scan in results) {
|
|
135
|
+
val network = Arguments.createMap().apply {
|
|
136
|
+
// ssid : nom du réseau. Vide si réseau caché.
|
|
137
|
+
putString("ssid", scan.SSID ?: "")
|
|
138
|
+
|
|
139
|
+
// bssid : adresse MAC du point d'accès (routeur)
|
|
140
|
+
putString("bssid", scan.BSSID ?: "")
|
|
141
|
+
|
|
142
|
+
// rssi : intensité du signal en dBm.
|
|
143
|
+
// Valeur négative : -30 (excellent) à -100 (inexistant)
|
|
144
|
+
putInt("rssi", scan.level)
|
|
145
|
+
|
|
146
|
+
// frequency : fréquence en MHz
|
|
147
|
+
// 2412-2484 = bande 2.4 GHz, 5180-5825 = bande 5 GHz
|
|
148
|
+
putInt("frequency", scan.frequency)
|
|
149
|
+
|
|
150
|
+
// capabilities : chaîne décrivant les protocoles de sécurité
|
|
151
|
+
// Exemple : "[WPA2-PSK-CCMP][ESS]"
|
|
152
|
+
putString("capabilities", scan.capabilities ?: "")
|
|
153
|
+
}
|
|
154
|
+
networksArray.pushMap(network)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Résolution de la Promise → retour à JavaScript
|
|
158
|
+
promise.resolve(networksArray)
|
|
159
|
+
|
|
160
|
+
} catch (e: Exception) {
|
|
161
|
+
promise.reject("SCAN_PARSE_ERROR",
|
|
162
|
+
"Erreur lors du traitement des résultats: ${e.message}")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── Enregistrement du receiver AVANT de lancer le scan ──────────────
|
|
168
|
+
// Important : enregistrer avant startScan() pour ne rater aucun événement
|
|
169
|
+
val filter = IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
|
|
170
|
+
reactContext.registerReceiver(scanReceiver, filter)
|
|
171
|
+
|
|
172
|
+
// ── Déclenchement du scan matériel ───────────────────────────────────
|
|
173
|
+
// Sur Android 9+, startScan() peut être throttlé (limité à 4 scans/2min).
|
|
174
|
+
// Si throttlé (started = false), on retourne quand même les derniers résultats en cache.
|
|
175
|
+
val started = wifi.startScan()
|
|
176
|
+
|
|
177
|
+
if (!started) {
|
|
178
|
+
// Scan throttlé ou impossible → utiliser les résultats en cache
|
|
179
|
+
try {
|
|
180
|
+
reactContext.unregisterReceiver(scanReceiver)
|
|
181
|
+
} catch (_: Exception) {}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
val results = wifi.scanResults
|
|
185
|
+
val networksArray = Arguments.createArray()
|
|
186
|
+
for (scan in results) {
|
|
187
|
+
val network = Arguments.createMap().apply {
|
|
188
|
+
putString("ssid", scan.SSID ?: "")
|
|
189
|
+
putString("bssid", scan.BSSID ?: "")
|
|
190
|
+
putInt("rssi", scan.level)
|
|
191
|
+
putInt("frequency", scan.frequency)
|
|
192
|
+
putString("capabilities", scan.capabilities ?: "")
|
|
193
|
+
}
|
|
194
|
+
networksArray.pushMap(network)
|
|
195
|
+
}
|
|
196
|
+
promise.resolve(networksArray)
|
|
197
|
+
} catch (e: Exception) {
|
|
198
|
+
promise.reject("SCAN_THROTTLED",
|
|
199
|
+
"Scan Wi-Fi limité par Android. Réessayez dans quelques secondes.")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// android/src/main/java/com/wifiscanner/RNWifiScannerPackage.kt
|
|
2
|
+
// Package React Native de la bibliothèque.
|
|
3
|
+
// Enregistre RNWifiScannerModule auprès du système React Native.
|
|
4
|
+
// L'application consommatrice doit ajouter ce package dans son MainApplication.kt.
|
|
5
|
+
|
|
6
|
+
package com.wifiscanner
|
|
7
|
+
|
|
8
|
+
import com.facebook.react.ReactPackage
|
|
9
|
+
import com.facebook.react.bridge.NativeModule
|
|
10
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
|
+
import com.facebook.react.uimanager.ViewManager
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RNWifiScannerPackage
|
|
15
|
+
*
|
|
16
|
+
* Ce package est fourni par la bibliothèque react-native-wifi-scanner.
|
|
17
|
+
* Il doit être déclaré dans le MainApplication.kt de l'application consommatrice :
|
|
18
|
+
*
|
|
19
|
+
* override fun getPackages(): List<ReactPackage> =
|
|
20
|
+
* PackageList(this).packages.apply {
|
|
21
|
+
* add(RNWifiScannerPackage()) // ← Ajouter cette ligne
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
class RNWifiScannerPackage : ReactPackage {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Retourne la liste des modules natifs fournis par cette bibliothèque.
|
|
28
|
+
* Un seul module : RNWifiScannerModule.
|
|
29
|
+
*/
|
|
30
|
+
override fun createNativeModules(
|
|
31
|
+
reactContext: ReactApplicationContext
|
|
32
|
+
): List<NativeModule> = listOf(RNWifiScannerModule(reactContext))
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Aucun composant UI natif dans cette bibliothèque.
|
|
36
|
+
*/
|
|
37
|
+
override fun createViewManagers(
|
|
38
|
+
reactContext: ReactApplicationContext
|
|
39
|
+
): List<ViewManager<*, *>> = emptyList()
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tenkam/react-native-wifi-scanner",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Bibliotheque React Native pour scanner les reseaux Wi-Fi environnants sur Android",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/",
|
|
8
|
+
"android/",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"android",
|
|
14
|
+
"wifi",
|
|
15
|
+
"scanner",
|
|
16
|
+
"network",
|
|
17
|
+
"rssi",
|
|
18
|
+
"wifi-scan"
|
|
19
|
+
],
|
|
20
|
+
"author": "Benito Tenkam",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/tenkam/react-native-wifi-scanner"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": ">=17.0.0",
|
|
28
|
+
"react-native": ">=0.65.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"react": "18.2.0",
|
|
32
|
+
"react-native": "0.73.4"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/WifiScanner.js
|
|
2
|
+
// Classe principale de la bibliothèque.
|
|
3
|
+
// Elle fait le pont entre JavaScript et le module natif Android.
|
|
4
|
+
|
|
5
|
+
import { NativeModules, Platform } from 'react-native';
|
|
6
|
+
|
|
7
|
+
// Récupération du module natif enregistré sous le nom "RNWifiScanner"
|
|
8
|
+
const { RNWifiScanner } = NativeModules;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Vérifie que le module natif est bien disponible.
|
|
12
|
+
* Si l'application consommatrice n'a pas correctement lié la bibliothèque,
|
|
13
|
+
* on lève une erreur explicative.
|
|
14
|
+
*/
|
|
15
|
+
function assertNativeModule() {
|
|
16
|
+
if (Platform.OS === 'android' && !RNWifiScanner) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
'[react-native-wifi-scanner] Le module natif RNWifiScanner est introuvable.\n' +
|
|
19
|
+
'Vérifiez que :\n' +
|
|
20
|
+
'1. La bibliothèque est correctement installée (npm install react-native-wifi-scanner)\n' +
|
|
21
|
+
'2. Le projet Android a été recompilé (npx react-native run-android)\n' +
|
|
22
|
+
'3. RNWifiScannerPackage est bien déclaré dans MainApplication.kt'
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* WifiScanner — Interface JavaScript principale de la bibliothèque
|
|
29
|
+
*
|
|
30
|
+
* Utilisation dans une application React Native :
|
|
31
|
+
*
|
|
32
|
+
* import { WifiScanner } from 'react-native-wifi-scanner';
|
|
33
|
+
*
|
|
34
|
+
* const networks = await WifiScanner.scanNetworks();
|
|
35
|
+
* const enabled = await WifiScanner.isWifiEnabled();
|
|
36
|
+
*/
|
|
37
|
+
const WifiScanner = {
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Lance un scan Wi-Fi et retourne la liste des réseaux détectés.
|
|
41
|
+
*
|
|
42
|
+
* @returns {Promise<Array<WifiNetwork>>} Tableau de réseaux Wi-Fi
|
|
43
|
+
*
|
|
44
|
+
* Chaque objet WifiNetwork contient :
|
|
45
|
+
* - ssid {string} Nom du réseau (SSID)
|
|
46
|
+
* - bssid {string} Adresse MAC du point d'accès
|
|
47
|
+
* - rssi {number} Intensité du signal en dBm (ex: -65)
|
|
48
|
+
* - frequency {number} Fréquence en MHz (ex: 2437 ou 5180)
|
|
49
|
+
* - capabilities {string} Protocoles de sécurité (ex: "[WPA2-PSK-CCMP]")
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const networks = await WifiScanner.scanNetworks();
|
|
53
|
+
* console.log(networks[0].ssid); // "MonReseau"
|
|
54
|
+
*/
|
|
55
|
+
async scanNetworks() {
|
|
56
|
+
// Sur iOS ou simulateur : retourner des données de test
|
|
57
|
+
if (Platform.OS !== 'android') {
|
|
58
|
+
return _getMockNetworks();
|
|
59
|
+
}
|
|
60
|
+
assertNativeModule();
|
|
61
|
+
try {
|
|
62
|
+
const results = await RNWifiScanner.scanNetworks();
|
|
63
|
+
return results || [];
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(`[WifiScanner.scanNetworks] ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Vérifie si le Wi-Fi est activé sur l'appareil.
|
|
71
|
+
*
|
|
72
|
+
* @returns {Promise<boolean>} true si le Wi-Fi est activé
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const enabled = await WifiScanner.isWifiEnabled();
|
|
76
|
+
* if (!enabled) alert('Activez le Wi-Fi !');
|
|
77
|
+
*/
|
|
78
|
+
async isWifiEnabled() {
|
|
79
|
+
if (Platform.OS !== 'android') return true;
|
|
80
|
+
assertNativeModule();
|
|
81
|
+
try {
|
|
82
|
+
return await RNWifiScanner.isWifiEnabled();
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Demande l'activation du Wi-Fi.
|
|
90
|
+
* - Android 9 et inférieur : active directement
|
|
91
|
+
* - Android 10 et supérieur : ouvre le panneau paramètres Wi-Fi
|
|
92
|
+
*
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
async requestEnableWifi() {
|
|
96
|
+
if (Platform.OS !== 'android') return;
|
|
97
|
+
assertNativeModule();
|
|
98
|
+
try {
|
|
99
|
+
await RNWifiScanner.requestEnableWifi();
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.warn('[WifiScanner.requestEnableWifi]', error.message);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Données fictives pour le développement sur iOS / simulateur Android.
|
|
108
|
+
* Représente 8 réseaux avec différents niveaux de signal, bandes et sécurités.
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
function _getMockNetworks() {
|
|
112
|
+
return [
|
|
113
|
+
{ ssid: 'Livebox-Premium', bssid: 'A4:BE:2B:11:22:33', rssi: -42, frequency: 5180, capabilities: '[WPA2-PSK-CCMP][ESS]' },
|
|
114
|
+
{ ssid: 'SFR_WiFi5G', bssid: 'C8:D7:19:44:55:66', rssi: -55, frequency: 5220, capabilities: '[WPA3-SAE][ESS]' },
|
|
115
|
+
{ ssid: 'FreeWifi', bssid: '00:24:D4:77:88:99', rssi: -67, frequency: 2437, capabilities: '[WPA2-PSK][ESS]' },
|
|
116
|
+
{ ssid: 'Orange-Fibre', bssid: 'B8:EE:65:AA:BB:CC', rssi: -71, frequency: 2412, capabilities: '[WPA2-PSK-CCMP][ESS]' },
|
|
117
|
+
{ ssid: '', bssid: 'F0:1D:BC:DD:EE:FF', rssi: -79, frequency: 2462, capabilities: '[WPA-PSK][ESS]' },
|
|
118
|
+
{ ssid: 'TP-Link_Gaming', bssid: '50:C7:BF:12:34:56', rssi: -83, frequency: 5745, capabilities: '[WPA2-PSK][ESS]' },
|
|
119
|
+
{ ssid: 'Bbox-Voisin', bssid: 'EC:AD:B8:78:90:AB', rssi: -88, frequency: 2452, capabilities: '[WEP][ESS]' },
|
|
120
|
+
{ ssid: 'Hotspot_Public', bssid: '3C:37:86:CD:EF:01', rssi: -92, frequency: 2427, capabilities: '[ESS]' },
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default WifiScanner;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// src/index.js
|
|
2
|
+
// Point d'entrée principal de la bibliothèque react-native-wifi-scanner
|
|
3
|
+
// Ce fichier exporte tout ce que l'application consommatrice peut utiliser
|
|
4
|
+
|
|
5
|
+
export { default as WifiScanner } from './WifiScanner';
|
|
6
|
+
export { default as useWifiScanner } from './useWifiScanner';
|
|
7
|
+
export * from './utils';
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// src/useWifiScanner.js
|
|
2
|
+
// Hook React personnalisé fourni par la bibliothèque.
|
|
3
|
+
// Encapsule toute la logique de scan (état, permissions, auto-refresh).
|
|
4
|
+
// L'application consommatrice n'a qu'à appeler ce hook pour tout avoir.
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
7
|
+
import { Platform, PermissionsAndroid, AppState, Linking } from 'react-native';
|
|
8
|
+
import WifiScanner from './WifiScanner';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* useWifiScanner — Hook React principal de la bibliothèque
|
|
12
|
+
*
|
|
13
|
+
* Utilisation dans une application React Native :
|
|
14
|
+
*
|
|
15
|
+
* import { useWifiScanner } from 'react-native-wifi-scanner';
|
|
16
|
+
*
|
|
17
|
+
* const {
|
|
18
|
+
* networks, // tableau des réseaux détectés
|
|
19
|
+
* isScanning, // booléen : scan en cours ?
|
|
20
|
+
* error, // message d'erreur ou null
|
|
21
|
+
* scan, // fonction : lancer un scan manuel
|
|
22
|
+
* stopAutoScan, // fonction : arrêter l'auto-scan
|
|
23
|
+
* startAutoScan, // fonction : démarrer l'auto-scan
|
|
24
|
+
* } = useWifiScanner({ autoScan: true, interval: 10000 });
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} options - Options de configuration
|
|
27
|
+
* @param {boolean} options.autoScan - Activer le scan automatique (défaut: true)
|
|
28
|
+
* @param {number} options.interval - Intervalle en ms entre les scans (défaut: 10000)
|
|
29
|
+
* @param {boolean} options.requestPermission - Demander la permission au démarrage (défaut: true)
|
|
30
|
+
*
|
|
31
|
+
* @returns {Object} État et fonctions du scanner
|
|
32
|
+
*/
|
|
33
|
+
function useWifiScanner(options = {}) {
|
|
34
|
+
const {
|
|
35
|
+
autoScan = true,
|
|
36
|
+
interval = 10000,
|
|
37
|
+
requestPermission = true,
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
// ── États exposés à l'application consommatrice ──────────────────────────
|
|
41
|
+
const [networks, setNetworks] = useState([]);
|
|
42
|
+
const [isScanning, setIsScanning] = useState(false);
|
|
43
|
+
const [error, setError] = useState(null);
|
|
44
|
+
const [hasPermission, setHasPermission] = useState(false);
|
|
45
|
+
const [isAutoScanning, setIsAutoScanning] = useState(autoScan);
|
|
46
|
+
const [lastScanTime, setLastScanTime] = useState(null);
|
|
47
|
+
|
|
48
|
+
const intervalRef = useRef(null);
|
|
49
|
+
const appStateRef = useRef(AppState.currentState);
|
|
50
|
+
|
|
51
|
+
// ── Vérification silencieuse de la permission ────────────────────────────
|
|
52
|
+
const checkPermission = useCallback(async () => {
|
|
53
|
+
if (Platform.OS !== 'android') {
|
|
54
|
+
setHasPermission(true);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const granted = await PermissionsAndroid.check(
|
|
59
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
|
|
60
|
+
);
|
|
61
|
+
setHasPermission(granted);
|
|
62
|
+
return granted;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
// ── Demande de permission avec dialog système ────────────────────────────
|
|
69
|
+
const askPermission = useCallback(async () => {
|
|
70
|
+
if (Platform.OS !== 'android') {
|
|
71
|
+
setHasPermission(true);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
// Vérifie si déjà accordée
|
|
76
|
+
const already = await PermissionsAndroid.check(
|
|
77
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
|
|
78
|
+
);
|
|
79
|
+
if (already) {
|
|
80
|
+
setHasPermission(true);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Demande au système Android d'afficher le dialog
|
|
85
|
+
const result = await PermissionsAndroid.request(
|
|
86
|
+
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
87
|
+
{
|
|
88
|
+
title: 'Permission Wi-Fi requise',
|
|
89
|
+
message:
|
|
90
|
+
"Cette application a besoin d'accéder à votre localisation " +
|
|
91
|
+
'pour scanner les réseaux Wi-Fi environnants.',
|
|
92
|
+
buttonNeutral: 'Plus tard',
|
|
93
|
+
buttonNegative: 'Refuser',
|
|
94
|
+
buttonPositive: 'Autoriser',
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const granted = result === PermissionsAndroid.RESULTS.GRANTED;
|
|
99
|
+
setHasPermission(granted);
|
|
100
|
+
|
|
101
|
+
// Si bloquée définitivement, proposer d'ouvrir les paramètres
|
|
102
|
+
if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
|
|
103
|
+
setError(
|
|
104
|
+
'Permission bloquée. Ouvrez les Paramètres > Applications > ' +
|
|
105
|
+
'votre app > Autorisations > Localisation.'
|
|
106
|
+
);
|
|
107
|
+
Linking.openSettings().catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return granted;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
setError(`Erreur permission: ${err.message}`);
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
// ── Scan effectif (sans gestion de permission) ───────────────────────────
|
|
118
|
+
const _doScan = useCallback(async () => {
|
|
119
|
+
if (isScanning) return;
|
|
120
|
+
setIsScanning(true);
|
|
121
|
+
setError(null);
|
|
122
|
+
try {
|
|
123
|
+
const wifiOn = await WifiScanner.isWifiEnabled();
|
|
124
|
+
if (!wifiOn) {
|
|
125
|
+
setError('Wi-Fi désactivé. Activez le Wi-Fi pour scanner.');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const results = await WifiScanner.scanNetworks();
|
|
129
|
+
setNetworks(results || []);
|
|
130
|
+
setLastScanTime(new Date());
|
|
131
|
+
} catch (err) {
|
|
132
|
+
setError(`Erreur scan: ${err.message}`);
|
|
133
|
+
} finally {
|
|
134
|
+
setIsScanning(false);
|
|
135
|
+
}
|
|
136
|
+
}, [isScanning]);
|
|
137
|
+
|
|
138
|
+
// ── Scan public : vérifie la permission puis scanne ──────────────────────
|
|
139
|
+
const scan = useCallback(async () => {
|
|
140
|
+
const permitted = requestPermission
|
|
141
|
+
? await askPermission()
|
|
142
|
+
: await checkPermission();
|
|
143
|
+
if (permitted) {
|
|
144
|
+
await _doScan();
|
|
145
|
+
}
|
|
146
|
+
}, [askPermission, checkPermission, _doScan, requestPermission]);
|
|
147
|
+
|
|
148
|
+
// ── Contrôle de l'auto-scan ──────────────────────────────────────────────
|
|
149
|
+
const startAutoScan = useCallback(() => setIsAutoScanning(true), []);
|
|
150
|
+
const stopAutoScan = useCallback(() => setIsAutoScanning(false), []);
|
|
151
|
+
|
|
152
|
+
// ── Scan initial au montage du hook ─────────────────────────────────────
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
scan();
|
|
155
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
// ── Gestion de l'auto-scan par intervalle ────────────────────────────────
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
if (isAutoScanning) {
|
|
161
|
+
intervalRef.current = setInterval(async () => {
|
|
162
|
+
const ok = await checkPermission();
|
|
163
|
+
if (ok) _doScan();
|
|
164
|
+
}, interval);
|
|
165
|
+
} else {
|
|
166
|
+
clearInterval(intervalRef.current);
|
|
167
|
+
}
|
|
168
|
+
// Cleanup : supprime l'intervalle quand le composant est démonté
|
|
169
|
+
return () => clearInterval(intervalRef.current);
|
|
170
|
+
}, [isAutoScanning, interval, checkPermission, _doScan]);
|
|
171
|
+
|
|
172
|
+
// ── Écoute AppState : relance scan quand l'app revient au premier plan ───
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
const sub = AppState.addEventListener('change', async nextState => {
|
|
175
|
+
const wasInBackground =
|
|
176
|
+
appStateRef.current === 'background' ||
|
|
177
|
+
appStateRef.current === 'inactive';
|
|
178
|
+
if (wasInBackground && nextState === 'active') {
|
|
179
|
+
const ok = await checkPermission();
|
|
180
|
+
if (ok) _doScan();
|
|
181
|
+
}
|
|
182
|
+
appStateRef.current = nextState;
|
|
183
|
+
});
|
|
184
|
+
return () => sub.remove();
|
|
185
|
+
}, [checkPermission, _doScan]);
|
|
186
|
+
|
|
187
|
+
// ── Valeurs retournées au consommateur ───────────────────────────────────
|
|
188
|
+
return {
|
|
189
|
+
networks, // Array<WifiNetwork> — liste des réseaux détectés
|
|
190
|
+
isScanning, // boolean — true pendant le scan
|
|
191
|
+
error, // string|null — message d'erreur
|
|
192
|
+
hasPermission, // boolean — permission de localisation accordée
|
|
193
|
+
isAutoScanning, // boolean — auto-scan actif
|
|
194
|
+
lastScanTime, // Date|null — heure du dernier scan réussi
|
|
195
|
+
scan, // () => Promise<void> — lancer un scan manuel
|
|
196
|
+
startAutoScan, // () => void — activer l'auto-scan
|
|
197
|
+
stopAutoScan, // () => void — désactiver l'auto-scan
|
|
198
|
+
askPermission, // () => Promise<boolean> — demander la permission
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export default useWifiScanner;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// src/utils.js
|
|
2
|
+
// Fonctions utilitaires exportées par la bibliothèque.
|
|
3
|
+
// L'application consommatrice peut les importer pour traiter les données Wi-Fi.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convertit le RSSI (dBm) en pourcentage de qualité (0–100%)
|
|
7
|
+
* Formule : ((rssi + 100) / 50) * 100
|
|
8
|
+
* @param {number} rssi - Valeur RSSI en dBm (ex: -65)
|
|
9
|
+
* @returns {number} Pourcentage entre 0 et 100
|
|
10
|
+
*/
|
|
11
|
+
export function rssiToPercent(rssi) {
|
|
12
|
+
if (rssi >= -50) return 100;
|
|
13
|
+
if (rssi <= -100) return 0;
|
|
14
|
+
return Math.round(((rssi + 100) / 50) * 100);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Retourne un label qualitatif selon le RSSI
|
|
19
|
+
* @param {number} rssi
|
|
20
|
+
* @returns {string} 'Excellent' | 'Bon' | 'Moyen' | 'Faible' | 'Très faible'
|
|
21
|
+
*/
|
|
22
|
+
export function rssiToLabel(rssi) {
|
|
23
|
+
if (rssi >= -50) return 'Excellent';
|
|
24
|
+
if (rssi >= -65) return 'Bon';
|
|
25
|
+
if (rssi >= -75) return 'Moyen';
|
|
26
|
+
if (rssi >= -85) return 'Faible';
|
|
27
|
+
return 'Très faible';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Détermine la bande Wi-Fi à partir de la fréquence en MHz
|
|
32
|
+
* @param {number} frequency - Fréquence en MHz
|
|
33
|
+
* @returns {string} '2.4 GHz' | '5 GHz' | '6 GHz' | 'Inconnu'
|
|
34
|
+
*/
|
|
35
|
+
export function frequencyToBand(frequency) {
|
|
36
|
+
if (frequency >= 2400 && frequency < 2500) return '2.4 GHz';
|
|
37
|
+
if (frequency >= 4900 && frequency < 5900) return '5 GHz';
|
|
38
|
+
if (frequency >= 5900 && frequency < 7200) return '6 GHz';
|
|
39
|
+
return 'Inconnu';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Calcule le canal Wi-Fi à partir de la fréquence en MHz
|
|
44
|
+
* @param {number} frequency
|
|
45
|
+
* @returns {number|string} Numéro de canal ou '?'
|
|
46
|
+
*/
|
|
47
|
+
export function frequencyToChannel(frequency) {
|
|
48
|
+
if (frequency === 2484) return 14;
|
|
49
|
+
if (frequency >= 2412 && frequency <= 2472) {
|
|
50
|
+
return Math.round((frequency - 2407) / 5);
|
|
51
|
+
}
|
|
52
|
+
if (frequency >= 5180 && frequency <= 5825) {
|
|
53
|
+
return Math.round((frequency - 5000) / 5);
|
|
54
|
+
}
|
|
55
|
+
return '?';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Estime la distance en mètres à partir du RSSI
|
|
60
|
+
* Utilise le modèle Free Space Path Loss
|
|
61
|
+
* @param {number} rssi
|
|
62
|
+
* @returns {string} Distance formatée (ex: '~5 m', '< 1 m', '> 100 m')
|
|
63
|
+
*/
|
|
64
|
+
export function rssiToDistance(rssi) {
|
|
65
|
+
const txPower = -59;
|
|
66
|
+
const n = 2.7;
|
|
67
|
+
const distance = Math.pow(10, (txPower - rssi) / (10 * n));
|
|
68
|
+
if (distance < 1) return '< 1 m';
|
|
69
|
+
if (distance > 100) return '> 100 m';
|
|
70
|
+
return `~${Math.round(distance)} m`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Détermine le type de sécurité depuis la chaîne capabilities Android
|
|
75
|
+
* @param {string} capabilities - Ex: '[WPA2-PSK-CCMP][ESS]'
|
|
76
|
+
* @returns {string} 'WPA3' | 'WPA2' | 'WPA' | 'WEP' | 'Ouvert'
|
|
77
|
+
*/
|
|
78
|
+
export function parseSecurityType(capabilities) {
|
|
79
|
+
if (!capabilities) return 'Ouvert';
|
|
80
|
+
const cap = capabilities.toUpperCase();
|
|
81
|
+
if (cap.includes('WPA3')) return 'WPA3';
|
|
82
|
+
if (cap.includes('WPA2')) return 'WPA2';
|
|
83
|
+
if (cap.includes('WPA')) return 'WPA';
|
|
84
|
+
if (cap.includes('WEP')) return 'WEP';
|
|
85
|
+
return 'Ouvert';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Normalise le SSID (réseau caché → label lisible)
|
|
90
|
+
* @param {string} ssid
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
export function formatSSID(ssid) {
|
|
94
|
+
if (!ssid || ssid.trim() === '') return '(Réseau caché)';
|
|
95
|
+
return ssid;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Formate le BSSID en majuscules
|
|
100
|
+
* @param {string} bssid
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
export function formatBSSID(bssid) {
|
|
104
|
+
if (!bssid) return 'N/A';
|
|
105
|
+
return bssid.toUpperCase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Trie un tableau de réseaux selon le critère choisi
|
|
110
|
+
* @param {Array} networks
|
|
111
|
+
* @param {'signal'|'name'|'frequency'|'security'} sortBy
|
|
112
|
+
* @returns {Array}
|
|
113
|
+
*/
|
|
114
|
+
export function sortNetworks(networks, sortBy) {
|
|
115
|
+
const copy = [...networks];
|
|
116
|
+
switch (sortBy) {
|
|
117
|
+
case 'signal': return copy.sort((a, b) => b.rssi - a.rssi);
|
|
118
|
+
case 'name': return copy.sort((a, b) => formatSSID(a.ssid).localeCompare(formatSSID(b.ssid)));
|
|
119
|
+
case 'frequency': return copy.sort((a, b) => b.frequency - a.frequency);
|
|
120
|
+
case 'security': return copy.sort((a, b) => parseSecurityType(a.capabilities).localeCompare(parseSecurityType(b.capabilities)));
|
|
121
|
+
default: return copy;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Filtre les réseaux par SSID ou BSSID
|
|
127
|
+
* @param {Array} networks
|
|
128
|
+
* @param {string} query
|
|
129
|
+
* @returns {Array}
|
|
130
|
+
*/
|
|
131
|
+
export function filterNetworks(networks, query) {
|
|
132
|
+
if (!query || query.trim() === '') return networks;
|
|
133
|
+
const q = query.toLowerCase();
|
|
134
|
+
return networks.filter(
|
|
135
|
+
n =>
|
|
136
|
+
(n.ssid && n.ssid.toLowerCase().includes(q)) ||
|
|
137
|
+
(n.bssid && n.bssid.toLowerCase().includes(q))
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Calcule les statistiques globales de la liste de réseaux
|
|
143
|
+
* @param {Array} networks
|
|
144
|
+
* @returns {Object} { total, strongest, weakest, band24, band5, band6 }
|
|
145
|
+
*/
|
|
146
|
+
export function computeStats(networks) {
|
|
147
|
+
if (!networks || networks.length === 0) {
|
|
148
|
+
return { total: 0, strongest: null, weakest: null, band24: 0, band5: 0, band6: 0 };
|
|
149
|
+
}
|
|
150
|
+
const sorted = [...networks].sort((a, b) => b.rssi - a.rssi);
|
|
151
|
+
return {
|
|
152
|
+
total: networks.length,
|
|
153
|
+
strongest: sorted[0],
|
|
154
|
+
weakest: sorted[sorted.length - 1],
|
|
155
|
+
band24: networks.filter(n => frequencyToBand(n.frequency) === '2.4 GHz').length,
|
|
156
|
+
band5: networks.filter(n => frequencyToBand(n.frequency) === '5 GHz').length,
|
|
157
|
+
band6: networks.filter(n => frequencyToBand(n.frequency) === '6 GHz').length,
|
|
158
|
+
};
|
|
159
|
+
}
|