{"record":{"id":"9ec361729202b3ec","repo":"koala73/worldmonitor","slug":"expected-finite-viewport-coordinates","errorCode":null,"errorMessage":"Expected finite viewport coordinates","messagePattern":"Expected finite viewport coordinates","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/aviation/v1/track-aircraft.ts","lineNumber":124,"sourceCode":"    return 'aviation:track:all:v2';\n}\n\n// Response-level source values (TrackAircraftResponse.source):\n//   'opensky'           — data from OpenSky via relay\n//   'wingbits'          — data from Wingbits via relay\n//   'none'              — all real sources returned empty or failed; positions = []\nexport async function trackAircraft(\n    ctx: ServerContext,\n    req: TrackAircraftRequest,\n): Promise<TrackAircraftResponse> {\n    const rawIcao24 = req.icao24 ?? '';\n    const rawCallsign = req.callsign ?? '';\n    if (rawIcao24.length > 16 || rawCallsign.length > 16) throw new ApiError(400, 'Aircraft identifier is too long', '');\n    const icao24 = rawIcao24.trim().toLowerCase();\n    const callsign = rawCallsign.trim().toUpperCase();\n    if (rawIcao24 && !/^[0-9a-f]{6}$/.test(icao24)) throw new ApiError(400, 'Expected a six-character hexadecimal ICAO address', '');\n    if (rawCallsign && !/^[A-Z0-9]{1,8}$/.test(callsign)) throw new ApiError(400, 'Expected an alphanumeric callsign of at most eight characters', '');\n    if (![req.swLat, req.swLon, req.neLat, req.neLon].every(Number.isFinite)) throw new ApiError(400, 'Expected finite viewport coordinates', '');\n    const lat1 = Math.max(-90, Math.min(90, req.swLat));\n    const lat2 = Math.max(-90, Math.min(90, req.neLat));\n    const lon1 = Math.max(-180, Math.min(180, req.swLon));\n    const lon2 = Math.max(-180, Math.min(180, req.neLon));\n    req = { ...req, icao24, callsign, swLat: Math.min(lat1, lat2), neLat: Math.max(lat1, lat2), swLon: Math.min(lon1, lon2), neLon: Math.max(lon1, lon2) };\n    if (icao24 || callsign) await admitIdentifierLookup(ctx.request);\n\n    const redistributableOnly = requiresRedistributableProviders(ctx.request);\n    const cacheKey = `${await buildCacheKey(req)}${redistributableOnly ? ':redistributable' : ''}`;\n\n    let result: { positions: PositionSample[]; source: string } | null = null;\n    try {\n        const positiveTtl = req.callsign ? CALLSIGN_CACHE_TTL : CACHE_TTL;\n        const negativeTtl = req.callsign ? CALLSIGN_NEGATIVE_TTL : CACHE_TTL;\n        result = await cachedFetchJson<{ positions: PositionSample[]; source: string }>(\n            cacheKey, positiveTtl, async () => {\n                const relayBase = getRelayBaseUrl();\n                const isCallsignOnly = !!req.callsign && !req.icao24 && isDegenerateBbox(req);","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/worldmonitor/aviation/v1/track-aircraft.ts#L106-L142","documentation":"trackAircraft requires all four viewport fields (swLat, swLon, neLat, neLon) to be finite numbers. If any is missing, null, or non-numeric (NaN/Infinity/string), it throws ApiError 400 before clamping coordinates into valid lat/lon ranges.","triggerScenarios":"Calling track-aircraft with an omitted viewport field, JSON body where a coordinate is a string like \"45.5\", NaN/Infinity from a previous computation, or null from an unset map viewport.","commonSituations":"Map component not yet initialized so its bounds are null; parse failures producing NaN; API clients omitting optional-looking fields the server actually requires.","solutions":["Validate all four coordinates are finite numbers before calling (Number.isFinite on each)","Only invoke trackAircraft after the map viewport/bounds are initialized","Coerce numeric strings with Number() and reject NaN prior to the request","Check the client serialization so numbers are not sent as strings"],"exampleFix":"// before\nawait trackAircraft({ icao24, swLat: bounds?.swLat, ... });\n// after\nconst { swLat, swLon, neLat, neLon } = bounds;\nif (![swLat, swLon, neLat, neLon].every(Number.isFinite)) throw new Error('viewport not ready');\nawait trackAircraft({ icao24, swLat, swLon, neLat, neLon });","handlingStrategy":"validation","validationCode":"if (![swLat, swLon, neLat, neLon].every(Number.isFinite)) throw new Error('viewport not ready');","typeGuard":"const hasValidViewport = (r) => [r.swLat, r.swLon, r.neLat, r.neLon].every(n => typeof n === 'number' && Number.isFinite(n));","tryCatchPattern":"try { await trackAircraft(req); } catch (e) { if (e instanceof ApiError && e.status === 400 && e.message.includes('finite viewport')) { await waitForMapReady(); return trackAircraft(req); } throw e; }","preventionTips":["Gate map-backed calls on the map's bounds-ready event","Coerce inputs with Number() and check Number.isFinite before sending","Make viewport fields non-optional in the client request type","Sanitize NaN/Infinity early at the state layer"],"tags":["validation","geometry","http"],"backgroundTag":"invalid-argument-value","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}