All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m47s
412 lines
9.9 KiB
Markdown
412 lines
9.9 KiB
Markdown
# 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`:
|
|
|
|
```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
|
|
|
|
```tsx
|
|
// 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
|
|
|
|
```tsx
|
|
// 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 clicked
|
|
- `showConnectionStatus?: boolean` - Show green/red/yellow connection dot (default: `true`)
|
|
- `className?: string` - Custom CSS classes
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
<NotificationBell
|
|
onClick={() => navigate('/notifications')}
|
|
showConnectionStatus={true}
|
|
className="mr-4"
|
|
/>
|
|
```
|
|
|
|
### `ConnectionStatus`
|
|
|
|
A simple status indicator showing if WebSocket is connected (no bell icon).
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
<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:**
|
|
|
|
```tsx
|
|
<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 connection
|
|
- `onDisconnect?: () => void` - Callback on disconnect
|
|
- `onError?: (error: Event) => void` - Callback on error
|
|
|
|
**Returns:**
|
|
|
|
- `isConnected: boolean` - Connection status
|
|
- `isConnecting: boolean` - Connecting state
|
|
- `error: string | null` - Error message if any
|
|
- `connect: () => void` - Manual connect function
|
|
- `disconnect: () => void` - Manual disconnect function
|
|
- `send: (message: WebSocketMessage) => void` - Send message to server
|
|
|
|
**Example:**
|
|
|
|
```tsx
|
|
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 for
|
|
- `callback: (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:**
|
|
|
|
```tsx
|
|
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
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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
|
|
|
|
```tsx
|
|
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:
|
|
|
|
```bash
|
|
# Get connection stats
|
|
curl -H "Authorization: Bearer <admin-token>" \
|
|
http://localhost:3001/api/admin/websocket/stats
|
|
```
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"totalUsers": 42,
|
|
"totalConnections": 67
|
|
}
|
|
}
|
|
```
|
|
|
|
### Admin Dashboard Integration
|
|
|
|
```tsx
|
|
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
|
|
|
|
1. **Check JWT Token**: WebSocket requires a valid JWT token in cookies or query string
|
|
2. **Check Server Logs**: Look for WebSocket connection errors in server logs
|
|
3. **Check Browser Console**: WebSocket errors are logged to console
|
|
4. **Verify Path**: WebSocket server is at `ws://localhost:3001/ws` (or `wss://` for HTTPS)
|
|
|
|
### Not Receiving Notifications
|
|
|
|
1. **Check Connection Status**: Use `<ConnectionStatus />` to verify connection
|
|
2. **Verify Event Name**: Ensure you're listening to the correct event (`notification:deal`, etc.)
|
|
3. **Check User ID**: Notifications are sent to specific users - verify JWT user_id matches
|
|
|
|
### High Memory Usage
|
|
|
|
1. **Connection Leaks**: Ensure components using `useWebSocket` are properly unmounting
|
|
2. **Event Listeners**: `useEventBus` automatically cleans up, but verify no manual listeners remain
|
|
3. **Check Stats**: Use `/api/admin/websocket/stats` to monitor connection count
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
|
|
```typescript
|
|
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](../src/tests/integration/websocket.integration.test.ts) for comprehensive integration tests.
|
|
|
|
## Related Documentation
|
|
|
|
- [ADR-022: Real-time Notification System](./adr/0022-real-time-notification-system.md)
|
|
- [ADR-036: Event Bus and Pub/Sub Pattern](./adr/0036-event-bus-and-pub-sub-pattern.md)
|
|
- [ADR-042: Email and Notification Architecture](./adr/0042-email-and-notification-architecture.md)
|