Add Socket.IO integration with useSocket hook and socketService

- Introduced a new useSocket hook for managing Socket.IO connections in React components, providing methods for emitting and listening to events.
- Created a socketService class to handle the Socket.IO connection, including automatic reconnection and event management.
- Implemented token-based authentication for socket connections using the Zustand auth store, ensuring secure communication with the server.
- Added event handlers for connection status and error management to enhance reliability.

These changes enable real-time communication capabilities in the application, improving user interaction and responsiveness.
This commit is contained in:
Steven Enamakel
2026-01-28 04:14:27 +05:30
parent 781439842b
commit 8c46040e20
2 changed files with 276 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
import { useEffect, useRef } from 'react';
import { socketService } from '../services/socketService';
import type { Socket } from 'socket.io-client';
interface UseSocketOptions {
autoConnect?: boolean;
}
/**
* React hook for using the Socket.IO connection
*
* @example
* ```tsx
* const { socket, isConnected, emit, on, off } = useSocket();
*
* useEffect(() => {
* on('ready', () => {
* console.log('Socket ready!');
* });
*
* return () => {
* off('ready');
* };
* }, [on, off]);
* ```
*/
export const useSocket = (options: UseSocketOptions = {}) => {
const { autoConnect = true } = options;
const listenersRef = useRef<Array<{ event: string; callback: (...args: unknown[]) => void }>>([]);
useEffect(() => {
if (autoConnect) {
socketService.connect();
}
return () => {
// Cleanup: remove all listeners registered through this hook
listenersRef.current.forEach(({ event, callback }) => {
socketService.off(event, callback);
});
listenersRef.current = [];
};
}, [autoConnect]);
const emit = (event: string, data?: unknown) => {
socketService.emit(event, data);
};
const on = (event: string, callback: (...args: unknown[]) => void) => {
socketService.on(event, callback);
listenersRef.current.push({ event, callback });
};
const off = (event: string, callback?: (...args: unknown[]) => void) => {
socketService.off(event, callback);
if (callback) {
listenersRef.current = listenersRef.current.filter(
(listener) => listener.event !== event || listener.callback !== callback
);
} else {
listenersRef.current = listenersRef.current.filter((listener) => listener.event !== event);
}
};
const once = (event: string, callback: (...args: unknown[]) => void) => {
socketService.once(event, callback);
};
return {
socket: socketService.getSocket() as Socket | null,
isConnected: socketService.isConnected(),
emit,
on,
off,
once,
connect: () => socketService.connect(),
disconnect: () => socketService.disconnect(),
};
};
+197
View File
@@ -0,0 +1,197 @@
import { io, Socket } from 'socket.io-client';
import { BACKEND_URL } from '../utils/config';
import { useAuthStore } from '../store/authStore';
class SocketService {
private socket: Socket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000; // Start with 1 second
/**
* Connect to the Socket.IO server using the JWT token from the auth store
*/
connect(): void {
const token = useAuthStore.getState().token;
if (!token) {
console.warn('[SocketService] No token available, cannot connect');
return;
}
// Disconnect existing connection if any
if (this.socket?.connected) {
this.disconnect();
}
console.log('[SocketService] Connecting to socket server...');
this.socket = io(BACKEND_URL, {
auth: {
token,
},
path: '/socket.io/',
transports: ['polling', 'websocket'],
reconnection: true,
reconnectionAttempts: this.maxReconnectAttempts,
reconnectionDelay: this.reconnectDelay,
reconnectionDelayMax: 5000,
});
this.setupEventHandlers();
}
/**
* Disconnect from the Socket.IO server
*/
disconnect(): void {
if (this.socket) {
console.log('[SocketService] Disconnecting from socket server...');
this.socket.disconnect();
this.socket = null;
this.reconnectAttempts = 0;
}
}
/**
* Check if the socket is connected
*/
isConnected(): boolean {
return this.socket?.connected ?? false;
}
/**
* Get the socket instance (use with caution)
*/
getSocket(): Socket | null {
return this.socket;
}
/**
* Emit an event to the server
*/
emit(event: string, data?: unknown): void {
if (!this.socket?.connected) {
console.warn(`[SocketService] Cannot emit '${event}': socket not connected`);
return;
}
this.socket.emit(event, data);
}
/**
* Listen to an event from the server
*/
on(event: string, callback: (...args: unknown[]) => void): void {
if (!this.socket) {
console.warn(`[SocketService] Cannot listen to '${event}': socket not initialized`);
return;
}
this.socket.on(event, callback);
}
/**
* Remove an event listener
*/
off(event: string, callback?: (...args: unknown[]) => void): void {
if (!this.socket) {
return;
}
this.socket.off(event, callback);
}
/**
* Listen to an event once
*/
once(event: string, callback: (...args: unknown[]) => void): void {
if (!this.socket) {
console.warn(`[SocketService] Cannot listen to '${event}': socket not initialized`);
return;
}
this.socket.once(event, callback);
}
/**
* Setup event handlers for connection, disconnection, and errors
*/
private setupEventHandlers(): void {
if (!this.socket) {
return;
}
this.socket.on('connect', () => {
console.log('[SocketService] Connected to socket server:', this.socket?.id);
this.reconnectAttempts = 0;
});
this.socket.on('disconnect', (reason) => {
console.log('[SocketService] Disconnected from socket server:', reason);
});
this.socket.on('connect_error', (error) => {
console.error('[SocketService] Connection error:', error.message);
this.reconnectAttempts++;
// If max attempts reached, try reconnecting with fresh token
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('[SocketService] Max reconnection attempts reached, reconnecting with fresh token...');
setTimeout(() => {
this.connect();
}, this.reconnectDelay * 2);
}
});
this.socket.on('ready', () => {
console.log('[SocketService] Server ready');
});
this.socket.on('error', (error: { message?: string; status?: number; requestId?: string }) => {
console.error('[SocketService] Server error:', error);
});
}
}
// Export a singleton instance
export const socketService = new SocketService();
// Auto-connect when token is available
// Listen to auth store changes
if (typeof window !== 'undefined') {
let previousToken: string | null = null;
const checkAndUpdateConnection = () => {
const currentToken = useAuthStore.getState().token;
// Only update connection if token actually changed
if (currentToken !== previousToken) {
previousToken = currentToken;
if (currentToken && !socketService.isConnected()) {
socketService.connect();
} else if (!currentToken && socketService.isConnected()) {
socketService.disconnect();
}
}
};
// Initial connection check
checkAndUpdateConnection();
// Subscribe to auth store changes
useAuthStore.subscribe((state) => {
const currentToken = state.token;
if (currentToken !== previousToken) {
previousToken = currentToken;
if (currentToken && !socketService.isConnected()) {
socketService.connect();
} else if (!currentToken && socketService.isConnected()) {
socketService.disconnect();
}
}
});
}