import { useChatStore } from '@/stores/chatStore'; import { useSocketStore } from '@/stores/socketStore'; import type { Message, MessageReaction } from '@/types'; import { io, type Socket } from 'socket.io-client'; import { toast } from 'sonner'; class SocketService { private socket: Socket | null = null; private listenersInitialized = false; connect(token: string): Socket { if (this.socket && this.socket.connected) { return this.socket; } const socketUrl = import.meta.env.VITE_SOCKET_URL || 'http://localhost:3000'; this.socket = io(socketUrl, { auth: { token, }, }); if (!this.listenersInitialized) { this.setupEventListeners(); this.listenersInitialized = true; } useSocketStore.getState().setSocket(this.socket); return this.socket; } disconnect() { if (this.socket) { this.removeAllListeners(); this.socket.disconnect(); this.socket = null; useSocketStore.getState().setSocket(null); useSocketStore.getState().setConnected(false); } } private removeAllListeners() { if (!this.socket) return; this.socket.removeAllListeners('new_message'); this.socket.removeAllListeners('message_reaction_updated'); this.socket.removeAllListeners('user_online'); this.socket.removeAllListeners('user_offline'); this.socket.removeAllListeners('rate_limit_exceeded'); this.socket.removeAllListeners('error'); this.socket.removeAllListeners('connect'); this.socket.removeAllListeners('disconnect'); this.socket.removeAllListeners('user_typing'); this.socket.removeAllListeners('user_stopped_typing'); } getSocket(): Socket | null { return this.socket; } private setupEventListeners() { if (!this.socket) return; this.removeAllListeners(); const { setConnected } = useSocketStore.getState(); const { addMessage, updateMessageReactions, addOnlineUser, removeOnlineUser, addTypingUser, removeTypingUser, } = useChatStore.getState(); this.socket.on('connect', () => { console.log('Connected to server'); setConnected(true); }); this.socket.on('disconnect', () => { console.log('Disconnected from server'); setConnected(false); }); this.socket.on('new_message', (message: Message) => { addMessage(message); }); this.socket.on( 'message_reaction_updated', (data: { messageId: string; reactions: MessageReaction[] }) => { updateMessageReactions(data.messageId, data.reactions); } ); this.socket.on( 'user_online', (data: { userId: string; username: string }) => { addOnlineUser(data.userId); } ); this.socket.on( 'online_users', (users: { id: string; username: string[] }[]) => { const onlineUserIds = new Set(users.map((user) => user.id)); useChatStore.getState().setOnlineUsers(onlineUserIds); } ); this.socket.on( 'user_offline', (data: { userId: string; username: string }) => { removeOnlineUser(data.userId); } ); this.socket.on( 'rate_limit_exceeded', (data: { message: string; remainingTime?: number }) => { console.warn('Rate limit exceeded:', data.message); toast.error(`Rate limit exceeded: ${data.message}`, { duration: data.remainingTime ? data.remainingTime * 1000 : 5000, position: 'top-right', style: { background: '#f8d7da', color: '#721c24', border: '1px solid #f5c6cb', }, }); } ); this.socket.on( 'user_typing', (data: { roomId: string; username: string }) => { addTypingUser(data.roomId, data.username); } ); this.socket.on( 'user_stopped_typing', (data: { roomId: string; username: string }) => { removeTypingUser(data.roomId, data.username); } ); this.socket.on( 'message_seen_update', (data: { messageId: string; seenBy: string[] }) => { Object.keys(useChatStore.getState().messages).forEach((roomId) => { useChatStore.getState().messages[roomId].forEach((msg) => { if (msg.id === data.messageId) { msg.seenBy = data.seenBy; } }); }); } ); this.socket.on('error', (data: { message: string }) => { console.error('Socket error:', data.message); toast.error(`Socket error: ${data.message}`, { duration: 5000, position: 'top-right', }); }); } } export const socketService = new SocketService();