@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 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
+ }