const { getRumProxyClientUrl, isEnabled, isRumProxyEnabled } = require('@librechat/api'); const { logger } = require('@librechat/data-schemas'); const DEFAULT_RUM_SERVICE_NAME = 'librechat-web'; function parseBooleanEnv(value, defaultValue = false) { if (value == null || value === '') { return defaultValue; } return isEnabled(value); } function parseNumberEnv(value) { if (value == null || value === '') { return undefined; } const parsed = Number(value); return Number.isFinite(parsed) ? parsed : undefined; } function parseCsvEnv(value) { if (!value) { return []; } return value .split(',') .map((item) => item.trim()) .filter(Boolean); } function parseUrl(value) { try { return new URL(value); } catch { return undefined; } } function isLocalhost(url) { return url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]'; } function isSafeRumUrl(url) { if (url.username || url.password) { return false; } if (url.protocol === 'https:') { return true; } return url.protocol === 'http:' && isLocalhost(url); } function isSafeTraceTarget(target) { if (target.includes('*')) { return false; } const url = parseUrl(target); if (!url || url.protocol !== 'https:') { return false; } return true; } function getRumConfig() { if (!parseBooleanEnv(process.env.RUM_ENABLED)) { return undefined; } const provider = process.env.RUM_PROVIDER || 'hyperdx'; if (provider !== 'hyperdx') { logger.warn(`[config] Unsupported RUM provider "${provider}", disabling RUM`); return undefined; } const authMode = process.env.RUM_AUTH_MODE || 'publicToken'; if (authMode !== 'publicToken' && authMode !== 'proxy') { logger.warn(`[config] Unsupported RUM auth mode "${authMode}", disabling RUM`); return undefined; } let rumUrl; if (authMode === 'proxy') { rumUrl = getRumProxyClientUrl(); if (!isRumProxyEnabled()) { logger.warn('[config] RUM proxy mode requires RUM_PROXY_TARGET_URL, disabling RUM'); return undefined; } } else { rumUrl = process.env.RUM_URL; const parsedUrl = rumUrl ? parseUrl(rumUrl) : undefined; if (!parsedUrl || !isSafeRumUrl(parsedUrl)) { logger.warn('[config] Invalid RUM_URL, disabling RUM'); return undefined; } if (!process.env.RUM_PUBLIC_TOKEN) { logger.warn('[config] RUM publicToken mode requires RUM_PUBLIC_TOKEN, disabling RUM'); return undefined; } rumUrl = parsedUrl.href.replace(/\/$/, ''); } const rawTracePropagationTargets = parseCsvEnv(process.env.RUM_TRACE_PROPAGATION_TARGETS); const tracePropagationTargets = rawTracePropagationTargets.filter(isSafeTraceTarget); if (rawTracePropagationTargets.length !== tracePropagationTargets.length) { logger.info('[config] Ignored unsafe RUM trace propagation targets'); } const configuredSampleRate = parseNumberEnv(process.env.RUM_SAMPLE_RATE); const sampleRate = configuredSampleRate != null && configuredSampleRate >= 0 && configuredSampleRate <= 1 ? configuredSampleRate : undefined; const consoleCapture = parseBooleanEnv(process.env.RUM_CONSOLE_CAPTURE); const advancedNetworkCapture = parseBooleanEnv(process.env.RUM_ADVANCED_NETWORK_CAPTURE); if (consoleCapture) { logger.warn('[config] RUM console capture is enabled and may collect sensitive browser logs'); } if (advancedNetworkCapture) { logger.warn('[config] RUM advanced network capture is enabled and may collect payload data'); } return { provider: 'hyperdx', enabled: true, url: rumUrl, serviceName: process.env.RUM_SERVICE_NAME || DEFAULT_RUM_SERVICE_NAME, authMode, ...(authMode === 'publicToken' ? { publicToken: process.env.RUM_PUBLIC_TOKEN } : {}), ...(tracePropagationTargets.length > 0 ? { tracePropagationTargets } : {}), consoleCapture, disableReplay: parseBooleanEnv(process.env.RUM_DISABLE_REPLAY, true), advancedNetworkCapture, ...(sampleRate != null ? { sampleRate } : {}), ...(process.env.RUM_ENVIRONMENT ? { environment: process.env.RUM_ENVIRONMENT } : {}), }; } module.exports = { getRumConfig };