0. 배경
1. 해결방법
0. 배경
서로 구분되는 채널에다가 메세지를 보내줄 수 있도록 구현이 필요했었다.
처음에는 socket.io에서 지원하는 room 을 통해 채널id 내부에있는 소켓들에게 메세지를 보내주는 식으로 고안했는데.. 하나의 소켓이 다른 채널(룸) 으로 참여할 때 기존의 룸에서 메세지를 보내도 다른 채널에서 메세지가 보이는(!) 문제가 발생했다.
그래서 아래와 같이 다른 채널에서 메세지를 보낼 때 구분하기 위해 channelId 와 메세지 내용을 객체로 던져주는 방식을 생각했다.
const eventMessageEmitDto = {
nickname: user.nickname,
message,
channelId,
};
이러한 과정중에 room으로 들어가기 위해 소켓을 가지고 와야하는 과정이 매끄럽지 않았다.
this.server.sockets.sockets 에서 get을 통해 소켓 전체의 객체를 가져오려고 했는데.. undefined으로 찍히고 소켓을 못가지고 오는 경우였다.
문제의 channels.gateway.ts
@SubscribeMessage('joinChannel')
async handlejoinChannel(
@ConnectedSocket() client: Socket,
@MessageBody() data: { channelId: string; channelSocketId: string },) {
const user = await this.authService.getUserFromSocket(client);
if (!user || user.channelSocketId !== client.id) {
throw WSBadRequestException('유저 정보가 일치하지 않습니다.');
}
const socket = this.server.sockets.sockets.get(data.channelSocketId);
if (!socket) {
throw WSBadRequestException('socket이 존재하지 않습니다.');
}
socket.join(data.channelId);
}
너무 궁금해서 this.server.sockets의 log를 찍어봤는데 아래와 같았다.
backend | <ref *1> Map(1) {
backend | 'ej06MNkjv1F8qxnOAAAB' => Socket {
backend | _events: [Object: null prototype] {
backend | error: [Function: noop],
backend | disconnect: [Array],
backend | message: [Function: handler],
backend | ClientToServer: [Function: handler]
backend | },
backend | _eventsCount: 4,
backend | _maxListeners: undefined,
backend | nsp: Namespace {
backend | _events: [Object: null prototype],
backend | _eventsCount: 1,
backend | _maxListeners: undefined,
backend | sockets: [Circular *1],
backend | _fns: [],
backend | _ids: 0,
backend | server: [Server],
backend | name: '/channels',
backend | adapter: [Adapter],
backend | [Symbol(shapeMode)]: false,
backend | [Symbol(kCapture)]: false
backend | },
backend | client: Client {
backend | sockets: [Map],
backend | nsps: [Map],
backend | server: [Server],
backend | conn: [Socket],
backend | encoder: [Encoder],
backend | decoder: [Decoder],
backend | id: 'zmDDJmUHg6txOshvAAAA',
backend | onclose: [Function: bound onclose],
backend | ondata: [Function: bound ondata],
backend | onerror: [Function: bound onerror],
backend | ondecoded: [Function: bound ondecoded],
backend | connectTimeout: undefined
backend | },
backend | recovered: false,
backend | data: {},
backend | connected: true,
backend | acks: Map(0) {},
backend | fns: [],
backend | flags: {},
backend | server: Server {
backend | _events: [Object: null prototype] {},
backend | _eventsCount: 0,
backend | _maxListeners: undefined,
backend | _nsps: [Map],
backend | parentNsps: Map(0) {},
backend | parentNamespacesFromRegExp: Map(0) {},
backend | _path: '/socket.io',
backend | clientPathRegex: /^\\/socket\\.io\\/socket\\.io(\\.msgpack|\\.esm)?(\\.min)?\\.js(\\.map)?(?:\\?|$)/,
backend | _connectTimeout: 45000,
backend | _serveClient: true,
backend | _parser: [Object],
backend | encoder: [Encoder],
backend | opts: [Object],
backend | _adapter: [class Adapter extends EventEmitter],
backend | sockets: [Namespace],
backend | eio: [Server],
backend | httpServer: [Server],
backend | engine: [Server],
backend | [Symbol(shapeMode)]: false,
backend | [Symbol(kCapture)]: false
backend | },
backend | adapter: Adapter {
backend | _events: [Object: null prototype] {},
backend | _eventsCount: 0,
backend | _maxListeners: undefined,
backend | nsp: [Namespace],
backend | rooms: [Map],
backend | sids: [Map],
backend | encoder: [Encoder],
backend | [Symbol(shapeMode)]: false,
backend | [Symbol(kCapture)]: false
backend | },
backend | id: 'ej06MNkjv1F8qxnOAAAB',
backend | handshake: {
backend | headers: [Object],
backend | time: 'Fri Dec 08 2023 02:33:38 GMT+0000 (Coordinated Universal Time)',
backend | address: '::ffff:192.168.65.1',
backend | xdomain: false,
backend | secure: true,
backend | issued: 1702002818896,
backend | url: '/socket.io/?EIO=4&transport=websocket',
backend | query: [Object: null prototype],
backend | auth: {}
backend | },
backend | [Symbol(shapeMode)]: false,
backend | [Symbol(kCapture)]: false
backend | }
backend | }
map 레퍼런스인 것 같은데, this.server.sockets.sockets.get() 이나 this.server.sockets.sockets.keys() 등 자바스크립트의 맵 자료구조 함수로 값을 가져오는게 불가능했다.
아마, this.server.sockets.sockets 자체가 undefined로 나오는 것을 보면, socket.io가 map 을 private으로 접근 불가하게 해놓은 것 같다.
socket.io의 버전마다 socket instance를 가져오는 방식이 다른 것 같다. ver3 이상에서는 this.server.sockets.sockets.get(socketId) 로 소켓을 가져오라고 한다.
버전은 "[socket.io](<http://socket.io/>)": "4.6.2"인데 TypeError: Cannot read properties of undefined (reading 'get') 에러가 나는 것으로 보아 이 방법은 안되는 것 같다.
해결
- fetchSockets()를 이용한다.
- socket.io의 fetchSockets 메서드는 서버에서 현재 연결된 모든 소켓(Socket)의 목록을 가져오는 기능을 제공한다. 이 메서드는 Socket.IO 서버 인스턴스에서 호출되며, 연결된 모든 소켓을 반환하는 Promise를 반환한다.
async joinChannelRoom(channelRoomName: string, channelSocketId: string) {
this.logger.log(
`joinChannelRoom: ${channelRoomName}, ${channelSocketId}`,
);
const sockets = await this.server.fetchSockets();
const socket = sockets.find((s) => s.id === channelSocketId);
if (!socket) {
return WSBadRequestException('socket이 존재하지 않습니다.');
}
this.logger.log(`socket.id: `, socket.id);
socket.join(channelRoomName);
}
https://docs.nestjs.com/websockets/gateways
https://velog.io/@fejigu/Socket.IO-client
https://socket.io/docs/v4/rooms/
'NestJS' 카테고리의 다른 글
[NestJS] nestJS 소켓통신 (part1. 기본설정) (0) | 2024.02.08 |
---|---|
[NestJS] TypeORM Tips1 (Part 1: Don't use save()) (0) | 2024.02.07 |
[NestJS] bcrypt로 패스워드 해시화 하기 (0) | 2024.02.06 |
[NestJS] 요즘 MZ들은 NGINX 대신 mkcert 쓴다. (1) | 2024.02.05 |
[NestJS] NestJS 에서 Swagger을 사용해보자 (0) | 2024.02.04 |