import os from "node:os"; function normalizeHost(value: string | null | undefined): string { return (value ?? "").trim(); } function isLoopbackHost(host: string): boolean { const normalized = normalizeHost(host).toLowerCase(); return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1"; } function isWildcardHost(host: string): boolean { const normalized = normalizeHost(host).toLowerCase(); return normalized === "0.0.0.0" || normalized === "::"; } function formatOrigin(protocol: string, host: string, port: number): string { const normalizedHost = host.includes(":") && !host.startsWith("[") && !host.endsWith("]") ? `[${host}]` : host; return `${protocol}//${normalizedHost}:${port}`; } function pushCandidate( candidates: string[], seen: Set, rawUrl: string | null | undefined, ): void { const trimmed = rawUrl?.trim(); if (!trimmed) return; try { const normalized = new URL(trimmed).origin; if (seen.has(normalized)) return; seen.add(normalized); candidates.push(normalized); } catch { // Ignore malformed candidates. } } export function choosePrimaryRuntimeApiUrl(input: { authPublicBaseUrl?: string | null; allowedHostnames: string[]; bindHost: string; port: number; }): string { const explicitPublicBaseUrl = input.authPublicBaseUrl?.trim(); if (explicitPublicBaseUrl) { try { return new URL(explicitPublicBaseUrl).origin; } catch { // Fall through to derived candidates if config parsing drifted. } } const allowedHostname = input.allowedHostnames .map((value) => value.trim()) .find(Boolean); if (allowedHostname) { return formatOrigin("http:", allowedHostname, input.port); } const bindHost = normalizeHost(input.bindHost); if (bindHost && !isWildcardHost(bindHost)) { return formatOrigin("http:", bindHost, input.port); } return formatOrigin("http:", "localhost", input.port); } export function buildRuntimeApiCandidateUrls(input: { authPublicBaseUrl?: string | null; allowedHostnames: string[]; bindHost: string; port: number; networkInterfacesMap?: NodeJS.Dict; }): string[] { const candidates: string[] = []; const seen = new Set(); const explicitPublicBaseUrl = input.authPublicBaseUrl?.trim() ?? ""; const explicitOrigin = (() => { if (!explicitPublicBaseUrl) return null; try { return new URL(explicitPublicBaseUrl).origin; } catch { return null; } })(); const protocol = explicitOrigin ? new URL(explicitOrigin).protocol : "http:"; pushCandidate(candidates, seen, explicitOrigin); for (const rawHost of input.allowedHostnames) { const host = normalizeHost(rawHost); if (!host) continue; pushCandidate(candidates, seen, formatOrigin(protocol, host, input.port)); } const bindHost = normalizeHost(input.bindHost); if (bindHost && !isWildcardHost(bindHost)) { pushCandidate(candidates, seen, formatOrigin(protocol, bindHost, input.port)); } if (explicitOrigin) { const hostname = new URL(explicitOrigin).hostname; if (isLoopbackHost(hostname)) { pushCandidate(candidates, seen, formatOrigin(protocol, "host.docker.internal", input.port)); } } const interfaces = input.networkInterfacesMap ?? os.networkInterfaces(); for (const entries of Object.values(interfaces)) { for (const entry of entries ?? []) { if (entry.internal) continue; const host = normalizeHost(entry.address); if (!host || isLoopbackHost(host) || isWildcardHost(host)) continue; pushCandidate(candidates, seen, formatOrigin(protocol, host, input.port)); } } if (candidates.length === 0) { pushCandidate( candidates, seen, choosePrimaryRuntimeApiUrl({ authPublicBaseUrl: input.authPublicBaseUrl, allowedHostnames: input.allowedHostnames, bindHost: input.bindHost, port: input.port, }), ); } return candidates; }