Spaces:
Runtime error
Runtime error
| function isLocalHost(host: string): boolean { | |
| const normalized = host.toLowerCase() | |
| return ( | |
| normalized === 'localhost' || | |
| normalized === '127.0.0.1' || | |
| normalized === '::1' || | |
| normalized.endsWith('.local') | |
| ) | |
| } | |
| function normalizeProtocol(protocol: string): 'ws:' | 'wss:' { | |
| if (protocol === 'https:' || protocol === 'wss:') return 'wss:' | |
| return 'ws:' | |
| } | |
| function preserveTokenQuery(parsed: URL): void { | |
| const token = parsed.searchParams.get('token') | |
| parsed.search = '' | |
| if (token) { | |
| parsed.searchParams.set('token', token) | |
| } | |
| } | |
| function normalizeGatewayPath(pathname: string): string { | |
| const path = String(pathname || '/').trim() || '/' | |
| if ( | |
| path === '/sessions' || | |
| path === '/sessions/' || | |
| path.startsWith('/sessions/') | |
| ) { | |
| return '/' | |
| } | |
| return path === '/' ? '/' : path.replace(/\/+$/, '') | |
| } | |
| function formatWebSocketUrl(parsed: URL): string { | |
| return parsed.toString().replace(/\/$/, '').replace('/?', '?') | |
| } | |
| export function buildGatewayWebSocketUrl(input: { | |
| host: string | |
| port: number | |
| browserProtocol?: string | |
| }): string { | |
| const rawHost = String(input.host || '').trim() | |
| const port = Number(input.port) | |
| const browserProtocol = input.browserProtocol === 'https:' ? 'https:' : 'http:' | |
| if (!rawHost) { | |
| // Default host is localhost — always use plain ws:// since local gateway has no TLS. | |
| return `ws://127.0.0.1:${port || 18789}` | |
| } | |
| const prefixed = | |
| rawHost.startsWith('ws://') || | |
| rawHost.startsWith('wss://') || | |
| rawHost.startsWith('http://') || | |
| rawHost.startsWith('https://') | |
| ? rawHost | |
| : null | |
| if (prefixed) { | |
| try { | |
| const parsed = new URL(prefixed) | |
| // Local hosts always use plain ws:// — no TLS on local gateway. | |
| parsed.protocol = isLocalHost(parsed.hostname) ? 'ws:' : normalizeProtocol(parsed.protocol) | |
| // Keep explicit proxy paths (e.g. /gw), but collapse known dashboard/session routes to root. | |
| parsed.pathname = normalizeGatewayPath(parsed.pathname) | |
| preserveTokenQuery(parsed) | |
| parsed.hash = '' | |
| return formatWebSocketUrl(parsed) | |
| } catch { | |
| return prefixed | |
| } | |
| } | |
| // Local gateway hosts always use plain ws:// — they don't speak TLS, | |
| // and browsers allow ws://localhost from HTTPS pages (mixed-content exception). | |
| const wsProtocol = isLocalHost(rawHost) ? 'ws' : (browserProtocol === 'https:' ? 'wss' : 'ws') | |
| const shouldOmitPort = | |
| wsProtocol === 'wss' && | |
| !isLocalHost(rawHost) && | |
| port === 18789 | |
| return shouldOmitPort | |
| ? `${wsProtocol}://${rawHost}` | |
| : `${wsProtocol}://${rawHost}:${port || 18789}` | |
| } | |