import { connectedUsers } from './state'; import { ConnectedUser } from './types'; import { broadcastToServer, findUserByOderId } from './broadcast'; interface WsMessage { [key: string]: unknown; type: string; } /** Sends the current user list for a given server to a single connected user. */ function sendServerUsers(user: ConnectedUser, serverId: string): void { const users = Array.from(connectedUsers.values()) .filter(cu => cu.serverIds.has(serverId) && cu.oderId !== user.oderId && cu.displayName) .map(cu => ({ oderId: cu.oderId, displayName: cu.displayName ?? 'Anonymous' })); user.ws.send(JSON.stringify({ type: 'server_users', serverId, users })); } function handleIdentify(user: ConnectedUser, message: WsMessage, connectionId: string): void { user.oderId = String(message['oderId'] || connectionId); user.displayName = String(message['displayName'] || 'Anonymous'); connectedUsers.set(connectionId, user); console.log(`User identified: ${user.displayName} (${user.oderId})`); } function handleJoinServer(user: ConnectedUser, message: WsMessage, connectionId: string): void { const sid = String(message['serverId']); const isNew = !user.serverIds.has(sid); user.serverIds.add(sid); user.viewedServerId = sid; connectedUsers.set(connectionId, user); console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) joined server ${sid} (new=${isNew})`); sendServerUsers(user, sid); if (isNew) { broadcastToServer(sid, { type: 'user_joined', oderId: user.oderId, displayName: user.displayName ?? 'Anonymous', serverId: sid }, user.oderId); } } function handleViewServer(user: ConnectedUser, message: WsMessage, connectionId: string): void { const viewSid = String(message['serverId']); user.viewedServerId = viewSid; connectedUsers.set(connectionId, user); console.log(`User ${user.displayName ?? 'Anonymous'} (${user.oderId}) viewing server ${viewSid}`); sendServerUsers(user, viewSid); } function handleLeaveServer(user: ConnectedUser, message: WsMessage, connectionId: string): void { const leaveSid = (message['serverId'] as string | undefined) ?? user.viewedServerId; if (!leaveSid) return; user.serverIds.delete(leaveSid); if (user.viewedServerId === leaveSid) user.viewedServerId = undefined; connectedUsers.set(connectionId, user); broadcastToServer(leaveSid, { type: 'user_left', oderId: user.oderId, displayName: user.displayName ?? 'Anonymous', serverId: leaveSid }, user.oderId); } function forwardRtcMessage(user: ConnectedUser, message: WsMessage): void { const targetUserId = String(message['targetUserId'] || ''); console.log(`Forwarding ${message.type} from ${user.oderId} to ${targetUserId}`); const targetUser = findUserByOderId(targetUserId); if (targetUser) { targetUser.ws.send(JSON.stringify({ ...message, fromUserId: user.oderId })); console.log(`Successfully forwarded ${message.type} to ${targetUserId}`); } else { console.log( `Target user ${targetUserId} not found. Connected users:`, Array.from(connectedUsers.values()).map(cu => ({ oderId: cu.oderId, displayName: cu.displayName })) ); } } function handleChatMessage(user: ConnectedUser, message: WsMessage): void { const chatSid = (message['serverId'] as string | undefined) ?? user.viewedServerId; if (chatSid && user.serverIds.has(chatSid)) { broadcastToServer(chatSid, { type: 'chat_message', serverId: chatSid, message: message['message'], senderId: user.oderId, senderName: user.displayName, timestamp: Date.now() }); } } function handleTyping(user: ConnectedUser, message: WsMessage): void { const typingSid = (message['serverId'] as string | undefined) ?? user.viewedServerId; if (typingSid && user.serverIds.has(typingSid)) { broadcastToServer(typingSid, { type: 'user_typing', serverId: typingSid, oderId: user.oderId, displayName: user.displayName }, user.oderId); } } export function handleWebSocketMessage(connectionId: string, message: WsMessage): void { const user = connectedUsers.get(connectionId); if (!user) return; switch (message.type) { case 'identify': handleIdentify(user, message, connectionId); break; case 'join_server': handleJoinServer(user, message, connectionId); break; case 'view_server': handleViewServer(user, message, connectionId); break; case 'leave_server': handleLeaveServer(user, message, connectionId); break; case 'offer': case 'answer': case 'ice_candidate': forwardRtcMessage(user, message); break; case 'chat_message': handleChatMessage(user, message); break; case 'typing': handleTyping(user, message); break; default: console.log('Unknown message type:', message.type); } }