|
import { Server, Socket } from 'socket.io' |
|
import jwt from 'jsonwebtoken' |
|
|
|
interface AuthenticatedSocket extends Socket { |
|
userId?: string |
|
user?: any |
|
} |
|
|
|
|
|
const activeConnections = new Map<string, AuthenticatedSocket>() |
|
|
|
export function setupSocketHandlers(io: Server) { |
|
|
|
io.use(async (socket: AuthenticatedSocket, next) => { |
|
try { |
|
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.replace('Bearer ', '') |
|
|
|
if (!token) { |
|
return next(new Error('Authentication error: No token provided')) |
|
} |
|
|
|
|
|
|
|
const decoded = { userId: 'user-1', email: 'user@example.com' } |
|
|
|
socket.userId = decoded.userId |
|
socket.user = decoded |
|
next() |
|
} catch (error) { |
|
next(new Error('Authentication error: Invalid token')) |
|
} |
|
}) |
|
|
|
io.on('connection', (socket: AuthenticatedSocket) => { |
|
console.log(`User ${socket.userId} connected`) |
|
|
|
|
|
if (socket.userId) { |
|
activeConnections.set(socket.userId, socket) |
|
} |
|
|
|
|
|
if (socket.userId) { |
|
socket.join(`user:${socket.userId}`) |
|
} |
|
|
|
|
|
socket.on('join_chat', (chatId: string) => { |
|
socket.join(`chat:${chatId}`) |
|
console.log(`User ${socket.userId} joined chat ${chatId}`) |
|
}) |
|
|
|
|
|
socket.on('leave_chat', (chatId: string) => { |
|
socket.leave(`chat:${chatId}`) |
|
console.log(`User ${socket.userId} left chat ${chatId}`) |
|
}) |
|
|
|
|
|
socket.on('send_message', async (data: { |
|
chatId: string |
|
content: string |
|
type?: string |
|
replyTo?: string |
|
}) => { |
|
try { |
|
|
|
const message = { |
|
id: `msg-${Date.now()}`, |
|
chatId: data.chatId, |
|
senderId: socket.userId, |
|
content: data.content, |
|
type: data.type || 'text', |
|
replyTo: data.replyTo, |
|
createdAt: new Date(), |
|
sender: socket.user |
|
} |
|
|
|
|
|
socket.to(`chat:${data.chatId}`).emit('new_message', message) |
|
|
|
|
|
socket.emit('message_sent', { messageId: message.id, status: 'sent' }) |
|
|
|
console.log(`Message sent in chat ${data.chatId} by user ${socket.userId}`) |
|
} catch (error) { |
|
socket.emit('error', { message: 'Failed to send message' }) |
|
} |
|
}) |
|
|
|
|
|
socket.on('typing_start', (chatId: string) => { |
|
socket.to(`chat:${chatId}`).emit('user_typing', { |
|
userId: socket.userId, |
|
chatId, |
|
isTyping: true |
|
}) |
|
}) |
|
|
|
socket.on('typing_stop', (chatId: string) => { |
|
socket.to(`chat:${chatId}`).emit('user_typing', { |
|
userId: socket.userId, |
|
chatId, |
|
isTyping: false |
|
}) |
|
}) |
|
|
|
|
|
socket.on('add_reaction', async (data: { |
|
messageId: string |
|
emoji: string |
|
}) => { |
|
try { |
|
|
|
const reaction = { |
|
messageId: data.messageId, |
|
userId: socket.userId, |
|
emoji: data.emoji, |
|
createdAt: new Date() |
|
} |
|
|
|
|
|
io.emit('reaction_added', reaction) |
|
|
|
console.log(`Reaction ${data.emoji} added to message ${data.messageId} by user ${socket.userId}`) |
|
} catch (error) { |
|
socket.emit('error', { message: 'Failed to add reaction' }) |
|
} |
|
}) |
|
|
|
|
|
socket.on('message_read', async (data: { |
|
messageId: string |
|
chatId: string |
|
}) => { |
|
try { |
|
|
|
|
|
|
|
socket.to(`chat:${data.chatId}`).emit('message_status_updated', { |
|
messageId: data.messageId, |
|
status: 'read', |
|
readBy: socket.userId, |
|
readAt: new Date() |
|
}) |
|
|
|
console.log(`Message ${data.messageId} marked as read by user ${socket.userId}`) |
|
} catch (error) { |
|
socket.emit('error', { message: 'Failed to update message status' }) |
|
} |
|
}) |
|
|
|
|
|
socket.on('update_presence', (status: 'online' | 'away' | 'busy' | 'offline') => { |
|
|
|
|
|
|
|
socket.broadcast.emit('user_presence_updated', { |
|
userId: socket.userId, |
|
status, |
|
lastSeen: new Date() |
|
}) |
|
|
|
console.log(`User ${socket.userId} presence updated to ${status}`) |
|
}) |
|
|
|
|
|
socket.on('disconnect', (reason) => { |
|
console.log(`User ${socket.userId} disconnected: ${reason}`) |
|
|
|
|
|
if (socket.userId) { |
|
activeConnections.delete(socket.userId) |
|
} |
|
|
|
|
|
socket.broadcast.emit('user_presence_updated', { |
|
userId: socket.userId, |
|
status: 'offline', |
|
lastSeen: new Date() |
|
}) |
|
}) |
|
|
|
|
|
socket.on('error', (error) => { |
|
console.error(`Socket error for user ${socket.userId}:`, error) |
|
}) |
|
}) |
|
|
|
|
|
const sendToUser = (userId: string, event: string, data: any) => { |
|
const userSocket = activeConnections.get(userId) |
|
if (userSocket) { |
|
userSocket.emit(event, data) |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
|
|
const sendToChat = (chatId: string, event: string, data: any) => { |
|
io.to(`chat:${chatId}`).emit(event, data) |
|
} |
|
|
|
|
|
return { |
|
sendToUser, |
|
sendToChat, |
|
getActiveConnections: () => activeConnections, |
|
getConnectionCount: () => activeConnections.size |
|
} |
|
} |
|
|