From 7c5d0a4badbe5f132753f2558e906901a2be85be Mon Sep 17 00:00:00 2001 From: Kevin Puig <119972216+k-puig@users.noreply.github.com> Date: Sun, 28 Sep 2025 05:58:06 -0400 Subject: [PATCH] Handle disconnect (willful and otherwise) --- concord-server/src/sockets/voiceHandler.ts | 109 +++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/concord-server/src/sockets/voiceHandler.ts b/concord-server/src/sockets/voiceHandler.ts index 7be95c8..f2adf8c 100644 --- a/concord-server/src/sockets/voiceHandler.ts +++ b/concord-server/src/sockets/voiceHandler.ts @@ -64,7 +64,116 @@ export function registerVoiceHandlers(io: Server) { connectedUserIds: Array.from(channelMembers.keys()).filter(e => e !== payload.userId) }); socket.to(payload.voiceChannelId).emit("user-joined-voicechannel", { userId: payload.userId }); + + // Store userId in socket.data for easier access later + socket.data.userId = payload.userId; + socket.data.currentVoiceChannelId = payload.voiceChannelId; }); + // Leave voice channel + socket.on("leave-voicechannel", async (data) => { + const payload = data as { + userId: string, + userToken: string, + voiceChannelId: string, + }; + if (!payload) { + socket.emit("error-voicechannel", "no payload in leave voice request"); + return; + } + + const channelMembers = voiceChannelMembers.get(payload.voiceChannelId); + if (!channelMembers) { + socket.emit("error-voicechannel", "voice channel not found"); + return; + } + + // authenticate user + const userCreds = await getUserCredentials(payload.userId); + if (!userCreds || !userCreds.token || userCreds.token != payload.userToken) { + socket.emit("error-voicechannel", "bad user creds in leave voice request"); + return; + } + + // Remove user from channel + if (channelMembers.has(payload.userId)) { + channelMembers.delete(payload.userId); + + // Leave the socket.io room + socket.leave(payload.voiceChannelId); + + // Notify other users in the channel + socket.to(payload.voiceChannelId).emit("user-left-voicechannel", { + userId: payload.userId, + voiceChannelId: payload.voiceChannelId + }); + + // Clean up empty channels + if (channelMembers.size === 0) { + voiceChannelMembers.delete(payload.voiceChannelId); + } + + // Confirm to the user that they've left + socket.emit("left-voicechannel", { + voiceChannelId: payload.voiceChannelId + }); + + // Clear socket data + socket.data.currentVoiceChannelId = undefined; + } else { + socket.emit("error-voicechannel", "user not in voice channel"); + } + }); + + // Handle disconnection + socket.on("disconnect", () => { + // Get the user ID and current voice channel from socket data + const userId = socket.data.userId; + const voiceChannelId = socket.data.currentVoiceChannelId; + + // If we have the channel ID stored, use it directly + if (userId && voiceChannelId) { + const channelMembers = voiceChannelMembers.get(voiceChannelId); + if (channelMembers && channelMembers.has(userId)) { + // Remove the user from the channel + channelMembers.delete(userId); + + // Notify other members + socket.to(voiceChannelId).emit("user-left-voicechannel", { + userId, + voiceChannelId, + reason: "disconnected" + }); + + // Clean up empty channels + if (channelMembers.size === 0) { + voiceChannelMembers.delete(voiceChannelId); + } + } + } else { + // If we don't have the info stored, search through all channels + voiceChannelMembers.forEach((members, channelId) => { + // Use Array.from to convert Map entries to array for iteration + Array.from(members.entries()).forEach(([memberId, memberSocket]) => { + if (memberSocket.id === socket.id) { + // Found the user in this channel + members.delete(memberId); + + // Notify other members + socket.to(channelId).emit("user-left-voicechannel", { + userId: memberId, + voiceChannelId: channelId, + reason: "disconnected" + }); + + // Clean up empty channels + if (members.size === 0) { + voiceChannelMembers.delete(channelId); + } + } + }); + }); + } + }); }); }