From ba9103a1ad6796d4a3d98cdcf54500120eef9255 Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Wed, 29 Oct 2025 08:10:50 +0100 Subject: [PATCH] feat: initial untested signaling --- nuxt.config.ts | 5 ++ server/routes/ws/signaling.ts | 89 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 server/routes/ws/signaling.ts diff --git a/nuxt.config.ts b/nuxt.config.ts index f247466..846d7d9 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -25,4 +25,9 @@ export default defineNuxtConfig({ */ componentDir: './components/ui' }, + nitro: { + experimental: { + websocket: true + } + }, }) \ No newline at end of file diff --git a/server/routes/ws/signaling.ts b/server/routes/ws/signaling.ts new file mode 100644 index 0000000..68b3ad8 --- /dev/null +++ b/server/routes/ws/signaling.ts @@ -0,0 +1,89 @@ +// thanks nitropack smh +type Peer = Parameters[0]['message']>>[0]; + +const rooms: Record = {}; + +export default defineWebSocketHandler({ + message(peer, message) { + // TODO: proper typing + const msg = message.json() as any; + console.log("[ws] message", peer.id, msg); + + if (msg.event === 'create-room') { + const roomId = generateRoomId(); + rooms[roomId] = { broadcaster: peer, viewers: [] }; + peer.send(JSON.stringify({ event: 'room-created', roomId })); + } + if (msg.event === 'join-room') { + const room = rooms[msg.roomId]; + if (room) { + room.viewers.push(peer); + room.broadcaster.send(JSON.stringify({ event: 'viewer-joined', viewerId: peer.id })); + } else { + peer.send(JSON.stringify({ event: 'error', message: 'Room not found' })); + } + } + if (msg.event === 'offer') { + const viewerSocket = findSocketById(msg.targetId); + if (viewerSocket) { + viewerSocket.send(JSON.stringify({ + event: 'offer', + sdp: msg.sdp, + senderId: peer.id, + })); + } + } + if (msg.event === 'answer') { + const broadcasterSocket = findSocketById(msg.targetId) + if (broadcasterSocket) { + broadcasterSocket.send({ + event: 'answer', + sdp: msg.sdp, + from: peer.id, + }) + } + } + if (msg.event === 'ice-candidate') { + const targetSocket = findSocketById(msg.targetId); + if (targetSocket) { + targetSocket.send(JSON.stringify({ + event: 'ice-candidate', + candidate: msg.candidate, + from: peer.id, + })); + } + } + }, + + close(peer, event) { + console.log("[ws] close", peer.id, event); + for (const [roomId, room] of Object.entries(rooms)) { + if (room.broadcaster.id === peer.id) { + // broadcaster disconnected, close room + room.viewers.forEach(viewer => { + viewer.send(JSON.stringify({ event: 'room-closed' })); + }); + delete rooms[roomId]; + } else { + const viewerIndex = room.viewers.findIndex(v => v.id === peer.id); + if (viewerIndex !== -1) { + room.viewers.splice(viewerIndex, 1); + room.broadcaster.send(JSON.stringify({ event: 'viewer-left', viewerId: peer.id })); + } + } + } + }, +}); + +function generateRoomId(): string { + return Math.random().toString(36).substring(2, 8).toUpperCase(); +} + +function findSocketById(id: string): Peer | null { + for (const room of Object.values(rooms)) { + if (room.broadcaster.id === id) return room.broadcaster; + const viewer = room.viewers.find(v => v.id === id); + if (viewer) return viewer; + } + return null; +}