import { networkInterfaces } from "node:os"; // Figure out which local IP a device should call back on. For input-push, the // device needs OUR address on ITS subnet — pick the local IPv4 interface whose // network contains the device's IP. Override with BACKEND_HOST_IP if the // auto-pick is wrong (e.g. multi-homed host). See wiki/concepts/device-input-flow.md. export function backendIpForDevice(deviceHost: string): string | null { if (process.env.BACKEND_HOST_IP) return process.env.BACKEND_HOST_IP; const ip = deviceHost.split(".").map(Number); if (ip.length !== 4 || ip.some((o) => Number.isNaN(o))) return null; for (const ifaces of Object.values(networkInterfaces())) { for (const i of ifaces ?? []) { if (i.family !== "IPv4" || i.internal) continue; const addr = i.address.split(".").map(Number); const mask = i.netmask.split(".").map(Number); if (addr.length !== 4 || mask.length !== 4) continue; const sameNet = ip.every((o, k) => (o & mask[k]!) === (addr[k]! & mask[k]!)); if (sameNet) return i.address; } } return null; } /** Backend port the device should call (the server's listen port). */ export function backendPort(): number { return Number(process.env.PORT ?? 3000); } export interface BackendIpCandidate { ip: string; iface: string; /** True if this interface's subnet contains the device IP (the likely one). */ onDeviceSubnet: boolean; } /** * List local IPv4 addresses the device could call back on, with the ones on the * device's own subnet flagged + sorted first. Lets the admin see/override the * auto-pick (important on multi-NIC hosts). BACKEND_HOST_IP, if set, is the only * candidate (the deterministic override). */ export function backendIpCandidates(deviceHost: string): BackendIpCandidate[] { if (process.env.BACKEND_HOST_IP) { return [{ ip: process.env.BACKEND_HOST_IP, iface: "BACKEND_HOST_IP", onDeviceSubnet: true }]; } const dev = deviceHost.split(".").map(Number); const validDev = dev.length === 4 && !dev.some((o) => Number.isNaN(o)); const out: BackendIpCandidate[] = []; for (const [iface, ifaces] of Object.entries(networkInterfaces())) { for (const i of ifaces ?? []) { if (i.family !== "IPv4" || i.internal) continue; const addr = i.address.split(".").map(Number); const mask = i.netmask.split(".").map(Number); const onDeviceSubnet = validDev && addr.length === 4 && mask.length === 4 && dev.every((o, k) => (o & mask[k]!) === (addr[k]! & mask[k]!)); out.push({ ip: i.address, iface, onDeviceSubnet }); } } // On-subnet candidates first. return out.sort((a, b) => Number(b.onDeviceSubnet) - Number(a.onDeviceSubnet)); }