export type AppRuntimeConfig = { title: string; userModelId: string; userDataDirectoryName: string; iconPathOverride?: string; nodecgPort: string; bundleName: string; mainDashboardRoute: string; loadingDashboardRoute: string; loadDelayMs: number; startupTimeoutMs: number; nodecgKillTimeoutMs: number; }; const MIN_TCP_PORT = 1; const MAX_TCP_PORT = 65535; export function getRuntimeConfig(): AppRuntimeConfig { // Centralized defaults keep local development and packaged builds consistent. return { title: getEnv("SCOREKO_APP_TITLE", "Scoreko"), userModelId: getEnv("SCOREKO_APP_USER_MODEL_ID", "com.scoreko.desktop"), userDataDirectoryName: getEnv("SCOREKO_APP_USER_DATA_DIRECTORY", "scoreko"), iconPathOverride: getOptionalEnv("SCOREKO_APP_ICON_PATH"), nodecgPort: parseEnvPort("NODECG_PORT", "9090"), bundleName: getEnv("NODECG_BUNDLE_NAME", "scoreko-dev"), mainDashboardRoute: getEnv("SCOREKO_DASHBOARD_ROUTE", "dashboard/scoreko-dev/main.html?standalone=true"), loadingDashboardRoute: getEnv("SCOREKO_LOADING_ROUTE", "dashboard/loading/main.html?standalone=true"), loadDelayMs: parseEnvIntInRange("ELECTRON_LOAD_DELAY_MS", 10000, 0, 600000), startupTimeoutMs: parseEnvIntInRange("NODECG_STARTUP_TIMEOUT_MS", 30000, 1000, 600000), nodecgKillTimeoutMs: parseEnvIntInRange("NODECG_KILL_TIMEOUT_MS", 2500, 0, 120000), }; } export function getOptionalEnv(name: string): string | undefined { const value = process.env[name]?.trim(); return value && value.length > 0 ? value : undefined; } export function getEnv(name: string, fallback: string): string { return getOptionalEnv(name) ?? fallback; } export function parseEnvInt(name: string, fallback: number): number { const rawValue = process.env[name]; if (!rawValue) { return fallback; } const parsedValue = Number.parseInt(rawValue, 10); return Number.isFinite(parsedValue) ? parsedValue : fallback; } export function parseEnvIntInRange(name: string, fallback: number, min: number, max: number): number { // We throw here instead of silently coercing to avoid hidden misconfiguration in production. const rawValue = process.env[name]; if (!rawValue) { return fallback; } const parsedValue = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsedValue) || parsedValue < min || parsedValue > max) { throw new Error(`The ${name} variable must be an integer between ${min} and ${max}. Received value: '${rawValue}'.`); } return parsedValue; } export function parseEnvPort(name: string, fallback: string): string { const rawValue = getEnv(name, fallback); const parsedValue = Number.parseInt(rawValue, 10); if (!Number.isFinite(parsedValue) || parsedValue < MIN_TCP_PORT || parsedValue > MAX_TCP_PORT) { throw new Error( `The ${name} variable must be a valid TCP port (${MIN_TCP_PORT}-${MAX_TCP_PORT}). Received value: '${rawValue}'.`, ); } return String(parsedValue); }