Closes #261 ## Summary - improve startup remote URL selection when the server binds to `0.0.0.0` - print additional reachable remote URLs instead of advertising only the first external address - add targeted tests for address ordering and advertisability behavior ## Problem When CodeNomad was started with `--host 0.0.0.0`, the CLI chose the first external IPv4 address it discovered and displayed only that one as the remote URL. On Windows machines with WSL, Hyper-V, Docker, or other virtual adapters, that often surfaced a virtual `172.x.x.x` address even though a more useful LAN address such as `192.168.x.x` was also reachable and usable from other devices. That made remote access look broken or confusing even though the server itself was accessible. ## What changed - reuse the resolved network-address list for both: - primary remote URL selection - startup logging of additional reachable URLs - choose the primary remote URL from the **advertisable** external addresses instead of any external address - print `Other Accessible URLs` when multiple useful remote URLs are available - avoid hard-coding a preference like `192.168 > 10 > 172` - suppress link-local `169.254.*` addresses from user-facing advertised URLs - add tests covering: - stable ordering across RFC1918 address ranges - link-local addresses being non-advertisable - link-local-first discovery not stealing the primary LAN URL ## Why this approach This keeps address derivation in the network-address resolver layer and limits `index.ts` to startup wiring and presentation. It also fixes the misleading terminal output without redesigning binding behavior, TLS behavior, or the server API contract. ## Validation - `npm run typecheck --workspace @neuralnomads/codenomad` - `npx tsx --test '.\\src\\server\\__tests__\\network-addresses.test.ts'` ## Notes - this change is intentionally focused on selection and presentation of reachable addresses - it does not attempt a broader virtual-adapter classification policy beyond suppressing clearly low-value link-local addresses in user-facing output --------- Co-authored-by: Shantur Rathore <i@shantur.com>
57 lines
1.5 KiB
TypeScript
57 lines
1.5 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
|
import { ServerMeta } from "../../api-types"
|
|
|
|
|
|
interface RouteDeps {
|
|
serverMeta: ServerMeta
|
|
}
|
|
|
|
export function registerMetaRoutes(app: FastifyInstance, deps: RouteDeps) {
|
|
app.get("/api/meta", async () => buildMetaResponse(deps.serverMeta))
|
|
}
|
|
|
|
function buildMetaResponse(meta: ServerMeta): ServerMeta {
|
|
const localPort = resolveLocalPort(meta)
|
|
const remote = resolveRemote(meta)
|
|
|
|
return {
|
|
...meta,
|
|
localPort,
|
|
remotePort: remote?.port,
|
|
listeningMode: meta.host === "0.0.0.0" || !isLoopbackHost(meta.host) ? "all" : "local",
|
|
}
|
|
}
|
|
|
|
function resolveLocalPort(meta: ServerMeta): number {
|
|
if (Number.isInteger(meta.localPort) && meta.localPort > 0) {
|
|
return meta.localPort
|
|
}
|
|
try {
|
|
const parsed = new URL(meta.localUrl)
|
|
const port = Number(parsed.port)
|
|
return Number.isInteger(port) && port > 0 ? port : 0
|
|
} catch {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
function resolveRemote(meta: ServerMeta): { protocol: "http" | "https"; port: number } | null {
|
|
if (!meta.remoteUrl) {
|
|
return null
|
|
}
|
|
try {
|
|
const parsed = new URL(meta.remoteUrl)
|
|
const protocol = parsed.protocol === "https:" ? "https" : "http"
|
|
const port = Number(parsed.port)
|
|
return { protocol, port: Number.isInteger(port) && port > 0 ? port : 0 }
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
function isLoopbackHost(host: string): boolean {
|
|
return host === "127.0.0.1" || host === "::1" || host.startsWith("127.")
|
|
}
|
|
|
|
// NetworkAddress shape is resolved in ../network-addresses
|