// Frontend authentication module - connects to backend API use serde::{Deserialize, Serialize}; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{Request, RequestInit, RequestMode, Response}; #[derive(Debug, Serialize, Deserialize)] pub struct CalDAVLoginRequest { pub server_url: String, pub username: String, pub password: String, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct UserPreferencesResponse { pub calendar_selected_date: Option, pub calendar_time_increment: Option, pub calendar_view_mode: Option, pub calendar_theme: Option, pub calendar_colors: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct AuthResponse { pub token: String, pub session_token: String, pub username: String, pub server_url: String, pub preferences: UserPreferencesResponse, } #[derive(Debug, Deserialize)] pub struct ApiErrorResponse { pub error: String, } // Frontend auth service - connects to backend API pub struct AuthService { base_url: String, } impl AuthService { pub fn new() -> Self { // Get backend URL from environment variable at compile time, fallback to localhost let base_url = option_env!("BACKEND_API_URL") .unwrap_or("http://localhost:3000/api") .to_string(); Self { base_url } } pub async fn login(&self, request: CalDAVLoginRequest) -> Result { self.post_json("/auth/login", &request).await } // Helper method for POST requests with JSON body async fn post_json Deserialize<'de>>( &self, endpoint: &str, body: &T, ) -> Result { let window = web_sys::window().ok_or("No global window exists")?; let json_body = serde_json::to_string(body).map_err(|e| format!("JSON serialization failed: {}", e))?; let opts = RequestInit::new(); opts.set_method("POST"); opts.set_mode(RequestMode::Cors); opts.set_body(&wasm_bindgen::JsValue::from_str(&json_body)); let url = format!("{}{}", self.base_url, endpoint); let request = Request::new_with_str_and_init(&url, &opts) .map_err(|e| format!("Request creation failed: {:?}", e))?; request .headers() .set("Content-Type", "application/json") .map_err(|e| format!("Header setting failed: {:?}", e))?; let resp_value = JsFuture::from(window.fetch_with_request(&request)) .await .map_err(|e| format!("Network request failed: {:?}", e))?; let resp: Response = resp_value .dyn_into() .map_err(|e| format!("Response cast failed: {:?}", e))?; let text = JsFuture::from( resp.text() .map_err(|e| format!("Text extraction failed: {:?}", e))?, ) .await .map_err(|e| format!("Text promise failed: {:?}", e))?; let text_string = text.as_string().ok_or("Response text is not a string")?; if resp.ok() { serde_json::from_str::(&text_string) .map_err(|e| format!("JSON parsing failed: {}", e)) } else { // Try to parse error response if let Ok(error_response) = serde_json::from_str::(&text_string) { Err(error_response.error) } else { Err(format!("Request failed with status {}", resp.status())) } } } }