diff --git a/server/public/socket-test.html b/server/public/socket-test.html index 74b1578..4bf47b9 100644 --- a/server/public/socket-test.html +++ b/server/public/socket-test.html @@ -1,129 +1,166 @@ - + - + Socket.IO Room Test - - + +

Socket.IO Room Test

- +
Disconnected
-
- - -
+
+ + +
+ +
+ + + +
-
- - - -
+
+ + + +
- + -

+      

     
- - \ No newline at end of file + + diff --git a/server/src/room/room.gateway.ts b/server/src/room/room.gateway.ts index fe70ba6..be498fc 100644 --- a/server/src/room/room.gateway.ts +++ b/server/src/room/room.gateway.ts @@ -1,118 +1,146 @@ -import { - ConnectedSocket, - MessageBody, - OnGatewayConnection, - OnGatewayDisconnect, - OnGatewayInit, - SubscribeMessage, - WebSocketGateway, - WebSocketServer, -} from '@nestjs/websockets'; -import { Server, Socket } from 'socket.io'; -import { RoomRepository } from './room.repository'; -import { Room } from './room.entity'; -import { RandomNameUtil } from '@/common/randomname/random-name.util'; - -@WebSocketGateway({ - namespace: 'rooms', - cors: { - origin: '*', - credentials: true, - }, -}) -export class RoomGateway - implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit -{ - @WebSocketServer() - server: Server; - - constructor(private readonly roomRepository: RoomRepository) {} - - afterInit(server: Server) { - console.log('WebSocket Gateway Initialized'); - } - - handleConnection(client: Socket) { - console.log(`Client connected: ${client.id}`); - } - - handleDisconnect(client: Socket) { - console.log(`Client disconnected: ${client.id}`); - } - - @SubscribeMessage('createRoom') - async handleCreateRoom( - @ConnectedSocket() client: Socket, - @MessageBody() - data: { - userId: string; - }, - ): Promise<{ success: boolean; room?: Room; error?: string }> { - console.log('createRoom event received', data); - try { - const roomId = await this.roomRepository.generateRoomId(); - const name = RandomNameUtil.generate(); - - const room = new Room({ - id: roomId, - name, - hostId: data.userId, - createdAt: new Date(), - }); - - await this.roomRepository.createRoom(roomId, room); - - client.emit('roomCreated', { - roomId: room.id, - name: room.name, - hostId: room.hostId, - }); - - return { - success: true, - room, - }; - } catch (error) { - console.error('Error in createRoom:', error); - return { - success: false, - error: error.message, - }; - } - } - - @SubscribeMessage('joinRoom') - async handleJoinRoom( - @ConnectedSocket() client: Socket, - @MessageBody() - data: { - roomId: string; - userId: string; - }, - ): Promise<{ success: boolean; message?: string; error?: string }> { - console.log('joinRoom event received', data); - try { - await this.roomRepository.joinRoom(data.userId, data.roomId); - - await client.join(data.roomId); - - const name = RandomNameUtil.generate(); - - client.emit('joinedRoom', { - roomId: data.roomId, - userId: data.userId, - name, - timestamp: new Date(), - }); - return { - success: true, - message: `Successfully joined room ${data.roomId}`, - }; - } catch (error) { - return { - success: false, - error: error.message, - }; - } - } -} +import { + ConnectedSocket, + MessageBody, + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + SubscribeMessage, + WebSocketGateway, + WebSocketServer, +} from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; +import { RoomRepository } from './room.repository'; +import { Room } from './room.entity'; +import { RandomNameUtil } from '@/common/randomname/random-name.util'; + +@WebSocketGateway({ + namespace: 'rooms', + cors: { + origin: '*', + methods: ['GET', 'POST'], + }, +}) +export class RoomGateway + implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit +{ + @WebSocketServer() + server: Server; + + constructor(private readonly roomRepository: RoomRepository) {} + + afterInit(server: Server) { + console.log('WebSocket Gateway Initialized'); + } + + handleConnection(client: Socket) { + console.log(`Client connected: ${client.id}`); + } + + handleDisconnect(client: Socket) { + console.log(`Client disconnected: ${client.id}`); + } + @SubscribeMessage('createRoom') + async handleCreateRoom( + @ConnectedSocket() client: Socket, + @MessageBody() + data: { + userId: string; + }, + ): Promise<{ success: boolean; room?: Room; error?: string }> { + console.log('createRoom event received', data); + try { + const roomId = await this.roomRepository.generateRoomId(); + const name = RandomNameUtil.generate(); + + const room = new Room({ + id: roomId, + name, + hostId: data.userId, + createdAt: new Date(), + }); + + await client.join(roomId); + + await this.roomRepository.createRoom(roomId, room); + + this.server.emit('roomCreated', { + roomId: room.id, + name: room.name, + hostId: room.hostId, + }); + + return { + success: true, + room, + }; + } catch (error) { + console.error('Error in createRoom:', error); + return { + success: false, + error: error.message, + }; + } + } + + @SubscribeMessage('joinRoom') + async handleJoinRoom( + @ConnectedSocket() client: Socket, + @MessageBody() + data: { + roomId: string; + userId: string; + }, + ): Promise<{ success: boolean; message?: string; error?: string }> { + console.log('joinRoom event received', data); + try { + await this.roomRepository.joinRoom(data.userId, data.roomId); + + await client.join(data.roomId); + + const name = RandomNameUtil.generate(); + + client.emit('joinedRoom', { + roomId: data.roomId, + userId: data.userId, + name, + timestamp: new Date(), + }); + return { + success: true, + message: `Successfully joined room ${data.roomId}`, + }; + } catch (error) { + return { + success: false, + error: error.message, + }; + } + } + + @SubscribeMessage('leaveRoom') + async handleLeaveRoom( + @ConnectedSocket() client: Socket, + @MessageBody() data: { roomId: string; userId: string }, + ): Promise<{ success: boolean; message?: string; error?: string }> { + try { + const room = await this.roomRepository.findRoom(data.roomId); + + if (!room) { + throw new Error('Room not found'); + } + + await this.roomRepository.leaveRoom(data.userId, data.roomId); + await client.leave(data.roomId); + + return { + success: true, + message: `Successfully left room ${data.roomId}`, + }; + } catch (e) { + return { + success: false, + message: e.message, + }; + } + } +} diff --git a/server/src/room/room.repository.ts b/server/src/room/room.repository.ts index 4cc98a1..c7c2c1d 100644 --- a/server/src/room/room.repository.ts +++ b/server/src/room/room.repository.ts @@ -50,6 +50,50 @@ export class RoomRepository { return `room_${roomCount}`; } + async leaveRoom(userId: string, roomId: string): Promise { + const roomKey = this.roomKey(roomId); + const roomUsersKey = this.roomUsersKey(roomId); + + const exists = await this.redisClient.exists(roomKey); + if (!exists) { + throw new Error('Room not found'); + } + + const isMember = await this.redisClient.sIsMember(roomUsersKey, userId); + if (!isMember) { + throw new Error('User is not in the room'); + } + + const multi = this.redisClient.multi(); + multi.sRem(roomUsersKey, userId); + multi.hIncrBy(roomKey, 'currentUsers', -1); + + await multi.exec(); + } + + async findRoom(roomId: string): Promise { + const roomKey = this.roomKey(roomId); + + const exists = await this.redisClient.exists(roomKey); + if (!exists) { + return null; + } + + const [id, name, hostId, createdAt] = await Promise.all([ + this.redisClient.hGet(roomKey, 'id'), + this.redisClient.hGet(roomKey, 'name'), + this.redisClient.hGet(roomKey, 'hostId'), + this.redisClient.hGet(roomKey, 'createdAt'), + ]); + + return new Room({ + id, + name, + hostId, + createdAt: new Date(createdAt), + }); + } + async validateRoom(roomKey: string): Promise { const exists = await this.redisClient.exists(roomKey); if (!exists) throw new Error('Room not found');