From 96950992dfa900538928f83ccf1881cc9e18ec69 Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Mon, 30 Mar 2026 05:42:38 +0200 Subject: [PATCH] feat: add company enrichment, ASPA timeout guard, map side panel, OIM telecoms - /api/enrich: Wikipedia + website meta scraping with redirect following - ASPA /api/aspa: 18s hard timeout guard + 8s per-call limit - WHOIS: defensive null check - Map: replace popups with left side panel - Map: OIM Telecoms fiber layer (OpenInfraMap vector tiles) - Map layer toggles: fix source-exists early-return bug - Provider graph: fix text colors for light background - Network Health: defensive HTML response check --- deploy/server.js | 45 ++++--- public/index-editorial.html | 238 +++++++++++++++++++++++++++++------- server.js | 45 ++++--- 3 files changed, 256 insertions(+), 72 deletions(-) diff --git a/deploy/server.js b/deploy/server.js index d287b6f..dc9fb18 100644 --- a/deploy/server.js +++ b/deploy/server.js @@ -601,14 +601,22 @@ function lookupAspaFromRpki(asn) { -// PeeringDB authenticated fetch helper -function fetchPeeringDB(path, options) { +// PeeringDB semaphore — limits concurrent PDB requests to avoid 429 rate-limits +const pdbSemaphore = new Semaphore(5); + +// PeeringDB authenticated fetch helper (throttled via semaphore) +async function fetchPeeringDB(path, options) { const url = PEERINGDB_API_URL + path; const headers = { "User-Agent": UA }; if (PEERINGDB_API_KEY) { headers["Authorization"] = "Api-Key " + PEERINGDB_API_KEY; } - return fetchJSON(url, { ...options, headers: { ...(options && options.headers || {}), ...headers } }); + await pdbSemaphore.acquire(); + try { + return await fetchJSON(url, { ...options, headers: { ...(options && options.headers || {}), ...headers } }); + } finally { + pdbSemaphore.release(); + } } // PeeringDB fetch with exponential backoff retries (handles rate-limits under concurrent load). @@ -3507,23 +3515,32 @@ const server = http.createServer(async (req, res) => { } } - // 3. Fallback: scrape website meta description + // 3. Fallback: scrape website meta description (follows up to 3 redirects) + function fetchPage(pageUrl, hops) { + if (hops <= 0) return Promise.resolve(null); + return new Promise((resolve) => { + const mod = pageUrl.startsWith("https") ? https : http; + const req = mod.get(pageUrl, { headers: { "User-Agent": UA_SCRAPE }, timeout: 6000 }, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + res.resume(); // drain to free socket + const next = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, pageUrl).href; + return resolve(fetchPage(next, hops - 1)); + } + let data = ""; + res.on("data", (c) => { data += c; if (data.length > 40000) { req.destroy(); resolve(data); } }); + res.on("end", () => resolve(data)); + }); + req.on("error", () => resolve(null)); + req.on("timeout", () => { req.destroy(); resolve(null); }); + }); + } if (!description && website) { let wsUrl = website; if (!wsUrl.startsWith("http")) wsUrl = "https://" + wsUrl; const aboutUrl = wsUrl.replace(/\/$/, "") + "/about"; const tryUrls = [aboutUrl, wsUrl]; for (const tryUrl of tryUrls) { - const resp = await new Promise((resolve) => { - const mod = tryUrl.startsWith("https") ? https : http; - const req = mod.get(tryUrl, { headers: { "User-Agent": UA_SCRAPE }, timeout: 5000 }, (res) => { - let data = ""; - res.on("data", (c) => { data += c; if (data.length > 40000) { req.destroy(); resolve(data); } }); - res.on("end", () => resolve(data)); - }); - req.on("error", () => resolve(null)); - req.on("timeout", () => { req.destroy(); resolve(null); }); - }); + const resp = await fetchPage(tryUrl, 3); if (!resp) continue; const metaPatterns = [ /]+name=["']description["'][^>]+content=["']([^"']{20,500})["']/i, diff --git a/public/index-editorial.html b/public/index-editorial.html index ec623c4..aeba8ca 100644 --- a/public/index-editorial.html +++ b/public/index-editorial.html @@ -508,14 +508,22 @@ a:hover{color:var(--purple)} + -
+
+
+
Click point for details
+
+
+
+
IXP Datacenter/Facility Submarine Cable Global Datacenter (PeeringDB) + OIM Fiber/Telecoms
@@ -930,6 +938,7 @@ async function doLookup() { if (d.ix_presence && d.ix_presence.connections) loadPeeringRecommendations(currentAsn, d.ix_presence.connections, d); // Load ASPA and bgproutes.io data asynchronously + loadOverviewEnrichment(raw, d.network ? d.network.name : '', d.network ? d.network.website : ''); loadHealthReport(raw); loadAspaData(raw); loadAspaVerifyData(raw); @@ -1164,6 +1173,9 @@ function renderDashboard(d) { if (n.org_name) ov += '
' + escHtml(n.org_name) + '
'; if (n.notes) ov += '
' + escHtml(n.notes) + '
'; + // Enrichment placeholder — filled async by loadOverviewEnrichment() + ov += '
'; + ov += '