tauri_plugin_network_manager/
lib.rs

1use commands::{
2    connect_to_wifi, delete_wifi_connection, disconnect_from_wifi, get_network_state,
3    get_saved_wifi_networks, list_wifi_networks, toggle_network_state,
4    get_wireless_enabled, set_wireless_enabled, is_wireless_available,
5    get_network_stats, get_network_interfaces
6};
7pub use models::{NetworkInfo, WiFiConnectionConfig, WiFiSecurityType};
8use serde::{Deserialize, Serialize};
9use std::result::Result;
10use std::sync::{Arc, RwLock};
11use tauri::{
12    plugin::{Builder, TauriPlugin},
13    AppHandle, Emitter, Manager, Runtime,
14};
15
16#[cfg(desktop)]
17pub mod desktop;
18
19mod commands;
20pub mod error;
21pub mod models;
22mod nm_constants;
23mod nm_helpers;
24mod network_stats;
25
26pub use crate::error::{NetworkError, Result as NetworkResult};
27
28pub struct NetworkManagerState<R: Runtime> {
29    pub manager: Arc<RwLock<Option<crate::models::VSKNetworkManager<'static, R>>>>,
30    pub stats_tracker: Arc<RwLock<Option<crate::network_stats::NetworkStatsTracker>>>,
31}
32
33impl<R: Runtime> Default for NetworkManagerState<R> {
34    fn default() -> Self {
35        Self {
36            manager: Arc::new(RwLock::new(None)),
37            stats_tracker: Arc::new(RwLock::new(None)),
38        }
39    }
40}
41
42pub fn spawn_network_change_emitter<R: tauri::Runtime>(
43    app: AppHandle<R>,
44    network_manager: crate::models::VSKNetworkManager<'static, R>,
45) {
46    let rx = match network_manager.listen_network_changes() {
47        Ok(rx) => rx,
48        Err(e) => {
49            eprintln!("No se pudo escuchar cambios de red: {:?}", e);
50            return;
51        }
52    };
53
54    std::thread::spawn(move || {
55        use std::time::{Duration, Instant};
56        use std::sync::mpsc::RecvTimeoutError;
57
58        let mut pending_event: Option<crate::models::NetworkInfo> = None;
59        let mut debounce_deadline: Option<Instant> = None;
60        let debounce_duration = Duration::from_millis(500);
61
62        loop {
63            if let Some(deadline) = debounce_deadline {
64                let now = Instant::now();
65                if now >= deadline {
66                    // Timeout reached, emit valid pending event
67                    if let Some(info) = pending_event.take() {
68                        let _ = app.emit("network-changed", &info);
69                    }
70                    debounce_deadline = None;
71                } else {
72                    // Wait for remaining time or new event
73                    let timeout = deadline - now;
74                    match rx.recv_timeout(timeout) {
75                        Ok(info) => {
76                            // New event during debounce window: update pending and extend deadline
77                            pending_event = Some(info);
78                            debounce_deadline = Some(Instant::now() + debounce_duration);
79                        }
80                        Err(RecvTimeoutError::Timeout) => {
81                            // Loop will handle emission
82                        }
83                        Err(RecvTimeoutError::Disconnected) => break,
84                    }
85                }
86            } else {
87                // No pending event, block until one arrives
88                match rx.recv() {
89                    Ok(info) => {
90                        pending_event = Some(info);
91                        // Emit immediately for first event? Or debounce?
92                        // Plan says "Debounce rapid state changes". So we should debounce.
93                        // But latency... 500ms latency on click ("Disconnect") might be annoying.
94                        // Ideally we emit first one immediately, then debounce subsequent?
95                        // "Trailing" vs "Leading".
96                        // Use trailing for stability (state is settling).
97                        debounce_deadline = Some(Instant::now() + debounce_duration);
98                    }
99                    Err(_) => break,
100                }
101            }
102        }
103    });
104}
105
106impl<R: Runtime> NetworkManagerState<R> {
107    pub fn new(manager: Option<crate::models::VSKNetworkManager<'static, R>>) -> Self {
108        Self {
109            manager: Arc::new(RwLock::new(manager)),
110            stats_tracker: Arc::new(RwLock::new(None)),
111        }
112    }
113
114    pub fn list_wifi_networks(&self) -> Result<Vec<NetworkInfo>, NetworkError> {
115        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
116        match manager.as_ref() {
117            Some(manager) => manager.list_wifi_networks(),
118            _none => Err(NetworkError::NotInitialized),
119        }
120    }
121
122    pub async fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<(), NetworkError> {
123        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
124        match manager.as_ref() {
125            Some(manager) => manager.connect_to_wifi(config).await,
126            _none => Err(NetworkError::NotInitialized),
127        }
128    }
129
130    pub async fn disconnect_from_wifi(&self) -> Result<(), NetworkError> {
131        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
132        match manager.as_ref() {
133            Some(manager) => manager.disconnect_from_wifi().await,
134            _none => Err(NetworkError::NotInitialized),
135        }
136    }
137
138    pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>, NetworkError> {
139        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
140        match manager.as_ref() {
141            Some(manager) => manager.get_saved_wifi_networks(),
142            _none => Err(NetworkError::NotInitialized),
143        }
144    }
145
146    pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool, NetworkError> {
147        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
148        match manager.as_ref() {
149            Some(manager) => manager.delete_wifi_connection(ssid),
150            _none => Err(NetworkError::NotInitialized),
151        }
152    }
153
154    pub fn toggle_network_state(&self, enabled: bool) -> Result<bool, NetworkError> {
155        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
156        match manager.as_ref() {
157            Some(manager) => manager.toggle_network_state(enabled),
158            _none => Err(NetworkError::NotInitialized),
159        }
160    }
161
162    pub fn get_wireless_enabled(&self) -> Result<bool, NetworkError> {
163        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
164        match manager.as_ref() {
165            Some(manager) => manager.get_wireless_enabled().map_err(|e| NetworkError::from(e)),
166            _none => Err(NetworkError::NotInitialized),
167        }
168    }
169
170    pub fn set_wireless_enabled(&self, enabled: bool) -> Result<(), NetworkError> {
171        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
172        match manager.as_ref() {
173             Some(manager) => manager.set_wireless_enabled(enabled).map_err(|e| NetworkError::from(e)),
174            _none => Err(NetworkError::NotInitialized),
175        }
176    }
177
178    pub fn is_wireless_available(&self) -> Result<bool, NetworkError> {
179        let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
180        match manager.as_ref() {
181            Some(manager) => manager.is_wireless_available().map_err(|e| NetworkError::from(e)),
182            _none => Err(NetworkError::NotInitialized),
183        }
184    }
185
186    pub fn get_network_stats(&self) -> Result<crate::models::NetworkStats, NetworkError> {
187        let mut tracker = self.stats_tracker.write().map_err(|_| NetworkError::LockError)?;
188        
189        // Initialize tracker if not already initialized
190        if tracker.is_none() {
191            // Get active interface from network manager
192            let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
193            if let Some(manager) = manager.as_ref() {
194                let network_state = manager.get_current_network_state()
195                    .map_err(|e| NetworkError::OperationError(e.to_string()))?;
196                
197                // Try to determine interface name from connection type
198                let interface = if network_state.connection_type == "WiFi" {
199                    // Try common WiFi interface names
200                    crate::network_stats::get_network_interfaces()
201                        .ok()
202                        .and_then(|interfaces| {
203                            interfaces.into_iter()
204                                .find(|i| i.starts_with("wl") || i.starts_with("wlan"))
205                        })
206                        .unwrap_or_else(|| "wlan0".to_string())
207                } else {
208                    // Try common ethernet interface names
209                    crate::network_stats::get_network_interfaces()
210                        .ok()
211                        .and_then(|interfaces| {
212                            interfaces.into_iter()
213                                .find(|i| i.starts_with("en") || i.starts_with("eth"))
214                        })
215                        .unwrap_or_else(|| "eth0".to_string())
216                };
217                
218                *tracker = crate::network_stats::NetworkStatsTracker::new(interface).ok();
219            }
220        }
221        
222        // Get stats from tracker
223        match tracker.as_mut() {
224            Some(t) => t.get_stats().map_err(|e| NetworkError::from(e)),
225            None => Err(NetworkError::OperationError("Stats tracker not initialized".to_string())),
226        }
227    }
228}
229
230#[derive(Serialize, Deserialize)]
231struct NetworkRequest {
232    ssid: String,
233    password: Option<String>,
234    security_type: WiFiSecurityType,
235    username: Option<String>,
236}
237
238/// Initializes the plugin.
239pub fn init() -> TauriPlugin<tauri::Wry> {
240    Builder::new("network-manager")
241        .invoke_handler(tauri::generate_handler![
242            get_network_state,
243            list_wifi_networks,
244            connect_to_wifi,
245            disconnect_from_wifi,
246            get_saved_wifi_networks,
247            delete_wifi_connection,
248            toggle_network_state,
249            get_wireless_enabled,
250            set_wireless_enabled,
251            is_wireless_available,
252            get_network_stats,
253            get_network_interfaces,
254        ])
255        .setup(|app, _api| -> Result<(), Box<dyn std::error::Error>> {
256            #[cfg(desktop)]
257            // Removed tokio runtime initialization
258            let rt = tokio::runtime::Builder::new_multi_thread()
259                .enable_all()
260                .build()?;
261            let network_manager = rt.block_on(async { crate::desktop::init(&app, _api).await })?;
262
263            app.manage(NetworkManagerState::<tauri::Wry>::new(Some(
264                network_manager,
265            )));
266
267            app.state::<NetworkManagerState<tauri::Wry>>()
268                .manager
269                .read()
270                .map_err(|_| NetworkError::LockError)?
271                .as_ref()
272                .map(|manager| {
273                    // Clone the manager with a 'static lifetime
274                    let manager_static: crate::models::VSKNetworkManager<'static, tauri::Wry> =
275                        crate::models::VSKNetworkManager {
276                            connection: manager.connection.clone(),
277                            proxy: manager.proxy.clone(),
278                            app: app.clone(),
279                        };
280                    spawn_network_change_emitter(app.clone(), manager_static);
281                });
282
283            Ok(())
284        })
285        .build()
286}
287
288/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the network-manager APIs.
289pub trait NetworkManagerExt<R: Runtime> {
290    fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>>;
291}
292
293impl<R: Runtime + Clone, T: Manager<R>> NetworkManagerExt<R> for T {
294    fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>> {
295        self.try_state::<NetworkManagerState<R>>()
296            .and_then(|state| {
297                state.manager.read().ok().and_then(|m| {
298                    m.as_ref().map(|x| crate::models::VSKNetworkManager {
299                        connection: x.connection.clone(),
300                        proxy: x.proxy.clone(),
301                        app: self.app_handle().clone(),
302                    })
303                })
304            })
305    }
306}