| import type { GatewayBonjourBeacon } from "../../infra/bonjour-discovery.js"; |
| import { colorize, theme } from "../../terminal/theme.js"; |
| import { parseTimeoutMsWithFallback } from "../parse-timeout.js"; |
|
|
| export type GatewayDiscoverOpts = { |
| timeout?: string; |
| json?: boolean; |
| }; |
|
|
| export function parseDiscoverTimeoutMs(raw: unknown, fallbackMs: number): number { |
| return parseTimeoutMsWithFallback(raw, fallbackMs, { invalidType: "error" }); |
| } |
|
|
| export function pickBeaconHost(beacon: GatewayBonjourBeacon): string | null { |
| |
| |
| const host = beacon.host || beacon.tailnetDns || beacon.lanHost; |
| return host?.trim() ? host.trim() : null; |
| } |
|
|
| export function pickGatewayPort(beacon: GatewayBonjourBeacon): number { |
| |
| const port = beacon.port ?? beacon.gatewayPort ?? 18789; |
| return port > 0 ? port : 18789; |
| } |
|
|
| export function dedupeBeacons(beacons: GatewayBonjourBeacon[]): GatewayBonjourBeacon[] { |
| const out: GatewayBonjourBeacon[] = []; |
| const seen = new Set<string>(); |
| for (const b of beacons) { |
| const host = pickBeaconHost(b) ?? ""; |
| const key = [ |
| b.domain ?? "", |
| b.instanceName ?? "", |
| b.displayName ?? "", |
| host, |
| String(b.port ?? ""), |
| String(b.gatewayPort ?? ""), |
| ].join("|"); |
| if (seen.has(key)) { |
| continue; |
| } |
| seen.add(key); |
| out.push(b); |
| } |
| return out; |
| } |
|
|
| export function renderBeaconLines(beacon: GatewayBonjourBeacon, rich: boolean): string[] { |
| const nameRaw = (beacon.displayName || beacon.instanceName || "Gateway").trim(); |
| const domainRaw = (beacon.domain || "local.").trim(); |
|
|
| const title = colorize(rich, theme.accentBright, nameRaw); |
| const domain = colorize(rich, theme.muted, domainRaw); |
|
|
| const host = pickBeaconHost(beacon); |
| const gatewayPort = pickGatewayPort(beacon); |
| const scheme = beacon.gatewayTls ? "wss" : "ws"; |
| const wsUrl = host ? `${scheme}://${host}:${gatewayPort}` : null; |
|
|
| const lines = [`- ${title} ${domain}`]; |
|
|
| if (beacon.tailnetDns) { |
| lines.push(` ${colorize(rich, theme.info, "tailnet")}: ${beacon.tailnetDns}`); |
| } |
| if (beacon.lanHost) { |
| lines.push(` ${colorize(rich, theme.info, "lan")}: ${beacon.lanHost}`); |
| } |
| if (beacon.host) { |
| lines.push(` ${colorize(rich, theme.info, "host")}: ${beacon.host}`); |
| } |
|
|
| if (wsUrl) { |
| lines.push(` ${colorize(rich, theme.muted, "ws")}: ${colorize(rich, theme.command, wsUrl)}`); |
| } |
| if (beacon.role) { |
| lines.push(` ${colorize(rich, theme.muted, "role")}: ${beacon.role}`); |
| } |
| if (beacon.transport) { |
| lines.push(` ${colorize(rich, theme.muted, "transport")}: ${beacon.transport}`); |
| } |
| if (beacon.gatewayTls) { |
| const fingerprint = beacon.gatewayTlsFingerprintSha256 |
| ? `sha256 ${beacon.gatewayTlsFingerprintSha256}` |
| : "enabled"; |
| lines.push(` ${colorize(rich, theme.muted, "tls")}: ${fingerprint}`); |
| } |
| if (typeof beacon.sshPort === "number" && beacon.sshPort > 0 && host) { |
| const ssh = `ssh -N -L 18789:127.0.0.1:18789 <user>@${host} -p ${beacon.sshPort}`; |
| lines.push(` ${colorize(rich, theme.muted, "ssh")}: ${colorize(rich, theme.command, ssh)}`); |
| } |
| return lines; |
| } |
|
|