use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::time::Duration; use tauri::{AppHandle, Emitter}; use serde::Serialize; use super::{AuthManager, User}; // Verification interval (5 minutes) const VERIFICATION_INTERVAL_MS: u64 = 300000; /// Session verification result event emitted to frontend #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase", tag = "type")] pub enum SessionVerificationEvent { Verified { user: User }, NeedsReauth { reason: String }, NetworkError { message: String }, } /// Background session verifier pub struct SessionVerifier { auth_manager: Arc, is_running: Arc, device_id: String, app_handle: Option, } impl SessionVerifier { /// Create a new session verifier pub fn new(auth_manager: Arc, device_id: String) -> Self { Self { auth_manager, is_running: Arc::new(AtomicBool::new(false)), device_id, app_handle: None, } } /// Set the Tauri app handle for event emission pub fn set_app_handle(&mut self, app_handle: AppHandle) { self.app_handle = Some(app_handle); } /// Start periodic session verification pub async fn start(&self) { if self.is_running.swap(true, Ordering::SeqCst) { log::info!("[SessionVerifier] Already running"); return; } log::info!("[SessionVerifier] Starting background verification"); let auth_manager = Arc::clone(&self.auth_manager); let is_running = Arc::clone(&self.is_running); let device_id = self.device_id.clone(); let app_handle = self.app_handle.clone(); tokio::spawn(async move { // Initial verification after short delay tokio::time::sleep(Duration::from_millis(2000)).await; while is_running.load(Ordering::SeqCst) { // Get current session let session = auth_manager.get_session().await; if let Some(session) = session { log::debug!("[SessionVerifier] Verifying session for: {}", session.username); // Verify the session match auth_manager .verify_session( &session.server_url, &session.user_id, &session.access_token, &device_id, ) .await { Ok(user) => { log::info!("[SessionVerifier] Session verified successfully"); // Emit success event if let Some(app) = &app_handle { let event = SessionVerificationEvent::Verified { user }; if let Err(e) = app.emit("auth:session-verified", event) { log::error!("[SessionVerifier] Failed to emit event: {}", e); } } // Update session as verified let mut updated_session = session; updated_session.verified = true; updated_session.needs_reauth = false; auth_manager.set_session(Some(updated_session)).await; } Err(e) => { log::warn!("[SessionVerifier] Verification failed: {}", e); // Classify error let is_auth_error = e.contains("401") || e.contains("403"); let is_network_error = e.contains("network") || e.contains("timeout") || e.contains("connection") || e.contains("DNS"); if is_auth_error { // Token is invalid - need re-authentication log::warn!("[SessionVerifier] Session requires re-authentication"); if let Some(app) = &app_handle { let event = SessionVerificationEvent::NeedsReauth { reason: "Session expired".to_string(), }; if let Err(e) = app.emit("auth:needs-reauth", event) { log::error!("[SessionVerifier] Failed to emit event: {}", e); } } // Update session let mut updated_session = session; updated_session.verified = false; updated_session.needs_reauth = true; auth_manager.set_session(Some(updated_session)).await; } else if is_network_error { // Network error - keep using cached session log::info!("[SessionVerifier] Network error during verification, keeping cached session"); if let Some(app) = &app_handle { let event = SessionVerificationEvent::NetworkError { message: e.clone(), }; if let Err(e) = app.emit("auth:network-error", event) { log::error!("[SessionVerifier] Failed to emit event: {}", e); } } } else { // Unknown error - log but don't invalidate log::error!("[SessionVerifier] Unknown error during verification: {}", e); } } } } // Wait for next verification tokio::time::sleep(Duration::from_millis(VERIFICATION_INTERVAL_MS)).await; } log::info!("[SessionVerifier] Stopped"); }); } /// Stop periodic verification pub fn stop(&self) { log::info!("[SessionVerifier] Stopping background verification"); self.is_running.store(false, Ordering::SeqCst); } }