Skip to content

Commit

Permalink
[FE] SSE 연동하여 방 목록을 가져온다. (#98)
Browse files Browse the repository at this point in the history
* chore: 환경 변수 설정

* feat: SSE 연동해서 rooms 데이터 가져오기

초기 데이터는 GET 요청 보내서 처리

* docs: README.md 업데이트
  • Loading branch information
studioOwol authored Nov 13, 2024
1 parent 083c5a5 commit 466a4ac
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 83 deletions.
1 change: 1 addition & 0 deletions fe/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
.env

# Editor directories and files
.vscode/*
Expand Down
27 changes: 11 additions & 16 deletions fe/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,18 @@

### 새로운 게임방이 추가될 때 순서대로 추가되지 않음

### 게임 페이지에서 새로고침하면 렌더링이 완전히 되지 않음
### SSE(Server Sent Events)로 방 목록 가져오기

- VolumeBar `defaultValue` state 업데이트가 연속적으로 발생할 수 있는 구조
- Controlled Component? Uncontrolled Component? 혼용하면 무한 상태 업데이트 루프 발생?
- [x] RoomList Page에서 새로고침 하면 방 목록이 보여야 한다.
- [x] 새로운 방이 생성되었을 때 각 사용자의 페이지에 생성된 방 목록이 바로 보여야 한다.
- [x] 새로운 탭을 열어 Page가 로드되면 방 목록이 보여야 한다.
- [x] 방이 닫히면 방 목록에서 바로 제거되어야 한다.

### 게임 페이지에서 새로고침하면 players들이 사라짐, 마이크도 안 됨
- 이 부분을 getRoomsQuery & setInterval 줘서 해뒀었다. 3초에 한 번씩 refetch 해서 rooms 상태를 저장했다.
- SSE를 사용해서 rooms의 상태가 변경될 때 이벤트를 수신해서 rooms 데이터를 받아올 수 있다.
- SSE는 서버에서 발생하는 새로운 이벤트(변경 사항)만 받아오는 방식이기 때문에 페이지를 처음 로드하거나 새로고침 할 때는 기존 데이터를 한 번 받아와야 한다. = GET 요청 필요

- 새로고침이 아주 문제다!
### useRoomStore 사용

### 개발 환경에서 잘 실행되다가 배포한 후 제대로 실행이 안 됨

- 빌드된 것은 잘 되는데.. `npm run dev` 하면 제대로 동작하지 않음
- JS 접근제어자 #을 써서 그런가 의심.. (확실하지 않음)
- 해결하지 못해서 잘 동작하던 상태로 되돌리고 진행🥲

### 방을 생성한 사용자만 currentRoom 상태가 Store에 저장 돼서, 이미 생성된 방에 입장하려는 사용자는 currentRoom에 저장된 상태가 없음

- signalingSocket.joinRoom을 하려면 현재 room을 전달해 주어야 한다. 그런데 저장된 currentRoom이 없다.
- getRoomsQuery로 방 목록을 가져오고 roomId에 해당하는 room을 찾은 후 상태를 저장하고 signalingSocket에 전달하도록 함.
- JoinDialog에서도 currentRoom을 구독하도록 하면 안 되려나..? 뭔가 방법이 있을 것 같은데,,
- 훅/컴포넌트 내부에서 사용: useRoomStore()
- React 외부의 비동기 작업, 이벤트 핸들러: useRoomStore.getState()
4 changes: 2 additions & 2 deletions fe/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import './App.css';
import RoomPage from './pages/RoomListPage';
import GamePage from './pages/GamePage';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import RoomListPage from './pages/RoomListPage';

const queryClient = new QueryClient();

Expand All @@ -12,7 +12,7 @@ function App() {
<div className="app">
<BrowserRouter>
<Routes>
<Route path="/" element={<RoomPage />} />
<Route path="/" element={<RoomListPage />} />
<Route path="/game/:roomId" element={<GamePage />} />
</Routes>
</BrowserRouter>
Expand Down
18 changes: 18 additions & 0 deletions fe/src/config/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const ENV = {
GAME_SERVER_URL: import.meta.env.VITE_GAME_SERVER_URL,
SIGNALING_SERVER_URL: import.meta.env.VITE_SIGNALING_SERVER_URL,
STUN_SERVERS: {
iceServers: [
{
urls: import.meta.env.VITE_STUN_SERVER,
},
{
urls: import.meta.env.VITE_TURN_SERVER,
username: import.meta.env.VITE_TURN_USERNAME,
credential: import.meta.env.VITE_TURN_CREDENTIAL,
},
],
},
SSE_URL: import.meta.env.VITE_GAME_SSE_URL,
REST_BASE_URL: import.meta.env.VITE_GAME_REST_BASE_URL,
};
13 changes: 0 additions & 13 deletions fe/src/constants/webRTC.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
export const STUN_SERVERS = Object.freeze({
iceServers: [
{
urls: 'stun:coturn.clovapatra.com:3478',
},
{
urls: 'turn:coturn.clovapatra.com:3478',
username: 'test',
credential: 'bestcompanynaver',
},
],
});

export const MEDIA_CONSTRAINTS = Object.freeze({
audio: {
echoCancellation: true, // 에코 제거
Expand Down
16 changes: 0 additions & 16 deletions fe/src/hooks/useRefreshRooms.ts

This file was deleted.

49 changes: 49 additions & 0 deletions fe/src/hooks/useRoomsSSE.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import useRoomStore from '@/stores/zustand/useRoomStore';
import { useEffect } from 'react';
import { Room } from '@/types/roomTypes';
import { ENV } from '@/config/env';
import { getRoomsQuery } from '@/stores/queries/getRoomsQuery';

export const useRoomsSSE = () => {
const { data: initialRooms } = getRoomsQuery();
const { setRooms } = useRoomStore();

console.log(initialRooms);

useEffect(() => {
// 초기 데이터 설정
if (initialRooms) {
setRooms(initialRooms);
}

// SSE 연결
const eventSource = new EventSource(ENV.SSE_URL);

// rooms 데이터 수신 처리
eventSource.onmessage = (event) => {
try {
const rooms = JSON.parse(event.data) as Room[];
setRooms(rooms);
} catch (error) {
console.error('Failed to parse rooms data:', error);
}
};

// 연결 시작
eventSource.onopen = () => {
console.log('SSE Connection opened');
};

// 에러 처리
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
eventSource.close();
};

// 컴포넌트 언마운트 시 연결 정리 (메모리 누수 예방)
return () => {
console.log('Closing SSE connection');
eventSource.close();
};
}, [initialRooms, setRooms]);
};
6 changes: 2 additions & 4 deletions fe/src/pages/GamePage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import useRoomStore from '@/stores/zustand/useRoomStore';
import PlayerList from './PlayerList/PlayerList';
import { getRoomsQuery } from '@/stores/queries/getRoomsQuery';

const GamePage = () => {
const [isAudioOn, setIsAudioOn] = useState(true);
const { roomId } = useParams();
const { data: rooms } = getRoomsQuery();
const { currentRoom, setCurrentRoom } = useRoomStore();
const { rooms, currentRoom, setCurrentRoom } = useRoomStore();

useEffect(() => {
if (rooms && roomId) {
Expand Down
4 changes: 1 addition & 3 deletions fe/src/pages/RoomListPage/RoomList/JoinDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { gameSocket } from '@/services/gameSocket';
import { signalingSocket } from '@/services/signalingSocket';
import { getRoomsQuery } from '@/stores/queries/getRoomsQuery';
import useRoomStore from '@/stores/zustand/useRoomStore';
import { RoomDialogProps } from '@/types/roomTypes';
import { useEffect, useState } from 'react';
Expand All @@ -25,9 +24,8 @@ const JoinDialog = ({ open, onOpenChange, roomId }: JoinDialogProps) => {
const [playerNickname, setPlayerNickname] = useState('');
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const { data: rooms } = getRoomsQuery();
const { rooms, setCurrentRoom } = useRoomStore();
const currentRoom = rooms.find((room) => room.roomId === roomId);
const { setCurrentRoom } = useRoomStore.getState();

const resetAndClose = () => {
setPlayerNickname('');
Expand Down
23 changes: 4 additions & 19 deletions fe/src/pages/RoomListPage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import SearchBar from '@/components/common/SearchBar';
import RoomHeader from './RoomHeader/RoomHeader';
import RoomList from './RoomList/RoomList';
import { gameSocket } from '@/services/gameSocket';
import { useEffect } from 'react';
import { signalingSocket } from '@/services/signalingSocket';
import { useRefreshRooms } from '@/hooks/useRefreshRooms';
import { useRoomsSSE } from '@/hooks/useRoomsSSE';

const RoomPage = () => {
const refetchRooms = useRefreshRooms();

useEffect(() => {
refetchRooms();

const interval = setInterval(() => {
refetchRooms();
}, 3000); // 3초에 한 번씩

return () => {
clearInterval(interval);
};
}, [refetchRooms]);
const RoomListPage = () => {
useRoomsSSE();

return (
<div>
Expand All @@ -30,4 +15,4 @@ const RoomPage = () => {
);
};

export default RoomPage;
export default RoomListPage;
5 changes: 2 additions & 3 deletions fe/src/services/gameSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import {
import { Room } from '@/types/roomTypes';
import { SocketService } from './SocketService';
import useRoomStore from '@/stores/zustand/useRoomStore';

const GAME_SOCKET_URL = 'wss://game.clovapatra.com/rooms';
import { ENV } from '@/config/env';

class GameSocket extends SocketService {
constructor() {
Expand All @@ -17,7 +16,7 @@ class GameSocket extends SocketService {
connect() {
if (this.socket?.connected) return;

const socket = io(GAME_SOCKET_URL, {
const socket = io(ENV.GAME_SERVER_URL, {
transports: ['websocket'],
withCredentials: false,
}) as Socket<ServerToClientEvents, ClientToServerEvents>;
Expand Down
8 changes: 4 additions & 4 deletions fe/src/services/signalingSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
SignalingData,
SignalingEvents,
} from '@/types/webrtcTypes';
import { MEDIA_CONSTRAINTS, STUN_SERVERS } from '@/constants/webRTC';
const SIGNALING_URL = 'wss://signaling.clovapatra.com';
import { MEDIA_CONSTRAINTS } from '@/constants/webRTC';
import { ENV } from '@/config/env';

class SignalingSocket extends SocketService {
// WebRTC 연결을 관리하는 객체 - key: peerId, value: RTCPeerConnection
Expand All @@ -26,7 +26,7 @@ class SignalingSocket extends SocketService {
connect() {
if (this.socket?.connected) return;

const socket = io(SIGNALING_URL, {
const socket = io(ENV.SIGNALING_SERVER_URL, {
transports: ['websocket'],
withCredentials: false,
}) as Socket<SignalingEvents>;
Expand Down Expand Up @@ -276,7 +276,7 @@ class SignalingSocket extends SocketService {
}

// 새로운 RTCPeerConnection 생성
const pc = new RTCPeerConnection(STUN_SERVERS);
const pc = new RTCPeerConnection(ENV.STUN_SERVERS);
this.peerConnections.set(peerId, pc);

// 로컬 미디어 스트림 추가
Expand Down
5 changes: 2 additions & 3 deletions fe/src/stores/queries/getRoomsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { useQuery } from '@tanstack/react-query';
import axios from 'axios';
import { Room } from '@/types/roomTypes';

const BASE_URL = 'https://game.clovapatra.com';
import { ENV } from '@/config/env';

const gameAPI = axios.create({
baseURL: BASE_URL,
baseURL: ENV.REST_BASE_URL,
timeout: 5000,
withCredentials: false,
});
Expand Down

0 comments on commit 466a4ac

Please sign in to comment.