본문 바로가기
Javascript

NestJS + Websocket으로 채팅만들기 #3 (feat. 채팅 "방" 만들기)

by for2gles 2021. 7. 26.
반응형

채팅방 만들기

채팅 "방"을 만들기 위해서는 각각 개별적인 구분이 필요하다.

입장 해 있는 회원목록이나 회원수가 필요할 것이고, 누가 방장이고, 방 이름이 무엇인지 입력이 필요하다.

직접 room의 Object를 컨트롤 해주는 방법 도 좋지만, Socket.io 에서는 Room 이라는 기능을 제공한다.

👉 [Namespace 와 Room 이란?]

 

단순하게 

    //채팅방 들어가기
    @SubscribeMessage('enterChatRoom')
    enterChatRoom(client: Socket, roomId: string) {
    	client.join(roomId)
    }

이런 형태로 client를 room 에 들어갈 수 있다. room은 별도로 생성 할 필요는 없고 단순히 join을 할 경우 room이 생성된다. 마찬가지로 leave 도 별도로 제거 할 필요 없이 해당 room에 아무도 없을 경우 사라진다.

 

Broadcast로 해당 room에 속해있는 유저에게 모두 발송을 하기위해서는

client.to(roomId).emit('message', message);

이렇게 단순하게 Broadcast가 가능하다.

 

하지만, 채팅 "방"을 만들기 위해 room 기능을 사용하려고 하는데 몇가지의 내가 구상하는 기능과 맞지 않는점이 몇가지 있었다.

1. room은 1개에만 속하는것이 아닌, 여러개의 room에 속해있을 수 있다.

2. socket이 connect 될 때 기본적으로 해당 소켓 id 이름의 room에 기본적으로 들어가있다.

3. socket에는 data라는 항목에 닉네임 등 custom 데이터를 저장 해 놓을 수 있지만, room은 custom 데이터를 저장 해 놓을만한 마땅한 공간이 별도로 없다.

 

일단 내가 구상하고있는 채팅방은 1명이 1개의 채팅방에만 속해있는것이고, 처음 접속했을경우, 나가기를 했을 경우 기본으로 로비 채팅방으로 접속하는것이다.

그러나 위 몇가지 맞지 않는 점을 해결해야한다.


일단 1번문제와 3번문제부터 해결해보려 한다.

1. room은 1개에만 속하는것이 아닌, 여러개의 room에 속해있을 수 있다.
3. socket에는 data라는 항목에 닉네임 등 custom 데이터를 저장 해 놓을 수 있지만, room은 custom 데이터를 저장 해 놓을만한 마땅한 공간이 별도로 없다.

이 문제를 해결하기 위해 그리고 좀 더 명확한 Room 관리를 위해 Service.ts로 분리했다.

chatRoom.service.ts

import { Injectable } from '@nestjs/common';
import { chatRoomListDTO } from './dto/chatBackEnd.dto';
import { Socket } from 'socket.io';
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class ChatRoomService {
    private chatRoomList: Record<string, chatRoomListDTO>;
    constructor() {
        this.chatRoomList = {
            'room:lobby': {
                roomId: 'room:lobby',
                roomName: '로비',
                cheifId: null,
            },
        };
    }
    createChatRoom(client: Socket, roomName: string): void {
        const roomId = `room:${uuidv4()}`;
        const nickname: string = client.data.nickname;
        this.chatRoomList[roomId] = {
            roomId,
            cheifId: client.id,
            roomName,
        };
        client.data.roomId = roomId;
        client.rooms.clear();
        client.join(roomId);
        client.emit('getMessage', {
            id: null,
            nickname: '안내',
            message:
                '"' + nickname + '"님이 "' + roomName + '"방을 생성하였습니다.',
        });
    }

    enterChatRoom(client: Socket, roomId: string) {
        client.data.roomId = roomId;
        client.rooms.clear();
        client.join(roomId);
        const { nickname } = client.data;
        const { roomName } = this.getChatRoom(roomId);
        client.to(roomId).emit('getMessage', {
            id: null,
            nickname: '안내',
            message: `"${nickname}"님이 "${roomName}"방에 접속하셨습니다.`,
        });
    }

    exitChatRoom(client: Socket, roomId: string) {
        client.data.roomId = `room:lobby`;
        client.rooms.clear();
        client.join(`room:lobby`);
        const { nickname } = client.data;
        client.to(roomId).emit('getMessage', {
            id: null,
            nickname: '안내',
            message: '"' + nickname + '"님이 방에서 나갔습니다.',
        });
    }

    getChatRoom(roomId: string): chatRoomListDTO {
        return this.chatRoomList[roomId];
    }

    getChatRoomList(): Record<string, chatRoomListDTO> {
        return this.chatRoomList;
    }

    deleteChatRoom(roomId: string) {
        delete this.chatRoomList[roomId];
    }
}

여러개의 room에 들어갈 수 있는 문제는 client.rooms.clear() 을 활용하여 해결하였다.

clear 함수를 통해 속해있는 모든 room을 제거해 줄 수 있어 만에하나 실수마저도 차단하여 오직 하나만의 room에 속할 수 있도록 하였다.

exitChatRoom 함수에는 나가기 했을 경우 기본적으로 room:lobby로 속해지도록 설정하였다.

 

Custom한 방 정보를 담기위해 this.chatRoomList라는 Object를 생성 해 주었고, 키는 roomId 로 하였다.

기본적으로 room:lobby가 존재하고, 방이 생성될 때 추가를 해주고, 방 삭제시 지워줌으로서 관리한다.

안에 들어가는 정보는

roomId : 방 고유 id / room:아이디 의 형태를 갖는다.
chiefId: 방장 socket id
roomName: 방 제목 / 방 생성할 시 입력받는다.

세가지 항목이 들어가고, DTO 도 만들어 보았다.

dto/chatBackEnd.dto.ts

export class chatRoomListDTO {
    roomId: string;
    cheifId: string;
    roomName: string;
}

 

이렇게 함수를 정리하는것을 통해 Custom 정보를 저장할 수 있게 되었다.

 

2번 문제는 간단하게 해결할 수 있었다.

2. socket이 connect 될 때 기본적으로 해당 소켓 id 이름의 room에 기본적으로 들어가있다.

gateway.ts

    //소켓 연결시 유저목록에 추가
    public handleConnection(client: Socket): void {
        console.log('connected', client.id);
        client.leave(client.id);
        client.data.roomId = `room:lobby`;
        client.join('room:lobby');
    }

handleConnection 함수에서 해결을 하였는데, connection이 이루어지자마자 본인 socket id의 room에서 나오고,(client.rooms.clear()을 활용해도 된다.) 기본 채팅방인 'room:lobby'로 들어간다.

 

이렇게 단순하면서 명확한 채팅방을 제작해 보았다.

반응형

댓글