9.9 KiB
WebSocket Real-Time Notifications - Usage Guide
This guide shows you how to use the WebSocket real-time notification system in your React components.
Quick Start
1. Enable Global Notifications
Add the NotificationToastHandler to your root App.tsx:
// src/App.tsx
import { Toaster } from 'react-hot-toast';
import { NotificationToastHandler } from './components/NotificationToastHandler';
function App() {
return (
<>
{/* React Hot Toast container */}
<Toaster position="top-right" />
{/* WebSocket notification handler (renders nothing, handles side effects) */}
<NotificationToastHandler
enabled={true}
playSound={false} // Set to true to play notification sounds
/>
{/* Your app routes and components */}
<YourAppContent />
</>
);
}
2. Add Notification Bell to Header
// src/components/Header.tsx
import { NotificationBell } from './components/NotificationBell';
import { useNavigate } from 'react-router-dom';
function Header() {
const navigate = useNavigate();
return (
<header className="flex items-center justify-between p-4">
<h1>Flyer Crawler</h1>
<div className="flex items-center gap-4">
{/* Notification bell with unread count */}
<NotificationBell onClick={() => navigate('/notifications')} showConnectionStatus={true} />
<UserMenu />
</div>
</header>
);
}
3. Listen for Notifications in Components
// src/pages/DealsPage.tsx
import { useEventBus } from '../hooks/useEventBus';
import { useCallback, useState } from 'react';
import type { DealNotificationData } from '../types/websocket';
function DealsPage() {
const [deals, setDeals] = useState([]);
// Listen for new deal notifications
const handleDealNotification = useCallback((data: DealNotificationData) => {
console.log('New deals received:', data.deals);
// Update your deals list
setDeals((prev) => [...data.deals, ...prev]);
// Or refetch from API
// refetchDeals();
}, []);
useEventBus('notification:deal', handleDealNotification);
return (
<div>
<h1>Deals</h1>
{/* Render deals */}
</div>
);
}
Available Components
NotificationBell
A notification bell icon with unread count and connection status indicator.
Props:
onClick?: () => void- Callback when bell is clickedshowConnectionStatus?: boolean- Show green/red/yellow connection dot (default:true)className?: string- Custom CSS classes
Example:
<NotificationBell
onClick={() => navigate('/notifications')}
showConnectionStatus={true}
className="mr-4"
/>
ConnectionStatus
A simple status indicator showing if WebSocket is connected (no bell icon).
Example:
<ConnectionStatus />
NotificationToastHandler
Global handler that listens for WebSocket events and displays toasts. Should be rendered once at app root.
Props:
enabled?: boolean- Enable/disable toast notifications (default:true)playSound?: boolean- Play sound on notifications (default:false)soundUrl?: string- Custom notification sound URL
Example:
<NotificationToastHandler enabled={true} playSound={true} soundUrl="/custom-sound.mp3" />
Available Hooks
useWebSocket
Connect to the WebSocket server and manage connection state.
Options:
autoConnect?: boolean- Auto-connect on mount (default:true)maxReconnectAttempts?: number- Max reconnect attempts (default:5)reconnectDelay?: number- Base reconnect delay in ms (default:1000)onConnect?: () => void- Callback on connectiononDisconnect?: () => void- Callback on disconnectonError?: (error: Event) => void- Callback on error
Returns:
isConnected: boolean- Connection statusisConnecting: boolean- Connecting stateerror: string | null- Error message if anyconnect: () => void- Manual connect functiondisconnect: () => void- Manual disconnect functionsend: (message: WebSocketMessage) => void- Send message to server
Example:
const { isConnected, error, connect, disconnect } = useWebSocket({
autoConnect: true,
maxReconnectAttempts: 3,
onConnect: () => console.log('Connected!'),
onDisconnect: () => console.log('Disconnected!'),
});
return (
<div>
<p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
{error && <p>Error: {error}</p>}
<button onClick={connect}>Reconnect</button>
</div>
);
useEventBus
Subscribe to event bus events (used with WebSocket integration).
Parameters:
event: string- Event name to listen forcallback: (data?: T) => void- Callback function
Available Events:
'notification:deal'- Deal notifications (DealNotificationData)'notification:system'- System messages (SystemMessageData)'notification:error'- Error messages ({ message: string; code?: string })
Example:
import { useEventBus } from '../hooks/useEventBus';
import type { DealNotificationData } from '../types/websocket';
function MyComponent() {
useEventBus<DealNotificationData>('notification:deal', (data) => {
console.log('Received deal:', data);
});
return <div>Listening for deals...</div>;
}
Message Types
Deal Notification
interface DealNotificationData {
notification_id?: string;
deals: Array<{
item_name: string;
best_price_in_cents: number;
store_name: string;
store_id: string;
}>;
user_id: string;
message: string;
}
System Message
interface SystemMessageData {
message: string;
severity: 'info' | 'warning' | 'error';
}
Advanced Usage
Custom Notification Handling
If you don't want to use the default NotificationToastHandler, you can create your own:
import { useWebSocket } from '../hooks/useWebSocket';
import { useEventBus } from '../hooks/useEventBus';
import type { DealNotificationData } from '../types/websocket';
function CustomNotificationHandler() {
const { isConnected } = useWebSocket({ autoConnect: true });
useEventBus<DealNotificationData>('notification:deal', (data) => {
// Custom handling - e.g., update Redux store
dispatch(addDeals(data.deals));
// Show custom UI
showCustomNotification(data.message);
});
return null; // Or return your custom UI
}
Conditional WebSocket Connection
import { useWebSocket } from '../hooks/useWebSocket';
import { useAuth } from '../hooks/useAuth';
function ConditionalWebSocket() {
const { user } = useAuth();
// Only connect if user is logged in
useWebSocket({
autoConnect: !!user,
});
return null;
}
Send Messages to Server
import { useWebSocket } from '../hooks/useWebSocket';
function PingComponent() {
const { send, isConnected } = useWebSocket();
const sendPing = () => {
send({
type: 'ping',
data: {},
timestamp: new Date().toISOString(),
});
};
return (
<button onClick={sendPing} disabled={!isConnected}>
Send Ping
</button>
);
}
Admin Monitoring
Get WebSocket Stats
Admin users can check WebSocket connection statistics:
# Get connection stats
curl -H "Authorization: Bearer <admin-token>" \
http://localhost:3001/api/admin/websocket/stats
Response:
{
"success": true,
"data": {
"totalUsers": 42,
"totalConnections": 67
}
}
Admin Dashboard Integration
import { useEffect, useState } from 'react';
function AdminWebSocketStats() {
const [stats, setStats] = useState({ totalUsers: 0, totalConnections: 0 });
useEffect(() => {
const fetchStats = async () => {
const response = await fetch('/api/admin/websocket/stats', {
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
setStats(data.data);
};
fetchStats();
const interval = setInterval(fetchStats, 5000); // Poll every 5s
return () => clearInterval(interval);
}, []);
return (
<div className="p-4 border rounded">
<h3>WebSocket Stats</h3>
<p>Connected Users: {stats.totalUsers}</p>
<p>Total Connections: {stats.totalConnections}</p>
</div>
);
}
Troubleshooting
Connection Issues
- Check JWT Token: WebSocket requires a valid JWT token in cookies or query string
- Check Server Logs: Look for WebSocket connection errors in server logs
- Check Browser Console: WebSocket errors are logged to console
- Verify Path: WebSocket server is at
ws://localhost:3001/ws(orwss://for HTTPS)
Not Receiving Notifications
- Check Connection Status: Use
<ConnectionStatus />to verify connection - Verify Event Name: Ensure you're listening to the correct event (
notification:deal, etc.) - Check User ID: Notifications are sent to specific users - verify JWT user_id matches
High Memory Usage
- Connection Leaks: Ensure components using
useWebSocketare properly unmounting - Event Listeners:
useEventBusautomatically cleans up, but verify no manual listeners remain - Check Stats: Use
/api/admin/websocket/statsto monitor connection count
Testing
Unit Tests
import { renderHook } from '@testing-library/react';
import { useWebSocket } from '../hooks/useWebSocket';
describe('useWebSocket', () => {
it('should connect automatically', () => {
const { result } = renderHook(() => useWebSocket({ autoConnect: true }));
expect(result.current.isConnecting).toBe(true);
});
});
Integration Tests
See src/tests/integration/websocket.integration.test.ts for comprehensive integration tests.