diff --git a/client/src/components/room/RoomCard/style.ts b/client/src/components/room/RoomCard/style.ts index e3e52506..d487d24c 100644 --- a/client/src/components/room/RoomCard/style.ts +++ b/client/src/components/room/RoomCard/style.ts @@ -7,6 +7,7 @@ export const RoomCardWrapper = styled.div` flex-direction: column; background-color: ${({ theme }) => theme.colors.white}; padding: ${({ theme }) => theme.paddings.base}; + min-width: 15rem; width: 100%; height: ${ROOM_CARD.height}rem; border-radius: ${({ theme }) => theme.borderRadius.base}; @@ -77,6 +78,7 @@ export const RoomCardBottom = styled.div` `; export const RoomFieldType = styled.div<{ bgColor: DevFieldType }>` + min-width: 5.1rem; width: 6rem; height: 2rem; ${({ theme, bgColor }) => css` diff --git a/client/src/components/video/Screenshare/index.tsx b/client/src/components/video/Screenshare/index.tsx index 30f47bfb..a449cbe0 100644 --- a/client/src/components/video/Screenshare/index.tsx +++ b/client/src/components/video/Screenshare/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-react'; import { useClient, useScreenVideoTrack } from '@components/video/config'; @@ -19,27 +19,35 @@ const ScreenShare = ({ }: ScreenShareDivProps): JSX.Element => { const client = useClient(); const { ready, tracks, error } = useScreenVideoTrack(); + const firstRenderRef = useRef(true); useEffect(() => { const pulishScreenShare = async () => { - setStart(false); await client.unpublish(preTracks[1]); await client.publish(tracks); - setStart(true); if (!Array.isArray(tracks)) { tracks.on('track-ended', async () => { - setScreenShare(false); await client.unpublish(tracks); + tracks.close(); if (trackState.video) { await client.publish(preTracks[1]); } + setScreenShare(false); }); } }; - if (ready) pulishScreenShare(); + if (ready && tracks) pulishScreenShare(); if (error) setScreenShare(false); + return () => { - client.unpublish(tracks); + if (firstRenderRef.current) { + firstRenderRef.current = false; + return; + } + if (!error && !Array.isArray(tracks)) { + client.unpublish(tracks); + tracks.close(); + } }; }, [setStart, setScreenShare, screenShare, client, preTracks, trackState, tracks, ready, error]); diff --git a/client/src/components/video/VideoCard/index.tsx b/client/src/components/video/VideoCard/index.tsx index f6894ba6..fe1a1e12 100644 --- a/client/src/components/video/VideoCard/index.tsx +++ b/client/src/components/video/VideoCard/index.tsx @@ -7,7 +7,7 @@ import { AgoraVideoPlayer, } from 'agora-rtc-react'; import { VideoWrap, VolumeVisualizer } from './style'; -import { SPEAK } from '@utils/constant'; +import { SCREEN_SHARE_HEIGHT, SPEAK } from '@utils/constant'; import { useToast } from '@hooks/useToast'; import { TOAST_MESSAGE } from '@utils/constant'; @@ -69,8 +69,9 @@ const VideoCard = ({ videoTrack, audioTrack }: VideoCardProps): JSX.Element => { { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - videoTrack?.getMediaStreamTrack().readyState === 'live' && openFullscreen(); + if (videoTrack?.getCurrentFrameData()?.height === SCREEN_SHARE_HEIGHT) { + openFullscreen(); + } }} ref={videoRef}> {videoTrack && ( diff --git a/client/src/components/video/VideoController/index.tsx b/client/src/components/video/VideoController/index.tsx index dd66b40d..31ad3bf4 100644 --- a/client/src/components/video/VideoController/index.tsx +++ b/client/src/components/video/VideoController/index.tsx @@ -43,7 +43,9 @@ const VideoController = ({ tracks, setStart, uuid, ownerId }: VideoControllerPro } }; - const handleScreenShare = () => setScreenShare(!screenShare); + const handleScreenShare = useCallback(() => { + setScreenShare((prev) => !prev); + }, []); const leaveChannel = useCallback(async () => { if (ownerId === user.id) { diff --git a/client/src/components/video/config.ts b/client/src/components/video/config.ts index 8a0ea086..883aeec5 100644 --- a/client/src/components/video/config.ts +++ b/client/src/components/video/config.ts @@ -4,7 +4,11 @@ import { createMicrophoneAndCameraTracks, createMicrophoneAudioTrack, createScreenVideoTrack, + ILocalAudioTrack, + ILocalVideoTrack, + AgoraRTCError, } from 'agora-rtc-react'; +import { SCREEN_SHARE_HEIGHT } from '@utils/constant'; const config: ClientConfig = { mode: 'rtc', @@ -14,12 +18,19 @@ const config: ClientConfig = { const useClient = createClient(config); const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks(); const useMicrophoneTrack = createMicrophoneAudioTrack(); -const useScreenVideoTrack = createScreenVideoTrack( - { - encoderConfig: '1080p_1', - optimizationMode: 'detail', - }, - 'disable', -); +const useScreenVideoTrack = (): { + ready: boolean; + tracks: ILocalVideoTrack | [ILocalVideoTrack, ILocalAudioTrack]; + error: AgoraRTCError | null; +} => { + const screenShare = createScreenVideoTrack( + { + encoderConfig: `${SCREEN_SHARE_HEIGHT}p_1`, + optimizationMode: 'detail', + }, + 'disable', + ); + return screenShare(); +}; export { useClient, useMicrophoneAndCameraTracks, useMicrophoneTrack, useScreenVideoTrack }; diff --git a/client/src/pages/Campfire/Campfire.tsx b/client/src/pages/Campfire/Campfire.tsx index 27cc96b9..5cfe6698 100644 --- a/client/src/pages/Campfire/Campfire.tsx +++ b/client/src/pages/Campfire/Campfire.tsx @@ -12,7 +12,6 @@ import BGMContextProvider from '@contexts/bgmContext'; import { useUser } from '@contexts/userContext'; import { RoomType } from '@utils/constant'; import { RoomInfoType } from '@src/types'; -import { postLeaveRoom } from '@src/apis'; interface LocationProps { pathname: string; @@ -35,7 +34,6 @@ const Campfire = ({ location }: RoomProps): JSX.Element => { useEffect(() => { if (!userInfo.login) { - postLeaveRoom(uuid); history.goBack(); return; } diff --git a/client/src/pages/Tadak/Tadak.tsx b/client/src/pages/Tadak/Tadak.tsx index 7e68795e..b8dc90f9 100644 --- a/client/src/pages/Tadak/Tadak.tsx +++ b/client/src/pages/Tadak/Tadak.tsx @@ -9,7 +9,6 @@ import VideoList from '@components/video/VideoList'; import Loader from '@components/common/Loader'; import { useUser } from '@contexts/userContext'; import { RoomInfoType } from '@src/types'; -import { postLeaveRoom } from '@src/apis'; interface LocationProps { pathname: string; @@ -23,7 +22,8 @@ interface TadakProps { const Tadak = ({ location }: TadakProps): JSX.Element => { const { agoraAppId, agoraToken, uuid, owner, maxHeadcount } = location?.state; const [users, setUsers] = useState([]); - const [start, setStart] = useState(false); + const [start, setStart] = useState(false); + const [isLoading, setIsLoading] = useState(true); const client = useClient(); const { ready, tracks } = useMicrophoneAndCameraTracks(); const userInfo = useUser(); @@ -31,7 +31,6 @@ const Tadak = ({ location }: TadakProps): JSX.Element => { useEffect(() => { if (!userInfo.login) { - postLeaveRoom(uuid); history.goBack(); return; } @@ -74,6 +73,7 @@ const Tadak = ({ location }: TadakProps): JSX.Element => { await tracks[1].setEnabled(false); await tracks[0].setEnabled(false); } + setIsLoading(false); setStart(true); }; @@ -85,14 +85,14 @@ const Tadak = ({ location }: TadakProps): JSX.Element => { return ( - {!start ? ( + {isLoading ? ( ) : ( <> - {tracks && } - {tracks && } + {start && tracks && } + {ready && tracks && } )} diff --git a/client/src/utils/constant.ts b/client/src/utils/constant.ts index 21fa7ca4..00e7c50b 100644 --- a/client/src/utils/constant.ts +++ b/client/src/utils/constant.ts @@ -34,6 +34,8 @@ export const INPUT = { export const TOAST_TIME = 2000; +export const SCREEN_SHARE_HEIGHT = 1080; + export const MODAL_NAME = { login: '로그인', join: '회원가입', @@ -86,7 +88,7 @@ export const TOAST_MESSAGE = { introduceHost: '호스트는 참가자를 강퇴할 수 있습니다.  강퇴 당한 사용자는 다시 이 방에 들어올 수 없으니 신중하게 사용해주세요!', narcissism: '누구나 자기 자신을 좋아합니다...!', - infoDoubleClick: '더블 클릭 하면 어떤 일이 일어날까요...?', + infoDoubleClick: '화면공유된 블록을 더블 클릭 하면 어떤 일이 일어날까요...?', introFireAnimation: '자세히 보시면 불꽃이 일렁입니다...!', introMoon: '자세히 보시면 달이 반짝입니다...!', introSky: '자세히 보시면 별이 반짝이고 별똥별이 떨어집니다...!', diff --git a/server/api/src/builder/dev-field.builder.ts b/server/api/src/builder/dev-field/dev-field.builder.ts similarity index 70% rename from server/api/src/builder/dev-field.builder.ts rename to server/api/src/builder/dev-field/dev-field.builder.ts index 43d84a4c..e68b6d30 100644 --- a/server/api/src/builder/dev-field.builder.ts +++ b/server/api/src/builder/dev-field/dev-field.builder.ts @@ -1,5 +1,5 @@ -import { BuilderCommon } from './builder'; -import { DevField } from '../domain/field/dev-field.entity'; +import { BuilderCommon } from '../builder'; +import { DevField } from '../../domain/field/dev-field.entity'; export class DevFieldBuilder extends BuilderCommon { constructor() { diff --git a/server/api/src/builder/history.builder.ts b/server/api/src/builder/history/history.builder.ts similarity index 67% rename from server/api/src/builder/history.builder.ts rename to server/api/src/builder/history/history.builder.ts index c6892b8d..8a2387c3 100644 --- a/server/api/src/builder/history.builder.ts +++ b/server/api/src/builder/history/history.builder.ts @@ -1,7 +1,7 @@ import { LocalDate } from 'js-joda'; -import { BuilderCommon } from './builder'; -import { User } from '../domain/user/user.entity'; -import { History } from '../domain/history/history.entity'; +import { BuilderCommon } from '../builder'; +import { User } from '../../domain/user/user.entity'; +import { History } from '../../domain/history/history.entity'; export class HistoryBuilder extends BuilderCommon { constructor() { diff --git a/server/api/src/builder/index.ts b/server/api/src/builder/index.ts index 3dd55a16..ee936dc3 100644 --- a/server/api/src/builder/index.ts +++ b/server/api/src/builder/index.ts @@ -1,8 +1,8 @@ export { RoomBuilder } from './room/room.builder'; export { UserBuilder } from './user/user.builder'; -export { HistoryBuilder } from './history.builder'; -export { VisitBuilder } from './visit.builder'; -export { DevFieldBuilder } from './dev-field.builder'; +export { HistoryBuilder } from './history/history.builder'; +export { VisitBuilder } from './visit/visit.builder'; +export { DevFieldBuilder } from './dev-field/dev-field.builder'; export { LoginRequestDtoBuilder } from './auth/login-request.dto.builder'; export { UserResponseDtoBuilder } from './user/user-response.dto.builder'; export { UserUpdateDtoBuilder } from './user/user-update.dto.builder'; diff --git a/server/api/src/builder/visit.builder.ts b/server/api/src/builder/visit/visit.builder.ts similarity index 77% rename from server/api/src/builder/visit.builder.ts rename to server/api/src/builder/visit/visit.builder.ts index 83aa2d9e..ffbe8dab 100644 --- a/server/api/src/builder/visit.builder.ts +++ b/server/api/src/builder/visit/visit.builder.ts @@ -1,6 +1,6 @@ import { LocalDate } from 'js-joda'; -import { BuilderCommon } from './builder'; -import { Visit } from '../domain/history/visit.entity'; +import { BuilderCommon } from '../builder'; +import { Visit } from '../../domain/history/visit.entity'; export class VisitBuilder extends BuilderCommon { constructor() { super(Visit); diff --git a/server/api/src/domain/auth/service/auth.service.spec.ts b/server/api/src/domain/auth/service/auth.service.spec.ts index 4f028ece..a186cae6 100644 --- a/server/api/src/domain/auth/service/auth.service.spec.ts +++ b/server/api/src/domain/auth/service/auth.service.spec.ts @@ -18,7 +18,7 @@ import { HistoryBuilder, LoginRequestDtoBuilder, UserBuilder, UserResponseDtoBui import { BadRequestException, HttpStatus, UnauthorizedException } from '@nestjs/common'; import { UserException } from '../../../exception'; import { LoginRequestDto } from '../dto/login-request.dto'; -import { Bcrypt } from '../../../utils/bcrypt.util'; +import { Bcrypt } from '../../../utils'; import { User } from '../../user/user.entity'; import { LocalDate } from 'js-joda'; import { DevField } from '../../field/dev-field.entity'; diff --git a/server/api/src/domain/auth/service/auth.service.ts b/server/api/src/domain/auth/service/auth.service.ts index 8e63c7da..fde2e7bd 100644 --- a/server/api/src/domain/auth/service/auth.service.ts +++ b/server/api/src/domain/auth/service/auth.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { JwtService } from '@nestjs/jwt'; import { LocalDate } from 'js-joda'; -import { Bcrypt } from '../../../utils/bcrypt.util'; +import { Bcrypt } from '../../../utils'; import { UserBuilder, UserResponseDtoBuilder } from '../../../builder'; import { DevFieldException, UserException } from '../../../exception'; import { User } from '../../user/user.entity'; diff --git a/server/api/src/domain/base-time.entity.ts b/server/api/src/domain/base-time.entity.ts index a648756b..ee052bfc 100644 --- a/server/api/src/domain/base-time.entity.ts +++ b/server/api/src/domain/base-time.entity.ts @@ -1,5 +1,5 @@ import { CreateDateColumn, Generated, PrimaryColumn, UpdateDateColumn } from 'typeorm'; -import { BigintValueTransformer } from '../transformer/bigint-value.transformer'; +import { BigintValueTransformer } from '../transformer'; export abstract class BaseTimeEntity { @Generated('increment') diff --git a/server/api/src/domain/history/history.entity.ts b/server/api/src/domain/history/history.entity.ts index 50d7907a..bc90c1e6 100644 --- a/server/api/src/domain/history/history.entity.ts +++ b/server/api/src/domain/history/history.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { LocalDate } from 'js-joda'; -import { LocalDateTransformer } from '../../transformer/local-date.transformer.'; +import { LocalDateTransformer } from '../../transformer'; import { User } from '../user/user.entity'; @Entity() diff --git a/server/api/src/domain/history/repository/visit.repository.ts b/server/api/src/domain/history/repository/visit.repository.ts index e2d1f6b8..1aa3787a 100644 --- a/server/api/src/domain/history/repository/visit.repository.ts +++ b/server/api/src/domain/history/repository/visit.repository.ts @@ -12,6 +12,6 @@ export class VisitRepository extends Repository { async addVisitCount(count: number) { const visit: Visit = new VisitBuilder().setDate(LocalDate.now().minusDays(1)).setTotalVisit(count).build(); - this.save(visit); + await this.save(visit); } } diff --git a/server/api/src/domain/history/service/history.service.ts b/server/api/src/domain/history/service/history.service.ts index ba9f3416..04730703 100644 --- a/server/api/src/domain/history/service/history.service.ts +++ b/server/api/src/domain/history/service/history.service.ts @@ -34,10 +34,7 @@ export class HistoryService { const user: User = await this.userRepository.findUserByUserEmail(email); if (!user) throw UserException.userNotFound(); const year = LocalDate.now().year(); - const result: HistoryResponseDto = new HistoryResponseDto( - await this.historyRepository.getMonthHistoryByUser(user, year), - ); - return result; + return new HistoryResponseDto(await this.historyRepository.getMonthHistoryByUser(user, year)); } async getLastVisitCount() { diff --git a/server/api/src/domain/history/visit.entity.ts b/server/api/src/domain/history/visit.entity.ts index 501cb886..4e299e48 100644 --- a/server/api/src/domain/history/visit.entity.ts +++ b/server/api/src/domain/history/visit.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { LocalDate } from 'js-joda'; -import { LocalDateTransformer } from '../../transformer/local-date.transformer.'; +import { LocalDateTransformer } from '../../transformer'; @Entity() export class Visit { diff --git a/server/api/src/domain/user/controller/user.controller.ts b/server/api/src/domain/user/controller/user.controller.ts index 9f9d4f86..c8255574 100644 --- a/server/api/src/domain/user/controller/user.controller.ts +++ b/server/api/src/domain/user/controller/user.controller.ts @@ -4,7 +4,6 @@ import { Req, Delete, Get, - Param, Patch, Post, UploadedFile, diff --git a/server/api/src/domain/user/user.entity.ts b/server/api/src/domain/user/user.entity.ts index ced78b3a..427d19f3 100644 --- a/server/api/src/domain/user/user.entity.ts +++ b/server/api/src/domain/user/user.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { LocalDate } from 'js-joda'; -import { LocalDateTransformer } from '../../transformer/local-date.transformer.'; +import { LocalDateTransformer } from '../../transformer'; import { DevField } from '../field/dev-field.entity'; import { History } from '../history/history.entity'; diff --git a/server/api/src/transformer/index.ts b/server/api/src/transformer/index.ts new file mode 100644 index 00000000..08b3984e --- /dev/null +++ b/server/api/src/transformer/index.ts @@ -0,0 +1,3 @@ +export { BigintValueTransformer } from './bigint-value.transformer'; +export { LocalDateTransformer } from './local-date.transformer.'; +export { LocalDateTimeTransformer } from './local-date-time.transformer'; diff --git a/server/api/src/transformer/local-date-time.transformer.ts b/server/api/src/transformer/local-date-time.transformer.ts index 90b827a8..57736e8a 100644 --- a/server/api/src/transformer/local-date-time.transformer.ts +++ b/server/api/src/transformer/local-date-time.transformer.ts @@ -1,6 +1,6 @@ import { ValueTransformer } from 'typeorm'; import { LocalDateTime } from 'js-joda'; -import { DateTimeUtil } from '../utils/date-time.util'; +import { DateTimeUtil } from '../utils'; export class LocalDateTimeTransformer implements ValueTransformer { to(entityValue: LocalDateTime): Date | null { diff --git a/server/api/src/transformer/local-date.transformer..ts b/server/api/src/transformer/local-date.transformer..ts index aaea2e76..2667ab81 100644 --- a/server/api/src/transformer/local-date.transformer..ts +++ b/server/api/src/transformer/local-date.transformer..ts @@ -1,6 +1,6 @@ import { ValueTransformer } from 'typeorm'; import { LocalDate } from 'js-joda'; -import { DateTimeUtil } from '../utils/date-time.util'; +import { DateTimeUtil } from '../utils'; export class LocalDateTransformer implements ValueTransformer { to(entityValue: LocalDate): Date | null { diff --git a/server/api/src/utils/index.ts b/server/api/src/utils/index.ts new file mode 100644 index 00000000..209b87fd --- /dev/null +++ b/server/api/src/utils/index.ts @@ -0,0 +1,2 @@ +export { Bcrypt } from './bcrypt.util'; +export { DateTimeUtil } from './date-time.util'; diff --git a/server/api/test/mock.object.ts b/server/api/test/mock.object.ts index 28107a86..03fe3c61 100644 --- a/server/api/test/mock.object.ts +++ b/server/api/test/mock.object.ts @@ -1,4 +1,4 @@ -import { DevFieldBuilder } from '../src/builder/dev-field.builder'; +import { DevFieldBuilder } from '../src/builder/dev-field/dev-field.builder'; import { RoomBuilder, UserBuilder } from '../src/builder'; import { datatype, internet, lorem } from 'faker'; import { RoomType } from '../src/domain/room/room.entity'; diff --git a/server/socket/package.json b/server/socket/package.json index 1f92c39a..dee7699c 100644 --- a/server/socket/package.json +++ b/server/socket/package.json @@ -12,7 +12,7 @@ "start": "nest start", "start:dev": "set NODE_ENV=dev&&nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "set NODE_ENV=prod&&nest start", + "start:prod": "set NODE_ENV=production&&nest start", "start:3000": "cross-env NODE_PORT=3000 nest start", "start:3001": "cross-env NODE_PORT=3001 nest start", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", diff --git a/server/socket/src/gateway/room.gateway.ts b/server/socket/src/gateway/room.gateway.ts index 1e98d49c..d44816ab 100644 --- a/server/socket/src/gateway/room.gateway.ts +++ b/server/socket/src/gateway/room.gateway.ts @@ -10,7 +10,7 @@ import { import { Server, Socket } from 'socket.io'; import { Logger } from '@nestjs/common'; import { IMessage, IRoomRequest } from './room.interface'; -import { LocalDateTime, ZoneId } from '@js-joda/core'; +import { LocalDateTime} from '@js-joda/core'; import { RoomEvent } from './room.event'; import { RoomService } from './room.service'; diff --git a/server/socket/src/gateway/room.service.ts b/server/socket/src/gateway/room.service.ts index b05e7366..23a202f4 100644 --- a/server/socket/src/gateway/room.service.ts +++ b/server/socket/src/gateway/room.service.ts @@ -27,16 +27,13 @@ export class RoomService { Redis.get(uuid, (err, data) => { if (err || !data) return this.emitEventForError({ client, server }, Exception.roomNotFound); const findRoom = JSON.parse(data); - const findOwnerNickname = findRoom['userList'][findRoom.owner].nickname; - const findMyNickname = findRoom['userList'][client.id].nickname; - if (findOwnerNickname === findMyNickname) { + if (this.isOwner(findRoom, client)) { for (const userInfo of Object.entries(findRoom.userList)) { const socketId: string = userInfo[0]; this.deRegisterUserBySocketID(socketId); } this.deRegisterRoom(uuid); - this.emitEventForEmptyUserList(server, uuid); - return; + return this.emitEventForEmptyUserList(server, uuid); } delete findRoom['userList'][client.id]; Redis.multi().set(uuid, JSON.stringify(findRoom)).del(client.id).exec(); @@ -48,9 +45,7 @@ export class RoomService { Redis.get(uuid, (err, data) => { if (err || !data) return this.emitEventForError({ client, server }, Exception.roomNotFound); const findRoom = JSON.parse(data); - const findOwnerNickname = findRoom['userList'][findRoom.owner].nickname; - const findMyNickname = findRoom['userList'][client.id].nickname; - if (findOwnerNickname !== findMyNickname) { + if (!this.isOwner(findRoom, client)) { return this.emitEventForError({ client, server }, Exception.clientUnauthorized); } for (const userInfo of Object.entries(findRoom.userList)) { @@ -71,9 +66,7 @@ export class RoomService { Redis.get(uuid, (err, data) => { if (err || !data) return this.emitEventForError({ client, server }, Exception.roomNotFound); const findRoom = JSON.parse(data); - const findOwnerNickname = findRoom['userList'][findRoom.owner].nickname; - const findMyNickname = findRoom['userList'][client.id].nickname; - if (findOwnerNickname === findMyNickname) { + if (this.isOwner(findRoom, client)) { for (const userInfo of Object.entries(findRoom.userList)) { const socketId: string = userInfo[0]; this.deRegisterUserBySocketID(socketId); @@ -111,15 +104,20 @@ export class RoomService { }); } + isOwner(findRoom: any, client: Socket): boolean { + const findOwnerNickname = findRoom['userList'][findRoom.owner].nickname; + const findMyNickname = findRoom['userList'][client.id].nickname; + return findOwnerNickname === findMyNickname; + } + disconnectClient(client: Socket, server: Server) { Redis.get(client.id, (err, uuid) => { - if (err || !uuid) return; + if (err || !uuid) return this.emitEventForError({ client, server }, Exception.clientNotFound); Redis.get(uuid, async (err, data) => { - if (err || !data) return; + if (err || !data) return this.emitEventForError({ client, server }, Exception.roomNotFound); const findRoom = JSON.parse(data); - const findOwnerNickname = findRoom['userList'][findRoom.owner].nickname; const findMyNickname = findRoom['userList'][client.id].nickname; - if (findMyNickname === findOwnerNickname) { + if (this.isOwner(findRoom, client)) { for (const userInfo of Object.entries(findRoom.userList)) { const socketId: string = userInfo[0]; this.deRegisterUserBySocketID(socketId); @@ -137,7 +135,7 @@ export class RoomService { } } this.saveRoomByUUID(uuid, findRoom); - + await this.leaveRoomRequestToApiServer(uuid); this.emitEventForUserList(server, uuid); } }); @@ -169,6 +167,11 @@ export class RoomService { Redis.multi().set(uuid, JSON.stringify(findRoom)).set(client.id, uuid).exec(); } + /** + * 소켓으로 유저 리스트 이벤트를 해당 방에 접속중인 클라이언트에게 보냅니다. + * @param server 보낼 주체(서버) + * @param uuid 이벤트를 보낼 대상이 접속중인 방의 고유 uuid + */ emitEventForUserList(server: Server, uuid: string) { Redis.get(uuid, (err, data) => { if (err || !data) return this.emitEventForError({ client: uuid, server }, Exception.roomNotFound); @@ -176,36 +179,56 @@ export class RoomService { }); } + /** + * 소켓으로 빈 유저 리스트 이벤트를 해당 방에 접속중인 클라이언트에게 보냅니다. + * @param server 보낼 주체(서버) + * @param uuid 이벤트를 보낼 대상이 접속중인 방의 고유 uuid + */ emitEventForEmptyUserList(server: Server, uuid: string) { server.to(uuid).emit(RoomEvent.UserList, {}); } + /** + * 소켓으로 검증 이벤트를 해당 클라이언트에게 보냅니다. + * @param client 소켓으로 검증 이벤트를 받을 클라이언트 + * @param server 소켓으로 검증 이벤트를 보낼 서버 + * @param isVerify 검증의 유무 + */ emitEventForVerify({ client, server }, isVerify: boolean) { server.to(client.id).emit(RoomEvent.IsVerify, isVerify); } + /** + * 소켓으로 에러 이벤트를 해당 클라이언트에게 보냅니다. + * @param client 소켓으로 에러 이벤트를 받을 클라이언트 + * @param server 소켓으로 에러 이벤트를 보낼 서버 + * @param message 에러 메시지 + */ emitEventForError({ client, server }, message) { server.to(client.id).emit(RoomEvent.Error, message); } - // Same := registerRoom() + /** + * Redis 에 uuid:roomData 구조로 저장합니다. + * registerRoom() 개념과 같습니다. + * @param uuid key + * @param roomData value + */ saveRoomByUUID(uuid: string, roomData: any): void { Redis.set(uuid, JSON.stringify(roomData)); } async leaveRoomRequestToApiServer(uuid): Promise { - await axios.post(`${baseURL}/api/room/socket/leave/${uuid}`, { - headers: { - 'socket-secret-key': process.env.SOCKET_SECRET_KEY ?? '', - }, - }); + const headers = { + 'socket-secret-key': process.env.SOCKET_SECRET_KEY ?? '', + }; + await axios.post(`${baseURL}/api/room/socket/leave/${uuid}`, undefined, { headers }); } async deleteRoomRequestToApiServer(uuid): Promise { - await axios.delete(`${baseURL}/api/room/socket/${uuid}`, { - headers: { - 'socket-secret-key': process.env.SOCKET_SECRET_KEY ?? '', - }, - }); + const headers = { + 'socket-secret-key': process.env.SOCKET_SECRET_KEY ?? '', + }; + await axios.delete(`${baseURL}/api/room/socket/${uuid}`, { headers }); } } diff --git a/server/socket/test/app.e2e-spec.ts b/server/socket/test/app.e2e-spec.ts index 50cda623..1a013bef 100644 --- a/server/socket/test/app.e2e-spec.ts +++ b/server/socket/test/app.e2e-spec.ts @@ -16,9 +16,6 @@ describe('AppController (e2e)', () => { }); it('/ (GET)', () => { - return request(app.getHttpServer()) - .get('/') - .expect(200) - .expect('Hello World!'); + return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!'); }); });