Commit Graph
1867 Commits
Author SHA1 Message Date
jason5ng32andClaude Opus 5.5 f81fce0c02 Fix(datasets): roll back a publish a timeout cuts short; skip only on a lost lock
publish took the combined signal (caller timeout + lock loss) and treated
any abort as a lost lock, so a scheduled or boot timeout firing between
renames skipped the rollback and cleanup while this process still held the
lock, leaving a mixed set and .next / .bak files. The combined signal still
cancels, but only the lock's own signal now stops rollback and cleanup.
Once every rename is done, only a lost lock keeps the version from being
recorded, so a timeout landing after a complete publish no longer forces a
re-download.

The lock-loss test now trips the engine's real onCompromised handler; a
new test covers the timeout path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CauPFca6bYL9qpuYEmsU9H
2026-10-05 01:45:45 +00:00
jason5ng32andClaude Opus 5.5 f867e0d189 Fix(datasets): watch from before the boot downloads; honour hand-named CAIDA files
- The watcher started only after every boot step, so a dataset published
  by another process (or placed by hand) while another step was running
  became the watcher's baseline and was never loaded. It now starts before
  the boot downloads.
- The CAIDA readers serve the newest *.txt under any name, but the boot
  download checked only the published names, so an upgrade with a
  hand-named snapshot re-downloaded it. A row can now supply hasSnapshot();
  as2org / as-rel use their reader's own rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:46:22 +08:00
jason5ng32andClaude Opus 5.5 1db89e1dc4 Fix(datasets): size the Sentry monitor's maxRuntime to the scheduled rows
Rows update one after another, each with up to 30 minutes, so four datasets
can legitimately take two hours while the monitor failed a run after one.
maxRuntime is now every scheduled row's timeout plus a 10-minute margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:44:05 +08:00
jason5ng32andClaude Opus 5.5 ce21aa33bb Fix(datasets): mark state in flux while publishing; no file work after a lost lock
Rolling back after a lost lock could overwrite what the lock's new holder
had just published. Once the lock is lost, publish now touches nothing more
(no rollback, no cleanup). To keep a half-done set from passing for
current, the state names no version from the start of publishing until the
final write, so whoever runs next — after a lost lock, a crash or a kill —
re-fetches and publishes a whole set. A rename failure with the lock still
held is rolled back as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:43:23 +08:00
jason5ng32andClaude Opus 5.5 c85c33d13a Fix(tests): expect a 502 from the ASN Profile handler test without CAIDA data
With connectivity now an error when no as-rel snapshot is loaded, the
network-down handler test has no working source on a data-less checkout
(CI), so the handler rightly answers 502. The test asserts that there and
keeps its 200 expectations where a snapshot exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:38:14 +08:00
jason5ng32andClaude Opus 5.5 034f5177c6 Fix(datasets): check for a lost lock between publish steps
The lock-loss signal was checked only before publishing began, so a lock
compromised mid-copy or mid-rename let publication continue. It is now
checked before every copy and rename, an abort taking the same rollback,
and again before the state is written.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:36:40 +08:00
jason5ng32andClaude Opus 5.5 5c0891a323 Docs(readme): use DATASET_AUTO_UPDATE in the docker run example
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:35:23 +08:00
jason5ng32andClaude Opus 5.5 d84fefbbea Fix(asn): report unloaded local data as an error, not a cacheable empty
- PeeringDB is 'disabled' only without a Cloudflare key; configured but not
  loaded (still downloading, or the download failed) is an 'error', so the
  ASN Profile isn't cached for a week without it.
- Connectivity without an as-rel snapshot is an 'error' too, not an empty
  graph; a missing as2org still only blocks while it downloads, as RIPEstat
  names the nodes.
- A CAIDA as2org / as-rel file that parses to nothing (corrupt) no longer
  counts as loaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:35:23 +08:00
jason5ng32andClaude Opus 5.5 02476aec7e Fix(datasets): abort on a lost lock, roll back new files, validate watched files
- A compromised lock (failed heartbeat; another process may take it over)
  aborts the update holding it, download included, before it publishes,
  instead of only being logged.
- A failed publish removes files it had just added when the dataset had none
  before, as well as restoring replaced ones from backup: never half a set.
- The watcher reloads hand-placed or externally published files only once
  they have held still for the settle interval and passed the row's
  validator, so a file still being copied never replaces healthy data.
- Back to write-file-atomic 8, with engines aligned to its range
  (^24.15.0 || >=26.0.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:31:28 +08:00
jason5ng32andClaude Opus 5.5 65e8432768 Fix(datasets): bound scheduled runs, catch up by schedule, retry failed reloads
- Scheduled and catch-up runs give each dataset 30 minutes, download
  included, so a stalled upstream can't hold up the rest or, under croner's
  overrun protection, every later run.
- Downloads go through fetchUpstream (project User-Agent, a timeout on the
  wait for a response); the body stays bounded by the caller's signal.
- Catch-up asks whether a scheduled run fell between the last check and now,
  instead of a 25-hour age that missed a check skipped just after one.
- A remote that names no version (no Last-Modified) is always fetched, not
  taken for unchanged; maxmindIdentifier is null when an edition omits it.
- The watcher records a dataset's files as loaded only after the reload
  succeeds, so a failed load is retried on their next change.
- write-file-atomic 7, which supports the project's Node >=24 (8 needs 24.15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:29:48 +08:00
jason5ng32andClaude Opus 5.5 b3cd1ceef7 Feat(backend): listen before offline data finishes downloading
The backend downloaded every missing dataset before listening, so a first
boot (or a newly added dataset) refused every request for a minute or more.
Snapshots already on disk now load before the listener opens and missing
ones download behind it. Meanwhile only routes reading still-missing data
answer 503 (never edge-cached): /api/maxmind, /api/asn-connectivity and
/api/service-status. An ASN Profile section whose data is still loading
answers 'error' on its own, and the connectivity graph client doesn't cache
a 503, so both recover once the data lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:22:06 +08:00
jason5ng32andClaude Opus 5.5 287c50fe1a Feat(datasets): one updater engine for every offline dataset
MaxMind and CAIDA each had their own updater with its own lock, state file,
publish step, boot download and timer. One engine (common/dataset-updater.js)
now owns all of that, and each dataset is a row in common/datasets.js that
holds only its own logic: find the newest remote version, fetch or distill
it, validate, reload.

- Locks: proper-lockfile, a heartbeat lock shared by every process, so a
  crashed holder's lock goes stale on its own; a booting server waits for a
  live holder and loads what it published.
- State: one file per dataset, written with write-file-atomic; the old
  .maxmind-update-state.json / .caida-update-state.json are read once, so an
  upgrade doesn't re-download.
- Schedule: croner at a fixed time (DATASET_UPDATE_CRON, default 04:30 local)
  instead of 24 h from process start, plus a catch-up run at boot for a check
  a downtime skipped. DATASET_AUTO_UPDATE (default on) replaces
  MAXMIND_AUTO_UPDATE / CAIDA_AUTO_UPDATE, which still apply when it is unset.
- Reload: the engine watches every dataset's files, so a publish by another
  process or a hand-placed .mmdb is picked up; MaxMind's own watcher goes.

MaxMind's City and ASN editions now publish together under one version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:21:58 +08:00
Jason Ng 152906b762 Merge pull request #492 from jason5ng32/feat/asn-profile-improve
ASN Profile improvements
2026-10-05 02:04:14 +08:00
jason5ng32andClaude Opus 5.5 dcff924d3d Fix(asn): show no country shares when the lookup budget runs out
Shares over the part of the space walked before the budget ran out would
read as the ASN's whole IPv4 distribution. An exhausted budget now gives no
shares, like a lookup without an answer, and the answer stays uncached.
Real ASNs use at most ~30k of the 100k lookups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:02:01 +08:00
jason5ng32andClaude Opus 5.5 57f68bacb5 Fix(asn): keep incomplete country shares and stale profiles out of the cache
buildCountryShares now reports whether its walk completed: a lookup with
no answer (no database, or a failed lookup) and the lookup budget, now a
hard cap across blocks, both mark it incomplete, and the bgp-prefixes view
serves such an answer uncached. A prefix-less AS needs no lookups, so it
stays cacheable even without MaxMind. ASN_PROFILE_VERSION goes to 6 so
profiles cached with first-address country shares are not served on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:57:54 +08:00
jason5ng32andClaude Opus 5.5 0054c9dde1 Fix(asn): follow ?q= changes while the ASN Profile stays open
The profile read `q` only on mount, so a new ASN link or history step with
the drawer open changed the URL but not the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:49:35 +08:00
jason5ng32andClaude Opus 5.5 8ebe3ea0ea Fix(asn): don't cache prefix countries computed without MaxMind
With MaxMind unavailable (a failed download) the bgp-prefixes view answered
`countries: []` as complete, and it was edge-cached for a week. The answer
is now marked partial, and the ASN Profile's prefixes section incomplete,
so neither is cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:49:35 +08:00
jason5ng32andClaude Opus 5.5 5ef0e48e8b Fix(asn): split a prefix's addresses across MaxMind networks
Country shares credited a whole announced prefix to the country of its
first address, so a /8 spanning many countries counted as one. Each prefix
is now walked one MaxMind network at a time, within a per-request lookup
budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:49:34 +08:00
jason5ng32andClaude Opus 5.5 8bcbb91624 Refactor(asn): drop prefix and relationship counts from the ASN info panel
The panel's IPv4/IPv6 prefix counts came from Radar's routes/stats, an
endpoint that fails often and, as one of the profile's summary segments,
kept complete ASN Profiles out of the edge cache. The ASN Profile already
shows prefix counts (from the pfx2as list) and relationships (the CAIDA
graph), so the panel drops both: no more routes/stats call, and the Radar
asn view no longer reads the local CAIDA snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:49:34 +08:00
Jason Ng 846deb50a8 Merge pull request #490 from jason5ng32/feat/asn-profile
Feat/asn profile
2026-10-04 00:10:33 +08:00
jason5ng32andClaude Fable 5.1 04079783af Fix(tests): keep the logger off its worker thread during specs
The pino-pretty transport runs in a worker thread. In a loaded parallel
run, a spec that logs shortly before finishing could occasionally be kept
alive by that thread and never exit, stalling the whole suite until the
CI timeout. `pnpm test` now preloads tests/setup.js, which switches the
logger to its synchronous JSON output unless LOG_FORMAT is already set.

Runtime logging is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 00:00:39 +08:00
jason5ng32andClaude Fable 5.1 d5715104d5 Fix(asn): never cache an upstream fault as an empty answer
Review findings on the ASN Profile work, mostly one class of bug: a failed
upstream read as "no data", which the 7-day edge cache would then pin.

- AS Rank: a 200 carrying GraphQL errors is a failure, not an unknown AS.
- Radar: an answer built from partly failed segments is still shown but
  flagged `incomplete` and kept out of the cache, on the aggregate and on
  the standalone asn view. A 404 is Radar's "no such AS" and counts as
  empty, as does a segment that simply has no figures.
- Reputation: a malformed or non-200 answer is an error.
- Prefixes: a payload without `prefix_origins` is an error; [] stays valid.
- Radar is `empty` when only local CAIDA counts are present.
- requireValidASN enforces AS1–AS4294967295 and canonicalizes; the range
  rule lives once in common/asn-input.js, shared with the frontend.
- The PeeringDB listing lookup goes through fetchUpstream.
- New functions follow the const-arrow convention; test stubs compare
  parsed hostnames instead of URL substrings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 23:43:55 +08:00
jason5ng32 a5d3cbf2c8 ASN Profile Shortcut 2026-10-03 22:55:28 +08:00
jason5ng32andClaude Fable 5.1 bf1e4a934a Style(asn): tighten the ASN Profile title and tool note
The tool is titled as a lookup, its card note is one short line, and the
intro names PeeringDB among its sources — across all six locales.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:53:17 +08:00
jason5ng32 076a9e36d8 Improvements 2026-10-03 22:42:22 +08:00
jason5ng32andClaude Fable 5.1 4eee98aa93 Feat(asn): PeeringDB data in ASN Profile
A third offline dataset joins the CAIDA updater: the daily PeeringDB dump,
distilled by a streaming pass into a compact per-ASN index (network type,
scope, peering policy, exchange and facility presence). Contact tables are
never parsed and the raw dump is deleted after distilling. It downloads
only when the Cloudflare key is configured, since the tool is hidden
without it.

The profile shows network type, scope and website in the header and a
"Peering & facilities" card whenever the AS lists an exchange or facility.

Aggregate deadlines now sit above the fetch timeouts they wrap, so a
source that degrades internally still delivers its partial answer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:31:11 +08:00
jason5ng32andClaude Fable 5.1 5ea781c513 Chore(asn): changelog entry and cleanup for ASN Profile
Adds the v7.7.0 changelog line for the new tool, drops two exports
nothing imports, and aligns the reputation test fixtures with the fields
the private API actually returns.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:43:29 +08:00
jason5ng32andClaude Fable 5.1 901092ef66 Feat(asn): ASN Profile tool
A full profile of one autonomous system at /tools/asn?q=AS<n>, served by
a single /api/asn-profile request: identity and registration with the raw
Whois record, key facts (rank, announced IPv4, prefixes, users), traffic
shares and connection quality, where its IPv4 space is used, the announced
prefix list with RPKI status, and connectivity with the topology graph.
A reputation card appears beside the header when the backend returns it.

- The ASN Info panel now opens the profile instead of linking out.
- ASNConnectivity gains `expandable` / `bordered` so the profile can embed
  it without the nested drawer and the second border.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:33:51 +08:00
jason5ng32andClaude Fable 5.1 6189a96498 Feat(asn): one aggregate endpoint for the ASN Profile page
/api/asn-profile gathers every source for one AS in parallel and answers
once: Radar entity and traffic data, announced prefixes with country
shares, the CAIDA topology and neighbours, the RDAP registration record,
AS Rank, and (when the private API is configured) reputation. Each source
has its own deadline; a slow or failed one is simply absent, flagged in a
`status` map, and only complete answers are edge-cached.

- Upstream / peer / customer counts now come from the local CAIDA
  snapshot with the same rule the topology graph applies, for both the
  profile and the ASN Info panel; the graph builder itself is unchanged.
- The ASN data family (aggregate, Radar asn and bgp-prefixes views)
  shares a 7-day edge cache.
- /api/asrank is folded into the aggregate.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 21:33:38 +08:00
jason5ng32andClaude Fable 5.1 c55634c8e0 Feat(asn): frontend groundwork for the ASN Profile tool
- Registry gate `requiresConfig`: a tool is listed in the Advanced Tools
  grid and the nav only when its backend config is present; the listing
  filter is one pure function. ASN Profile requires the Cloudflare key.
- `useAsnInfo` owns the Radar ASN fetch and its session cache, shared by
  the IP cards and the query dialog.
- ASN Info's traffic-share and connection-quality blocks become small
  components over pure helpers, reusable by the profile page.
- ASN Profile lands as a mock-data page for now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:47:15 +08:00
jason5ng32andClaude Fable 5.1 0e6e411beb Feat(asn): backend data for the ASN Profile page
- cfradar `view=asn` keeps the RPKI route counts from routes/stats.
- New cfradar `view=bgp-prefixes`: the AS's announced prefixes (pfx2as)
  plus IPv4 country shares resolved against the local MaxMind database.
- New /api/asrank: CAIDA AS Rank lookup (rank, customer cone, degree).
  Misses answer 200 with null fields and stay out of the edge cache via
  the new `cacheIf` option on `cacheable`.
- /api/asn-connectivity adds `neighbours`: provider / peer / customer
  counts and name lists from the local AS-relationship snapshot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:40:52 +08:00
jason5ng32andClaude Fable 5.1 09d3cef4c0 Feat(whois): look up AS numbers
The Whois tool and /api/whois now take `AS<n>` (prefix in any case) next
to IPs and domains, answered from the RIR's RDAP autnum record. A bare
number stays invalid input, so it can never be mistaken for anything else.

The raw blocks no longer start with the template's own indentation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:10:14 +08:00
jason5ng32 3495204fc4 Improvements 2026-10-03 11:52:56 +08:00
jason5ng32 3dea2421d2 Improvements 2026-09-29 16:00:44 +08:00
jason5ng32 aedee4fd3d Improvements 2026-09-29 15:48:24 +08:00
jason5ng32 8ace7a2e30 Improvements 2026-09-29 15:38:54 +08:00
jason5ng32 681051b1bc Improvements 2026-09-29 15:31:25 +08:00
jason5ng32 b24874b69e Improvements 2026-09-29 15:27:43 +08:00
jason5ng32andClaude Opus 5.5 cf3bb69d8b Feat(ipinfo): show how the quality score was reached and rename the proxy field
Follows the v3 score output of MyIP-api (this frontend no longer reads
the v2 shape; deploy it right after the API).

- A toggle next to the quality score opens a panel: a short explanation
  assembled from the score dimensions (network type, anonymity with its
  last-observed date, reputation, the ASN's neighbourhood, the verdict),
  and all boolean attributes as check / cross / question-mark icons.
  Residential proxies seen only in the API's own sighting history read
  as "suspected".
- The card's "Proxy" field becomes "Anonymizer": Tor exit, iCloud
  Private Relay, residential proxy, proxy, VPN (each with a suspected
  variant when only one source agrees) or not detected. Card fields are
  anonymity / anonymityCode / anonymityProvider / anonymityProtocol; the
  provider line hides when the API returns null. Texts live under
  ipInfos.anonymity.*, with one code-to-key map for card and report.
- Diagnostic reports store the anonymity code (REPORT_VERSION 2; v1
  links show the unsupported-version page, reworded to fit either
  direction). The persona collector no longer sends the unused isProxy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:19:14 +08:00
jason5ng32 74b2c69524 Improvements 2026-09-26 17:02:59 +08:00
jason5ng32andClaude Opus 5.5 78d5f95238 Chore(history): cap IP history retention at 30 days
Ninety days is longer than most visitors need for a local record. Stored
preferences above the new cap clamp to 30 on read, and older day buckets
are pruned on the next load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 09:54:53 +08:00
jason5ng32 5c59c6677d Improvements 2026-09-24 09:37:12 +08:00
jason5ng32 2b5485f316 Improvements 2026-09-22 15:30:32 +08:00
jason5ng32 d77342cc83 Improvements 2026-09-21 23:21:46 +08:00
jason5ng32 47446b3039 Merge branch 'dev' of https://github.com/jason5ng32/MyIP into dev 2026-09-21 23:21:23 +08:00
jason5ng32andGPT-6 aa9f31472e Chore(release): set the v7.6.0 release date
Apply only the release-date change from
f7e332c15d.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-21 23:20:03 +08:00
jason5ng32 f7e332c15d Improvements 2026-09-21 23:17:23 +08:00
jason5ng32 7f3d38e147 Add Map to IP History 2026-09-21 23:11:43 +08:00
jason5ng32andGPT-6 5d764cff29 Feat(history): group IP records with a daily view
Collapse repeated daily IP records into summaries ordered by last seen.
Keep country and IP-type filters across both views and expand dates only
for IPs recorded on multiple days. Preserve the daily storage format.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-21 22:43:01 +08:00
jason5ng32 ab42ba6acf Improvements 2026-09-18 10:15:28 +08:00