feat: implement real-time chat with Socket.IO

Implemented WebSocket-based real-time messaging for both event rooms and private match chats using Socket.IO with comprehensive test coverage.

Backend changes:
- Installed socket.io@4.8.1 for WebSocket server
- Created Socket.IO server with JWT authentication middleware
- Implemented event room management (join/leave/messages)
- Added active users tracking with real-time updates
- Implemented private match room messaging
- Integrated Socket.IO with Express HTTP server
- Messages are persisted to PostgreSQL via Prisma
- Added 12 comprehensive unit tests (89.13% coverage)

Frontend changes:
- Installed socket.io-client for WebSocket connections
- Created socket service layer for connection management
- Updated EventChatPage with real-time messaging
- Updated MatchChatPage with real-time private chat
- Added connection status indicators (● Connected/Disconnected)
- Disabled message input when not connected

Infrastructure:
- Updated nginx config to proxy WebSocket connections at /socket.io
- Added Upgrade and Connection headers for WebSocket support
- Set long timeouts (7d) for persistent WebSocket connections

Key features:
- JWT-authenticated socket connections
- Room-based architecture for events and matches
- Real-time message broadcasting
- Active users list with automatic updates
- Automatic cleanup on disconnect
- Message persistence in database

Test coverage:
- 12 tests passing (authentication, event rooms, match rooms, disconnect, errors)
- Socket.IO module: 89.13% statements, 81.81% branches, 91.66% functions
- Overall coverage: 81.19%

Phase 1, Step 4 completed. Ready for Phase 2 (Core Features).
This commit is contained in:
Radosław Gierwiało
2025-11-12 22:42:15 +01:00
parent 3788274f73
commit 75cb4b16e7
11 changed files with 1472 additions and 63 deletions

View File

@@ -0,0 +1,56 @@
import { io } from 'socket.io-client';
const SOCKET_URL = 'http://localhost:8080';
let socket = null;
export function connectSocket() {
const token = localStorage.getItem('token');
if (!token) {
console.error('No token found for socket connection');
return null;
}
if (socket && socket.connected) {
return socket;
}
socket = io(SOCKET_URL, {
auth: {
token,
},
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
});
socket.on('connect', () => {
console.log('✅ Socket connected:', socket.id);
});
socket.on('disconnect', (reason) => {
console.log('❌ Socket disconnected:', reason);
});
socket.on('connect_error', (error) => {
console.error('Socket connection error:', error.message);
});
socket.on('error', (error) => {
console.error('Socket error:', error);
});
return socket;
}
export function disconnectSocket() {
if (socket) {
socket.disconnect();
socket = null;
}
}
export function getSocket() {
return socket;
}