본문 바로가기
프로젝트

[웹챗] 기본적인 메시지 전송 & 수신 이벤트 구현

by ISA(류) 2021. 11. 23.

프론트엔드

이번에는 채팅창에 대한 기본적인 레이아웃(DOM)을 잡고, 그에 따른 기본적인 소켓 연결과 해제, 메시지 전송과 전달 등의 기본적인 로직을 작성하고 컴포넌트를 나누었다.

socket.io에 대한 테스트코드를 작성하면서 비동기 및 소켓 통신에 대한 테스트 코드가 처음이다 보니 많이 어려웠다. 사실 기본적인 로직의 경우 별거 없기 때문에 로직구현보다 테스트 코드 작성에 시간을 더 많이 소요했다.

webChat.test.tsx

describe('webChat 채팅창', () => {
    let socket: any;
    beforeAll((done) => {
        socket = io('http://localhost:7778/webChat', {
            transports: ['websocket']
        });
        socket.on('connect', done);
    });
    afterAll(() => {
        socket.close();
    });
    test('render', () => {
        const { container } = render(
            <WebChat />
        );

        expect(screen.getByRole('article')).toBeVisible();
        const chatBox = container.querySelector('.chat-room');
        expect(chatBox).toBeVisible();
        const chatInput = container.querySelector('.chat-form');
        expect(chatInput).toBeVisible();
        expect(chatInput?.querySelector('.chat-input')).toBeVisible();
        expect(chatInput?.querySelector('.chat-request')).toHaveTextContent('전송')
        expect(screen.getByText('채팅창 구현하기')).toBeVisible();
    });

    test('socket', (done) => {
        socket.on('room', (rooms: any) => {
            expect(rooms).toBeEnabled();
            done();
        });
        socket.on('receive', (msg: string) => {
            expect(msg).toBe('테스트용 메시지 입니다.');
            done();
        });
        socket.emit('send', '테스트용 메시지 입니다.');
    });
});

지난번의 render 테스트 코드에서 추가적으로 beforeAll 과 afterAll을 통해서 테스트 전과 끝난 후의 소켓 연결과 연결해제 코드를 작성해주고 소켓 이벤트에 대한 로직을 작성해주었다. fireEvent를 통한 UI부분도 테스트 해보고 싶었지만, 자료를 찾고 고민을 해봐도 마땅치 않아서 일단 보류했다.

 

채팅창 자체는 저번의 구조에서 크게 달라질게 없었지만 채팅창의 메시지를 서버에서 받아서 렌더링 해주는 부분이나, 메시지 전송부분을 구현하면서 예상했던대로 소켓 연결에 대한 리렌더링 이슈가 있기에 해당 부분들을 따로 자식 컴포넌트로 만들어서 분리해주었다. 그렇지 않다면 리렌더링시 마다 소켓연결에 대한 로직을 재실행하면서 에러가 발생하는 이슈가 있다.

webchat.tsx 소켓연결 로직

해당 부분을 작업하면서 고민한 점이 있다면 당초 기획했던 익명 오픈채팅기능 과 1대1 채팅 기능을 어떤 식으로 구현하는게 좋을까라는 생각을 조금 해봤다. 초기 셋팅에 따라서 하나의 room을 사용하거나 채팅 관련 flow에 채팅방이나 채팅 상대를 선택하는 ui와 로직을 추가할 필요가 있지 않을까? 라는 생각과 추가한다면 어떤 방식으로 추가하는게 좋을지에 대한 자그만한 고민을 했고 일단은 1대1 채팅으로 로직을 구현하고 거기서 1:N의 오픈채팅으로 구현하는 방향으로 개선해보기로 결정했다. 초기버전에서는 채팅방 선택 없이 다대다 채팅만 제공하는게 내가 생각했던 기획에 더 적합하기에 다른 기능은 추후 개발을 검토해보기로 했다.(처음부터 너무 복잡하게 할 필요는 없다.)

그외에는 연결에 유지 및 재연결 등에 대한 안정성을 어떻게 확보할 것인가를 조금 고민해볼 필요는 있다고 생각이들고, 채팅참가자의 닉네임 설정이나, 참가자 목록등을 보는 ui를 어떻게 제공할지를 몇가지 방안 사이에서 고민 중이다.

ChatRoom 컴포넌트

받은 메시지들을 수신해서 보여주는 채팅창 부분에 대한 코드들, 관심사 분리 뿐 아니라 리렌더링 문제로 인해서 컴포넌트 분리를 했다.

 

SendForm 컴포넌트

메시지를 입력하고 전송하는 부분에 대한 코드들 컴포넌트 분리 및 제어컴포넌트로 리렌더링하는 문제가 있어서 분리했다. 

실제 e2e 테스트 결과 별다른 문제점은 발견되지 않았다.(당연히 혼자 메시지를 주고 받는게 다니까)

 

메시지 입력
메시지 전송 및 수신

현재 채팅메시지의 경우 메시지를 주고 받을때 메시지 전송시 바로 렌더링 해주는게 아니라 전송한 메시지를 다시 수신받아야지 렌더링 해주는 방향이 맞나? 라는 고민을 했지만 카카오톡을 기반으로 생각해봐도 전송실패한 경우 해당 부분에 대한 피드백을 주는 부분이나 메시지에 대한 컨트롤을 추가하기 전까지는 수신 받은 메시지를 렌더링 해서 표시해주는게 오히려 사용자 ux측면에서 피드백이 더 나을거 같아서 이대로 진행하기로 결정했다. 추후 기회가 된다면 변경할 예정이다.

 

벡엔드

벡엔드의 경우 기존에 작업한 코드에서 필요한 이벤트들을 만들고, 포트번호 정도 변경한게 다라서 별다른 내용은 없다.

테스트코드를 굳이 작성할 이유가 없기에 생략했고, 관련된 로직들도 최대한 프론트엔드에 넣을 예정이다. 그게 이 프로젝트의 방향성에 적합하다고 판단했다. @SubscribeMessage를 통해서 이벤트를 수신하고 emit로 이벤트를 전송하는 단순한 로직들이다.

/* eslint-disable prettier/prettier */
import { Logger } from '@nestjs/common';
import {
  MessageBody,
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnGatewayInit,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway(7778, {
  transports: ['websocket'],
  namespace: 'webChat',
})
export class AppGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  constructor() {}

  @WebSocketServer() server: Server;

  private logger: Logger = new Logger('AppGateway');
  @SubscribeMessage('init')
  handleEvent(@MessageBody() data: string) {
    this.logger.log(data);
  }
  @SubscribeMessage('join')
  handleJoin(@MessageBody() data: string) {
    this.logger.log(data);
    this.server.socketsJoin(data);
  }
  @SubscribeMessage('send')
  handleSend(@MessageBody() data: string) {
    this.logger.log(data);
    this.server.emit('receive', data);
  }

  afterInit(server: Server) {
    this.logger.log('init');
  }
  handleDisconnect(client: Socket) {
    this.logger.log(`Client Disconnected : ${client.id}`);
  }
  handleConnection(client: Socket, ...args: any[]) {
    this.logger.log(`Client Connected : ${client.id}`);
    client.join('#1');
    client.emit('room', Array.from(client.rooms));
  }
}

 

서버 로그

프론트 Repo
https://github.com/yoonjonglyu/webChat

 

GitHub - yoonjonglyu/webChat: 간단한 채팅 웹

간단한 채팅 웹. Contribute to yoonjonglyu/webChat development by creating an account on GitHub.

github.com

벡엔드 Repo
https://github.com/yoonjonglyu/webChat-server

 

GitHub - yoonjonglyu/webChat-server: webchat, socket.io, nest.js, 채팅서버

webchat, socket.io, nest.js, 채팅서버. Contribute to yoonjonglyu/webChat-server development by creating an account on GitHub.

github.com

반응형