Skip to content

Restore the no-radio path with a configurable aggregator (#66) - #67

Open
ethanstoner wants to merge 1 commit into
cpaczek:mainfrom
ethanstoner:fix/configurable-aggregator-api
Open

Restore the no-radio path with a configurable aggregator (#66)#67
ethanstoner wants to merge 1 commit into
cpaczek:mainfrom
ethanstoner:fix/configurable-aggregator-api

Conversation

@ethanstoner

Copy link
Copy Markdown

Fixes #66.

What's broken

api.airplanes.live is feeder-only now. The public endpoint answers 403 with a "contact us" body:

$ curl -s -o /dev/null -w '%{http_code}\n' \
    "https://api.airplanes.live/v2/point/37.6188/-122.3750/20"
403

So the api source — the whole "runs with no radio" promise in the README — is dead for anyone who isn't feeding them. server/src/sfo-ground.ts pinned the same host separately, so the "who's next" ground panel died with it.

Four people are stacked in #66 asking the same question: is there another provider?

What this changes

The aggregator is now a config field, not a baked-in host. apiUrl sits next to radioUrl: live-editable from the control panel's Source section, validated, persisted, and re-read on every poll. Switching provider is one field and applies on the next tick — no rebuild, no restart, no env var. sfo-ground.ts follows the same field, so a dead host is one fix instead of two.

The default moves to adsb.lol — free, no key, and it serves the same readsb JSON on the same /v2/point/{lat}/{lon}/{r} shape, so normalize() is untouched.

One trap worth flagging: adsb.lol 403s any request without a descriptive User-Agent, including Node's default. Repointing the URL alone still fails — which is why this looked like the same 403 twice. Requests now identify Skylight the way the geocoder already does for Nominatim:

adsb.lol  no UA        -> 403
adsb.lol  skylight UA  -> 200  aircraft=45

Errors say what to do. A 401/403 on the api source now reads:

api fetch failed: HTTP 403 — the aggregator refused the request; it likely
serves feeders only now, so try a different API URL (api.adsb.lol)

instead of a bare HTTP 403 that reads like a bug in Skylight — the same misleading dead end the forum thread in #66 describes. Rate-limit detection moved off string-matching that message onto the status code carried by the error, so the #24 backoff still fires.

Verified

Ran the server against the live API, DATA_SOURCE=api, default config (SFO, 3 mi):

  • adsb.lol — 38 aircraft with airlines, types and routes; UAL968 SFO→AMS, AFR83K SFO→CDG. Rendered on the display with runways, planets and trails.
  • Live provider switch to adsb.fi, no restart — 40 aircraft. It takes /lat/{lat}/lon/{lon}/dist/{r}, a different path shape, which the existing placeholders already cover. That's why the whole URL is the unit of config rather than a provider name.
  • Bad URL rejectedPOST /api/config {"apiUrl":"not a url"}400 {"error":"apiUrl must be an http or https URL"}.
  • Suite 198 passing (8 new), pnpm typecheck and pnpm build clean.

New tests in server/test/api-source.test.ts cover the config-driven URL, both provider path shapes, a live provider switch, the User-Agent, the 403 guidance, and 429 backoff.

Upgrades

Existing installs fix themselves: config.json has no apiUrl, so the merge supplies the new default. An explicit API_URL env still wins as a first-run default, and a value already set in the panel wins over both — same precedence radioUrl has.

Notes

  • README gains an Aggregators table (adsb.lol · adsb.fi · airplanes.live with its current status), a troubleshooting entry for 403/401, and an API_URL row in the env table.
  • These are volunteer-run services, so the docs ask users to keep polling polite and to feed back whichever one they use — that's also how you keep access if they move to keyed APIs. Worth knowing: adsb.lol's README says a key obtained by feeding may be required in future, so being one field away from switching seemed like the durable fix rather than swapping one hardcoded host for another.
  • Happy to split the SFO-ground change or the error-message change out if you'd rather review them separately.

airplanes.live went feeder-only. Its public endpoint now answers 403 with a
"contact us" body, so the api source - the whole point of "runs with no radio"
- is dead for every install that isn't feeding them, and so is the SFO ground
panel, which pinned the same host separately.

The provider was baked into two files and only overridable by an env var at
boot, so a user hit by this cannot fix it from the control panel. It is now a
config field beside radioUrl: live-editable, validated, persisted, and read on
every poll. Switching aggregators is one field and takes effect on the next
tick. sfo-ground.ts follows the same field instead of its own hardcoded URL,
so a dead host is one fix, not two.

The default moves to adsb.lol, which is free, needs no key, and serves the
same readsb JSON on the same /v2/point/{lat}/{lon}/{r} shape, so the
normalizer is unchanged. Verified live at SFO: 38 aircraft with routes and
types rendering on the display, and the ground panel populated.

One trap worth naming: adsb.lol 403s any request without a descriptive
User-Agent, including Node's default, so simply repointing the URL still
fails. Requests now identify Skylight the same way the geocoder already does
for Nominatim. Confirmed both ways - 403 without the header, 200 with it.

adsb.fi is documented as the alternative and verified end to end by switching
providers on a running server: it takes /lat/{lat}/lon/{lon}/dist/{r}, a
different path shape, which the existing template placeholders already cover.
That is why the whole URL is the unit of config rather than a provider name.

A 401/403 on the api source now says the aggregator refused the request and
points at the API URL setting, instead of reporting a bare "HTTP 403" that
reads like a bug in Skylight - the same misleading dead end the linked forum
thread describes. Rate-limit detection moved off string-matching the message
onto the status code carried by the error, so the cpaczek#24 backoff still fires.

Existing installs upgrade silently: config.json has no apiUrl, so the merge
supplies the new default. An explicit API_URL env still wins as a first-run
default, and a value already set in the panel wins over both.

Tests cover the config-driven URL, both provider path shapes, a live provider
switch, the User-Agent, the 403 guidance, and 429 backoff. Full suite: 198
passing, typecheck and build clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Airplanes.live API is no longer publicly available

1 participant