A login over the socket could hang the full 60s timeout when the
reverse proxy allowed the WebSocket handshake but dropped the
server->client direction without closing the socket (so onclose never
fired and the client saw a live-but-unresponsive link).
- Track last inbound frame time (any frame, incl. hello/pong) as proof
the link is bidirectional.
- ensureLive(): before sending an RPC, replace a socket that has been
silent for >30s (SOCKET_STALE_MS).
- On timeout, recycle the connection and retry once on a fresh socket
instead of hard-failing; only idempotent-damage-free flows are affected
since the first attempt is torn down first.
Reduces the observed "درخواست از طریق وبسوکت زمانبر شد" on login to a
single reconnect+retry rather than a hard 60s wait.