fix(meshcore): surface backend error messages and extend polling window

- Store last status message on MeshcoreClient so error details survive
  beyond the SSE event (which isn't active during connecting state)
- Status endpoint now returns message field so the frontend can show
  the real reason (e.g. 'Connection failed after retries: ...')
- Extend JS polling from 30s to 90s to outlast the backend's 65s
  retry sequence (5+15+45s delays) before declaring timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-05-13 20:59:33 +01:00
parent 32f245e6ef
commit 98e01f4c5b
3 changed files with 12 additions and 5 deletions
+5 -1
View File
@@ -48,7 +48,11 @@ def status():
}
)
c = _client()
return jsonify({"available": True, "state": c.get_state().value})
state, message = c.get_state()
payload = {"available": True, "state": state.value}
if message:
payload["message"] = message
return jsonify(payload)
@meshcore_bp.route("/connect", methods=["POST"])
+2 -1
View File
@@ -97,7 +97,8 @@ const MeshCore = (function () {
function _pollUntilConnected(attempts) {
if (_connected) return;
if (attempts > 15) {
if (attempts > 45) {
// Backend retry window (5+15+45s) has elapsed — give up
_updateStatusUI('error', 'Connection timed out');
return;
}
+5 -3
View File
@@ -250,6 +250,7 @@ class MeshcoreClient:
def __init__(self) -> None:
self._state = ConnectionState.DISCONNECTED
self._status_message: str | None = None
self._config: ConnectionConfig | None = None
self._event_queue: queue.Queue = queue.Queue(maxsize=500)
self._nodes: dict[str, MeshcoreNode] = {}
@@ -261,14 +262,15 @@ class MeshcoreClient:
# -- State --
def get_state(self) -> ConnectionState:
"""Return the current connection state."""
def get_state(self) -> tuple[ConnectionState, str | None]:
"""Return the current connection state and last status message."""
with self._lock:
return self._state
return self._state, self._status_message
def _set_state(self, state: ConnectionState, **extra) -> None:
with self._lock:
self._state = state
self._status_message = extra.get("message")
# Push the status event OUTSIDE the lock (avoids deadlock; _push is queue-based)
payload: dict = {"state": state.value}
payload.update(extra)