Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c6b5c94
Feat(browser-info): expand reveal surface + drop detect-gpu
jason5ng32 May 13, 2026
5712b77
Improvements
jason5ng32 May 13, 2026
64d5d29
Fix(cf-radar): tolerate sparse Cloudflare Radar responses
jason5ng32 May 13, 2026
bb0465a
Feat(ip-info): ASN upstream connectivity graph from CAIDA data
jason5ng32 May 13, 2026
d4da045
Docs(asn): document CAIDA_AUTO_UPDATE; credit RIPE NCC + CAIDA
jason5ng32 May 13, 2026
aed481c
Docs(readme): add ASN history & upstream topology feature entry
jason5ng32 May 13, 2026
7316201
UX Improvement
jason5ng32 May 13, 2026
64a304c
UI Improvement
jason5ng32 May 13, 2026
739ff6f
Feat(asn-connectivity): cleaner routing + interactive drawer
jason5ng32 May 14, 2026
fe7409e
Refactor(asn-history): show RIS visibility % instead of raw peer count
jason5ng32 May 14, 2026
858fae0
Fix(asn-history): align timeouts so slow RIPEstat calls don't AbortError
jason5ng32 May 14, 2026
f0b800a
Improvements
jason5ng32 May 14, 2026
0a24ff6
Improvements
jason5ng32 May 14, 2026
582a888
Improvements
jason5ng32 May 14, 2026
2899030
Improvements
jason5ng32 May 14, 2026
690bfa8
Change defaults
jason5ng32 May 15, 2026
4cd3225
Improvements
jason5ng32 May 15, 2026
1ca0d08
Improvements
jason5ng32 May 15, 2026
b596f73
Improvements
jason5ng32 May 15, 2026
6f2a0b8
Improvements
jason5ng32 May 15, 2026
6a096fa
Improvements
jason5ng32 May 15, 2026
274baa6
Improvements
jason5ng32 May 15, 2026
4941163
Improvements
jason5ng32 May 15, 2026
1d623da
Refactor(ip-detail): clearer proxy / nativeness labels
jason5ng32 May 16, 2026
c7bed7d
Reuse Copy Button
jason5ng32 May 16, 2026
716cfb4
Reuse Copy Button
jason5ng32 May 16, 2026
5724f6c
Added SDP Log to WebRTC Test tool
jason5ng32 May 16, 2026
dcce35f
Improvements
jason5ng32 May 16, 2026
fe01a7a
Improvements
jason5ng32 May 16, 2026
bf249d6
Improvements
jason5ng32 May 16, 2026
853d5e0
Improvements
jason5ng32 May 16, 2026
9abac86
Improvements
jason5ng32 May 16, 2026
2a33a47
Improvements
jason5ng32 May 16, 2026
5813eae
Improvements
jason5ng32 May 16, 2026
596fa91
Improvements
jason5ng32 May 17, 2026
405b1ef
Improvements
jason5ng32 May 18, 2026
56950f4
Merge pull request #333 from jason5ng32/dev
jason5ng32 May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ RIPESTAT_SOURCE_APP=""
SECURITY_BLACKLIST_LOG_FILE_PATH=""
SECURITY_RATE_LIMIT=""
SECURITY_DELAY_AFTER=""
# SITE
VITE_SITE_URL="https://ipcheck.ing"
# CURL API
VITE_CURL_IPV4_DOMAIN=""
VITE_CURL_IPV6_DOMAIN=""
Expand All @@ -27,6 +29,8 @@ VITE_GOOGLE_ANALYTICS_ID=""
MAXMIND_ACCOUNT_ID=""
MAXMIND_LICENSE_KEY=""
MAXMIND_AUTO_UPDATE="false"
# CAIDA
CAIDA_AUTO_UPDATE="false"
# Logging — LOG_LEVEL: debug/info/warn/error (default info)
# LOG_FORMAT: "json" for log shippers, anything else = pretty
# LOG_HTTP: "true" to enable per-request /api logging (off by default)
Expand Down
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,31 @@ common/maxmind-db/.maxmind-update.lock
common/maxmind-db/*.bak
common/maxmind-db/*.next
common/maxmind-db/*.mmdb

# CAIDA datasets — Non-Commercial license, don't commit to public repo.
common/as-org-db/*.txt
common/as-rel-db/*.txt
common/as-org-db/.caida-update-state.json
common/as-rel-db/.caida-update-state.json
common/as-org-db/.caida-update.lock
common/as-rel-db/.caida-update.lock
common/as-org-db/*.bak
common/as-rel-db/*.bak
common/as-org-db/*.next
common/as-rel-db/*.next
.learnings/
docs/

# Local Scripts
scripts/

# Site configuration
public/sitemap.xml
public/robots.txt
public/llms.txt
public/llms-full.txt
public/tools/
public/robots.txt

# Private AI context (per-machine, not for the public repo)
local-context.md
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Feel free to bookmark the demo or deploy your own.
* 🛜 **View Your IPs**: Detects and displays your local IPs, sourcing from multiple IPv4 and IPv6 providers.
* 🔍 **Search IP Information**: Provides a tool for querying information about any IP address.
* 🕵️ **IP Information**: Presents detailed information for all IP addresses, including country, region, ASN, geographic location, and more.
* 🛰️ **ASN History & Upstream Topology**: View historical AS announcements for an IP prefix, and visualize the upstream paths from an ASN to the Tier 1 backbone networks.
* 🚦 **Availability Check**: Tests the accessibility of various websites, such as Google, GitHub, YouTube, ChatGPT, and others.
* 🚥 **WebRTC Detection**: Identifies the IP address used during WebRTC connections.
* 🛑 **DNS Leak Test**: Shows DNS endpoint data to evaluate the risk of DNS leaks when using VPNs or proxies.
Expand All @@ -50,7 +51,6 @@ Feel free to bookmark the demo or deploy your own.
## 💪 Also

* 🌗 **Dark Mode**: Automatically toggles between dark and daylight modes based on system settings, with an option for manual switching.
* 📱 **Minimalist Mode**: A mobile-optimized mode that shortens page length for quick access to essential information..
* 📲 **PWA Supported**:Can be added as a desktop app on your phone as well as a Chrome app on your computer.
* ⌨️ **Keyboard Shortcuts**: Supports keyboard shortcuts for all functions, press `?` to view the shortcut list.
* 🌍 Based on availability test results, it indicates whether global internet access is currently feasible.
Expand Down Expand Up @@ -125,6 +125,7 @@ Download `GeoLite2-City.mmdb` and `GeoLite2-ASN.mmdb` from your MaxMind account
| `MAXMIND_ACCOUNT_ID` | **Yes** | `""` | MaxMind account ID, paired with `MAXMIND_LICENSE_KEY` to download GeoLite2 databases. See the MaxMind section above. |
| `MAXMIND_LICENSE_KEY` | **Yes** | `""` | MaxMind license key, paired with `MAXMIND_ACCOUNT_ID`. See the MaxMind section above. |
| `MAXMIND_AUTO_UPDATE` | **Yes** | `"false"` | Set to `"true"` to auto-download GeoLite2 databases ~60s after startup and refresh every 24h. **Required for Docker.** Can stay `"false"` only if you've pre-seeded the `.mmdb` files manually. |
| `CAIDA_AUTO_UPDATE` | No | `"false"` | Set to `"true"` to refresh the CAIDA datasets daily (as2org for ASN org-name lookup, as-rel2 for the ASN connectivity graph). When `"false"`, missing snapshots are still downloaded at startup but never refreshed afterwards. |
| `VITE_GOOGLE_ANALYTICS_ID` | **Yes** | `""` | Google Analytics ID, used to track user behavior |
| `BACKEND_PORT` | No | `"11966"` | The running port of the backend part of the program |
| `FRONTEND_PORT` | No | `"18966"` | The running port of the frontend part of the program |
Expand Down
3 changes: 2 additions & 1 deletion README_FR.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Notes: Vous pouvez utiliser ma démo gratuitement et vous pouvez également la d
* 🛜 **Afficher vos adresses IP** : Détecte et affiche votre adresse IP locale, provenant de plusieurs fournisseurs IPv4 et IPv6.
* 🔍 **Recherche d'informations sur l'adresse IP** : Fournit un outil pour interroger des informations sur n'importe quelle adresse IP.
* 🕵️ **Informations sur l'adresse IP** : Présente des informations détaillées pour toutes les adresses IP, y compris le pays, la région, l'ASN, la localisation géographique, et plus encore.
* 🛰️ **Historique ASN et topologie amont** : Consultez l'historique des annonces AS pour un préfixe IP, et visualisez les chemins amont d'un ASN vers les réseaux dorsaux Tier 1.
* 🚦 **Vérification de disponibilité** : Teste l'accessibilité de différents sites web, tels que Google, GitHub, YouTube, ChatGPT, et d'autres.
* 🚥 **Détection WebRTC** : Identifie l'adresse IP utilisée lors des connexions WebRTC.
* 🛑 **Test de fuite DNS** : Affiche les données de point de terminaison DNS pour évaluer le risque de fuites DNS lors de l'utilisation de VPN ou de proxies.
Expand All @@ -50,7 +51,6 @@ Notes: Vous pouvez utiliser ma démo gratuitement et vous pouvez également la d
## 💪Également

* 🌗 **Mode sombre** : Bascule automatiquement entre les modes sombre et clair en fonction des paramètres du système, avec une option de basculement manuel.
* 📱 **Mode minimaliste** : Un mode optimisé pour les mobiles qui réduit la longueur de la page pour un accès rapide aux informations essentielles.
* 📲 **Prise en charge de PWA** : Peut être ajouté en tant qu'application de bureau sur votre téléphone ainsi qu'en tant qu'application Chrome sur votre ordinateur.
* ⌨️ **Raccourcis clavier** : Prend en charge les raccourcis clavier pour toutes les fonctions, appuyez sur `?` pour afficher la liste des raccourcis.
* 🌍 Basé sur les résultats des tests de disponibilité, il indique si l'accès Internet mondial est actuellement réalisable.
Expand Down Expand Up @@ -125,6 +125,7 @@ Téléchargez `GeoLite2-City.mmdb` et `GeoLite2-ASN.mmdb` depuis votre compte Ma
| `MAXMIND_ACCOUNT_ID` | **Oui** | `""` | ID de compte MaxMind, associé à `MAXMIND_LICENSE_KEY` pour télécharger les bases GeoLite2. Voir la section MaxMind ci-dessus. |
| `MAXMIND_LICENSE_KEY` | **Oui** | `""` | Clé de licence MaxMind, associée à `MAXMIND_ACCOUNT_ID`. Voir la section MaxMind ci-dessus. |
| `MAXMIND_AUTO_UPDATE` | **Oui** | `"false"` | Définissez sur `"true"` pour télécharger automatiquement les bases GeoLite2 environ 60 s après le démarrage et les rafraîchir toutes les 24 h. **Obligatoire pour Docker.** Peut rester à `"false"` uniquement si vous avez pré-déposé les fichiers `.mmdb` manuellement. |
| `CAIDA_AUTO_UPDATE` | Non | `"false"` | Définissez sur `"true"` pour rafraîchir quotidiennement les jeux de données CAIDA (as2org pour la résolution du nom d'organisation par ASN, as-rel2 pour le graphe de connectivité ASN). Lorsque `"false"`, les snapshots manquants sont quand même téléchargés au démarrage mais ne sont jamais rafraîchis ensuite. |
| `VITE_GOOGLE_ANALYTICS_ID` | **Oui** | `""` | Identifiant Google Analytics, utilisé pour l'analyse des utilisateurs |
| `BACKEND_PORT` | Non | `"11966"` | Le port d'exécution de la partie backend du programme |
| `FRONTEND_PORT` | Non | `"18966"` | Le port d'exécution de la partie frontend du programme |
Expand Down
3 changes: 2 additions & 1 deletion README_TR.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Demo'yu yer imlerine ekleyebilir veya kendi kurulumunuzu yapabilirsiniz.
* 🛜 **IP'lerinizi Görüntüleyin**: Yerel IP'lerinizi tespit eder ve birden fazla IPv4/IPv6 sağlayıcısından alır.
* 🔍 **IP Bilgisi Arama**: Herhangi bir IP adresi hakkında sorgu yapma aracı sağlar.
* 🕵️ **IP Bilgileri**: Ülke, bölge, ASN, coğrafi konum ve daha fazlasını içeren ayrıntılı IP bilgileri sunar.
* 🛰️ **ASN Geçmişi ve Üst Topoloji**: Bir IP önekinin geçmiş AS duyurularını ve ASN'den Tier 1 omurga ağlarına giden üst bağlantı yollarını görüntüleyin.
* 🚦 **Erişilebilirlik Kontrolü**: Google, GitHub, YouTube, ChatGPT ve diğerleri gibi sitelerin erişilebilirliğini test eder.
* 🚥 **WebRTC Tespiti**: WebRTC bağlantısında kullanılan IP adresini belirler.
* 🛑 **DNS Leak Testi**: VPN veya proxy kullanırken DNS sızıntısı riskini değerlendirmek için DNS uç nokta verilerini gösterir.
Expand All @@ -50,7 +51,6 @@ Demo'yu yer imlerine ekleyebilir veya kendi kurulumunuzu yapabilirsiniz.
## 💪 Ayrıca

* 🌗 **Karanlık Mod**: Sistem ayarlarına göre otomatik olarak gündüz/karanlık mod arasında geçiş yapar; manuel geçiş seçeneği de vardır.
* 📱 **Minimal Mod**: Mobil için optimize edilmiş, sayfa uzunluğunu kısaltan hızlı erişim modu.
* 📲 **PWA Desteği**:Telefonunuza masaüstü uygulaması olarak veya bilgisayarınızda Chrome uygulaması olarak eklenebilir.
* ⌨️ **Klavye Kısayolları**: Tüm işlevler için kısayolları destekler; kısayol listesini görmek için `?` tuşuna basın.
* 🌍 Erişilebilirlik test sonuçlarına göre küresel internet erişiminin şu an mümkün olup olmadığını gösterir.
Expand Down Expand Up @@ -125,6 +125,7 @@ MaxMind hesabınızdan `GeoLite2-City.mmdb` ve `GeoLite2-ASN.mmdb` dosyalarını
| `MAXMIND_ACCOUNT_ID` | **Evet** | `""` | MaxMind hesap ID'si, GeoLite2 veritabanlarını indirmek için `MAXMIND_LICENSE_KEY` ile birlikte kullanılır. Yukarıdaki MaxMind bölümüne bakın. |
| `MAXMIND_LICENSE_KEY` | **Evet** | `""` | MaxMind lisans anahtarı, `MAXMIND_ACCOUNT_ID` ile birlikte kullanılır. Yukarıdaki MaxMind bölümüne bakın. |
| `MAXMIND_AUTO_UPDATE` | **Evet** | `"false"` | `"true"` yapıldığında GeoLite2 veritabanları başlatmadan yaklaşık 60 saniye sonra otomatik olarak indirilir ve her 24 saatte bir yenilenir. **Docker için zorunlu.** Yalnızca `.mmdb` dosyalarını manuel olarak yerleştirdiyseniz `"false"` olarak kalabilir. |
| `CAIDA_AUTO_UPDATE` | Hayır | `"false"` | `"true"` yapıldığında CAIDA veri setleri günlük olarak yenilenir (as2org ASN org adı için, as-rel2 ASN bağlantı haritası için). `"false"` olduğunda, eksik anlık görüntüler başlatmada yine de indirilir ancak sonradan yenilenmez. |
| `VITE_GOOGLE_ANALYTICS_ID` | **Evet** | `""` | Google Analytics ID, kullanıcı davranışını izlemek için |
| `BACKEND_PORT` | Hayır | `"11966"` | Backend kısmının çalıştığı port |
| `FRONTEND_PORT` | Hayır | `"18966"` | Frontend kısmının çalıştığı port |
Expand Down
3 changes: 2 additions & 1 deletion README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* 🛜 **看自己的 IP**:从多个 IPv4 和 IPv6 来源检测显示本机的 IP
* 🔍 **查任意 IP 信息**:可以通过小工具查询任意 IP 的信息
* 🕵️ **看 IP 信息**:显示所有 IP 的相关信息,包括国家、地区、ASN、地理位置等
* 🛰️ **ASN 历史与上游拓扑**:查看 IP 前缀的历史 AS 宣告记录,以及该 ASN 到 Tier 1 骨干网的上游路径图
* 🚦 **可用性检测**:检测一些网站的可用性:Google, Github, Youtube, 网易, 百度等
* 🚥 **WebRTC 检测**:查看使用 WebRTC 连接时使用的 IP
* 🛑 **DNS 泄露检测**:查看 DNS 出口信息,以便查看在 VPN/代理的情况下,是否存在 DNS 泄露隐私的风险
Expand All @@ -50,7 +51,6 @@
## 💪 同时还支持

* 🌗 **暗黑模式**:根据系统设置自动切换暗黑/白天模式,也可以手动切换
* 📱 **简约模式**:为移动版提供的专门模式,缩短页面长度,快速查看最重要的信息
* 📲 **支持 PWA**:可以添加为手机应用以及电脑里的桌面应用,方便使用
* ⌨️ **支持快捷键**:可以随时输入 `?` 查看快捷键菜单
* 🌍 根据可用性检测结果,返回目前是否可以访问全世界网络的提示
Expand Down Expand Up @@ -125,6 +125,7 @@ MyIP 依赖 MaxMind 提供的免费 **GeoLite2** 数据库(City + ASN)来进
| `MAXMIND_ACCOUNT_ID` | **是** | `""` | MaxMind 账号 ID,和 `MAXMIND_LICENSE_KEY` 一起用于下载 GeoLite2 数据库。详见上方 MaxMind 配置说明。 |
| `MAXMIND_LICENSE_KEY` | **是** | `""` | MaxMind License Key,和 `MAXMIND_ACCOUNT_ID` 配合使用。详见上方 MaxMind 配置说明。 |
| `MAXMIND_AUTO_UPDATE` | **是** | `"false"` | 设置为 `"true"` 时,程序会在启动后 60 秒左右自动下载 GeoLite2 数据库,之后每 24 小时刷新一次。**Docker 部署必须设置为 `"true"`。** 只有当你已经手动放置了 `.mmdb` 文件时,才能保持为 `"false"`。 |
| `CAIDA_AUTO_UPDATE` | 否 | `"false"` | 设置为 `"true"` 时,每天自动刷新 CAIDA 数据集(as2org 用于 ASN 组织名查询、as-rel2 用于 ASN 连接图)。设置为 `"false"` 时仍会在启动时下载缺失的快照,之后保持不变。 |
| `VITE_GOOGLE_ANALYTICS_ID` | **是** | `""` | Google Analytics 的 ID,用于统计访问量 |
| `BACKEND_PORT` | 否 | `"11966"` | 程序后端部分的运行端口 |
| `FRONTEND_PORT` | 否 | `"18966"` | 程序前端部分的运行端口 |
Expand Down
1 change: 1 addition & 0 deletions api/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ common/
- `requireReferer` is mounted globally on `/api/*` in `backend-server.js`. It rejects any request whose `Referer` header isn't on the `ALLOWED_DOMAINS` list (plus `localhost` always). **Handlers must not repeat the referer check.**
- `requireValidIP()` is attached per-route to every handler that takes `?ip=`. It rejects missing or malformed IPs before the handler runs. **Handlers must not repeat the IP check** — inside the handler body, `req.query.ip` is already known to be a well-formed string.
- `requireValidPrefix()` is the same pattern for `?prefix=` (CIDR-shaped param). Used by `asn-history` so the frontend can quantize the user's IP to its BGP DFZ-floor (/24 v4 or /48 v6) before the request lands, maximizing CF edge cache reuse across every IP in the same prefix.
- `requireValidASN()` does the same for `?asn=` (numeric, with optional leading `AS`). Strips the prefix and rewrites `req.query.asn` to a pure numeric string. Used by `asn-connectivity`; older handlers (`cf-radar`) still validate inline.
- If you add a new handler that needs a different-shape param guard, add the guard to `common/guards.js` and attach it in `backend-server.js` rather than open-coding the check in the handler.

### Private-API header pass-through (intentional exception)
Expand Down
145 changes: 145 additions & 0 deletions api/asn-connectivity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// /api/asn-connectivity — BFS over CAIDA AS-Relationships starting at the
// origin AS, halting at Tier 1s or MAX_DEPTH. Returns a graph the frontend
// renders dagre-style: origin (left) → intermediates → Tier 1s (right).
// Inspired by bgp.tools' /as/<N>#connectivity view.
//
// Data is fully local (common/as-rel-db.js + common/as-org-db.js), so the
// whole BFS is synchronous. We only hit RIPEstat for as-overview as a
// rare fallback when as2org doesn't have an ASN's org name.

import { fetchAsOverview, extractOrgFromHolder } from '../common/ripestat.js';
import { lookupAsOrgName } from '../common/as-org-db.js';
import { providersOf, customerCountOf, isTier1 } from '../common/as-rel-db.js';
import logger from '../common/logger.js';

// How deep to recurse from the origin. 3 covers regional networks reaching
// Tier 1s through 1-2 intermediates; deeper just adds noise at the periphery.
const MAX_DEPTH = 3;

// Per-node cap on non-Tier-1 providers to recurse into. Matters mainly for
// hyperscalers (Cloudflare-class has 100+ providers); ranked by customerCount
// as a proxy for "primary transit".
const MAX_INTERMEDIATE_BRANCH = 3;

// Two-tier org name resolver: local CAIDA as2org first (µs), RIPEstat
// as-overview fallback when the snapshot doesn't have the ASN.
async function resolveOrgName(asn) {
const local = lookupAsOrgName(asn);
if (local) return local;
try {
const res = await fetchAsOverview(asn);
if (!res.ok) return null;
const payload = await res.json();
return extractOrgFromHolder(payload?.data?.holder);
} catch {
return null;
}
}

async function buildGraph(origin) {
const nodes = new Map();
const edgeSet = new Set();
const orgPromises = new Map();

// If the queried AS is itself a Tier 1, mark it 'origin-tier1' so the
// frontend can render the combined origin+Tier1 styling; the rest of
// the BFS is a no-op (Tier 1s have no providers) and the graph is
// legitimately a single node.
const originType = isTier1(origin) ? 'origin-tier1' : 'origin';
nodes.set(origin, { asn: origin, type: originType, name: null });
orgPromises.set(origin, resolveOrgName(origin));

let currentLayer = [origin];

for (let depth = 0; depth < MAX_DEPTH; depth++) {
if (currentLayer.length === 0) break;
const nextLayer = [];

for (const asn of currentLayer) {
const providers = providersOf(asn);
if (providers.length === 0) continue;

// Tier 1 hits are terminal — record the edge + node, no recursion.
for (const p of providers) {
if (!isTier1(p)) continue;
edgeSet.add(`${asn}->${p}`);
if (!nodes.has(p)) {
nodes.set(p, { asn: p, type: 'tier1', name: null });
orgPromises.set(p, resolveOrgName(p));
}
}

// Non-Tier-1 providers: pick top-N by customerCount so when we
// truncate, the displayed ones are the more meaningful transits.
const intermediates = providers
.filter(p => !isTier1(p))
.sort((a, b) => customerCountOf(b) - customerCountOf(a))
.slice(0, MAX_INTERMEDIATE_BRANCH);

for (const p of intermediates) {
edgeSet.add(`${asn}->${p}`);
if (!nodes.has(p)) {
nodes.set(p, { asn: p, type: 'intermediate', name: null });
orgPromises.set(p, resolveOrgName(p));
nextLayer.push(p);
}
}
}

currentLayer = nextLayer;
}

// Await all org lookups. They've been running in the background since
// each node was discovered, so most are already settled.
for (const [asn, promise] of orgPromises) {
try {
const name = await promise;
const node = nodes.get(asn);
if (node && name) node.name = name;
} catch {
// node keeps name=null
}
}

const allNodes = [...nodes.values()];
const allEdges = [...edgeSet].map(s => {
const [from, to] = s.split('->').map(Number);
return { from, to };
});
return pruneLeafIntermediates(allNodes, allEdges);
}

// Iteratively drop intermediate nodes with no outgoing edge — visual
// dead-ends that contribute no info. Iterates to fixed-point because
// removing one leaf can turn its parent into a leaf. origin / origin-tier1
// / tier1 are never pruned.
function pruneLeafIntermediates(nodes, edges) {
let currentNodes = nodes;
let currentEdges = edges;
while (true) {
const hasOutgoing = new Set(currentEdges.map(e => e.from));
const survivors = currentNodes.filter(n =>
n.type !== 'intermediate' || hasOutgoing.has(n.asn)
);
if (survivors.length === currentNodes.length) {
return { nodes: currentNodes, edges: currentEdges };
}
const survivorAsns = new Set(survivors.map(n => n.asn));
currentEdges = currentEdges.filter(e =>
survivorAsns.has(e.from) && survivorAsns.has(e.to)
);
currentNodes = survivors;
}
}

export default async (req, res) => {
// ASN presence + numeric validity guaranteed by requireValidASN middleware.
const asn = parseInt(req.query.asn, 10);
try {
const graph = await buildGraph(asn);
res.json({ origin: asn, ...graph });
} catch (error) {
logger.error({ err: error, asn }, 'asn-connectivity handler failed');
res.status(500).json({ error: error.message });
}
};
Loading
Loading