mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
fix(space): respect provider cooldowns and bound TLE groups
This commit is contained in:
1 parent
de2138e84c
commit
9d3d5164fe
7 files changed
+196
-16
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import path from 'node:path';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { celestrakTleUrl } from '../../../src/data/spaceProviderRequests.js';
|
||||
import { providerCooldownMs } from './cooldown.js';
|
||||
import { admitSameSite } from '../common/same-site.js';
|
||||
import { readResponseTextCapped } from '../common/http.js';
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,16 @@ import { readResponseTextCapped } from '../common/http.js';
|
||||
export function celestrakProxy() {
|
||||
const TLE_TTL_MS = 6 * 3600_000;
|
||||
const CACHE_DIR = path.join(process.cwd(), '.gev-cache');
|
||||
const groups = new Set([
|
||||
'stations',
|
||||
'visual',
|
||||
'gps-ops',
|
||||
'glo-ops',
|
||||
'galileo',
|
||||
'geo',
|
||||
'starlink',
|
||||
]);
|
||||
let cooldownUntil = 0;
|
||||
const mem = new Map(); // group -> { at: epochMs, body: string }
|
||||
const inflight = new Map(); // group -> Promise<{at, body}|null>
|
||||
|
||||
@@ -64,7 +76,11 @@ export function celestrakProxy() {
|
||||
'gods-eye-view-celestrak-proxy/1.0 (+https://github.com/bilawalsidhu/gods-eye-view)',
|
||||
},
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
if (!res.ok) {
|
||||
cooldownUntil = Date.now() + providerCooldownMs(res);
|
||||
void res.body?.cancel().catch(() => {});
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const body = await readResponseTextCapped(
|
||||
res,
|
||||
CELESTRAK_MAX_RESPONSE_BYTES,
|
||||
@@ -80,12 +96,18 @@ export function celestrakProxy() {
|
||||
const group = String(req.url || '')
|
||||
.replace(/^\//, '')
|
||||
.split('?')[0];
|
||||
if (!/^[a-z0-9-]+$/i.test(group)) {
|
||||
if (admitSameSite(req, res)) return;
|
||||
if (req.method !== 'GET') {
|
||||
res.writeHead(405);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
if (!groups.has(group)) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('invalid group');
|
||||
return;
|
||||
}
|
||||
const send = (status, body, cacheStatus) => {
|
||||
const send = (status, body, cacheStatus, entry) => {
|
||||
// Guard against a double-send (e.g. a throw AFTER a response already
|
||||
// went out routing into the catch's send): writeHead after headersSent
|
||||
// throws "Cannot set headers after they are sent".
|
||||
@@ -93,6 +115,17 @@ export function celestrakProxy() {
|
||||
res.writeHead(status, {
|
||||
'Content-Type': 'text/plain',
|
||||
'x-tle-cache': cacheStatus,
|
||||
'Cache-Control': 'no-store',
|
||||
...(entry
|
||||
? { 'X-TLE-Cached-At': new Date(entry.at).toISOString() }
|
||||
: {}),
|
||||
...(cooldownUntil > 0 && Date.now() < cooldownUntil
|
||||
? {
|
||||
'Retry-After': String(
|
||||
Math.ceil((cooldownUntil - Date.now()) / 1000),
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
res.end(body);
|
||||
};
|
||||
@@ -104,7 +137,16 @@ export function celestrakProxy() {
|
||||
if (entry) mem.set(group, entry);
|
||||
}
|
||||
if (entry && now - entry.at < TLE_TTL_MS) {
|
||||
send(200, entry.body, 'HIT');
|
||||
send(200, entry.body, 'HIT', entry);
|
||||
return;
|
||||
}
|
||||
if (Date.now() < cooldownUntil) {
|
||||
send(
|
||||
entry ? 200 : 502,
|
||||
entry?.body || 'celestrak upstream cooling down',
|
||||
entry ? 'STALE-ERROR' : 'NONE',
|
||||
entry,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Stale or missing → refresh, single-flight per group.
|
||||
@@ -118,6 +160,8 @@ export function celestrakProxy() {
|
||||
return fresh;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (Date.now() >= cooldownUntil)
|
||||
cooldownUntil = Date.now() + 60000;
|
||||
console.warn(
|
||||
'[celestrak-proxy] refresh failed — serving cache if any',
|
||||
);
|
||||
@@ -128,9 +172,9 @@ export function celestrakProxy() {
|
||||
}
|
||||
const fresh = await inflight.get(group);
|
||||
if (fresh) {
|
||||
send(200, fresh.body, 'MISS');
|
||||
send(200, fresh.body, 'MISS', fresh);
|
||||
} else if (entry) {
|
||||
send(200, entry.body, 'STALE-ERROR'); // upstream down — stale beats empty
|
||||
send(200, entry.body, 'STALE-ERROR', entry); // upstream down — stale beats empty
|
||||
} else {
|
||||
send(502, 'celestrak fetch failed and no cache available', 'NONE');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user