diff --git a/README.md b/README.md index c994646..529181a 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,14 @@ code lives in `src/main.js` with a class-based architecture: - **11 WAN hosts**: datavi.be, Anthropic API, OpenAI API, AWS, GCP, Azure, DigitalOcean, Cloudflare, Fastly, Akamai, GitHub -- **1 Local host**: Local Gateway (192.168.100.1), tracked separately from WAN - stats +- **Local CPE**: Cable modem at 192.168.100.1 (always monitored) +- **Local Gateway**: Auto-detected on startup by probing common default gateway + addresses (192.168.1.1, 192.168.0.1, 192.168.8.1, 10.0.0.1); first responder + wins. Note: modern browsers enforce Private Network Access restrictions that + block public-origin pages from reaching RFC1918 addresses, so local targets + only work when NetWatch is served from localhost or a private address. + +Local hosts are tracked separately from WAN stats. ### Latency measurement diff --git a/src/main.js b/src/main.js index b817cdc..1dd4a2e 100644 --- a/src/main.js +++ b/src/main.js @@ -2,6 +2,9 @@ import "./styles.css"; // --- Configuration ----------------------------------------------------------- +// Timing, axis labels, and display constants. Latency above maxLatency is +// clamped to "unreachable". The history buffer holds maxHistoryPoints +// samples (historyDuration / updateInterval). const CONFIG = Object.freeze({ updateInterval: 2000, historyDuration: 300, @@ -15,6 +18,9 @@ const CONFIG = Object.freeze({ }, }); +// WAN endpoints to monitor. These are used for the aggregate health/stats +// display (min/max/avg). Ordered: personal, LLM APIs, big-3 cloud, then +// CDN/hosting/other. const WAN_HOSTS = [ { name: "datavi.be", url: "https://datavi.be" }, { name: "Anthropic API", url: "https://api.anthropic.com" }, @@ -29,7 +35,55 @@ const WAN_HOSTS = [ { name: "GitHub", url: "https://github.com" }, ]; -const LOCAL_HOSTS = [{ name: "Local Gateway", url: "http://192.168.100.1" }]; +// The cable modem / CPE upstream of the local gateway — always monitored. +const LOCAL_CPE = { name: "Local CPE", url: "http://192.168.100.1" }; + +// Common default gateway addresses. On startup we probe each one and use +// whichever responds first as the "Local Gateway" monitor target. +// +// NOTE: Modern browsers enforce Private Network Access (PNA) restrictions +// that block pages served from public origins from making requests to +// RFC1918 addresses. These local targets will likely only work when +// NetWatch is served from localhost or another private address. +const GATEWAY_CANDIDATES = [ + "http://192.168.1.1", + "http://192.168.0.1", + "http://192.168.8.1", + "http://10.0.0.1", +]; + +// --- Gateway Detection ------------------------------------------------------- + +// Probe each gateway candidate with a short timeout. Returns the first one +// that responds, or null if none do. We race them all in parallel and take +// whichever wins. +async function detectGateway() { + try { + const result = await Promise.any( + GATEWAY_CANDIDATES.map(async (url) => { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 1500); + try { + await fetch(url, { + method: "HEAD", + mode: "no-cors", + cache: "no-store", + signal: controller.signal, + }); + clearTimeout(timeoutId); + return { name: "Local Gateway", url }; + } catch { + clearTimeout(timeoutId); + throw new Error("no response"); + } + }), + ); + return result; + } catch { + // All candidates failed + return null; + } +} // --- App State --------------------------------------------------------------- @@ -75,9 +129,9 @@ class HostState { } class AppState { - constructor() { + constructor(localHosts) { this.wan = WAN_HOSTS.map((h) => new HostState(h)); - this.local = LOCAL_HOSTS.map((h) => new HostState(h)); + this.local = localHosts.map((h) => new HostState(h)); this.paused = false; } @@ -543,8 +597,13 @@ function handleResize(state) { // --- Bootstrap --------------------------------------------------------------- -function init() { - const state = new AppState(); +async function init() { + // Probe common gateway IPs to find the local router + const gateway = await detectGateway(); + const localHosts = [LOCAL_CPE]; + if (gateway) localHosts.push(gateway); + + const state = new AppState(localHosts); buildUI(state); document