Files
flyer-crawler.projectium.com/docs/WEBSOCKET_USAGE.md
Torben Sorensen cf476e7afc
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m47s
ADR-022 - websocket notificaitons - also more test fixes with stores
2026-01-19 10:53:42 -08:00

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)