| 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<string>, |
| 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 { |
| |
| } |
| } |
|
|
| 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 { |
| |
| } |
| } |
|
|
| 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<os.NetworkInterfaceInfo[]>; |
| }): string[] { |
| const candidates: string[] = []; |
| const seen = new Set<string>(); |
| 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; |
| } |
|
|