/** * Configuration Loader - Secure Configuration Loading Pattern * * This implements the expert-recommended blocking initialization pattern: * 1. loadConfig() fetches from /api/config and stores it * 2. getConfig() returns config or throws if not loaded * 3. Application only initializes after loadConfig() completes * * This prevents race conditions where components try to access config * before it's been fetched from the server. */ /** * Module-scoped variable to hold the fetched server configuration. * @type {Object|null} */ let serverConfig = null; /** * Fetches configuration from the server and stores it. * This MUST be called before the application initializes any components * that depend on the configuration. * * @async * @returns {Promise} * @throws {Error} If the config endpoint is unreachable or returns an error */ export async function loadConfig() { // Prevent double-loading if (serverConfig) { console.warn('[ConfigLoader] Configuration already loaded.'); return; } try { const response = await fetch('/api/config'); if (!response.ok) { throw new Error(`Server returned status ${response.status}: ${response.statusText}`); } serverConfig = await response.json(); console.log('[ConfigLoader] Configuration loaded successfully', { dssHost: serverConfig.dssHost, dssPort: serverConfig.dssPort, storybookPort: serverConfig.storybookPort, }); } catch (error) { console.error('[ConfigLoader] Failed to load configuration:', error); // Re-throw to be caught by the bootstrap function throw new Error(`Failed to load server configuration: ${error.message}`); } } /** * Returns the entire configuration object. * MUST ONLY be called after loadConfig() has completed successfully. * * @returns {Object} The server configuration * @throws {Error} If called before loadConfig() has completed */ export function getConfig() { if (!serverConfig) { throw new Error('[ConfigLoader] getConfig() called before configuration was loaded. Did you forget to await loadConfig()?'); } return serverConfig; } /** * Convenience getter for just the DSS host. * @returns {string} The DSS host */ export function getDssHost() { const config = getConfig(); return config.dssHost; } /** * Convenience getter for DSS port. * @returns {string} The DSS port */ export function getDssPort() { const config = getConfig(); return config.dssPort; } /** * Convenience getter for Storybook port. * @returns {number} The Storybook port (always 6006) */ export function getStorybookPort() { const config = getConfig(); return config.storybookPort; } /** * Builds the full Storybook URL from config. * Points to Storybook running on port 6006 on the current host. * * @returns {string} The full Storybook URL (e.g., "http://dss.overbits.luz.uy:6006") */ export function getStorybookUrl() { const dssHost = getDssHost(); const protocol = window.location.protocol; // "http:" or "https:" // Point to Storybook on port 6006 return `${protocol}//${dssHost}:6006`; } /** * TESTING ONLY: Reset the configuration state * This allows tests to load different configurations * @internal */ export function __resetForTesting() { serverConfig = null; } export default { loadConfig, getConfig, getDssHost, getDssPort, getStorybookPort, getStorybookUrl, __resetForTesting, };