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 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
321 let current_interface = if let Some(manager) = manager.as_ref() {
322 let network_state = manager.get_current_network_state()
323 .map_err(|e| NetworkError::OperationError(e.to_string()))?;
324
325 if network_state.connection_type == "WiFi" {
327 crate::network_stats::get_network_interfaces()
329 .ok()
330 .and_then(|interfaces| {
331 interfaces.into_iter()
332 .find(|i| i.starts_with("wl") || i.starts_with("wlan"))
333 })
334 .unwrap_or_else(|| "wlan0".to_string())
335 } else {
336 crate::network_stats::get_network_interfaces()
338 .ok()
339 .and_then(|interfaces| {
340 interfaces.into_iter()
341 .find(|i| i.starts_with("en") || i.starts_with("eth"))
342 })
343 .unwrap_or_else(|| "eth0".to_string())
344 }
345 } else {
346 return Err(NetworkError::NotInitialized);
347 };
348 drop(manager); let needs_reinit = tracker.is_none() ||
352 tracker.as_ref().map(|t| t.get_interface() != current_interface).unwrap_or(false);
353
354 if needs_reinit {
355 *tracker = crate::network_stats::NetworkStatsTracker::new(current_interface).ok();
356 }
357
358 match tracker.as_mut() {
360 Some(t) => t.get_stats().map_err(|e| NetworkError::from(e)),
361 None => Err(NetworkError::OperationError("Stats tracker not initialized".to_string())),
362 }
363 }
364
365 pub fn list_vpn_profiles(&self) -> Result<Vec<VpnProfile>, NetworkError> {
366 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
367 match manager.as_ref() {
368 Some(manager) => manager.list_vpn_profiles(),
369 _none => Err(NetworkError::NotInitialized),
370 }
371 }
372
373 pub fn get_vpn_status(&self) -> Result<VpnStatus, NetworkError> {
374 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
375 match manager.as_ref() {
376 Some(manager) => manager.get_vpn_status(),
377 _none => Err(NetworkError::NotInitialized),
378 }
379 }
380
381 pub fn connect_vpn(&self, uuid: String) -> Result<(), NetworkError> {
382 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
383 match manager.as_ref() {
384 Some(manager) => manager.connect_vpn(uuid),
385 _none => Err(NetworkError::NotInitialized),
386 }
387 }
388
389 pub fn disconnect_vpn(&self, uuid: Option<String>) -> Result<(), NetworkError> {
390 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
391 match manager.as_ref() {
392 Some(manager) => manager.disconnect_vpn(uuid),
393 _none => Err(NetworkError::NotInitialized),
394 }
395 }
396
397 pub fn create_vpn_profile(&self, config: VpnCreateConfig) -> Result<VpnProfile, NetworkError> {
398 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
399 match manager.as_ref() {
400 Some(manager) => manager.create_vpn_profile(config),
401 _none => Err(NetworkError::NotInitialized),
402 }
403 }
404
405 pub fn update_vpn_profile(&self, config: VpnUpdateConfig) -> Result<VpnProfile, NetworkError> {
406 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
407 match manager.as_ref() {
408 Some(manager) => manager.update_vpn_profile(config),
409 _none => Err(NetworkError::NotInitialized),
410 }
411 }
412
413 pub fn delete_vpn_profile(&self, uuid: String) -> Result<(), NetworkError> {
414 let manager = self.manager.read().map_err(|_| NetworkError::LockError)?;
415 match manager.as_ref() {
416 Some(manager) => manager.delete_vpn_profile(uuid),
417 _none => Err(NetworkError::NotInitialized),
418 }
419 }
420}
421
422pub fn init() -> TauriPlugin<tauri::Wry> {
424 Builder::new("network-manager")
425 .invoke_handler(tauri::generate_handler![
426 get_network_state,
427 list_wifi_networks,
428 connect_to_wifi,
429 disconnect_from_wifi,
430 get_saved_wifi_networks,
431 rescan_wifi,
432 delete_wifi_connection,
433 toggle_network_state,
434 get_wireless_enabled,
435 set_wireless_enabled,
436 is_wireless_available,
437 get_network_stats,
438 get_network_interfaces,
439 list_vpn_profiles,
440 get_vpn_status,
441 connect_vpn,
442 disconnect_vpn,
443 create_vpn_profile,
444 update_vpn_profile,
445 delete_vpn_profile,
446 ])
447 .setup(|app, _api| -> Result<(), Box<dyn std::error::Error>> {
448 #[cfg(desktop)]
449 let rt = tokio::runtime::Builder::new_multi_thread()
451 .enable_all()
452 .build()?;
453 let network_manager = rt.block_on(async { crate::desktop::init(&app, _api).await })?;
454
455 app.manage(NetworkManagerState::<tauri::Wry>::new(Some(
456 network_manager,
457 )));
458
459 app.state::<NetworkManagerState<tauri::Wry>>()
460 .manager
461 .read()
462 .map_err(|_| NetworkError::LockError)?
463 .as_ref()
464 .map(|manager| {
465 let manager_static: crate::models::VSKNetworkManager<'static, tauri::Wry> =
467 crate::models::VSKNetworkManager {
468 connection: manager.connection.clone(),
469 proxy: manager.proxy.clone(),
470 app: app.clone(),
471 };
472 spawn_network_change_emitter(app.clone(), manager_static);
473 });
474
475 Ok(())
476 })
477 .build()
478}
479
480pub trait NetworkManagerExt<R: Runtime> {
482 fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>>;
483}
484
485impl<R: Runtime + Clone, T: Manager<R>> NetworkManagerExt<R> for T {
486 fn network_manager(&self) -> Option<crate::models::VSKNetworkManager<'static, R>> {
487 self.try_state::<NetworkManagerState<R>>()
488 .and_then(|state| {
489 state.manager.read().ok().and_then(|m| {
490 m.as_ref().map(|x| crate::models::VSKNetworkManager {
491 connection: x.connection.clone(),
492 proxy: x.proxy.clone(),
493 app: self.app_handle().clone(),
494 })
495 })
496 })
497 }
498}