/** * Runtime Configuration Service * * This service fetches and caches configuration from the /api/config endpoint, * allowing the application to use runtime environment variables instead of * build-time values. * * Usage: * import { getApiUrl, getBackofficeUrl, initConfig } from '@/lib/config'; * * // Initialize once at app startup (optional, auto-initializes on first use) * await initConfig(); * * // Get URLs (returns cached values or defaults) * const apiUrl = getApiUrl(); */ export interface RuntimeConfig { apiUrl: string; backofficeUrl: string; seederApiUrl: string; scraperApiUrl: string; } // Default values (fallback to build-time env or hardcoded defaults) const defaultConfig: RuntimeConfig = { apiUrl: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8521', backofficeUrl: process.env.NEXT_PUBLIC_BACKOFFICE_URL || 'http://localhost:3001', seederApiUrl: process.env.NEXT_PUBLIC_SEEDER_API_URL || 'http://localhost:3002', scraperApiUrl: process.env.NEXT_PUBLIC_SCRAPER_API_URL || 'http://localhost:3003', }; let cachedConfig: RuntimeConfig | null = null; let isInitialized = false; let initPromise: Promise | null = null; /** * Initialize the config from the server. * Safe to call multiple times - only fetches once. */ export async function initConfig(): Promise { // If already initialized, return cached config if (isInitialized && cachedConfig) { return cachedConfig; } // If already initializing, wait for that promise if (initPromise) { return initPromise; } // Start initialization initPromise = (async () => { try { // Only fetch if we're in the browser if (typeof window !== 'undefined') { const response = await fetch('/api/config'); if (response.ok) { cachedConfig = await response.json(); console.log('[Config] Loaded runtime config:', cachedConfig); } else { console.warn('[Config] Failed to fetch config, using defaults'); cachedConfig = defaultConfig; } } else { // Server-side: use defaults cachedConfig = defaultConfig; } } catch (error) { console.warn('[Config] Error fetching config, using defaults:', error); cachedConfig = defaultConfig; } isInitialized = true; return cachedConfig!; })(); return initPromise; } /** * Get the full config object. * Returns cached config if initialized, otherwise defaults. */ export function getConfig(): RuntimeConfig { // Trigger async init in background if not initialized if (!isInitialized && typeof window !== 'undefined') { initConfig(); } return cachedConfig || defaultConfig; } /** * Get the API base URL. * @returns The API URL (e.g., "https://api.gohorsejobs.com") */ export function getApiUrl(): string { return getConfig().apiUrl; } /** * Get the full API URL with /api/v1 suffix. * @returns The full API URL (e.g., "https://api.gohorsejobs.com/api/v1") */ export function getApiV1Url(): string { return `${getConfig().apiUrl}/api/v1`; } /** * Get the Backoffice URL. */ export function getBackofficeUrl(): string { return getConfig().backofficeUrl; } /** * Get the Seeder API URL. */ export function getSeederApiUrl(): string { return getConfig().seederApiUrl; } /** * Get the Scraper API URL. */ export function getScraperApiUrl(): string { return getConfig().scraperApiUrl; }