const https = require("https"); const { UA } = require("../data/constants"); const BGPROUTES_API_KEY = process.env.BGPROUTES_API_KEY || ""; const BGPROUTES_API_URL = process.env.BGPROUTES_API_URL || "https://api.bgproutes.io/v1"; function fetchJSON(url, options) { const timeoutMs = (options && options.timeout) || 8000; return new Promise((resolve) => { const reqOptions = { headers: { "User-Agent": UA, ...(options && options.headers ? options.headers : {}) }, timeout: timeoutMs, }; const timer = setTimeout(() => resolve(null), timeoutMs + 500); https .get(url, reqOptions, (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { clearTimeout(timer); if (res.statusCode === 429) { console.warn("[PDB] Rate limited (429):", url.substring(0, 80)); return resolve(null); } try { resolve(JSON.parse(data)); } catch (_e) { resolve(null); } }); }) .on("timeout", () => { clearTimeout(timer); resolve(null); }) .on("error", () => { clearTimeout(timer); resolve(null); }); }); } // Generic JSON fetch with one retry — for sources that occasionally fail under load (RIPE Stat, Atlas) async function fetchJSONWithRetry(url, options) { const result = await fetchJSON(url, options); if (result !== null) return result; await new Promise(r => setTimeout(r, 1000)); return fetchJSON(url, options); } function fetchHTML(url, options) { return new Promise((resolve) => { const reqOptions = { headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", ...(options && options.headers ? options.headers : {}), }, }; const lib = url.startsWith("https") ? require("https") : require("http"); lib .get(url, reqOptions, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { return fetchHTML(res.headers.location, options).then(resolve); } let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => resolve(data)); }) .on("error", () => resolve(null)); }); } function postJSON(url, body, options) { return new Promise((resolve) => { const data = JSON.stringify(body); const parsed = new URL(url); const timeout = (options && options.timeout) || 10000; const reqOptions = { hostname: parsed.hostname, port: parsed.port || 443, path: parsed.pathname + parsed.search, method: "POST", headers: { "User-Agent": UA, "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data), ...(options && options.headers ? options.headers : {}), }, }; let done = false; const timer = setTimeout(() => { if (!done) { done = true; req.destroy(); resolve(null); } }, timeout); const req = https.request(reqOptions, (res) => { let chunks = ""; res.on("data", (chunk) => (chunks += chunk)); res.on("end", () => { if (done) return; done = true; clearTimeout(timer); try { resolve(JSON.parse(chunks)); } catch (_e) { resolve(null); } }); }); req.on("error", () => { if (!done) { done = true; clearTimeout(timer); resolve(null); } }); req.write(data); req.end(); }); } // bgproutes.io visibility fallback helper // Queries the RIB endpoint to estimate prefix visibility across vantage points function fetchBgproutesVisibility(prefix) { if (!BGPROUTES_API_KEY) return Promise.resolve(null); const url = BGPROUTES_API_URL + "/rib?prefix=" + encodeURIComponent(prefix) + "&prefix_match=exact"; return fetchJSON(url, { timeout: 15000, headers: { "Authorization": "Bearer " + BGPROUTES_API_KEY, "User-Agent": UA, }, }).then(function(data) { if (!data || !data.data) return null; // data.data should be an array of RIB entries from different vantage points var entries = Array.isArray(data.data) ? data.data : (data.data.entries || data.data.routes || []); var vpSet = new Set(); entries.forEach(function(e) { if (e.vantage_point || e.vp || e.collector || e.peer_asn) { vpSet.add(e.vantage_point || e.vp || e.collector || e.peer_asn); } }); return { vps_seeing: vpSet.size, total_entries: entries.length, source: "bgproutes.io" }; }).catch(function() { return null; }); } module.exports = { fetchJSON, fetchJSONWithRetry, fetchHTML, postJSON, fetchBgproutesVisibility };