import type { Server as HTTPServer } from 'http'; import { Server } from 'socket.io'; import { verifyAccessToken } from '../utils/jwt.js'; import { prisma } from '../config/database.js'; // Online status tracking const onlineUsers = new Map>(); export function setupSocket(httpServer: HTTPServer) { const io = new Server(httpServer, { cors: { origin: process.env['CLIENT_URL'] || 'http://localhost:5173', credentials: true, }, }); io.use((socket, next) => { const token = socket.handshake.auth.token; if (!token) return next(new Error('Authentication required')); try { const payload = verifyAccessToken(token); socket.data.userId = payload.userId; next(); } catch { next(new Error('Invalid token')); } }); io.on('connection', (socket) => { const userId = socket.data.userId; socket.join(`user:${userId}`); // Track online status if (!onlineUsers.has(userId)) { onlineUsers.set(userId, new Set()); } onlineUsers.get(userId)!.add(socket.id); // Broadcast online status socket.broadcast.emit('user_online', { userId }); socket.on('join_conversation', (conversationId: string) => { socket.join(`conversation:${conversationId}`); }); socket.on('leave_conversation', (conversationId: string) => { socket.leave(`conversation:${conversationId}`); }); socket.on('send_message', async (data: { conversationId: string; content: string; offerAmount?: number }) => { try { const message = await prisma.message.create({ data: { content: data.content, senderId: userId, conversationId: data.conversationId, offerAmount: data.offerAmount, }, include: { sender: { select: { id: true, fullName: true, avatar: true } } }, }); await prisma.conversation.update({ where: { id: data.conversationId }, data: { updatedAt: new Date() }, }); io.to(`conversation:${data.conversationId}`).emit('new_message', message); const conversation = await prisma.conversation.findUnique({ where: { id: data.conversationId } }); if (conversation) { const recipientId = conversation.user1Id === userId ? conversation.user2Id : conversation.user1Id; io.to(`user:${recipientId}`).emit('message_notification', { conversationId: data.conversationId, message }); // Create notification const sender = await prisma.user.findUnique({ where: { id: userId }, select: { fullName: true } }); const notification = await prisma.notification.create({ data: { userId: recipientId, type: 'NEW_MESSAGE', title: 'New Message', body: `${sender?.fullName || 'Someone'} sent you a message`, data: { conversationId: data.conversationId }, }, }); io.to(`user:${recipientId}`).emit('new_notification', notification); } } catch (error) { socket.emit('error', { message: 'Failed to send message' }); } }); socket.on('typing', (conversationId: string) => { socket.to(`conversation:${conversationId}`).emit('user_typing', { userId, conversationId }); }); socket.on('stop_typing', (conversationId: string) => { socket.to(`conversation:${conversationId}`).emit('user_stop_typing', { userId, conversationId }); }); socket.on('mark_read', async (conversationId: string) => { await prisma.message.updateMany({ where: { conversationId, senderId: { not: userId }, isRead: false }, data: { isRead: true }, }); }); socket.on('get_online_users', () => { const users = Array.from(onlineUsers.keys()); socket.emit('online_users', users); }); socket.on('disconnect', () => { const userSockets = onlineUsers.get(userId); if (userSockets) { userSockets.delete(socket.id); if (userSockets.size === 0) { onlineUsers.delete(userId); socket.broadcast.emit('user_offline', { userId }); } } }); }); return io; }