이건 놀라운 사실인데 nestjs에서는 dynamic namespace를 정규표현식으로 적용하는 방법이 없다. 이걸 안것이 3일이나 걸렸다는 사실이 너무 슬프다.
공식 문서를 통해 기초를 구현하고 확장시킨 것이니 기초적인 구현은 공식문서를 보도록 하자.
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayDisconnect,
OnGatewayInit,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({ namespace: 'anything' })
export class SocketGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
@WebSocketServer()
public server: Server;
handleConnection(client: Socket): void {
}
afterInit() {
console.log('socket is open!');
}
handleDisconnect(client: Socket): void {
delete this.clients[client.id];
}
@SubscribeMessage('chat')
handleMessage(@MessageBody() { roomId, message }: Data): void {
this.server.emit('chat', message);
}
}
이함수는 socket이 열리면 실행되는 함수이다.
이함수는 OnGatewayConnection를 implement하면 꼭선언해주어야한다. client가 연결이 되었을 때 실행되는 함수다
이함수는 OnGatewayDisconnect를 implement하면 꼭선언해줘야한다.
client가 연결이 끊겼을 때 실행되는 함수다.
@SubscribeMessage('chat')
이것은 client에서 'chat'이라고 보낸 event를 받겟다는 의미이다.
받은 이벤트와 같이 온 parameter를 선언해주는 데코레이터다.
@SubscribeMessage('chat')
handleMessage(@MessageBody() message:string){
console.log(message);
}// 이런식으로 클라이언트에서 보낸 파라미터를 이용할 수 있다.
이벤트를 보낸 클라이언트의 소켓을 불러올수 있다.
@SubscribeMessage('chat')
handleMessage(@MessageBody() message:string, @ConnectedSocket socket:Socket){
socket.emit('chat',message);
}
// 이벤트를 보낸 클라이언트에게 이런식으로 이벤트를 날릴 수 있다.
여기서 말하는 this.server 말그대로 socket 서버를 의미한다.
그래서 전체에게 이벤트를 보내고싶으면 아래와 같이 사용가능하다.
handleAll(){
this.server.emit('all','전체에게 보냅니다.);
this.server.(/*이부분에 socketId나 room Id를 넣어주면 그클라이언트나 room에있는 모든 유저에게 */).emit('event','해당하는 모두');
}
req.user을 받으려면 Adapter를 사용을 해서 socket에 request 정보를 넣어야한다.
adapter.ts
import { INestApplicationContext } from '@nestjs/common';
import { IoAdapter } from '@nestjs/platform-socket.io';
import { Server, ServerOptions } from 'socket.io';
import express from 'express';
import * as passport from 'passport';
export class SessionAdapter extends IoAdapter {
private session: express.RequestHandler;
constructor(session: express.RequestHandler, app: INestApplicationContext) {
super(app);
this.session = session;
}
create(port: number, options?: ServerOptions): Server {
const server: Server = super.create(port, options);
// 미들웨어로 request를 등록한다.
const wrap = (middleware) => (socket, next) =>
middleware(socket.request, {}, next);
server.use((socket, next) => {
next();
});
server.use(wrap(this.session));
server.use(wrap(passport.initialize()));
server.use(wrap(passport.session()));
return server;
}
}
main.ts
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import * as passport from 'passport';
import { SessionAdapter } from './session.adapter';
import * as session from 'express-session';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const sessionMiddleware = session({
secret: process.env.SECRET, // 세션을 암호화하기 위한 암호기 설정
resave: false, // 모든 request마다 기존에 있던 session에 아무런 변경 사항이 없을 시에도 그 session을 다시 저장하는 옵션
// saveUnitialized: 초기화되지 않은 세션을 저장할지 여부를 나타낸다.
saveUninitialized: false,
// 세션 쿠키에 대한 설정을 나타낸다.
cookie: {
maxAge: 60000 * 60, // 1 hour
httpOnly: true,
},
});
app.use(sessionMiddleware);
app.useWebSocketAdapter(new SessionAdapter(sessionMiddleware, app));
// Passport를 초기화하는 미들웨어, 이를 통해 Passport의 인증/인가를 사용할 수 있다.
app.use(passport.initialize());
// Passport 세션을 사용하기 위한 미들웨어이다. 이를 통해 Passport는 세션을 기반으로 사용자의 인증 상태를 유지 관리 할 수 있다.
app.use(passport.session());
await app.listen(3000);
}
bootstrap();
connectedSocket() client : any
any로 설정해줘야 socket정보에 request 정보가 들어가기 때문에 꼭 any로 type을 지정해줘야한다.
handleConnection(@ConnectedSocket() client: any) {
this.server.emit('list', client.request.user);
}