diff --git a/server.js b/server.js index c6f9c55..4d8a4e0 100644 --- a/server.js +++ b/server.js @@ -514,7 +514,7 @@ const server = http.createServer(async (req, res) => { var probeAsns = new Set(atlasState.cache.asns_with_probes || []); var enriched = pdbData.data.map(function(n) { - var org = pdbOrgCountryMap.get(n.org_id) || {}; + var org = pdbOrgState.map.get(n.org_id) || {}; var cc = org.country || ""; return { asn: n.asn, @@ -533,7 +533,7 @@ const server = http.createServer(async (req, res) => { with_probes: enriched.filter(function(n) { return n.has_probe; }).length, without_probes: enriched.filter(function(n) { return !n.has_probe; }).length, atlas_unique_asns: probeAsns.size, - org_countries_loaded: pdbOrgCountryMap.size, + org_countries_loaded: pdbOrgState.map.size, fetched_at: new Date().toISOString(), }); @@ -3605,77 +3605,7 @@ ${html} const { atlasState, fetchAllAtlasProbes } = require("./server/atlas-probes"); -// ============================================================ -// PeeringDB Org → Country Cache (for Lia's Paradise) -// ============================================================ -let pdbOrgCountryMap = new Map(); // org_id → { country, name } - -function fetchPdbOrgCountries() { - var cacheFile = require("path").join(__dirname, ".pdb-org-cache.json"); - var fs = require("fs"); - - // Try disk cache first (valid for 24h) - try { - var stat = fs.statSync(cacheFile); - var ageHours = (Date.now() - stat.mtimeMs) / 3600000; - if (ageHours < 24) { - var cached = JSON.parse(fs.readFileSync(cacheFile, "utf8")); - pdbOrgCountryMap = new Map(Object.entries(cached)); - console.log("[PDB-ORG] Loaded " + pdbOrgCountryMap.size + " orgs from disk cache (" + Math.round(ageHours) + "h old)"); - return Promise.resolve(); - } - } catch (_) { /* no cache or invalid */ } - - console.log("[PDB-ORG] Fetching PeeringDB org countries (fresh)..."); - return new Promise(function(resolve) { - var chunks = []; - var req = require("https").get("https://www.peeringdb.com/api/org?status=ok&depth=0", { - headers: { - "User-Agent": UA, - "Authorization": PEERINGDB_API_KEY ? "Api-Key " + PEERINGDB_API_KEY : undefined, - }, - timeout: 120000, - }, function(res) { - if (res.statusCode !== 200) { - console.error("[PDB-ORG] HTTP " + res.statusCode + " — using stale cache or empty"); - resolve(); - return; - } - res.on("data", function(chunk) { chunks.push(chunk); }); - res.on("end", function() { - try { - var body = Buffer.concat(chunks).toString("utf8"); - var data = JSON.parse(body); - if (data && data.data) { - pdbOrgCountryMap = new Map(); - var cacheObj = {}; - data.data.forEach(function(o) { - if (o.id && o.country) { - pdbOrgCountryMap.set(o.id, { country: o.country, name: o.name || "" }); - cacheObj[o.id] = { country: o.country, name: o.name || "" }; - } - }); - // Save to disk cache - try { fs.writeFileSync(cacheFile, JSON.stringify(cacheObj)); } catch (_) {} - console.log("[PDB-ORG] Loaded " + pdbOrgCountryMap.size + " org→country mappings (cached to disk)"); - } - } catch (e) { - console.error("[PDB-ORG] Parse error:", e.message); - } - resolve(); - }); - }); - req.on("error", function(e) { - console.error("[PDB-ORG] Fetch error:", e.message); - resolve(); - }); - req.on("timeout", function() { - console.error("[PDB-ORG] Timeout after 120s"); - req.destroy(); - resolve(); - }); - }); -} +const { pdbOrgState, fetchPdbOrgCountries } = require("./server/pdb-org-countries"); const PORT = process.env.PORT || 3101; diff --git a/server/pdb-org-countries.js b/server/pdb-org-countries.js new file mode 100644 index 0000000..8e6e8c5 --- /dev/null +++ b/server/pdb-org-countries.js @@ -0,0 +1,92 @@ +const fs = require("fs"); +const https = require("https"); +const path = require("path"); +const { UA } = require("./data/constants"); +const { PEERINGDB_API_KEY } = require("./services/peeringdb"); + +// Repo root -- NOT this module's own directory. See hijack-monitoring/store.js +// for the same __dirname-fallback fix applied for the same reason. +const REPO_ROOT = path.join(__dirname, ".."); + +// PeeringDB Org → Country Cache (for Lia's Paradise). Exposed via a mutable +// state object (not a bare reassigned export) for the same reason as +// atlas-probes.js's atlasState -- the Map itself is reassigned wholesale on +// refresh, not just mutated in place. +const pdbOrgState = { map: new Map() }; // org_id → { country, name } + +function fetchPdbOrgCountries() { + var cacheFile = path.join(REPO_ROOT, ".pdb-org-cache.json"); + + // Try disk cache first (valid for 24h) + try { + var stat = fs.statSync(cacheFile); + var ageHours = (Date.now() - stat.mtimeMs) / 3600000; + if (ageHours < 24) { + var cached = JSON.parse(fs.readFileSync(cacheFile, "utf8")); + pdbOrgState.map = new Map(Object.entries(cached)); + console.log("[PDB-ORG] Loaded " + pdbOrgState.map.size + " orgs from disk cache (" + Math.round(ageHours) + "h old)"); + return Promise.resolve(); + } + } catch (_) { /* no cache or invalid */ } + + console.log("[PDB-ORG] Fetching PeeringDB org countries (fresh)..."); + return new Promise(function(resolve) { + var chunks = []; + var req = https.get("https://www.peeringdb.com/api/org?status=ok&depth=0", { + headers: { + "User-Agent": UA, + // Bug fix 2026-07-16: a bare `PEERINGDB_API_KEY ? ... : undefined` + // sets the Authorization header value to `undefined` when no key is + // configured. Node 22's stricter header validation throws + // synchronously on that (ERR_HTTP_INVALID_HEADER_VALUE) instead of + // omitting the header, crashing the whole process at startup via + // this function's unhandled rejection (found empirically while + // trying to boot server.js locally without a real PeeringDB key). + // Conditionally spread the key in instead so the header key itself + // is simply absent when unconfigured. + ...(PEERINGDB_API_KEY ? { "Authorization": "Api-Key " + PEERINGDB_API_KEY } : {}), + }, + timeout: 120000, + }, function(res) { + if (res.statusCode !== 200) { + console.error("[PDB-ORG] HTTP " + res.statusCode + " — using stale cache or empty"); + resolve(); + return; + } + res.on("data", function(chunk) { chunks.push(chunk); }); + res.on("end", function() { + try { + var body = Buffer.concat(chunks).toString("utf8"); + var data = JSON.parse(body); + if (data && data.data) { + pdbOrgState.map = new Map(); + var cacheObj = {}; + data.data.forEach(function(o) { + if (o.id && o.country) { + pdbOrgState.map.set(o.id, { country: o.country, name: o.name || "" }); + cacheObj[o.id] = { country: o.country, name: o.name || "" }; + } + }); + // Save to disk cache + try { fs.writeFileSync(cacheFile, JSON.stringify(cacheObj)); } catch (_) {} + console.log("[PDB-ORG] Loaded " + pdbOrgState.map.size + " org→country mappings (cached to disk)"); + } + } catch (e) { + console.error("[PDB-ORG] Parse error:", e.message); + } + resolve(); + }); + }); + req.on("error", function(e) { + console.error("[PDB-ORG] Fetch error:", e.message); + resolve(); + }); + req.on("timeout", function() { + console.error("[PDB-ORG] Timeout after 120s"); + req.destroy(); + resolve(); + }); + }); +} + +module.exports = { pdbOrgState, fetchPdbOrgCountries };