Files
concord/concord-server/src/sockets/voiceHandler.ts

195 lines
6.8 KiB
TypeScript
Raw Normal View History

2025-09-28 03:18:11 -04:00
import { Server, Socket } from "socket.io";
2025-09-28 05:39:05 -04:00
import { getUserCredentials, getUserInformation } from "../services/userService";
import { getAllInstances, getInstanceByChannelId, getInstancesByUserId } from "../services/instanceService";
import { getCategoriesByInstance, getCategory, getChannel } from "../services/channelService";
// Change to Map of voiceChannelId to Map of userId to socket
const voiceChannelMembers = new Map<string, Map<string, Socket>>();
2025-09-28 03:18:11 -04:00
export function registerVoiceHandlers(io: Server) {
io.on("connection", (socket: Socket) => {
2025-09-28 05:39:05 -04:00
// Join voice channel
socket.on("join-voicechannel", async (data) => {
const payload = data as {
userId: string
userToken: string,
voiceChannelId: string,
};
if (!payload) {
socket.emit("error-voicechannel", "no payload in voice conn")
return;
}
2025-09-28 03:18:11 -04:00
2025-09-28 05:39:05 -04:00
// Initialize map for channel if not present
if (!voiceChannelMembers.has(payload.voiceChannelId)) {
voiceChannelMembers.set(payload.voiceChannelId, new Map());
}
2025-09-28 03:18:11 -04:00
2025-09-28 05:39:05 -04:00
const channelMembers = voiceChannelMembers.get(payload.voiceChannelId)!;
2025-09-28 03:18:11 -04:00
2025-09-28 05:39:05 -04:00
// Remove user if already present in this channel
if (channelMembers.has(payload.userId)) {
channelMembers.delete(payload.userId);
}
// authenticate user
const userCreds = await getUserCredentials(payload.userId);
if (!userCreds || !userCreds.token || userCreds.token != payload.userToken) {
socket.emit("error-voicechannel", "bad user creds in voice conn");
return;
}
2025-09-28 03:18:11 -04:00
2025-09-28 05:39:05 -04:00
// determine if channel is voice channel
const channel = await getChannel(payload.voiceChannelId);
if (!channel || channel.type !== "voice" || !channel.categoryId) {
socket.emit("error-voicechannel", "bad channel or channel type in voice conn");
return;
}
// authorize user using role
const user = await getUserInformation(payload.userId);
const instance = await getInstanceByChannelId(payload.voiceChannelId);
const instances = await getInstancesByUserId(payload.userId);
if (!user || !instance || !instances || !instances.find(e => e.id === instance.id)) {
socket.emit("error-voicechannel", "user not authorized for channel in voice conn");
return;
}
// add to map
channelMembers.set(payload.userId, socket);
socket.join(payload.voiceChannelId);
socket.emit("joined-voicechannel", {
voiceChannelId: payload.voiceChannelId,
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;
2025-09-28 03:18:11 -04:00
});
2025-09-28 05:39:05 -04:00
// 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);
}
}
});
});
}
});
2025-09-28 06:14:22 -04:00
// Handle WebRTC Offer
socket.on("webrtc-offer", async (data) => {
// Implementation for handling WebRTC offer
});
// Handle WebRTC Answer
socket.on("webrtc-answer", async (data) => {
// Implementation for handling WebRTC answer
});
// Handle ICE Candidates
socket.on("webrtc-ice-candidate", async (data) => {
// Implementation for handling ICE candidates
});
2025-09-28 03:18:11 -04:00
});
}