koala73/worldmonitor · error · Error
GDACS malformed response
Error message
GDACS malformed response
What it means
After a successful GDACS fetch, fetchGdacs parses the body as JSON and requires it to be a GeoJSON FeatureCollection, i.e. data?.features must be an array. This error is thrown when the response is valid HTTP/JSON but not in the expected GeoJSON shape (missing 'features', wrong root type, or an HTML/error page served as JSON-ish text).
Solutions
- Inspect the raw body (res.text()) to see what GDACS actually returned — an HTML page means you are being blocked or proxied
- Confirm GDACS is serving GeoJSON: curl the GDACS_API URL with the same Accept/User-Agent headers and check the root object has type:'FeatureCollection'
- If a WAF/consent page is returned with 200, adjust headers (realistic User-Agent, Accept: application/json) or use an allowlisted egress IP
- Wrap res.json() in its own try/catch to distinguish invalid JSON from valid-but-wrong-shape JSON
- Add a schema check that logs JSON.stringify(data).slice(0, 200) on failure to speed diagnosis of upstream format changes
Example fix
// before
const data = await res.json();
if (!Array.isArray(data?.features)) throw new Error('GDACS malformed response');
// after
let data;
try {
data = await res.json();
} catch (err) {
throw new Error(`GDACS returned non-JSON body: ${err.message}`);
}
if (!Array.isArray(data?.features)) {
throw new Error(`GDACS malformed response: ${JSON.stringify(data).slice(0, 200)}`);
} Defensive patterns
Strategy: validation
Validate before calling
function looksLikeGdacsFeatureCollection(data) {
return data !== null && typeof data === 'object'
&& data.type === 'FeatureCollection'
&& Array.isArray(data.features);
}
const data = await res.json();
if (!looksLikeGdacsFeatureCollection(data)) throw new Error('GDACS malformed response'); Type guard
function isGdacsFeatureCollection(v) {
return typeof v === 'object' && v !== null
&& v.type === 'FeatureCollection'
&& Array.isArray(v.features);
} Try / catch
try {
const data = await res.json();
if (!isGdacsFeatureCollection(data)) throw new Error('GDACS malformed response');
} catch (err) {
console.error('GDACS payload rejected:', err.message);
return fallbackEvents(); // use cached/seeded events
} Prevention
- Check res headers content-type includes application/json before parsing
- Validate the GeoJSON root type immediately after parse
- Keep a fallback cached event set so one bad response does not break the dashboard
- Log a body preview on failure to detect WAF/consent HTML pages
When it happens
Trigger: Calling fetchGdacs() when GDACS returns 200 with an unexpected body: an HTML maintenance or consent page, a JSON error object like {error: ...} with no features array, a truncated response, or a null/empty body.
Common situations: GDACS behind a captive-portal or WAF returning an HTML block page with status 200; GDACS changing its API response schema; proxy/CDN error pages served with 200; body intercepted and rewritten by a corporate proxy.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- NHC_POINT_RESPONSE_INVALID
- GDACS ${res.status}
- Physical divergence snapshot must contain gold and silver re
- ECCC_MALFORMED_PAGE
- ${name} cronSchedule must be a string or null
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/53df01c96782665a.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/seed-natural-events.mjs:219
const pressureMatch = desc.match(/(\d{3,4})\s*(?:mb|hPa|mbar)/i);
if (pressureMatch) {
const p = parseInt(pressureMatch[1], 10);
if (p >= 850 && p <= 1050) fields.pressureMb = p;
}
return fields;
}
async function fetchGdacs(fetchFn = globalThis.fetch) {
const res = await fetchFn(GDACS_API, {
headers: { Accept: 'application/json', 'User-Agent': CHROME_UA },
signal: AbortSignal.timeout(15_000),
});
if (!res.ok) throw new Error(`GDACS ${res.status}`);
const data = await res.json();
if (!Array.isArray(data?.features)) throw new Error('GDACS malformed response');
const features = data.features;
const seen = new Set();
const events = [];
for (const f of features) {
if (!f.geometry || f.geometry.type !== 'Point') continue;
const props = f.properties;
const key = `${props.eventtype}-${props.eventid}`;
if (seen.has(key)) continue;
seen.add(key);
if (props.alertlevel === 'Green') continue;
const category = GDACS_TO_CATEGORY[props.eventtype] || 'manmade';
const alertPrefix = props.alertlevel === 'Red' ? '\u{1F534} ' : props.alertlevel === 'Orange' ? '\u{1F7E0} ' : '';
const description = props.description || EVENT_TYPE_NAMES[props.eventtype] || props.eventtype;
const severity = props.severitydata?.severitytext || '';
View on GitHub (pinned to 7d06c8633d)