mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
fix(dashboard): return 404 JSON for unmatched /api paths instead of SPA HTML
The SPA catch-all (serve_spa) served index.html for any unmatched GET,
including unregistered /api/* endpoints. A missing API route therefore
came back as <!doctype html> with status 200, and JSON clients (the
desktop app's fetchJson) crashed with an opaque
'SyntaxError: Unexpected token <' instead of a clear error.
- web_server.py: unmatched /api or /api/... now returns 404 JSON
('No such API endpoint'); non-api paths still serve the SPA for
client-side routing.
- main.cjs fetchJson: detect an HTML body / text/html content-type on a
2xx response and reject with a clear message naming the URL, rather
than a raw JSON.parse SyntaxError. Empty bodies resolve to null;
malformed JSON reports the URL plus a snippet.
This commit is contained in:
parent
6bd8132bf9
commit
4ed01f2fa4
2 changed files with 33 additions and 3 deletions
|
|
@ -1617,10 +1617,29 @@ function fetchJson(url, token, options = {}) {
|
|||
reject(new Error(`${res.statusCode}: ${text || res.statusMessage}`))
|
||||
return
|
||||
}
|
||||
if (!text) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
// A 2xx response whose body is HTML means the request fell through
|
||||
// to the SPA index.html (e.g. an unregistered /api path). JSON.parse
|
||||
// would throw an opaque `Unexpected token '<'` here, so surface a
|
||||
// clear diagnostic with the offending URL instead.
|
||||
const looksHtml = /^\s*<(?:!doctype|html)/i.test(text)
|
||||
const contentType = String(res.headers['content-type'] || '')
|
||||
if (looksHtml || contentType.includes('text/html')) {
|
||||
reject(
|
||||
new Error(
|
||||
`Expected JSON from ${url} but got HTML (status ${res.statusCode}). ` +
|
||||
'The endpoint is likely missing on the Hermes backend.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
try {
|
||||
resolve(text ? JSON.parse(text) : null)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
resolve(JSON.parse(text))
|
||||
} catch {
|
||||
reject(new Error(`Invalid JSON from ${url} (status ${res.statusCode}): ${text.slice(0, 200)}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4758,6 +4758,17 @@ def mount_spa(application: FastAPI):
|
|||
@application.get("/{full_path:path}")
|
||||
async def serve_spa(full_path: str, request: Request):
|
||||
prefix = _normalise_prefix(request.headers.get("x-forwarded-prefix"))
|
||||
# An unmatched /api/* path is a missing/renamed endpoint, NOT a
|
||||
# client-side route. Falling through to index.html here returns
|
||||
# `<!doctype html>` with status 200, which makes JSON clients (the
|
||||
# desktop app's fetchJson, dashboard fetch wrappers) blow up with an
|
||||
# opaque `SyntaxError: Unexpected token '<'`. Return a real 404 JSON
|
||||
# so the caller sees a clear "no such endpoint" instead.
|
||||
if full_path == "api" or full_path.startswith("api/"):
|
||||
return JSONResponse(
|
||||
{"detail": f"No such API endpoint: /{full_path}"},
|
||||
status_code=404,
|
||||
)
|
||||
file_path = WEB_DIST / full_path
|
||||
# Prevent path traversal via url-encoded sequences (%2e%2e/)
|
||||
if (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue