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