diff --git a/apis/briefing.mjs b/apis/briefing.mjs index 2cb5f2e..b2b4bea 100644 --- a/apis/briefing.mjs +++ b/apis/briefing.mjs @@ -59,7 +59,8 @@ export async function runSource(name, fn, ...args) { }); const data = await Promise.race([dataPromise, timeoutPromise]); const hasError = Boolean(data?.error); - const isDegraded = hasError || ['no_credentials', 'degraded', 'failed'].includes(data?.status); + const degradedStatuses = ['no_credentials', 'no_key', 'disabled', 'degraded', 'failed', 'error']; + const isDegraded = hasError || degradedStatuses.includes(data?.status); return { name, status: isDegraded ? 'degraded' : 'ok', diff --git a/apis/sources/adsb.mjs b/apis/sources/adsb.mjs index b954d75..b05c5a8 100644 --- a/apis/sources/adsb.mjs +++ b/apis/sources/adsb.mjs @@ -1,7 +1,8 @@ // ADS-B Exchange — Unfiltered Flight Tracking (including Military) // Unlike FlightRadar24/FlightAware, ADS-B Exchange does NOT filter military aircraft. // Public feed access varies; RapidAPI tier available for programmatic use. -// This module attempts the public endpoints and falls back to a documented stub. +// This module reports explicit disabled/degraded state instead of making +// unavailable aircraft data look live. import { safeFetch } from '../utils/fetch.mjs'; @@ -140,6 +141,7 @@ async function fetchViaRapidApi(apiKey) { // Get all military aircraft const data = await safeFetch(`${ENDPOINTS.rapidApi}/mil`, { timeout: 20000, + source: 'adsb-rapidapi', headers: { 'X-RapidAPI-Key': apiKey, 'X-RapidAPI-Host': 'adsbexchange-com1.p.rapidapi.com', @@ -151,21 +153,26 @@ async function fetchViaRapidApi(apiKey) { // Attempt to fetch from public feed async function fetchPublicFeed() { - const data = await safeFetch(ENDPOINTS.publicFeed, { timeout: 15000 }); + const data = await safeFetch(ENDPOINTS.publicFeed, { timeout: 15000, source: 'adsb-public' }); return data; } -// Get military aircraft from available sources -export async function getMilitaryAircraft(apiKey) { +async function getMilitaryAircraftResult(apiKey) { + const failures = []; + // Try RapidAPI first if key available if (apiKey) { const data = await fetchViaRapidApi(apiKey); if (data && !data.error) { const aircraft = data.ac || data.aircraft || []; if (Array.isArray(aircraft)) { - return aircraft.map(classifyAircraft).filter(a => a.isMilitary); + return { + provider: 'rapidapi', + aircraft: aircraft.map(classifyAircraft).filter(a => a.isMilitary), + }; } } + failures.push({ provider: 'rapidapi', error: data?.error || 'RapidAPI returned an unsupported payload' }); } // Try public feed @@ -173,11 +180,21 @@ export async function getMilitaryAircraft(apiKey) { if (pubData && !pubData.error) { const aircraft = pubData.ac || pubData.aircraft || pubData.states || []; if (Array.isArray(aircraft)) { - return aircraft.map(classifyAircraft).filter(a => a.isMilitary); + return { + provider: 'public-feed', + aircraft: aircraft.map(classifyAircraft).filter(a => a.isMilitary), + }; } } + failures.push({ provider: 'public-feed', error: pubData?.error || 'Public feed returned an unsupported payload' }); - return null; // all sources failed + return { provider: null, aircraft: null, failures }; +} + +// Get military aircraft from available sources +export async function getMilitaryAircraft(apiKey) { + const result = await getMilitaryAircraftResult(apiKey); + return result.aircraft; } // Get all aircraft in a geographic bounding box via RapidAPI @@ -208,7 +225,8 @@ export async function getAircraftInArea(lat, lon, radiusNm = 250, apiKey) { // Briefing — attempt to get military flight data, document what's available export async function briefing() { const apiKey = process.env.ADSB_API_KEY || process.env.RAPIDAPI_KEY || null; - const militaryAircraft = await getMilitaryAircraft(apiKey); + const result = await getMilitaryAircraftResult(apiKey); + const militaryAircraft = result.aircraft; // If we got data, analyze it if (militaryAircraft && militaryAircraft.length > 0) { @@ -255,6 +273,7 @@ export async function briefing() { source: 'ADS-B Exchange', timestamp: new Date().toISOString(), status: 'live', + provider: result.provider, totalMilitary: militaryAircraft.length, byCountry, categories: { @@ -269,10 +288,18 @@ export async function briefing() { } // No data available — return stub with integration documentation + const status = apiKey ? 'degraded' : 'disabled'; + const error = apiKey + ? 'ADS-B providers returned no usable aircraft data' + : 'ADSB_API_KEY or RAPIDAPI_KEY is not configured'; + return { source: 'ADS-B Exchange', timestamp: new Date().toISOString(), - status: apiKey ? 'error' : 'no_key', + status, + provider: result.provider, + error, + failures: result.failures, militaryAircraft: [], message: apiKey ? 'ADS-B Exchange API returned no data. The endpoint may be temporarily unavailable.' diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html index 39f2c7e..8c14f2a 100644 --- a/dashboard/public/jarvis.html +++ b/dashboard/public/jarvis.html @@ -387,7 +387,24 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,