import { refreshRuntimeUrlAuthToken, setRuntimeBearerToken } from '@/lib/runtime-auth'; import { configureRuntimeUrlResolver } from '@/lib/runtime-url'; export type RuntimeEndpointChangedDetail = { apiBaseUrl: string; previousApiBaseUrl: string; runtimeKey: string; previousRuntimeKey: string; }; const RUNTIME_ENDPOINT_CHANGED_EVENT = 'openchamber:runtime-endpoint-changed'; let activeApiBaseUrl = ''; let activeRuntimeKey = ''; const normalizeRuntimeUrlKey = (value: string): string => { try { const url = new URL(value); url.hash = ''; url.search = ''; url.pathname = url.pathname.replace(/\/+$/, '') || '/'; return `url:${url.toString().replace(/\/+$/, '')}`; } catch { return `url:${value.trim().replace(/\/+$/, '') || 'default'}`; } }; const readInjectedApiBaseUrl = (): string => { if (typeof window === 'undefined') return ''; const injected = (window as typeof window & { __OPENCHAMBER_API_BASE_URL__?: string }).__OPENCHAMBER_API_BASE_URL__; return typeof injected === 'string' ? injected.trim() : ''; }; const readInjectedLocalOrigin = (): string => { if (typeof window === 'undefined') return ''; const injected = (window as typeof window & { __OPENCHAMBER_LOCAL_ORIGIN__?: string }).__OPENCHAMBER_LOCAL_ORIGIN__; return typeof injected === 'string' ? injected.trim() : ''; }; const sameOrigin = (left: string, right: string): boolean => { if (!left || !right) return false; try { return new URL(left).origin === new URL(right).origin; } catch { return false; } }; export const getRuntimeApiBaseUrl = (): string => activeApiBaseUrl || readInjectedApiBaseUrl(); export const getRuntimeKey = (): string => { if (activeRuntimeKey) return activeRuntimeKey; const apiBaseUrl = getRuntimeApiBaseUrl(); if (sameOrigin(apiBaseUrl, readInjectedLocalOrigin())) return 'local'; return normalizeRuntimeUrlKey(apiBaseUrl); }; export const initializeRuntimeEndpoint = (options: { apiBaseUrl?: string | null; runtimeKey?: string | null } = {}): void => { if (activeApiBaseUrl || activeRuntimeKey) { return; } const apiBaseUrl = options.apiBaseUrl?.trim() || readInjectedApiBaseUrl(); if (!apiBaseUrl) { return; } activeApiBaseUrl = apiBaseUrl; activeRuntimeKey = options.runtimeKey?.trim() || (sameOrigin(apiBaseUrl, readInjectedLocalOrigin()) ? 'local' : normalizeRuntimeUrlKey(apiBaseUrl)); }; export const switchRuntimeEndpoint = (options: { apiBaseUrl: string; clientToken?: string | null; runtimeKey?: string | null }): void => { const apiBaseUrl = options.apiBaseUrl.trim(); const previousApiBaseUrl = getRuntimeApiBaseUrl(); const previousRuntimeKey = getRuntimeKey(); const runtimeKey = options.runtimeKey?.trim() || normalizeRuntimeUrlKey(apiBaseUrl); activeApiBaseUrl = apiBaseUrl; activeRuntimeKey = runtimeKey; if (typeof window !== 'undefined') { const runtimeWindow = window as typeof window & { __OPENCHAMBER_API_BASE_URL__?: string; __OPENCHAMBER_CLIENT_TOKEN__?: string; }; runtimeWindow.__OPENCHAMBER_API_BASE_URL__ = apiBaseUrl; runtimeWindow.__OPENCHAMBER_CLIENT_TOKEN__ = options.clientToken || undefined; } configureRuntimeUrlResolver({ apiBaseUrl, realtimeBaseUrl: apiBaseUrl }); setRuntimeBearerToken(options.clientToken || null); void refreshRuntimeUrlAuthToken(apiBaseUrl).catch(() => {}); if (typeof window !== 'undefined') { window.dispatchEvent(new CustomEvent(RUNTIME_ENDPOINT_CHANGED_EVENT, { detail: { apiBaseUrl, previousApiBaseUrl, runtimeKey, previousRuntimeKey }, })); } }; export const subscribeRuntimeEndpointChanged = (callback: (detail: RuntimeEndpointChangedDetail) => void): (() => void) => { if (typeof window === 'undefined') return () => {}; const listener = (event: Event) => { callback((event as CustomEvent).detail); }; window.addEventListener(RUNTIME_ENDPOINT_CHANGED_EVENT, listener); return () => window.removeEventListener(RUNTIME_ENDPOINT_CHANGED_EVENT, listener); };