This document describes the WebSocket implementation for real-time tip notifications in TipStream. The feature eliminates the need for constant polling by pushing tip events to connected clients as they occur.
Location: chainhook/websocket.js
The backend WebSocket server is built using the ws library and integrates with the existing Chainhook event ingestion pipeline.
-
WebSocketManager Class
- Manages all connected WebSocket clients
- Handles client lifecycle (connect, disconnect, heartbeat)
- Broadcasts tip events to subscribed clients
- Supports address-based filtering
-
Integration Points
- Attached to the HTTP server on
/wsendpoint - Receives tip events from Chainhook webhook handler
- Broadcasts events in real-time to connected clients
- Attached to the HTTP server on
- Heartbeat Monitoring: 30-second ping interval with 60-second timeout
- Address Subscription: Clients can subscribe to specific Stacks addresses
- Graceful Shutdown: Properly closes all connections during server shutdown
- Connection Statistics:
/api/ws/statsendpoint for monitoring
Server → Client:
connected: Sent immediately after connectiontip_event: Broadcast when a new tip is receivedping: Heartbeat to keep connection aliveerror: Error messages (e.g., invalid JSON)
Client → Server:
subscribe: Subscribe to a specific addressunsubscribe: Remove address filter
Locations:
frontend/src/hooks/useWebSocket.js- Low-level WebSocket hookfrontend/src/context/TipContext.jsx- Integration with app statefrontend/src/components/WsConnectionBadge.jsx- Connection status UI
-
useWebSocket Hook
- Manages WebSocket connection lifecycle
- Automatic reconnection with exponential backoff
- Heartbeat timeout detection
- Address subscription management
-
TipContext Integration
- Receives real-time tip events via WebSocket
- Merges WebSocket events with polled data
- Reduces polling frequency when WebSocket is active
- Falls back to polling when WebSocket unavailable
-
WsConnectionBadge Component
- Visual indicator of connection status
- Shows "Live", "Connecting", "Reconnecting", or "Polling"
- Only renders when WebSocket URL is configured
The WebSocket server is automatically attached to the HTTP server when it starts. No additional configuration is required.
Environment Variables: None (uses same port as HTTP server)
Environment Variable: VITE_WS_URL
# Development
VITE_WS_URL=ws://localhost:3001/ws
# Production
VITE_WS_URL=wss://your-domain.com/wsWhen VITE_WS_URL is not set, the frontend falls back to polling-only mode.
- Frontend creates WebSocket connection to configured URL
- Server sends
connectedmessage - If user is authenticated, frontend sends
subscribemessage with address - Server filters future broadcasts to match subscribed address
- Chainhook webhook receives new block data
- Server parses tip events and calls
wsManager.broadcast(tipEvent) - WebSocketManager sends
tip_eventto all relevant clients - Frontend receives message and injects event into local cache
- UI updates immediately without polling
- Connection lost (network issue, server restart, etc.)
- Frontend detects disconnect via
oncloseevent - Automatic reconnection after 3-second delay
- Maximum 5 reconnection attempts
- Falls back to polling if reconnection fails
The frontend maintains polling as a fallback mechanism:
- WebSocket Disconnected: Poll every 30 seconds (normal)
- WebSocket Connected: Poll every 120 seconds (reduced)
This hybrid approach ensures:
- Data consistency even if WebSocket messages are missed
- Graceful degradation when WebSocket is unavailable
- No duplicate events (deduplication handles overlaps)
Location: chainhook/websocket.test.js
- 17 tests covering connection lifecycle, broadcasting, subscriptions, and cleanup
- Uses Node.js native test runner
- Run with:
npm test -- websocket.test.js(inchainhook/directory)
Locations:
frontend/src/hooks/useWebSocket.test.js(22 tests)frontend/src/components/WsConnectionBadge.test.jsx(13 tests)frontend/src/test/TipContext.websocket.test.jsx(10 tests)
Total: 45 frontend tests
Run with: npm test (in frontend/ directory)
Endpoint: GET /api/ws/stats
Returns:
{
"connectedClients": 5,
"uptime": 3600
}The WebSocket server logs all significant events:
- Client connections/disconnections
- Broadcast operations
- Subscription changes
- Errors and timeouts
- No Authentication: WebSocket connections are unauthenticated. Clients can subscribe to any address.
- Rate Limiting: No rate limiting on WebSocket connections (relies on HTTP server limits)
- Message Validation: All incoming messages are validated and malformed JSON is rejected
- HTTPS/WSS: Use WSS (WebSocket Secure) in production
- Memory: ~1KB per connected client
- CPU: Minimal (event-driven architecture)
- Network: ~100 bytes per tip event broadcast
- Memory: Single WebSocket connection per tab
- CPU: Negligible (message parsing only)
- Network: Reduced by 75% compared to polling-only
No special deployment steps required. The WebSocket server starts automatically with the HTTP server.
- Set
VITE_WS_URLenvironment variable - Build frontend:
npm run build - Deploy static assets
Note: Ensure your reverse proxy (nginx, etc.) supports WebSocket upgrades:
location /ws {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}- Check
VITE_WS_URLis set correctly - Verify backend is running and accessible
- Check browser console for connection errors
- Ensure firewall allows WebSocket connections
- Check WebSocket connection status in UI
- Verify backend logs show broadcast operations
- Check if address subscription is correct
- Confirm events are being ingested by Chainhook
- Check network stability
- Verify backend server health
- Review heartbeat timeout settings
- Check for proxy/load balancer issues
- Authentication for WebSocket connections
- Rate limiting per client
- Message compression for large broadcasts
- Presence detection (online users)
- Typing indicators for messages
- Read receipts for notifications