import { Config, HostPort, UIConfig } from './types'; import type { I18nMap } from './types'; import { defaultConfig } from './config/defaults'; /** * Merge config from WordPress localized script data and data-* attributes. */ export function readConfig(root: Element): Config { // Read from WordPress localized script data const cfgScript = (window as any).webircChatConfig || {}; // eslint-disable-line @typescript-eslint/no-explicit-any const server = (cfgScript.server as string) || root.getAttribute('data-server') || defaultConfig.server; const channel = (cfgScript.channel as string) || root.getAttribute('data-channel') || defaultConfig.channel; let subprotocols: string[] = []; const subRaw = cfgScript.subprotocols as unknown; if (Array.isArray(subRaw)) { subprotocols = subRaw as string[]; } else if (subRaw) { subprotocols = [String(subRaw)]; } // Handle debugLogs properly - wp_localize_script converts booleans to strings // Also check data-attributes for consistency with allowRaw let debugLogs = false; const debugValue = cfgScript.debugLogs; if ( debugValue === true || debugValue === '1' || debugValue === 1 || debugValue === 'true' || root.getAttribute('data-debug-logs') === '1' || root.getAttribute('data-debug-logs') === 'true' ) { debugLogs = true; } // Parse allowRaw flag – default false for security let allowRaw = false; const rawValue = cfgScript.allowRaw; if ( rawValue === true || rawValue === '1' || rawValue === 1 || rawValue === 'true' || root.getAttribute('data-allow-raw') === '1' || root.getAttribute('data-allow-raw') === 'true' ) { allowRaw = true; } // Parse UI configuration with defaults const uiConfig: UIConfig = { defaultTheme: (cfgScript.ui?.defaultTheme as 'light' | 'dark') || 'light', allowThemeSwitch: cfgScript.ui?.allowThemeSwitch !== false, showDebugLog: cfgScript.ui?.showDebugLog === true, sidebarDefaultCollapsed: cfgScript.ui?.sidebarDefaultCollapsed === true, }; // Parse user persistence configuration const userPersistence = cfgScript.userPersistence ? { isLoggedIn: cfgScript.userPersistence.isLoggedIn === true, ajaxUrl: String(cfgScript.userPersistence.ajaxUrl || ''), nonce: String(cfgScript.userPersistence.nonce || ''), savedNickname: cfgScript.userPersistence.savedNickname ? String(cfgScript.userPersistence.savedNickname) : undefined, } : undefined; return { server, channel, gatewayUrl: String(cfgScript.gatewayUrl || ''), wsPath: typeof cfgScript.wsPath === 'string' ? cfgScript.wsPath : defaultConfig.wsPath, subprotocols: subprotocols.length > 0 ? subprotocols : defaultConfig.subprotocols, debugLogs, i18n: (cfgScript.i18n ?? defaultConfig.i18n) as I18nMap, allowRaw, ui: uiConfig, userPersistence, }; } /** * Normalize host and port from server specification. */ export function normalizeHostPort(spec?: string | null): HostPort { if (!spec || typeof spec !== 'string') { return { host: 'irc.livefreeonline.club', port: 443 }; } if (/^wss?:\/\//i.test(spec)) { try { const u = new URL(spec); const host = u.hostname; let port: number; if (u.port) { port = parseInt(u.port, 10); } else { port = u.protocol === 'wss:' ? 443 : 80; } return { host, port }; } catch { return { host: 'irc.livefreeonline.club', port: 443 }; } } const parts = spec.split(':'); const host = (parts[0] || '').trim(); const port = parts[1] ? parseInt(parts[1], 10) : 443; const validHost = /^[a-z0-9.\-]+$/i.test(host) ? host : ''; return { host: validHost || 'irc.livefreeonline.club', port: Number.isFinite(port) ? port : 443, }; } /** * Generate WebSocket URL from configuration. */ export function wsUrlFromConfig(cfg: Config, hostPort: HostPort): string { if (cfg.gatewayUrl) { try { const u = new URL(cfg.gatewayUrl); u.searchParams.set('server', hostPort.host); u.searchParams.set('port', String(hostPort.port)); u.searchParams.set('secure', 'true'); return u.toString(); } catch { // ignore and fall through } } const path = String(cfg.wsPath || '').replace(/^\/+/, ''); return `wss://${hostPort.host}:${hostPort.port}${path ? '/' + path : ''}`; }