mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-27 01:46:35 +00:00
70 lines
2.2 KiB
TypeScript
70 lines
2.2 KiB
TypeScript
|
|
/**
|
||
|
|
* Pick the kiosk's preferred LAN IP for direct HTTP reach.
|
||
|
|
*
|
||
|
|
* Behind Docker/Angie the heartbeat source IP we see (kiosk.local_last_ip)
|
||
|
|
* is the proxy/container bridge (e.g. 172.31.0.2), not the kiosk's real LAN
|
||
|
|
* address. Kiosks report all their interfaces via the heartbeat
|
||
|
|
* (network_interfaces_json from `ip -j addr show`). Prefer the first
|
||
|
|
* non-loopback / non-link-local IP from that list; fall back to the
|
||
|
|
* heartbeat source only when we have nothing reported.
|
||
|
|
*/
|
||
|
|
import type { Kiosk } from "./types.js";
|
||
|
|
|
||
|
|
interface ReportedInterface {
|
||
|
|
name: string;
|
||
|
|
mac?: string | null;
|
||
|
|
operstate?: string | null;
|
||
|
|
ips: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
function ipWithoutCidr(ip: string): string {
|
||
|
|
return ip.includes("/") ? ip.slice(0, ip.indexOf("/")) : ip;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isUsableLanIp(ip: string): boolean {
|
||
|
|
const bare = ipWithoutCidr(ip);
|
||
|
|
return bare !== "::1"
|
||
|
|
&& !bare.startsWith("127.")
|
||
|
|
&& !bare.startsWith("169.254.")
|
||
|
|
&& !bare.startsWith("fe80:");
|
||
|
|
}
|
||
|
|
|
||
|
|
function parseInterfaces(raw: string | null): ReportedInterface[] {
|
||
|
|
if (!raw) return [];
|
||
|
|
try {
|
||
|
|
const parsed = JSON.parse(raw);
|
||
|
|
if (!Array.isArray(parsed)) return [];
|
||
|
|
return parsed
|
||
|
|
.map((item) => ({
|
||
|
|
name: typeof item?.name === "string" ? item.name : "unknown",
|
||
|
|
operstate: typeof item?.operstate === "string" ? item.operstate : null,
|
||
|
|
ips: Array.isArray(item?.ips)
|
||
|
|
? item.ips.filter((ip: unknown) => typeof ip === "string")
|
||
|
|
: [],
|
||
|
|
}))
|
||
|
|
.filter((item) => item.ips.length > 0);
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Returns a bare IP (no CIDR) suitable for `http://<ip>:<port>/...`. */
|
||
|
|
export function pickKioskLanIp(kiosk: Kiosk): string | null {
|
||
|
|
const ifaces = parseInterfaces(kiosk.network_interfaces_json);
|
||
|
|
// Prefer interfaces marked UP, then any with usable IPs.
|
||
|
|
const sorted = [...ifaces].sort((a, b) => {
|
||
|
|
const aUp = a.operstate?.toLowerCase() === "up" ? 0 : 1;
|
||
|
|
const bUp = b.operstate?.toLowerCase() === "up" ? 0 : 1;
|
||
|
|
return aUp - bUp;
|
||
|
|
});
|
||
|
|
for (const iface of sorted) {
|
||
|
|
for (const ip of iface.ips) {
|
||
|
|
if (isUsableLanIp(ip)) return ipWithoutCidr(ip);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (kiosk.local_last_ip && isUsableLanIp(kiosk.local_last_ip)) {
|
||
|
|
return ipWithoutCidr(kiosk.local_last_ip);
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|