mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
The flight, military and vessel feeds were named after the providers the bundled server uses (/api/opensky, /api/adsblol/mil, /api/ais-live and their track routes; createOpenSkySource, createAdsbLolSource, createAisStreamSource). Any server should be able to answer a feed from its own provider, so the feeds are now: /api/flights /api/flights/track?icao24= /api/military /api/military/track?hex= /api/vessels /api/vessels/track?mmsi= with createFlightSource, createMilitarySource and createVesselSource. The old routes are removed. Each server names its provider in X-Feed-Source (the bundled server sends OpenSky Network, adsb.lol and AISStream, so its attribution is unchanged) and the military feed's cache state and age in X-Feed-Cache and X-Feed-Age-Ms. Sources report the provider the server names and fall back to the feed's name.
133 lines
4.6 KiB
JavaScript
133 lines
4.6 KiB
JavaScript
import { getOpenSkyToken } from './opensky.js';
|
|
import { readCappedResponseText } from '../common/http.js';
|
|
/**
|
|
* Vite plugin: aircraft track-history backfill proxies (PRD WS-F F1/F2).
|
|
*
|
|
* /api/flights/track?icao24=<hex6> — OpenSky GET /tracks/all (experimental;
|
|
* own credit bucket, 4 credits per call on the free tier). OAuth via the
|
|
* shared coalesced token. 60s per-icao cache; 404/429 forwarded so the
|
|
* client can fall back to its accumulated trail silently.
|
|
* /api/military/track?hex=<hex> — adsb.lol tar1090 readsb trace
|
|
* (undocumented but live; no browser CORS, hence this proxy). Up to ~24h
|
|
* of real history per aircraft. Treat as best-effort; data is ODbL —
|
|
* credit "adsb.lol (ODbL)" in the UI.
|
|
*/
|
|
export function trackBackfillProxies() {
|
|
const TRACK_CACHE_MS = 60000;
|
|
const TRACK_CACHE_MAX = 200;
|
|
const RESPONSE_CAP_BYTES = 5 * 1024 * 1024;
|
|
/** @type {Map<string, {at:number,status:number,body:string}>} */
|
|
const cache = new Map();
|
|
|
|
function cachePut(key, entry) {
|
|
cache.set(key, entry);
|
|
if (cache.size > TRACK_CACHE_MAX) {
|
|
const oldest = [...cache.entries()].sort((a, b) => a[1].at - b[1].at)[0];
|
|
if (oldest) cache.delete(oldest[0]);
|
|
}
|
|
}
|
|
|
|
async function proxyJson(res, key, upstreamUrl, headers = {}) {
|
|
const cached = cache.get(key);
|
|
if (cached && Date.now() - cached.at < TRACK_CACHE_MS) {
|
|
res.statusCode = cached.status;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
res.end(cached.body);
|
|
return;
|
|
}
|
|
const upstream = await fetch(upstreamUrl, {
|
|
headers,
|
|
signal: AbortSignal.timeout(12000),
|
|
});
|
|
const { tooLarge, text } = await readCappedResponseText(
|
|
upstream,
|
|
RESPONSE_CAP_BYTES,
|
|
);
|
|
let body;
|
|
let status = upstream.status;
|
|
if (tooLarge) {
|
|
status = 502;
|
|
body = JSON.stringify({ error: 'Upstream track response too large' });
|
|
} else if (!upstream.ok) {
|
|
// Sanitize upstream error surface; status code is signal enough
|
|
body = JSON.stringify({ error: `Track source HTTP ${upstream.status}` });
|
|
} else {
|
|
body = text;
|
|
}
|
|
// Cache the 502 like any upstream error: /tracks/all costs OpenSky
|
|
// credits, so a retry inside the window must not re-download the body.
|
|
cachePut(key, { at: Date.now(), status, body });
|
|
res.statusCode = status;
|
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
res.setHeader('Cache-Control', 'no-store');
|
|
res.end(body);
|
|
}
|
|
|
|
function install(middlewares) {
|
|
middlewares.use('/api/flights/track', async (req, res) => {
|
|
try {
|
|
const incoming = new URL(req.url || '', 'http://localhost');
|
|
const icao24 = String(incoming.searchParams.get('icao24') || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
if (!/^[0-9a-f]{6}$/.test(icao24)) {
|
|
res.statusCode = 400;
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(
|
|
JSON.stringify({ error: 'icao24 must be a 6-char hex string' }),
|
|
);
|
|
return;
|
|
}
|
|
const token = await getOpenSkyToken();
|
|
await proxyJson(
|
|
res,
|
|
`osky:${icao24}`,
|
|
`https://opensky-network.org/api/tracks/all?icao24=${icao24}&time=0`,
|
|
token ? { Authorization: `Bearer ${token}` } : {},
|
|
);
|
|
} catch (error) {
|
|
res.statusCode = 502;
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({ error: 'OpenSky track fetch failed' }));
|
|
}
|
|
});
|
|
|
|
middlewares.use('/api/military/track', async (req, res) => {
|
|
try {
|
|
const incoming = new URL(req.url || '', 'http://localhost');
|
|
const hex = String(incoming.searchParams.get('hex') || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
if (!/^[0-9a-f~]{6,7}$/.test(hex)) {
|
|
res.statusCode = 400;
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(
|
|
JSON.stringify({ error: 'hex must be a 6-7 char hex string' }),
|
|
);
|
|
return;
|
|
}
|
|
await proxyJson(
|
|
res,
|
|
`lol:${hex}`,
|
|
`https://adsb.lol/data/traces/${hex.slice(-2)}/trace_full_${hex}.json`,
|
|
);
|
|
} catch (error) {
|
|
res.statusCode = 502;
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.end(JSON.stringify({ error: 'adsb.lol trace fetch failed' }));
|
|
}
|
|
});
|
|
}
|
|
|
|
return {
|
|
name: 'track-backfill-proxies',
|
|
configureServer(server) {
|
|
install(server.middlewares);
|
|
},
|
|
configurePreviewServer(server) {
|
|
install(server.middlewares);
|
|
},
|
|
};
|
|
}
|