spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: apps/api/src/pollers/open-notify.ts6 * Purpose: Open Notify poller (1 h) for humans_in_space — extremely failure-tolerant, fixture value stays as fallback7 */89import { buildStaticRtModel } from "@earth-now/models";10import { type ModelStore, displayHintsOf } from "../store.js";11import { type PollerHandle, NOOP_POLLER, fetchJson, pollersEnabled } from "./poller.js";1213const METRIC_ID = "humans_in_space";14const SOURCE_ID = "open_notify";15const ASTROS_URL = "http://api.open-notify.org/astros.json";16// The API is known to be flaky: only flag the metric stale after repeated failures.17const MAX_CONSECUTIVE_FAILURES = 3;1819export const OPEN_NOTIFY_POLL_INTERVAL_MS = 60 * 60_000;2021async function pollOnce(store: ModelStore): Promise<void> {22 const data = await fetchJson(ASTROS_URL);23 const count = (data as { number?: unknown }).number;24 if (typeof count !== "number" || !Number.isInteger(count) || count < 0 || count > 100) {25 throw new Error("astros.json malformed or implausible 'number' field");26 }27 const entry = store.registry.byId.get(METRIC_ID);28 if (!entry) throw new Error(`registry has no '${METRIC_ID}' entry`);29 const nowIso = new Date().toISOString();30 store.updateRtModel(31 METRIC_ID,32 buildStaticRtModel(33 {34 metricId: METRIC_ID,35 sourceId: SOURCE_ID,36 observedAt: nowIso,37 displayHints: displayHintsOf(entry),38 },39 { value: count, at: nowIso },40 ),41 );42}4344export function startOpenNotifyPoller(45 store: ModelStore,46 intervalMs: number = OPEN_NOTIFY_POLL_INTERVAL_MS,47): PollerHandle {48 if (!pollersEnabled()) return NOOP_POLLER;49 let consecutiveFailures = 0;50 const tick = (): void => {51 void pollOnce(store)52 .then(() => {53 consecutiveFailures = 0;54 })55 .catch((err: unknown) => {56 consecutiveFailures += 1;57 if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) store.markStale(METRIC_ID);58 console.error(59 `[open-notify] poll failed ${consecutiveFailures}× (fixture/previous value keeps serving): ${String(err)}`,60 );61 });62 };63 tick();64 const timer = setInterval(tick, intervalMs);65 timer.unref();66 return { stop: () => clearInterval(timer) };67}68