//! Runtime configuration for the Syntaxis Dashboard //! //! This module handles loading API configuration at runtime from the browser environment. //! The configuration is injected into `window.SYNTAXIS_CONFIG` by `index.html`. //! //! # Configuration Sources //! //! In order of precedence: //! 1. URL query parameter: `?apiUrl=http://custom-api:3000` //! 2. `window.SYNTAXIS_CONFIG.apiUrl` (set by index.html) //! 3. Default: `window.location.origin` (same-origin, current domain) //! //! # Example //! //! ``` //! // Default behavior (auto-detect from current domain) //! let url = get_api_base_url(); //! // Result: "http://localhost:3000" (if served on localhost:3000) //! //! // Override via URL parameter //! // Visit: http://localhost:8080?apiUrl=http://remote-api:3000 //! let url = get_api_base_url(); //! // Result: "http://remote-api:3000" //! ``` #![forbid(unsafe_code)] use js_sys::Reflect; use wasm_bindgen::JsValue; use web_sys::window; /// Get the API base URL from browser configuration /// /// Returns the configured API URL with priority: /// 1. URL parameter `?apiUrl=...` /// 2. `window.SYNTAXIS_CONFIG.apiUrl` /// 3. `window.location.origin` (same-origin default) /// /// # Panics /// /// Panics if `window` is not available (shouldn't happen in browser context) #[must_use] pub fn get_api_base_url() -> String { let window = window().expect("window should be available"); // Try to get apiUrl from window.SYNTAXIS_CONFIG let config_url = get_config_api_url(&window); if let Some(url) = config_url { return url; } // Fall back to window.location.origin window .location() .origin() .unwrap_or_else(|_| "http://localhost:3000".to_string()) } /// Get the API path prefix from configuration (default: /api) #[must_use] pub fn get_api_path() -> String { let window = match window() { Some(w) => w, None => return "/api".to_string(), }; get_config_api_path(&window).unwrap_or_else(|| "/api".to_string()) } /// Get the full API URL including path prefix /// /// Example: `http://localhost:3000/api` #[must_use] pub fn get_api_url() -> String { format!("{}{}", get_api_base_url(), get_api_path()) } fn get_config_api_url(window: &web_sys::Window) -> Option { // First, try URL parameter (?apiUrl=...) if let Some(url) = get_url_param_api_url(window) { return Some(url); } // Then try window.SYNTAXIS_CONFIG.apiUrl let config = Reflect::get(window, &JsValue::from_str("SYNTAXIS_CONFIG")).ok()?; Reflect::get(&config, &JsValue::from_str("apiUrl")) .ok()? .as_string() } fn get_config_api_path(window: &web_sys::Window) -> Option { let config = Reflect::get(window, &JsValue::from_str("SYNTAXIS_CONFIG")).ok()?; Reflect::get(&config, &JsValue::from_str("apiPath")) .ok()? .as_string() } fn get_url_param_api_url(window: &web_sys::Window) -> Option { let location = window.location(); let search = location.search().ok()?; // Parse URL parameters let params = web_sys::UrlSearchParams::new_with_str(&search).ok()?; params.get("apiUrl") } #[cfg(test)] mod tests { #[test] fn test_get_api_path() { // This test runs in a test environment without a browser // The actual function requires window, so we just verify the default let path = "/api".to_string(); assert_eq!(path, "/api"); } #[test] fn test_api_url_construction() { let base = "http://localhost:3000".to_string(); let path = "/api".to_string(); let full_url = format!("{}{}", base, path); assert_eq!(full_url, "http://localhost:3000/api"); } }