1use commands::{
2 connect_to_wifi, connect_vpn, create_vpn_profile, delete_vpn_profile, delete_wifi_connection,
3 disconnect_from_wifi, disconnect_vpn, get_network_state, get_vpn_status,
4 get_saved_wifi_networks, list_wifi_networks, rescan_wifi, toggle_network_state,
5 get_wireless_enabled, list_vpn_profiles, set_wireless_enabled, is_wireless_available,
6 update_vpn_profile, get_network_stats, get_network_interfaces
7};
8pub use models::{
9 NetworkInfo, VpnConnectionState, VpnCreateConfig, VpnEventPayload, VpnProfile, VpnStatus,
10 VpnUpdateConfig, WiFiConnectionConfig, WiFiSecurityType,
11};
12use std::result::Result;
13use std::sync::{Arc, RwLock};
14use std::time::{Duration, Instant};
15use tauri::{
16 plugin::{Builder, TauriPlugin},
17 AppHandle, Emitter, Manager, Runtime,
18};
19
20#[cfg(desktop)]
21pub mod desktop;
22
23mod commands;
24pub mod error;
25pub mod models;
26mod nm_constants;
27mod nm_helpers;
28mod network_stats;
29
30pub use crate::error::{NetworkError, Result as NetworkResult};
31
32pub struct NetworkManagerState<R: Runtime> {
33 pub manager: Arc<RwLock<Option<crate::models::VSKNetworkManager<'static, R>>>>,
34 pub stats_tracker: Arc<RwLock<Option<crate::network_stats::NetworkStatsTracker>>>,
35 pub wifi_networks_cache: Arc<RwLock<Option<WifiNetworksCache>>>,
36}
37
38pub struct WifiNetworksCache {
39 pub data: Vec<NetworkInfo>,
40 pub fetched_at: Instant,
41}
42
43impl<R: Runtime> Default for NetworkManagerState<R> {
44 fn default() -> Self {
45 Self {
46 manager: Arc::new(RwLock::new(None)),
47 stats_tracker: Arc::new(RwLock::new(None)),
48 wifi_networks_cache: Arc::new(RwLock::new(None)),
49 }
50 }
51}
52
53pub fn spawn_network_change_emitter<R: tauri::Runtime>(
54 app: AppHandle<R>,
55 network_manager: crate::models::VSKNetworkManager<'static, R>,
56) {
57 let rx = match network_manager.listen_network_changes() {
58 Ok(rx) => rx,
59 Err(e) => {
60 eprintln!("No se pudo escuchar cambios de red: {:?}", e);
61 return;
62 }
63 };
64
65 std::thread::spawn(move || {
66 use std::time::{Duration, Instant};
67 use std::sync::mpsc::RecvTimeoutError;
68
69 let mut pending_event: Option<crate::models::NetworkInfo> = None;
70 let mut pending_vpn_status: Option<crate::models::VpnStatus> = None;
71 let mut last_vpn_status: Option<crate::models::VpnStatus> = None;
72 let mut debounce_deadline: Option<Instant> = None;
73 let debounce_duration = Duration::from_millis(250);
74
75 loop {
76 if let Some(deadline) = debounce_deadline {
77 let now = Instant::now();
78 if now >= deadline {
79 if let Some(info) = pending_event.take() {
81 let _ = app.emit("network-changed", &info);
82 }
83 if let Some(vpn_status) = pending_vpn_status.take() {
84 emit_vpn_events(&app, &network_manager, &mut last_vpn_status, vpn_status);
85 }
86 debounce_deadline = None;
87 } else {
88 let timeout = deadline - now;
90 match rx.recv_timeout(timeout) {
91 Ok(info) => {
92 pending_event = Some(info);
94 if let Ok(vpn_status) = network_manager.get_vpn_status() {
95 pending_vpn_status = Some(vpn_status);
96 }
97 debounce_deadline = Some(Instant::now() + debounce_duration);
98 }
99 Err(RecvTimeoutError::Timeout) => {
100 }
102 Err(RecvTimeoutError::Disconnected) => break,
103 }
104 }
105 } else {
106 match rx.recv() {
108 Ok(info) => {
109 let _ = app.emit("network-changed", &info);
111 if let Ok(vpn_status) = network_manager.get_vpn_status() {
112 emit_vpn_events(&app, &network_manager, &mut last_vpn_status, vpn_status);
113 }
114 pending_event = None;
115 pending_vpn_status = None;
116 debounce_deadline = Some(Instant::now() + debounce_duration);
117 }
118 Err(_) => break,
119 }
120 }
121 }
122 });
123}
124
125fn resolve_active_vpn_profile<R: tauri::Runtime>(
126 network_manager: &crate::models::VSKNetworkManager<'static, R>,
127 status: &VpnStatus,
128) -> Option<VpnProfile> {
129 let active_uuid = status.active_profile_uuid.as_deref()?;
130 let profiles = network_manager.list_vpn_profiles().ok()?;
131 profiles.into_iter().find(|profile| profile.uuid == active_uuid)
132}
133
134fn emit_vpn_events<R: tauri::Runtime>(
135 app: &AppHandle<R>,
136 network_manager: &crate::models::VSKNetworkManager<'static, R>,
137 last_vpn_status: &mut Option<VpnStatus>,
138 status: VpnStatus,
139) {
140 let previous = last_vpn_status.clone();
141 if previous.as_ref() == Some(&status) {
142 return;
143 }
144
145 let profile = resolve_active_vpn_profile(network_manager, &status);
146 let payload = VpnEventPayload {
147 status: status.clone(),
148 profile,
149 reason: None,
150 };
151
152 let _ = app.emit("vpn-changed", &payload);
153
154 let previous_state = previous
155 .as_ref()
156 .map(|s| s.state.clone())
157 .unwrap_or(VpnConnectionState::Unknown);
158
159 match status.state {
160 VpnConnectionState::Connected => {
161 if previous_state != VpnConnectionState::Connected {
162 let _ = app.emit("vpn-connected", &payload);
163 }
164 }
165 VpnConnectionState::Disconnected => {
166 if previous_state == VpnConnectionState::Connected
167 || previous_state == VpnConnectionState::Disconnecting
168 {
169 let _ = app.emit("vpn-disconnected", &payload);
170 }
171 }
172 VpnConnectionState::Failed => {
173 let failed_payload = VpnEventPayload {
174 reason: Some("vpn-connection-failed".to_string()),
175 ..payload.clone()
176 };
177 let _ = app.emit("vpn-failed", &failed_payload);
178 }
179 _ => {}
180 }
181
182 *last_vpn_status = Some(status);
183}
184
185impl<R: Runtime> NetworkManagerState<R> {
186 pub fn new(manager: Option<crate::models::VSKNetworkManager<'static, R>>) -> Self {
187 Self {
188 manager: Arc::new(RwLock::new(manager)),
189 stats_tracker: Arc::new(RwLock::new(None)),
190 wifi_networks_cache: Arc::new(RwLock::new(None)),
191 }
192 }
193
194 pub fn list_wifi_networks(
195 &self,
196 force_refresh: bool,
197 ttl_ms: Option<u64>,
198 ) -> Result<Vec<NetworkInfo>, NetworkError> {
199 let ttl = Duration::from_millis(ttl_ms.unwrap_or(3000).clamp(250, 30000));
200
201 if !force_refresh {
202 let cache = self
203 .wifi_networks_cache
204 .read()
205 .map_err(|_| NetworkError::LockError)?;
206 if let Some(cache_entry) = cache.as_ref() {
207 if cache_entry.fetched_at.elapsed() <= ttl {
208 return Ok(cache_entry.data.clone());
209 }
210 }
211 }
212
213 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
214 let networks = match manager.as_ref() {
215 Some(manager) => manager.list_wifi_networks(),
216 _none => Err(NetworkError::NotInitialized),
217 }?;
218
219 let mut cache = self
220 .wifi_networks_cache
221 .write()
222 .map_err(|_| NetworkError::LockError)?;
223 *cache = Some(WifiNetworksCache {
224 data: networks.clone(),
225 fetched_at: Instant::now(),
226 });
227
228 Ok(networks)
229 }
230
231 pub fn invalidate_wifi_networks_cache(&self) -> Result<(), NetworkError> {
232 let mut cache = self
233 .wifi_networks_cache
234 .write()
235 .map_err(|_| NetworkError::LockError)?;
236 *cache = None;
237 Ok(())
238 }
239
240 pub fn rescan_wifi(&self) -> Result<Vec<NetworkInfo>, NetworkError> {
241 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
242 match manager.as_ref() {
243 Some(manager) => manager.rescan_wifi()?,
244 _none => return Err(NetworkError::NotInitialized),
245 };
246 drop(manager);
247
248 self.invalidate_wifi_networks_cache()?;
249 self.list_wifi_networks(true, None)
250 }
251
252 pub fn connect_to_wifi(&self, config: WiFiConnectionConfig) -> Result<(), NetworkError> {
253 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
254 match manager.as_ref() {
255 Some(manager) => manager.connect_to_wifi(config),
256 _none => Err(NetworkError::NotInitialized),
257 }
258 }
259
260 pub fn disconnect_from_wifi(&self) -> Result<(), NetworkError> {
261 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
262 match manager.as_ref() {
263 Some(manager) => manager.disconnect_from_wifi(),
264 _none => Err(NetworkError::NotInitialized),
265 }
266 }
267
268 pub fn get_saved_wifi_networks(&self) -> Result<Vec<NetworkInfo>, NetworkError> {
269 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
270 match manager.as_ref() {
271 Some(manager) => manager.get_saved_wifi_networks(),
272 _none => Err(NetworkError::NotInitialized),
273 }
274 }
275
276 pub fn delete_wifi_connection(&self, ssid: &str) -> Result<bool, NetworkError> {
277 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
278 match manager.as_ref() {
279 Some(manager) => manager.delete_wifi_connection(ssid),
280 _none => Err(NetworkError::NotInitialized),
281 }
282 }
283
284 pub fn toggle_network_state(&self, enabled: bool) -> Result<bool, NetworkError> {
285 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
286 match manager.as_ref() {
287 Some(manager) => manager.toggle_network_state(enabled),
288 _none => Err(NetworkError::NotInitialized),
289 }
290 }
291
292 pub fn get_wireless_enabled(&self) -> Result<bool, NetworkError> {
293 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
294 match manager.as_ref() {
295 Some(manager) => manager.get_wireless_enabled().map_err(|e| NetworkError::from(e)),
296 _none => Err(NetworkError::NotInitialized),
297 }
298 }
299
300 pub fn set_wireless_enabled(&self, enabled: bool) -> Result<(), NetworkError> {
301 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
302 match manager.as_ref() {
303 Some(manager) => manager.set_wireless_enabled(enabled).map_err(|e| NetworkError::from(e)),
304 _none => Err(NetworkError::NotInitialized),
305 }
306 }
307
308 pub fn is_wireless_available(&self) -> Result<bool, NetworkError> {
309 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
310 match manager.as_ref() {
311 Some(manager) => manager.is_wireless_available().map_err(|e| NetworkError::from(e)),
312 _none => Err(NetworkError::NotInitialized),
313 }
314 }
315
316 pub fn get_network_stats(&self) -> Result<crate::models::NetworkStats, NetworkError> {
317 let mut tracker = self.stats_tracker.write().map_err(|_| NetworkError::LockError)?;
318
319 if tracker.is_none() {
321 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
323 if let Some(manager) = manager.as_ref() {
324 let network_state = manager.get_current_network_state()
325 .map_err(|e| NetworkError::OperationError(e.to_string()))?;
326
327 let interface = if network_state.connection_type == "WiFi" {
329 crate::network_stats::get_network_interfaces()
331 .ok()
332 .and_then(|interfaces| {
333 interfaces.into_iter()
334 .find(|i| i.starts_with("wl") || i.starts_with("wlan"))
335 })
336 .unwrap_or_else(|| "wlan0".to_string())
337 } else {
338 crate::network_stats::get_network_interfaces()
340 .ok()
341 .and_then(|interfaces| {
342 interfaces.into_iter()
343 .find(|i| i.starts_with("en") || i.starts_with("eth"))
344 })
345 .unwrap_or_else(|| "eth0".to_string())
346 };
347
348 *tracker = crate::network_stats::NetworkStatsTracker::new(interface).ok();
349 }
350 }
351
352 match tracker.as_mut() {
354 Some(t) => t.get_stats().map_err(|e| NetworkError::from(e)),
355 None => Err(NetworkError::OperationError("Stats tracker not initialized".to_string())),
356 }
357 }
358
359 pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>, NetworkError> {
360 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
361 match manager.as_ref() {
362 Some(manager) => manager.list_vpn_profiles(),
363 _none => Err(NetworkError::NotInitialized),
364 }
365 }
366
367 pub fn get_vpn_status(&self) -> Result<VpnStatus, NetworkError> {
368 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
369 match manager.as_ref() {
370 Some(manager) => manager.get_vpn_status(),
371 _none => Err(NetworkError::NotInitialized),
372 }
373 }
374
375 pub fn connect_vpn(&self, uuid: String) -> Result<(), NetworkError> {
376 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
377 match manager.as_ref() {
378 Some(manager) => manager.connect_vpn(uuid),
379 _none => Err(NetworkError::NotInitialized),
380 }
381 }
382
383 pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<(), NetworkError> {
384 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
385 match manager.as_ref() {
386 Some(manager) => manager.disconnect_vpn(uuid),
387 _none => Err(NetworkError::NotInitialized),
388 }
389 }
390
391 pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile, NetworkError> {
392 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
393 match manager.as_ref() {
394 Some(manager) => manager.create_vpn_profile(config),
395 _none => Err(NetworkError::NotInitialized),
396 }
397 }
398
399 pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile, NetworkError> {
400 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
401 match manager.as_ref() {
402 Some(manager) => manager.update_vpn_profile(config),
403 _none => Err(NetworkError::NotInitialized),
404 }
405 }
406
407 pub fn delete_vpn_profile(&self, uuid: String) -> Result<(), NetworkError> {
408 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
409 match manager.as_ref() {
410 Some(manager) => manager.delete_vpn_profile(uuid),
411 _none => Err(NetworkError::NotInitialized),
412 }
413 }
414}
415
416pub fn init() -> TauriPlugin<tauri::Wry> {
418 Builder::new("network-manager")
419 .invoke_handler(tauri::generate_handler![
420 get_network_state,
421 list_wifi_networks,
422 connect_to_wifi,
423 disconnect_from_wifi,
424 get_saved_wifi_networks,
425 rescan_wifi,
426 delete_wifi_connection,
427 toggle_network_state,
428 get_wireless_enabled,
429 set_wireless_enabled,
430 is_wireless_available,
431 get_network_stats,
432 get_network_interfaces,
433 list_vpn_profiles,
434 get_vpn_status,
435 connect_vpn,
436 disconnect_vpn,
437 create_vpn_profile,
438 update_vpn_profile,
439 delete_vpn_profile,
440 ])
441 .setup(|app, _api| -> Result<(), Box<dyn std::error::Error>> {
442 #[cfg(desktop)]
443 let rt = tokio::runtime::Builder::new_multi_thread()
445 .enable_all()
446 .build()?;
447 let network_manager = rt.block_on(async { crate::desktop::init(&app, _api).await })?;
448
449 app.manage(NetworkManagerState::<tauri::Wry>::new(Some(
450 network_manager,
451 )));
452
453 app.state::<NetworkManagerState<tauri::Wry>>()
454 .manager
455 .read()
456 .map_err(|_| NetworkError::LockError)?
457 .as_ref()
458 .map(|manager| {
459 let manager_static: crate::models::VSKNetworkManager<'static, tauri::Wry> =
461 crate::models::VSKNetworkManager {
462 connection: manager.connection.clone(),
463 proxy: manager.proxy.clone(),
464 app: app.clone(),
465 };
466 spawn_network_change_emitter(app.clone(), manager_static);
467 });
468
469 Ok(())
470 })
471 .build()
472}
473
474pub trait NetworkManagerExt<R: Runtime> {
476 fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>>;
477}
478
479impl<R: Runtime + Clone, T: Manager<R>> NetworkManagerExt<R> for T {
480 fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>> {
481 self.try_state::<NetworkManagerState<R>>()
482 .and_then(|state| {
483 state.manager.read().ok().and_then(|m| {
484 m.as_ref().map(|x| crate::models::VSKNetworkManager {
485 connection: x.connection.clone(),
486 proxy: x.proxy.clone(),
487 app: self.app_handle().clone(),
488 })
489 })
490 })
491 }
492}