templates.js and renderer.js content extracted via direct line-slicing (not retyped) given their size (~660-line HTML/CSS template functions) -- avoids transcription risk on template-literal-heavy code. Verified via smoke-test harness (28/28 match). server.js: 4492 -> 3784 lines (6838 originally -- 45% reduction so far).
81 lines
3.1 KiB
JavaScript
81 lines
3.1 KiB
JavaScript
const { RIR_DELEGATION_URLS } = require("./data/rir-delegation-urls");
|
|
|
|
// FEATURE 4: IPv6 Adoption per RIR
|
|
|
|
// In-memory cache: { data, ts }
|
|
let ipv6StatsCache = null;
|
|
const IPV6_STATS_TTL = 24 * 60 * 60 * 1000; // 24h
|
|
|
|
// Fetch and parse one RIR delegation file.
|
|
// Returns { ipv4, ipv6, asns } count objects.
|
|
function fetchRirDelegation(rir, url) {
|
|
return new Promise((resolve) => {
|
|
const result = { rir, ipv4: 0, ipv6: 0, asns: 0, ipv4_countries: new Set(), ipv6_countries: new Set() };
|
|
const mod = url.startsWith('https') ? require('https') : require('http');
|
|
const req = mod.get(url, { headers: { 'User-Agent': 'PeerCortex/1.0 (+https://peercortex.org)' }, timeout: 20000 }, res => {
|
|
if (res.statusCode !== 200) { resolve(result); return; }
|
|
let buf = '';
|
|
res.on('data', d => { buf += d; });
|
|
res.on('end', () => {
|
|
for (const line of buf.split('\n')) {
|
|
if (line.startsWith('#') || line.startsWith(rir.toLowerCase() + '|*') || !line.includes('|')) continue;
|
|
const parts = line.split('|');
|
|
if (parts.length < 7) continue;
|
|
const type = parts[2];
|
|
const status = parts[6].trim();
|
|
const cc = parts[1];
|
|
if (status !== 'allocated' && status !== 'assigned') continue;
|
|
if (type === 'ipv4') { result.ipv4++; result.ipv4_countries.add(cc); }
|
|
else if (type === 'ipv6') { result.ipv6++; result.ipv6_countries.add(cc); }
|
|
else if (type === 'asn') { result.asns++; }
|
|
}
|
|
resolve(result);
|
|
});
|
|
});
|
|
req.on('error', () => resolve(result));
|
|
req.on('timeout', () => { req.destroy(); resolve(result); });
|
|
});
|
|
}
|
|
|
|
// Fetch all 5 RIR delegation files and aggregate IPv6 stats.
|
|
// Cached for 24 hours.
|
|
async function fetchIpv6AdoptionStats(force) {
|
|
if (!force && ipv6StatsCache && (Date.now() - ipv6StatsCache.ts) < IPV6_STATS_TTL) {
|
|
return ipv6StatsCache.data;
|
|
}
|
|
console.log('[IPv6] Fetching RIR delegation stats...');
|
|
const results = await Promise.all(
|
|
Object.entries(RIR_DELEGATION_URLS).map(([rir, url]) => fetchRirDelegation(rir, url))
|
|
);
|
|
const rirs = results.map(r => ({
|
|
rir: r.rir,
|
|
ipv4_records: r.ipv4,
|
|
ipv6_records: r.ipv6,
|
|
asn_records: r.asns,
|
|
ipv6_pct: (r.ipv4 + r.ipv6) > 0
|
|
? Math.round((r.ipv6 / (r.ipv4 + r.ipv6)) * 10000) / 100
|
|
: 0,
|
|
countries_with_ipv6: r.ipv6_countries.size,
|
|
countries_with_ipv4: r.ipv4_countries.size,
|
|
}));
|
|
const total_ipv4 = rirs.reduce((s, r) => s + r.ipv4_records, 0);
|
|
const total_ipv6 = rirs.reduce((s, r) => s + r.ipv6_records, 0);
|
|
const data = {
|
|
fetched_at: new Date().toISOString(),
|
|
global_ipv6_pct: total_ipv4 + total_ipv6 > 0
|
|
? Math.round((total_ipv6 / (total_ipv4 + total_ipv6)) * 10000) / 100
|
|
: 0,
|
|
total_ipv4_records: total_ipv4,
|
|
total_ipv6_records: total_ipv6,
|
|
by_rir: rirs,
|
|
};
|
|
ipv6StatsCache = { data, ts: Date.now() };
|
|
console.log(`[IPv6] Done. Global IPv6%: ${data.global_ipv6_pct}% across ${rirs.length} RIRs`);
|
|
return data;
|
|
}
|
|
|
|
// Pre-fetch on startup (non-blocking)
|
|
fetchIpv6AdoptionStats().catch(() => {});
|
|
|
|
module.exports = { fetchRirDelegation, fetchIpv6AdoptionStats };
|