From 226ee3f998d872576bc5cbc3e0a97cf0f5afbd2a Mon Sep 17 00:00:00 2001 From: Kevin Puig <119972216+k-puig@users.noreply.github.com> Date: Sun, 28 Sep 2025 05:39:05 -0400 Subject: [PATCH] Join voice channel with sockets --- .../20250928083913_maxchar2000/migration.sql | 8 ++ .../src/services/instanceService.ts | 61 +++++++++++++ concord-server/src/sockets/voiceHandler.ts | 85 +++++++++++++------ 3 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 concord-server/migrations/20250928083913_maxchar2000/migration.sql diff --git a/concord-server/migrations/20250928083913_maxchar2000/migration.sql b/concord-server/migrations/20250928083913_maxchar2000/migration.sql new file mode 100644 index 0000000..096b66b --- /dev/null +++ b/concord-server/migrations/20250928083913_maxchar2000/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to alter the column `text` on the `Message` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(2000)`. + +*/ +-- AlterTable +ALTER TABLE "public"."Message" ALTER COLUMN "text" SET DATA TYPE VARCHAR(2000); diff --git a/concord-server/src/services/instanceService.ts b/concord-server/src/services/instanceService.ts index bb4008a..7ac6381 100644 --- a/concord-server/src/services/instanceService.ts +++ b/concord-server/src/services/instanceService.ts @@ -57,3 +57,64 @@ export async function getAllInstances() { }; } } + +export async function getInstanceByChannelId(id: string) { + try { + const instance = await prisma.instance.findFirst({ + where: { + Category: { + some: { + Channel: { + some: { + id: id + } + } + } + } + }, + }); + + if (!instance) { + return null; + } + + return instance; + } catch (error) { + console.error("Error fetching instance by channel ID:", error); + return null; + } +} + +export async function getInstancesByUserId(id: string) { + try { + const user = await getUserInformation(id); + if (user && user.admin) { + const adminInstances = await getAllInstances(); + if (adminInstances && adminInstances.success) { + return adminInstances.data; + } + } + + const instance = await prisma.instance.findMany({ + where: { + Role: { + some: { + User: { + id: id + } + } + } + } + }); + + if (!instance) { + return null; + } + + return instance; + } catch (error) { + console.error("Error fetching instance by channel ID:", error); + return null; + } +} + diff --git a/concord-server/src/sockets/voiceHandler.ts b/concord-server/src/sockets/voiceHandler.ts index 1dc0484..7be95c8 100644 --- a/concord-server/src/sockets/voiceHandler.ts +++ b/concord-server/src/sockets/voiceHandler.ts @@ -1,33 +1,70 @@ import { Server, Socket } from "socket.io"; +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>(); -//TEST IGNORE export function registerVoiceHandlers(io: Server) { io.on("connection", (socket: Socket) => { - console.log(`Voice socket connected: ${socket.id}`); + // 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; + } - socket.on("join-voice-channel", (channelId: string) => { - socket.join(channelId); - console.log(`Socket ${socket.id} joined voice channel ${channelId}`); - // Optionally, notify others in the channel - socket.to(channelId).emit("user-joined-voice", socket.id); + // Initialize map for channel if not present + if (!voiceChannelMembers.has(payload.voiceChannelId)) { + voiceChannelMembers.set(payload.voiceChannelId, new Map()); + } + + const channelMembers = voiceChannelMembers.get(payload.voiceChannelId)!; + + // 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; + } + + // 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 }); }); - socket.on("leave-voice-channel", (channelId: string) => { - socket.leave(channelId); - console.log(`Socket ${socket.id} left voice channel ${channelId}`); - // Optionally, notify others in the channel - socket.to(channelId).emit("user-left-voice", socket.id); - }); - - socket.on("voice-data", (channelId: string, data: any) => { - // Broadcast voice data to all other clients in the same channel - socket.to(channelId).emit("voice-data", socket.id, data); - }); - - socket.on("disconnect", () => { - console.log(`Voice socket disconnected: ${socket.id}`); - // Handle user leaving all voice channels they were in - // (e.g., iterate through socket.rooms if you need to emit to specific channels) - }); }); }