Skip to content

Commit

Permalink
[Server] shared-checklist 소켓 구현 (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
yangdongsuk authored Nov 23, 2023
1 parent f795888 commit 304ea79
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 6 deletions.
2 changes: 2 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-ws": "^10.2.10",
"@nestjs/typeorm": "^10.0.0",
"@nestjs/websockets": "^10.2.10",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"nest-winston": "^1.9.4",
Expand Down
8 changes: 5 additions & 3 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import {
} from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WinstonModule } from 'nest-winston';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { CommonModule } from './common/common.module';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { FolderModel } from './folders/entities/folder.entity';
import { FoldersModule } from './folders/folders.module';
import { PrivateChecklistModel } from './folders/private-checklists/entities/private-checklist.entity';
import { ChecklistsModule } from './folders/private-checklists/private-checklists.module';
import { SharedChecklistModel } from './shared-checklists/entities/shared-checklist.entity';
import { SharedChecklistsModule } from './shared-checklists/shared-checklists.module';
import { UserModel } from './users/entities/user.entity';
import { UsersModule } from './users/users.module';
import { AuthModule } from './auth/auth.module';
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
import { winstonConfig } from './utils/winston.config';
import { WinstonModule } from 'nest-winston';

@Module({
imports: [
Expand Down Expand Up @@ -48,6 +49,7 @@ import { WinstonModule } from 'nest-winston';
FoldersModule,
ChecklistsModule,
AuthModule,
SharedChecklistsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
5 changes: 4 additions & 1 deletion server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { WsAdapter } from '@nestjs/platform-ws';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
Expand All @@ -14,6 +15,8 @@ async function bootstrap() {
forbidNonWhitelisted: true, // 데코레이터가 없는 속성이 있으면 요청 자체를 막아버림
}),
);
app.useWebSocketAdapter(new WsAdapter(app));

await app.listen(3000);
}
bootstrap();
109 changes: 109 additions & 0 deletions server/src/shared-checklists/shared-checklists.gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
ConnectedSocket,
MessageBody,
OnGatewayConnection,
OnGatewayDisconnect,
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
} from '@nestjs/websockets';
import { parse } from 'url';
import * as WebSocket from 'ws';

/**
* 웹소켓 통신을 통해 클라이언트들의 체크리스트 공유를 관리하는 게이트웨이.
*/
@WebSocketGateway({ path: '/share-checklist' })
export class SharedChecklistsGateway
implements OnGatewayConnection, OnGatewayDisconnect
{
@WebSocketServer() server: WebSocket.Server;

// 각 checklist ID별로 연결된 클라이언트들을 추적하기 위한 맵
private clients: Map<string, Set<WebSocket>> = new Map();

/**
* 클라이언트가 연결을 시도할 때 호출되는 메서드.
* 연결된 클라이언트에 sharedChecklistId를 할당하고 관리한다.
* @param client 연결된 클라이언트의 웹소켓 객체
*/
handleConnection(@ConnectedSocket() client: WebSocket, ...args: any[]) {
const request = args[0];
const { query } = parse(request.url, true);
const sharedChecklistId = query.cid as string;

// 클라이언트에 할당된 sharedChecklistId를 바탕으로 클라이언트 관리
if (sharedChecklistId) {
client['sharedChecklistId'] = sharedChecklistId;
if (!this.clients.has(sharedChecklistId)) {
this.clients.set(sharedChecklistId, new Set());
}
this.clients.get(sharedChecklistId)?.add(client);
}
}

/**
* 클라이언트 연결이 해제될 때 호출되는 메서드.
* 해당 클라이언트를 관리 목록에서 제거한다.
* @param client 연결 해제된 클라이언트의 웹소켓 객체
*/
handleDisconnect(@ConnectedSocket() client: WebSocket) {
const sharedChecklistId = client['sharedChecklistId'];
if (sharedChecklistId && this.clients.has(sharedChecklistId)) {
const clientsSet = this.clients.get(sharedChecklistId);
clientsSet?.delete(client);
// 더 이상 해당 sharedChecklistId에 연결된 클라이언트가 없으면 맵에서 제거
if (clientsSet?.size === 0) {
this.clients.delete(sharedChecklistId);
}
}
}

/**
* 특정 sharedChecklistId를 가진 클라이언트들에게 이벤트와 데이터를 브로드캐스트한다.
* 메시지를 보낸 클라이언트는 브로드캐스트에서 제외한다.
* @param sharedChecklistId 브로드캐스트 대상의 checklist ID
* @param event 브로드캐스트할 이벤트 이름
* @param data 전송할 데이터
* @param excludeClient 브로드캐스트에서 제외할 클라이언트
*/
private broadcastToChecklist(
sharedChecklistId: string,
event: string,
data: any,
excludeClient: WebSocket,
) {
const clients = this.clients.get(sharedChecklistId);
if (clients) {
clients.forEach((client) => {
if (client !== excludeClient && client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ event, data }));
}
});
}
}

/**
* 'sendChecklist' 이벤트를 처리하고, 해당 sharedChecklistId를 가진 다른 클라이언트들에게
* 'listenChecklist' 이벤트를 브로드캐스트한다.
* @param client 메시지를 보낸 클라이언트의 웹소켓 객체
* @param data 클라이언트로부터 받은 데이터
* @returns 이벤트 처리 결과를 나타내는 객체
*/
@SubscribeMessage('sendChecklist')
async handleSendChecklist(
@ConnectedSocket() client: WebSocket,
@MessageBody() data: string,
) {
const sharedChecklistId = client['sharedChecklistId'];
if (sharedChecklistId) {
this.broadcastToChecklist(
sharedChecklistId,
'listenChecklist',
data,
client,
);
}
return { event: 'sendChecklist', data: data };
}
}
5 changes: 3 additions & 2 deletions server/src/shared-checklists/shared-checklists.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FoldersModule } from '../folders/folders.module';
import { UsersModule } from '../users/users.module';
import { SharedChecklistModel } from './entities/shared-checklist.entity';
import { SharedChecklistsController } from './shared-checklists.controller';
import { SharedChecklistsGateway } from './shared-checklists.gateway';
import { SharedChecklistsService } from './shared-checklists.service';

@Module({
Expand All @@ -13,6 +14,6 @@ import { SharedChecklistsService } from './shared-checklists.service';
UsersModule,
],
controllers: [SharedChecklistsController],
providers: [SharedChecklistsService],
providers: [SharedChecklistsService, SharedChecklistsGateway],
})
export class ChecklistsModule {}
export class SharedChecklistsModule {}
27 changes: 27 additions & 0 deletions server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,14 @@
multer "1.4.4-lts.1"
tslib "2.6.2"

"@nestjs/platform-ws@^10.2.10":
version "10.2.10"
resolved "https://registry.yarnpkg.com/@nestjs/platform-ws/-/platform-ws-10.2.10.tgz#d6a4df6bebcf85e27fd437cf764d29921a924889"
integrity sha512-x9L7jixAEtbNjP9hIm9Fmx+kL9ruFQLu2cUb0EdSNtwK/efAJZ3+Taz9T8g/Nm5DG4k0356X6hmRk74ChJHg9g==
dependencies:
tslib "2.6.2"
ws "8.14.2"

"@nestjs/schematics@^10.0.0", "@nestjs/schematics@^10.0.1":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.0.3.tgz#0f48af0a20983ffecabcd8763213a3e53d43f270"
Expand All @@ -803,6 +811,15 @@
dependencies:
uuid "9.0.0"

"@nestjs/websockets@^10.2.10":
version "10.2.10"
resolved "https://registry.yarnpkg.com/@nestjs/websockets/-/websockets-10.2.10.tgz#f4aee5956adcb767a26d5a3e138f82a807b8e488"
integrity sha512-L1AkxwLUj/ntk26jO1SXYl3GRElQE6Fikzfy/3MPFURk0GDs7tHUzLcb8BC8q8u5ZpUjBAC2wFVQzrY5R0MHNw==
dependencies:
iterare "1.2.1"
object-hash "3.0.0"
tslib "2.6.2"

"@nodelib/[email protected]":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
Expand Down Expand Up @@ -4196,6 +4213,11 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==

[email protected]:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==

object-hash@^2.0.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
Expand Down Expand Up @@ -5643,6 +5665,11 @@ write-file-atomic@^4.0.2:
imurmurhash "^0.1.4"
signal-exit "^3.0.7"

[email protected]:
version "8.14.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==

xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
Expand Down

0 comments on commit 304ea79

Please sign in to comment.