Skip to content

Commit

Permalink
refactor: 채팅을 웹소켓으로 교체한다
Browse files Browse the repository at this point in the history
  • Loading branch information
cheonjiyun committed Oct 25, 2024
1 parent 834c94b commit a7864fb
Show file tree
Hide file tree
Showing 13 changed files with 190 additions and 46 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@stomp/stompjs": "^7.0.0",
"@tanstack/react-query": "^5.32.0",
"axios": "^1.6.8",
"event-source-polyfill": "^1.0.31",
Expand Down
8 changes: 4 additions & 4 deletions src/axios/http.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useSuspenseQuery } from '@tanstack/react-query';

import {
Chat,
ChatArray,
ChatRequest,
DeadResult,
GameExist,
Expand Down Expand Up @@ -45,8 +45,8 @@ export const useChatsQuery = () => {
const { data: chats, ...rest } = useSuspenseQuery({
queryKey: ['chats', localStorage.getItem('auth')],
queryFn: () => getChats(),
refetchInterval: 500,
staleTime: 500,
// refetchInterval: 500,
// staleTime: 500,
});
return {
chats,
Expand Down Expand Up @@ -85,7 +85,7 @@ export const existGame = () => {
};

export const getChats = () => {
return http.get<Chat[]>(`/chat`);
return http.get<ChatArray>(`/v2/chat`);
};

export const postChats = (payload: ChatRequest) => {
Expand Down
3 changes: 2 additions & 1 deletion src/axios/instances.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

export const BASE_URL = 'https://dev.mafia-together.com/api';
export const DOMAIN = 'dev.mafia-together.com';
export const BASE_URL = `https://${DOMAIN}/api`;

export const axiosInstance = axios.create({
baseURL: BASE_URL,
Expand Down
45 changes: 45 additions & 0 deletions src/components/chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import { useRecoilState } from 'recoil';

import { roomInfoState } from '../../recoil/roominfo/atom';
import { VariablesCSS } from '../../styles/VariablesCSS';
import { ChatArray } from '../../type';
import { ChatForm } from './ChatForm';
import { Chats } from './Chats';

type PropsType = {
publishChat: (content: string) => void;
chats: ChatArray;
setChats: React.Dispatch<React.SetStateAction<ChatArray>>;
};

export const Chat = ({ publishChat, chats, setChats }: PropsType) => {
/* 방 정보 */
const [roomInfo] = useRecoilState(roomInfoState);

// 내가 살아있는지
const isAlive = roomInfo?.isAlive;

return (
<>
{/* 채팅목록 */}
<div css={middle}>
<Chats chats={chats} setChats={setChats} />
</div>

{/* 살아있는 경우에만 input창이 보인다. */}
{isAlive && <ChatForm publishChat={publishChat} />}
</>
);
};

const middle = css`
height: calc(100% - ${VariablesCSS.top} - 55px - 20px);
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
`;
10 changes: 7 additions & 3 deletions src/components/chat/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@
import { css } from '@emotion/react';
import { useState } from 'react';

import { postChats } from '../../axios/http';
import { VariablesCSS } from '../../styles/VariablesCSS';

function isInvalidInputChat(inputChat: string) {
inputChat = inputChat.trim();
return inputChat.length === 0;
}

export const ChatForm = () => {
type PropsType = {
publishChat: (content: string) => void;
};

export const ChatForm = ({ publishChat }: PropsType) => {
const [inputChat, setInputChat] = useState<string>('');
return (
<form
css={chatForm}
onSubmit={event => {
event.preventDefault();
postChats({ contents: inputChat });
publishChat(inputChat);
// postChats({ content: inputChat });
setInputChat('');
}}
>
Expand Down
8 changes: 6 additions & 2 deletions src/components/chat/ChatGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ export default forwardRef(function ChatGroup(props: PropsType, ref: any) {
<PlayerChat job={chats[0].job} />
<div css={right(props)}>
<p css={nameText}>{chats[0].name}</p>
{chats.map(chat => (
<ChatMessage contents={chat.contents} isOwner={chat.isOwner} key={`${chat.timestamp}`} />
{chats.map((chat, idx) => (
<ChatMessage
contents={chat.content}
isOwner={chat.isOwner}
key={`${chat.timeStamp} ${idx}`}
/>
))}
</div>
</div>
Expand Down
11 changes: 7 additions & 4 deletions src/components/chat/Chats.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useEffect, useRef } from 'react';

import { useChatsQuery } from '../../axios/http';
import { Chat } from '../../type';
import { Chat, ChatArray } from '../../type';
import ChatGroup from './ChatGroup';

export const Chats = () => {
type PropsType = {
chats: ChatArray;
setChats: React.Dispatch<React.SetStateAction<ChatArray>>;
};

export const Chats = ({ chats }: PropsType) => {
const chatRef = useRef<HTMLDivElement | null>(null);

const { chats } = useChatsQuery();
useEffect(() => {
if (!chatRef.current) return;
chatRef.current.scrollIntoView({ block: 'end' });
Expand Down
27 changes: 7 additions & 20 deletions src/pages/Day.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { css } from '@emotion/react';
import { Suspense, useEffect, useState } from 'react';
import { useRecoilState } from 'recoil';

import { ChatForm } from '../components/chat/ChatForm';
import { Chats } from '../components/chat/Chats';
import { Chat } from '../components/chat/Chat';
import { Loading } from '../components/etc/Loading';
import AppContainerCSS from '../components/layout/AppContainerCSS';
import ModalContainer from '../components/modal/ModalContainer';
Expand All @@ -16,13 +15,16 @@ import VoteResult from '../components/modal/VoteResult';
import TopDay from '../components/top/TopDay';
import { gameRound, roomInfoState } from '../recoil/roominfo/atom';
import { VariablesCSS } from '../styles/VariablesCSS';
import { Status } from '../type';
import { ChatArray, Status } from '../type';

type PropsType = {
statusType: Status;
publishChat: (content: string) => void;
chats: ChatArray;
setChats: React.Dispatch<React.SetStateAction<ChatArray>>;
};

export default function Day({ statusType }: PropsType) {
export default function Day({ statusType, publishChat, chats, setChats }: PropsType) {
// 라운드 (몇일차)
const [gameRoundState] = useRecoilState(gameRound);

Expand Down Expand Up @@ -64,12 +66,7 @@ export default function Day({ statusType }: PropsType) {
statusType={statusType}
/>

<div css={middle}>
<Chats />
</div>

{/* 살아있는 경우에만 input창이 보인다. */}
{isAlive && <ChatForm />}
<Chat publishChat={publishChat} chats={chats} setChats={setChats} />

{/* 공지 모달 TIME*/}
<ModalContainer isOpen={statusType === 'NOTICE'}>
Expand Down Expand Up @@ -131,13 +128,3 @@ const gameMessage = css`
color: ${VariablesCSS.day};
animation: smoothshow 0.8s;
`;

const middle = css`
height: calc(100% - ${VariablesCSS.top} - 55px - 20px);
overflow: scroll;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
`;
97 changes: 90 additions & 7 deletions src/pages/Game.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import * as StompJs from '@stomp/stompjs';
import { EventListener, EventSourcePolyfill } from 'event-source-polyfill';
import { useEffect, useRef, useState } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';

import { getGamesInfo } from '../axios/http';
import { getChats, getGamesInfo } from '../axios/http';
import { BASE_URL } from '../axios/instances';
import { gameRound, roomInfoState } from '../recoil/roominfo/atom';
import { ChatArray, ChatResponse, GameStatus } from '../type';
import Day from './Day';
import Night from './Night';
import Result from './Result';
import WaitingRoom from './WaitingRoom';

export default function Game() {
const auth = localStorage.getItem('auth');
const [chats, setChats] = useState<ChatArray>([]);
const socketClientState = useRef<StompJs.Client | null>(null);
const [chatSubscribeId, setChatSubscribeId] = useState<StompJs.StompSubscription | null>(null);
const [roomsInfoState, setRoomsInfoState] = useRecoilState(roomInfoState); // 방 정보
const setGameRoundState = useSetRecoilState(gameRound);

// 방 상태 불러오기
const [gamesStatus, setGameStatus] = useState({ statusType: 'WAIT' });
const [gamesStatus, setGameStatus] = useState<GameStatus>({ statusType: 'WAIT' });

// SSE
const eventSource = useRef<EventSourcePolyfill | null>(null);
Expand All @@ -37,10 +46,77 @@ export default function Game() {
};
}, []);

// 방 정보 저장 (방 상태가 바뀔때만 작동?)
const setRoomsInfoState = useSetRecoilState(roomInfoState); // 방 정보
// WebSocket
const connect = () => {
const socket = new StompJs.Client({
brokerURL: `wss://dev.mafia-together.com/api/stomp`,
reconnectDelay: 10000,
});

const setGameRoundState = useSetRecoilState(gameRound);
if (!socket.active) {
socket.activate();
}

socketClientState.current = socket;
};

// 채팅구독
const subscribeChat = () => {
if (!socketClientState.current?.connected) return;

const chatSubscribeId = socketClientState.current.subscribe(`/sub/chat/${auth}`, response => {
const msg: ChatResponse = JSON.parse(response.body);

const isOwner = msg.name == roomsInfoState.myName;
setChats(chats => [...chats, { ...msg, isOwner: isOwner }]);
});

setChatSubscribeId(chatSubscribeId);
};

// 채팅구독끊기
const unsubscribeChat = () => {
if (!socketClientState.current?.connected) return;
chatSubscribeId?.unsubscribe();
};

// 채팅보내기
const publishChat = (content: string) => {
if (!socketClientState.current?.connected) return;

socketClientState.current.publish({
destination: `/pub/chat/${auth}`,
body: JSON.stringify({ content: content }),
});
};

const disConnect = () => {
socketClientState.current?.deactivate();
};

// 웹소켓 연결
useEffect(() => {
connect();
return () => disConnect();
}, []);

// 채팅구독
useEffect(() => {
if (gamesStatus.statusType === 'DAY') {
subscribeChat();
}
return () => unsubscribeChat();
}, [gamesStatus.statusType]);

// 본래 채팅불러오기
useEffect(() => {
(async () => {
const response = await getChats();
setChats(response);
})();
}, []);

// 방 정보 저장 (방 상태가 바뀔때만 작동?)
useEffect(() => {
// 방 정보 불러오기
(async () => {
Expand All @@ -63,7 +139,14 @@ export default function Game() {
gamesStatus.statusType === 'NOTICE' ||
gamesStatus.statusType === 'DAY' ||
gamesStatus.statusType === 'VOTE' ||
gamesStatus.statusType === 'VOTE_RESULT') && <Day statusType={gamesStatus.statusType} />}
gamesStatus.statusType === 'VOTE_RESULT') && (
<Day
statusType={gamesStatus.statusType}
publishChat={publishChat}
chats={chats}
setChats={setChats}
/>
)}
{(gamesStatus.statusType === 'NIGHT_INTRO' || gamesStatus.statusType === 'NIGHT') && (
<Night statusType={gamesStatus.statusType} />
)}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/WaitingRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Toaster } from 'react-hot-toast';
import { useNavigate } from 'react-router-dom';

import { getRoomsCode, startGame, useGamesInfoQuery } from '../axios/http';
import { DOMAIN } from '../axios/instances';
import BigButton from '../components/button/BigButton';
import { Loading } from '../components/etc/Loading';
import AppContainerCSS from '../components/layout/AppContainerCSS';
Expand Down Expand Up @@ -66,7 +67,7 @@ export default function WaitingRoom() {

const onShareLink = async () => {
// 링크 공유s
const inviteLink = 'https://dev.mafia-together.com/api' + '/#/participate?code=' + code;
const inviteLink = DOMAIN + '/#/participate?code=' + code;
const shareData = {
url: inviteLink,
};
Expand Down
2 changes: 1 addition & 1 deletion src/recoil/roominfo/atom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { atom } from 'recoil';

import { Job, GameInfo } from '../../type';
import { GameInfo, Job } from '../../type';

export const gameRound = atom({
key: 'gameRound',
Expand Down
Loading

0 comments on commit a7864fb

Please sign in to comment.