quoting naddr1qq…gcd2WebSockets make real-time apps snappy, but they can silently “die” without the browser knowing (no native ping/pong), especially over flaky mobile networks. Cashu’s NUT-17 protocol defines JSON-RPC subscriptions but doesn’t include a protocol-level heartbeat, so wallets can end up listening on a broken socket with no idea until updates stop.
The Problem
- Zombie connections: WebSocket
.readyState === OPENbut server can’t be reached.- No built-in ping/pong in browsers (unlike Node.js
ws).- Cashu NUT-17 standard covers subscribe/unsubscribe/notification, but not health checks.
- Stale subscriptions: Wallets never know the socket is dead, so UI hangs silently.
The Solution: Subscription-Based Heartbeat
Leverage your existing NUT-17 subscriptions as a “health probe.” Every 30 seconds (configurable), pick one active subscription and re-send its subscribe request. If it fails, we know the socket is dead—trigger an automatic reconnect.
1. Configure the Heartbeat Interval
import { MintCommunicator } from "almnd"; const communicator = new MintCommunicator(mintUrl, { enableWebSocket: true, webSocketHeartbeatInterval: 30_000, // every 30 seconds });2. Start & Stop the Heartbeat
In your
WebSocketManager, kick off a timer on connect and clear it on close:private startHeartbeat() { this.stopHeartbeat(); this.heartbeatTimer = setInterval( () => this.performHealthCheck(), this.heartbeatInterval ); } private stopHeartbeat() { clearInterval(this.heartbeatTimer); }3. Perform the Health Check
private async performHealthCheck(): Promise<void> { if (!this.isConnected() || this.subscriptions.size === 0) return; // Don’t overlap checks if (this.isPerformingHealthCheck) return; this.isPerformingHealthCheck = true; // Pick a random active subId const subs = Array.from(this.subscriptions.keys()); const subId = subs[Math.floor(Math.random() * subs.length)]; const { filters, kind } = this.subscriptions.get(subId)!; try { // Resend subscribe as a probe await this.sendSubscription(subId, filters, kind); this.logger?.log("Heartbeat: Connection healthy ✓"); } catch (err) { this.logger?.log(`Heartbeat: Connection unhealthy → reconnecting`); this.isConnecting = false; if (this.shouldReconnect && this.reconnectUrl) { this.scheduleReconnect(); } } finally { this.isPerformingHealthCheck = false; } }4. Integrate on Connection Lifecycle
this.ws.onopen = () => { // Resubscribe existing subscriptions… this.startHeartbeat(); // ← start heartbeat }; this.ws.onclose = () => { this.stopHeartbeat(); // ← stop heartbeat this.scheduleReconnect(); // ← try to reconnect };Benefits
- Zero protocol changes: Reuses NUT-17 subscribe messages.
- No extra network overhead: Piggybacks on existing subscription flow.
- Immediate detection: Spots broken sockets in as little as one interval.
- Configurable: Adjust
heartbeatIntervalto suit your needs.With this simple subscription-based heartbeat, Cashu wallets stay reliably connected even on shaky networks—automatically reconnecting whenever the socket silently dies.
