From 5de33e5bab5548402505155be191b4823c6a521b Mon Sep 17 00:00:00 2001 From: michang Date: Sat, 28 Dec 2024 23:02:28 +0900 Subject: [PATCH 01/14] Update why --- backend/game/matchingConsumers.py | 77 +++++++++---- backend/game/onlineConsumers.py | 39 ++++--- backend/login/views.py | 3 + frontend/src/app.js | 37 +----- frontend/src/components/Main-Menu.js | 71 ++++++------ frontend/src/components/Match-Wait.js | 39 ++++--- frontend/src/core/router.js | 153 +++++++++++++------------ frontend/src/core/showLoading.js | 156 ++++++++++++++++++++++++++ 8 files changed, 381 insertions(+), 194 deletions(-) create mode 100644 frontend/src/core/showLoading.js diff --git a/backend/game/matchingConsumers.py b/backend/game/matchingConsumers.py index 1286de7..5f615eb 100644 --- a/backend/game/matchingConsumers.py +++ b/backend/game/matchingConsumers.py @@ -54,9 +54,9 @@ async def initialize(self, user1, user2): self.left_ball = Ball(0, SCREEN_HEIGHT, SCREEN_WIDTH, self.left_bar) self.right_ball = Ball(1, SCREEN_HEIGHT, SCREEN_WIDTH, self.right_bar) self.score = [0, 0] - print("user1:", user1, "user2:", user2) + # print("user1:", user1, "user2:", user2) user1_nickname, user2_nickname = await get_user_nicknames(user1, user2) - print("user1_nickname:", user1_nickname, "user2_nickname:", user2_nickname) + # print("user1_nickname:", user1_nickname, "user2_nickname:", user2_nickname) self.player = [user1_nickname, user2_nickname] self.penalty_time = [0, 0] print("initializing game state finished successfully!") @@ -88,7 +88,6 @@ async def receive(self, text_data): await self.close(code=4001) return self.authenticated = True - self.my_uid = 0 # Join room group after successful authentication await self.channel_layer.group_add(self.room_group_name, self.channel_name) @@ -158,22 +157,51 @@ def authenticate(self, token): return False async def disconnect(self, close_code): - # Leave room group + # 인증된 소켓만 처리 if self.authenticated: - await self.channel_layer.group_discard( - self.room_group_name, self.channel_name - ) - # Decrease the client count for this room + print("disconnected: ", self.user_index) + await self.channel_layer.group_discard(self.room_group_name, self.channel_name) + + # 현재 방의 접속자 수를 1 줄임 if self.room_group_name in MatchingGameConsumer.client_counts: MatchingGameConsumer.client_counts[self.room_group_name] -= 1 - if MatchingGameConsumer.client_counts[self.room_group_name] <= 0: - MatchingGameConsumer.game_tasks[self.room_group_name].cancel() - del MatchingGameConsumer.game_tasks[self.room_group_name] - del MatchingGameConsumer.game_states[self.room_group_name] - del MatchingGameConsumer.client_counts[self.room_group_name] + + # 만약 이제 방에 1명만 남았다면 -> 남은 사람이 승자 + if MatchingGameConsumer.client_counts[self.room_group_name] == 1: + # 누가 남았는지 판별 (내가 0이라면 1이 승자, 내가 1이라면 0이 승자) + winner_index = 1 - self.user_index + state = MatchingGameConsumer.game_states[self.room_group_name] + + # send_game_result를 통해 DB에 게임결과 저장 및 + # 나머지(승자)에게 게임 종료 메시지 전송 + await self.send_game_result(winner_index) + + # 이후 방 정리(루프 종료 + state 정리) + # 한쪽이 승리 처리가 끝났으므로 game_loop도 종료시키는 편이 좋습니다. + if self.room_group_name in MatchingGameConsumer.game_tasks: + MatchingGameConsumer.game_tasks[self.room_group_name].cancel() + + # 방 청소 + if self.room_group_name in MatchingGameConsumer.game_states: + del MatchingGameConsumer.game_states[self.room_group_name] + if self.room_group_name in MatchingGameConsumer.client_counts: + del MatchingGameConsumer.client_counts[self.room_group_name] + if self.room_group_name in MatchingGameConsumer.game_tasks: + del MatchingGameConsumer.game_tasks[self.room_group_name] + + # 아무도 안 남았다면 방을 완전히 정리 + elif MatchingGameConsumer.client_counts[self.room_group_name] <= 0: + if self.room_group_name in MatchingGameConsumer.game_tasks: + MatchingGameConsumer.game_tasks[self.room_group_name].cancel() + del MatchingGameConsumer.game_tasks[self.room_group_name] + if self.room_group_name in MatchingGameConsumer.game_states: + del MatchingGameConsumer.game_states[self.room_group_name] + if self.room_group_name in MatchingGameConsumer.client_counts: + del MatchingGameConsumer.client_counts[self.room_group_name] else: MatchingGameConsumer.client_counts[self.room_group_name] = 0 + async def send_initialize_game(self): state = MatchingGameConsumer.game_states[self.room_group_name] await self.send( @@ -300,16 +328,21 @@ async def game_result_message(self, event): score = event["score"] winner = event["winner"] - # Send the game result to the WebSocket - await self.send( - text_data=json.dumps( - { - "type": "game_result", - "score": score, - "winner": winner, - } + if not self.scope["client"]: + return + try: + await self.send( + text_data=json.dumps( + { + "type": "game_result", + "score": score, + "winner": winner, + } + ) ) - ) + except Exception as e: + print("Failed to send game_result_message:", e, "for", self.user_index) + async def send_game_state(self): state = MatchingGameConsumer.game_states[self.room_group_name] diff --git a/backend/game/onlineConsumers.py b/backend/game/onlineConsumers.py index a7db3ed..d538371 100644 --- a/backend/game/onlineConsumers.py +++ b/backend/game/onlineConsumers.py @@ -5,11 +5,12 @@ from django.conf import settings from channels.exceptions import DenyConnection from .matchingConsumers import MatchingGameConsumer, MatchingGameState +import random class OnlineConsumer(AsyncWebsocketConsumer): online_user_list = set([]) - matching_queue = [] + matching_queue = set([]) matching_task = None @classmethod @@ -23,8 +24,9 @@ async def start_matching_task(cls): @classmethod async def start_game(cls): - user1 = cls.matching_queue.pop(0) - user2 = cls.matching_queue.pop(0) + user1, user2 = random.sample(cls.matching_queue, 2) + cls.matching_queue.remove(user1) + cls.matching_queue.remove(user2) room_name = f"{user1.uid}_{user2.uid}" @@ -94,23 +96,28 @@ def authenticate(self, token): return False async def enter_matching(self): - # Add user to matching queue - if self not in OnlineConsumer.matching_queue: - OnlineConsumer.matching_queue.append(self) - print( - "Matching queue: ", [user.uid for user in OnlineConsumer.matching_queue] - ) + for user in list(OnlineConsumer.matching_queue): + if user.uid == self.uid: + OnlineConsumer.matching_queue.remove(user) + print(f"Replaced existing user {self.uid} in the matching queue.") + break + OnlineConsumer.matching_queue.add(self) + print( + "Matching queue: ", [user.uid for user in OnlineConsumer.matching_queue] + ) async def leave_matching(self): - # Remove user from matching queue - if self in OnlineConsumer.matching_queue: - OnlineConsumer.matching_queue.remove(self) - print(f"User {self.uid} removed from matching queue") - print( - "Matching queue: ", [user.uid for user in OnlineConsumer.matching_queue] - ) + for user in list(OnlineConsumer.matching_queue): + if user.uid == self.uid: + OnlineConsumer.matching_queue.remove(user) + print(f"User {self.uid} removed from matching queue") + break + print( + "Matching queue: ", [user.uid for user in OnlineConsumer.matching_queue] + ) async def disconnect(self, close_code): + await self.leave_matching() try: if self.uid in OnlineConsumer.online_user_list: OnlineConsumer.online_user_list.remove(self.uid) diff --git a/backend/login/views.py b/backend/login/views.py index de45531..31fd5fe 100644 --- a/backend/login/views.py +++ b/backend/login/views.py @@ -24,14 +24,17 @@ def login(request): def callback(request): code = request.data.get("code") if not code: + print("No code in request") return Response(status=401) access_token = get_acccess_token(code) if not access_token: + print("Failed to get access token!!!") return Response(status=401) user_data = get_user_info(access_token) if not user_data: + print("Failed to get user info") return Response(status=401) user, created = get_or_save_user(user_data) diff --git a/frontend/src/app.js b/frontend/src/app.js index 6be822d..9cecadd 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -1,5 +1,5 @@ -import { initializeRouter, createRoutes, changeUrl } from "./core/router.js"; -import { getCookie } from "./core/jwt.js"; +import { createRoutes } from "./core/router.js"; +import { showLoading } from "./core/showLoading.js"; class App { app; @@ -12,37 +12,6 @@ class App { export const root = new App(); export const routes = createRoutes(root); - export let socketList = []; -const closeAllSockets = () => { - socketList.forEach(socket => { - if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) { - socket.close(); - } - }); - socketList = []; -}; - -const online = () => { - const onlineSocket = new WebSocket( - 'wss://' - + "localhost:443" - + '/ws/online/' - ); - socketList.push(onlineSocket); - - console.log(onlineSocket); - onlineSocket.onopen = () => { - const token = getCookie("jwt"); - onlineSocket.send(JSON.stringify({ 'action': 'authenticate', 'token': token })); - }; - onlineSocket.onclose = () => { - console.log("online socket closed"); - closeAllSockets(); - changeUrl("/error", false); - }; -} - -initializeRouter(routes); -online(); \ No newline at end of file +showLoading(routes, socketList); \ No newline at end of file diff --git a/frontend/src/components/Main-Menu.js b/frontend/src/components/Main-Menu.js index 383d092..5064735 100644 --- a/frontend/src/components/Main-Menu.js +++ b/frontend/src/components/Main-Menu.js @@ -2,6 +2,8 @@ import { Component } from "../core/Component.js"; import { List } from "./List.js"; import { changeUrl } from "../core/router.js"; import { parseJWT } from "../core/jwt.js"; +import { closeAllSockets } from "../core/showLoading.js"; +import { socketList } from "../app.js"; export class Menu extends Component { translate() { @@ -19,12 +21,12 @@ export class Menu extends Component { userMenuTexts: ["友達", "プロフィール", "ログアウト"], } }; - + this.translations = languages[this.props.lan.value]; - + } - template () { + template() { const payload = parseJWT(); if (!payload) this.uid = null; else this.uid = payload.id; @@ -42,29 +44,29 @@ export class Menu extends Component { `; } - mounted(){ - new List(document.querySelector("ul#gameMenu"), { className: "gameMode", ids: ["LocalGame", "MultiGame", "Tournament"], contents: this.translations.gameMenuTexts}); - new List(document.querySelector("ul#userMenu"), { className: "showInfo", ids: ["Friends", "Profile", "Logout"], contents: this.translations.userMenuTexts}); + mounted() { + new List(document.querySelector("ul#gameMenu"), { className: "gameMode", ids: ["LocalGame", "MultiGame", "Tournament"], contents: this.translations.gameMenuTexts }); + new List(document.querySelector("ul#userMenu"), { className: "showInfo", ids: ["Friends", "Profile", "Logout"], contents: this.translations.userMenuTexts }); } - - setEvent () { - + + setEvent() { + this.addEvent('click', '#Friends', () => { changeUrl("/main/friends"); }); - + this.addEvent('click', '#LocalGame', () => { changeUrl(`/game/local/${this.uid}`); }); - + this.addEvent('click', "#MultiGame", () => { changeUrl("/main/matching"); }); - + this.addEvent('click', "#Tournament", () => { changeUrl("/main/tournament"); }); - + function storeLang(value) { fetch("https://localhost:443/api/language/", { method: 'PUT', @@ -76,28 +78,28 @@ export class Menu extends Component { language: value }) }) - .then(response => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - }) - .catch(error => { - console.error('Fetch error:', error); - changeUrl("/"); - }); + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + }) + .catch(error => { + console.error('Fetch error:', error); + changeUrl("/"); + }); changeUrl("/main"); } - + this.addEvent('click', '#enButton', () => { this.props.lan.value = 0; storeLang(this.props.lan.value); }); - + this.addEvent('click', '#koButton', () => { this.props.lan.value = 1; storeLang(this.props.lan.value); }); - + this.addEvent('click', '#jpButton', () => { this.props.lan.value = 2; storeLang(this.props.lan.value); @@ -114,14 +116,17 @@ export class Menu extends Component { method: 'POST', credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) }) - .then(response => { - if (response.ok) changeUrl(`/`); - else throw new Error('Network response was not ok'); - }) - .catch(error => { - console.error('Fetch error:', error); - changeUrl("/"); - }); + .then(response => { + if (response.ok) { + closeAllSockets(socketList); + location.reload(true); + } + else throw new Error('Network response was not ok'); + }) + .catch(error => { + console.error('Fetch error:', error); + location.reload(true); + }); }); } } diff --git a/frontend/src/components/Match-Wait.js b/frontend/src/components/Match-Wait.js index b910e95..0e1951c 100644 --- a/frontend/src/components/Match-Wait.js +++ b/frontend/src/components/Match-Wait.js @@ -6,18 +6,19 @@ import { socketList } from "../app.js" export class WaitForMatch extends Component { initState() { - if (socketList[0] !== undefined) - { - console.log("send enter-matching"); - socketList[0].send(JSON.stringify({ 'action': 'enter-matching' })); - socketList[0].onmessage = (e) => { - const data = JSON.parse(e.data); - console.log(data); - if (data.action === 'start_game') { - console.log("start game on " + data.room_name); - changeUrl('/game/vs/' + data.room_name); - } - }; + if (socketList[0] !== undefined) { + setTimeout(() => { + console.log("send enter-matching"); + socketList[0].send(JSON.stringify({ 'action': 'enter-matching' })); + socketList[0].onmessage = (e) => { + const data = JSON.parse(e.data); + console.log(data); + if (data.action === 'start_game') { + console.log("start game on " + data.room_name); + changeUrl('/game/vs/' + data.room_name); + } + }; + }, 1000); // 1초 지연 } return {}; } @@ -34,12 +35,12 @@ export class WaitForMatch extends Component { mathcingText: ["ピッタリの相手を探してるよ..."], } }; - + this.translations = languages[this.props.lan.value]; - + } - template () { + template() { const translations = this.translations; return ` @@ -70,11 +71,17 @@ export class WaitForMatch extends Component { setEvent() { this.addEvent('click', '#goBack', (event) => { + console.log("send leave-matching"); + if (socketList[0] !== undefined) + socketList[0].send(JSON.stringify({ 'action': 'leave-matching' })); + window.removeEventListener('popstate', handleSocketClose); changeUrl("/main", false); }); const handleSocketClose = (e) => { - socketList[0].send(JSON.stringify({ 'action': 'leave-matching' })); + console.log("send leave-matching"); + if (socketList[0] !== undefined) + socketList[0].send(JSON.stringify({ 'action': 'leave-matching' })); window.removeEventListener('popstate', handleSocketClose); } diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 2d7f39a..9c79917 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -50,12 +50,16 @@ export const createRoutes = (root) => { component: (props) => new GameTournament(root.app, props), }, "/game/tournament/:uid/result/:winner": { - component: (props) => { props["isTournament"] = true; - return new GameResult(root.app, props);} + component: (props) => { + props["isTournament"] = true; + return new GameResult(root.app, props); + } }, "/game/:uid/result/:winner": { - component: (props) => { props["isTournament"] = false; - return new GameResult(root.app, props);} + component: (props) => { + props["isTournament"] = false; + return new GameResult(root.app, props); + } }, "/game/vs/:room": { component: (props) => new GameMatching(root.app, props), @@ -82,7 +86,8 @@ export async function parsePath(path) { const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('code')) { const code = urlParams.get('code'); - + + console.log("code:" + code); // code 보내고 2FA 여부 확인!! (추가 부분!!) fetch('https://localhost:443/api/callback/', { method: 'POST', @@ -92,57 +97,57 @@ export async function parsePath(path) { }, body: JSON.stringify({ code }) }) - .then(response => { - if (response.status == 200) - return response.json(); - else - return null; - }) - .then(data => { - if (data){ - if (data.is_2FA) { - // email 전송 요청 - fetch('https://localhost:443/api/send-mail/',{ - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => { - if (response.status == 200) - return changeUrl("/2FA", false); - else - return changeUrl("/", false); - }); - } else { - // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 - fetch("https://localhost:443/api/language/", { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) - }) - .then(response => { - if (!response.ok){ - changeUrl("/"); - return null; - } - return response.json(); - }) - .then(data => { - if (data){ - console.log(data.language); - root.lan.value = data.language; - changeUrl('/main'); // 메인 페이지로 이동 - } - }); + .then(response => { + if (response.status == 200) + return response.json(); + else + return null; + }) + .then(data => { + if (data) { + if (data.is_2FA) { + // email 전송 요청 + fetch('https://localhost:443/api/send-mail/', { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => { + if (response.status == 200) + return changeUrl("/2FA", false); + else + return changeUrl("/", false); + }); + } else { + // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 + fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + }) + .then(response => { + if (!response.ok) { + changeUrl("/"); + return null; + } + return response.json(); + }) + .then(data => { + if (data) { + console.log(data.language); + root.lan.value = data.language; + changeUrl('/main'); // 메인 페이지로 이동 + } + }); + } } - } - else return changeUrl("/", false); - }) - .catch(error => { - console.error('Error:', error); - }); - return ; + else return changeUrl("/", false); + }) + .catch(error => { + console.error('Error:', error); + }); + return; } const isAuthenticated = await checkAuth(); @@ -172,27 +177,29 @@ export async function parsePath(path) { changeUrl("/404", false); } -export const initializeRouter = () => { +export const initializeRouter = async () => { window.addEventListener("popstate", async () => { await parsePath(window.location.pathname); }); - fetch("https://localhost:443/api/language/", { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) - }) - .then(response => { - if (!response.ok){ - console.log("so bad"); - return null; - } - return response.json(); - }) - .then(data => { - if (data){ - root.lan.value = data.language; - } - parsePath(window.location.pathname); - }); + + // fetch("https://localhost:443/api/language/", { + // method: 'GET', + // credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) + // }) + // .then(response => { + // if (!response.ok) { + // console.log("so bad"); + // return null; + // } + // return response.json(); + // }) + // .then(data => { + // if (data) { + // root.lan.value = data.language; + // return; + // } + // // parsePath(window.location.pathname); + // }); }; async function checkAuth() { diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js new file mode 100644 index 0000000..6b30851 --- /dev/null +++ b/frontend/src/core/showLoading.js @@ -0,0 +1,156 @@ +import { initializeRouter } from "./router.js"; +import { changeUrl } from "./router.js"; +import { getCookie } from "./jwt.js"; +import { parsePath } from "./router.js"; + +export const closeAllSockets = (socketList) => { + socketList.forEach(socket => { + if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) { + socket.close(); + } + }); + socketList.length = 0; // 배열 비우기 +}; + +function waitOneSecond(time) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); // 1000ms = 1초 + }); + } + +export const showLoading = async (routes, socketList) => { + const online = async () => { + await waitOneSecond(100); + const token = getCookie("jwt"); + // if (!token) { + // changeUrl("/", false); + // return; + // } + const onlineSocket = new WebSocket( + 'wss://' + "localhost:443" + '/ws/online/' + ); + socketList.push(onlineSocket); // socketList에 추가 + + onlineSocket.onopen = () => { + onlineSocket.send(JSON.stringify({ action: 'authenticate', token })); + console.log("online socket opened"); + }; + onlineSocket.onclose = () => { + console.log("online socket closed"); + closeAllSockets(socketList); + changeUrl("/error", false); + }; + }; + + const loadingElement = document.createElement("div"); + loadingElement.id = "loading"; + loadingElement.style = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + `; + + const spinner = document.createElement("div"); + spinner.style = ` + width: 80px; + height: 80px; + border: 8px solid rgba(255, 255, 255, 0.2); + border-top: 8px solid #fff; + border-radius: 50%; + animation: spin 1s linear infinite; + `; + + const loadingText = document.createElement("div"); + loadingText.style = ` + position: absolute; + top: 60%; + color: white; + font-size: 24px; + font-family: Arial, sans-serif; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); + animation: fadeIn 1.5s ease-in-out infinite; + `; + loadingText.innerText = "Loading, please wait..."; + + loadingElement.appendChild(spinner); + loadingElement.appendChild(loadingText); + document.body.appendChild(loadingElement); + + const style = document.createElement("style"); + style.innerHTML = ` + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + @keyframes fadeIn { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } + } + `; + document.head.appendChild(style); + + // console.log("showLoading"); + // setTimeout(async () => { + // // 1초 뒤에 수행 + // console.log("wait 1"); + + // // parsePath, initializeRouter가 끝날 때까지 기다림 + // await parsePath(window.location.pathname); + // await initializeRouter(routes); + + // // parsePath와 initializeRouter가 끝난 뒤에만 online 호출 + // console.log("wait 2"); + // online(); + + // // 1초 뒤에 로딩 엘리먼트 제거 + // setTimeout(() => { + // console.log("wait 3"); + // document.body.removeChild(loadingElement); + // }, 1000); + + // }, 1000); + + // console.log("showLoading"); + // setTimeout(async () => { + // console.log("wait 1"); + // await parsePath(window.location.pathname); + // await initializeRouter(routes); + // setTimeout(() => { + // console.log("wait 2"); + // online(); // online 호출 + // setTimeout(() => { + // console.log("wait 3"); + // document.body.removeChild(loadingElement); + // }, 1000); + // }, 2000); + // }, 1000); + console.log("showLoading"); + + // setTimeout(async () => { + // 1초 뒤에 수행 + console.log("wait 1"); + + // parsePath, initializeRouter가 끝날 때까지 기다림 + await parsePath(window.location.pathname); + await initializeRouter(routes); + await online(); + // parsePath와 initializeRouter가 끝난 뒤에만 online 호출 + console.log("wait 2"); + + // 1초 뒤에 로딩 엘리먼트 제거 + setTimeout(() => { + console.log("wait 3"); + document.body.removeChild(loadingElement); + }, 1000); + + // }, 1000); +}; From ec8f0b16ee4eb58c7ec60877b7f6a091f17dc853 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 01:49:43 +0900 Subject: [PATCH 02/14] Refactor 2FA and language fetching logic in router.js to use async/await for improved readability and error handling --- frontend/src/core/router.js | 104 ++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 57 deletions(-) diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 9c79917..3bbc0ca 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -88,66 +88,56 @@ export async function parsePath(path) { const code = urlParams.get('code'); console.log("code:" + code); - // code 보내고 2FA 여부 확인!! (추가 부분!!) - fetch('https://localhost:443/api/callback/', { - method: 'POST', - credentials: 'include', // 쿠키를 포함하여 요청 - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ code }) - }) - .then(response => { - if (response.status == 200) - return response.json(); - else - return null; - }) - .then(data => { - if (data) { - if (data.is_2FA) { - // email 전송 요청 - fetch('https://localhost:443/api/send-mail/', { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => { - if (response.status == 200) - return changeUrl("/2FA", false); - else - return changeUrl("/", false); - }); + try { + const response = await fetch('https://localhost:443/api/callback/', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ code }) + }); + + if (response.status === 200) { + const data = await response.json(); + if (data.is_2FA) { + const mailResponse = await fetch('https://localhost:443/api/send-mail/', { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + } + }); + + if (mailResponse.status === 200) { + return changeUrl("/2FA", false); } else { - // API!!! jwt가 있으면 해당 유저의 데이터베이스에서 언어 번호 (0 or 1 or 2) 얻어오기 - fetch("https://localhost:443/api/language/", { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) - }) - .then(response => { - if (!response.ok) { - changeUrl("/"); - return null; - } - return response.json(); - }) - .then(data => { - if (data) { - console.log(data.language); - root.lan.value = data.language; - changeUrl('/main'); // 메인 페이지로 이동 - } - }); + return changeUrl("/", false); + } + } else { + const langResponse = await fetch("https://localhost:443/api/language/", { + method: 'GET', + credentials: 'include', + }); + + if (!langResponse.ok) { + changeUrl("/"); + return null; + } + + const langData = await langResponse.json(); + if (langData) { + console.log(langData.language); + root.lan.value = langData.language; + changeUrl('/main'); } } - else return changeUrl("/", false); - }) - .catch(error => { - console.error('Error:', error); - }); - return; + } else { + return changeUrl("/", false); + } + } catch (error) { + console.error('Error:', error); + } } const isAuthenticated = await checkAuth(); From b9c36f557722d16bd90829bb7065e576bad0f6d5 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 01:58:55 +0900 Subject: [PATCH 03/14] Refactor showLoading function to improve token validation and remove unnecessary delays. Cleaned up commented-out code for better readability. --- frontend/src/core/showLoading.js | 73 ++++++-------------------------- 1 file changed, 12 insertions(+), 61 deletions(-) diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index 6b30851..b2f6375 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -22,15 +22,14 @@ function waitOneSecond(time) { export const showLoading = async (routes, socketList) => { const online = async () => { - await waitOneSecond(100); const token = getCookie("jwt"); - // if (!token) { - // changeUrl("/", false); - // return; - // } - const onlineSocket = new WebSocket( - 'wss://' + "localhost:443" + '/ws/online/' - ); + if (!token) { + changeUrl("/", false); + return; //여기서 에러나면 재앙일껄? + } + const onlineSocket = new WebSocket( + 'wss://' + "localhost:443" + '/ws/online/' + ); socketList.push(onlineSocket); // socketList에 추가 onlineSocket.onopen = () => { @@ -98,59 +97,11 @@ export const showLoading = async (routes, socketList) => { `; document.head.appendChild(style); - // console.log("showLoading"); - // setTimeout(async () => { - // // 1초 뒤에 수행 - // console.log("wait 1"); - - // // parsePath, initializeRouter가 끝날 때까지 기다림 - // await parsePath(window.location.pathname); - // await initializeRouter(routes); - - // // parsePath와 initializeRouter가 끝난 뒤에만 online 호출 - // console.log("wait 2"); - // online(); - - // // 1초 뒤에 로딩 엘리먼트 제거 - // setTimeout(() => { - // console.log("wait 3"); - // document.body.removeChild(loadingElement); - // }, 1000); - - // }, 1000); - - // console.log("showLoading"); - // setTimeout(async () => { - // console.log("wait 1"); - // await parsePath(window.location.pathname); - // await initializeRouter(routes); - // setTimeout(() => { - // console.log("wait 2"); - // online(); // online 호출 - // setTimeout(() => { - // console.log("wait 3"); - // document.body.removeChild(loadingElement); - // }, 1000); - // }, 2000); - // }, 1000); console.log("showLoading"); - - // setTimeout(async () => { - // 1초 뒤에 수행 - console.log("wait 1"); - - // parsePath, initializeRouter가 끝날 때까지 기다림 - await parsePath(window.location.pathname); - await initializeRouter(routes); - await online(); - // parsePath와 initializeRouter가 끝난 뒤에만 online 호출 - console.log("wait 2"); - - // 1초 뒤에 로딩 엘리먼트 제거 - setTimeout(() => { - console.log("wait 3"); - document.body.removeChild(loadingElement); - }, 1000); - // }, 1000); + // parsePath, initializeRouter가 끝날 때까지 기다림 + await parsePath(window.location.pathname); + await initializeRouter(routes); + await online(); + document.body.removeChild(loadingElement); }; From b69627659edfbdd402a2be062df92ce9e6fd1d9d Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:04:15 +0900 Subject: [PATCH 04/14] refactor: Add loading element and styles for improved user experience during asynchronous operations - Introduced a new loading.js file that contains functions to create a loading element and add necessary styles. - Updated showLoading function to utilize the new loading element and styles, enhancing the visual feedback during loading states. - Removed redundant loading element creation code from showLoading for better maintainability. --- frontend/src/core/loading.js | 57 ++++++++++++++++++++++ frontend/src/core/router.js | 19 -------- frontend/src/core/showLoading.js | 84 ++++++-------------------------- 3 files changed, 73 insertions(+), 87 deletions(-) create mode 100644 frontend/src/core/loading.js diff --git a/frontend/src/core/loading.js b/frontend/src/core/loading.js new file mode 100644 index 0000000..bd41d30 --- /dev/null +++ b/frontend/src/core/loading.js @@ -0,0 +1,57 @@ +export function createLoadingElement() { + const loadingElement = document.createElement("div"); + loadingElement.id = "loading"; + loadingElement.style = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + `; + + const spinner = document.createElement("div"); + spinner.style = ` + width: 80px; + height: 80px; + border: 8px solid rgba(255, 255, 255, 0.2); + border-top: 8px solid #fff; + border-radius: 50%; + animation: spin 1s linear infinite; + `; + + const loadingText = document.createElement("div"); + loadingText.style = ` + position: absolute; + top: 60%; + color: white; + font-size: 24px; + font-family: Arial, sans-serif; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); + animation: fadeIn 1.5s ease-in-out infinite; + `; + loadingText.innerText = "Loading, please wait..."; + + loadingElement.appendChild(spinner); + loadingElement.appendChild(loadingText); + return loadingElement; +} + +export function addLoadingStyles() { + const style = document.createElement("style"); + style.innerHTML = ` + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + @keyframes fadeIn { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } + } + `; + document.head.appendChild(style); +} \ No newline at end of file diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 3bbc0ca..a39b399 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -171,25 +171,6 @@ export const initializeRouter = async () => { window.addEventListener("popstate", async () => { await parsePath(window.location.pathname); }); - - // fetch("https://localhost:443/api/language/", { - // method: 'GET', - // credentials: 'include', // 쿠키를 포함하여 요청 (사용자 인증 필요 시) - // }) - // .then(response => { - // if (!response.ok) { - // console.log("so bad"); - // return null; - // } - // return response.json(); - // }) - // .then(data => { - // if (data) { - // root.lan.value = data.language; - // return; - // } - // // parsePath(window.location.pathname); - // }); }; async function checkAuth() { diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index b2f6375..2813a56 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -2,6 +2,7 @@ import { initializeRouter } from "./router.js"; import { changeUrl } from "./router.js"; import { getCookie } from "./jwt.js"; import { parsePath } from "./router.js"; +import { createLoadingElement, addLoadingStyles } from "./loading.js"; export const closeAllSockets = (socketList) => { socketList.forEach(socket => { @@ -12,25 +13,17 @@ export const closeAllSockets = (socketList) => { socketList.length = 0; // 배열 비우기 }; -function waitOneSecond(time) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, time); // 1000ms = 1초 - }); - } - export const showLoading = async (routes, socketList) => { const online = async () => { const token = getCookie("jwt"); if (!token) { - changeUrl("/", false); - return; //여기서 에러나면 재앙일껄? - } + changeUrl("/", false); + return; + } const onlineSocket = new WebSocket( - 'wss://' + "localhost:443" + '/ws/online/' + 'wss://' + "localhost:443" + '/ws/online/' ); - socketList.push(onlineSocket); // socketList에 추가 + socketList.push(onlineSocket); onlineSocket.onopen = () => { onlineSocket.send(JSON.stringify({ action: 'authenticate', token })); @@ -43,65 +36,20 @@ export const showLoading = async (routes, socketList) => { }; }; - const loadingElement = document.createElement("div"); - loadingElement.id = "loading"; - loadingElement.style = ` - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - `; - - const spinner = document.createElement("div"); - spinner.style = ` - width: 80px; - height: 80px; - border: 8px solid rgba(255, 255, 255, 0.2); - border-top: 8px solid #fff; - border-radius: 50%; - animation: spin 1s linear infinite; - `; - - const loadingText = document.createElement("div"); - loadingText.style = ` - position: absolute; - top: 60%; - color: white; - font-size: 24px; - font-family: Arial, sans-serif; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); - animation: fadeIn 1.5s ease-in-out infinite; - `; - loadingText.innerText = "Loading, please wait..."; - - loadingElement.appendChild(spinner); - loadingElement.appendChild(loadingText); + const loadingElement = createLoadingElement(); document.body.appendChild(loadingElement); - - const style = document.createElement("style"); - style.innerHTML = ` - @keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } - } - @keyframes fadeIn { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } - } - `; - document.head.appendChild(style); + addLoadingStyles(); console.log("showLoading"); - - // parsePath, initializeRouter가 끝날 때까지 기다림 + console.log("wait 1"); + await parsePath(window.location.pathname); await initializeRouter(routes); await online(); - document.body.removeChild(loadingElement); + console.log("wait 2"); + + setTimeout(() => { + console.log("wait 3"); + document.body.removeChild(loadingElement); + }, 1000); }; From 7e1dfb0cf7ef4dca4ce549189ed97763bc173615 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:09:21 +0900 Subject: [PATCH 05/14] refactor: Enhance showLoading function with improved WebSocket handling and code organization - Consolidated WebSocket URL definition for better maintainability. - Refactored online function to streamline token validation and WebSocket connection logic. - Updated showLoading to utilize Promise.all for asynchronous operations, improving readability and performance. - Removed redundant imports and cleaned up code for clarity. --- frontend/src/core/showLoading.js | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index 2813a56..d76da15 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -1,9 +1,9 @@ -import { initializeRouter } from "./router.js"; -import { changeUrl } from "./router.js"; +import { initializeRouter, changeUrl, parsePath } from "./router.js"; import { getCookie } from "./jwt.js"; -import { parsePath } from "./router.js"; import { createLoadingElement, addLoadingStyles } from "./loading.js"; +const WEBSOCKET_URL = 'wss://localhost:443/ws/online/'; + export const closeAllSockets = (socketList) => { socketList.forEach(socket => { if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) { @@ -13,29 +13,27 @@ export const closeAllSockets = (socketList) => { socketList.length = 0; // 배열 비우기 }; -export const showLoading = async (routes, socketList) => { - const online = async () => { - const token = getCookie("jwt"); - if (!token) { - changeUrl("/", false); - return; - } - const onlineSocket = new WebSocket( - 'wss://' + "localhost:443" + '/ws/online/' - ); - socketList.push(onlineSocket); - - onlineSocket.onopen = () => { - onlineSocket.send(JSON.stringify({ action: 'authenticate', token })); - console.log("online socket opened"); - }; - onlineSocket.onclose = () => { - console.log("online socket closed"); - closeAllSockets(socketList); - changeUrl("/error", false); - }; +const online = async (socketList) => { + const token = getCookie("jwt"); + if (!token) { + changeUrl("/", false); + return; + } + const onlineSocket = new WebSocket(WEBSOCKET_URL); + socketList.push(onlineSocket); + + onlineSocket.onopen = () => { + onlineSocket.send(JSON.stringify({ action: 'authenticate', token })); + console.log("online socket opened"); }; + onlineSocket.onclose = () => { + console.log("online socket closed"); + closeAllSockets(socketList); + changeUrl("/error", false); + }; +}; +export const showLoading = async (routes, socketList) => { const loadingElement = createLoadingElement(); document.body.appendChild(loadingElement); addLoadingStyles(); @@ -43,9 +41,11 @@ export const showLoading = async (routes, socketList) => { console.log("showLoading"); console.log("wait 1"); - await parsePath(window.location.pathname); - await initializeRouter(routes); - await online(); + await Promise.all([ + initializeRouter(routes), + parsePath(window.location.pathname), + ]); + await online(socketList); console.log("wait 2"); setTimeout(() => { From 6e65c3ed7b486c5b700956283794e48689c225c5 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:13:14 +0900 Subject: [PATCH 06/14] refactor: Simplify API call in parsePath function by removing unnecessary headers - Removed redundant headers from the mail fetch request in the parsePath function. - Streamlined the code for better readability and maintainability. --- frontend/src/core/router.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index a39b399..03e2072 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -103,10 +103,7 @@ export async function parsePath(path) { if (data.is_2FA) { const mailResponse = await fetch('https://localhost:443/api/send-mail/', { method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json' - } + credentials: 'include' }); if (mailResponse.status === 200) { From f1f56c1e2aa1433f2dcd7c74f9275c9fc29b9b73 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:19:03 +0900 Subject: [PATCH 07/14] rename: rename loading components for enhanced seonjo experience --- frontend/src/core/{loading.js => loadingComponents.js} | 0 frontend/src/core/showLoading.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/core/{loading.js => loadingComponents.js} (100%) diff --git a/frontend/src/core/loading.js b/frontend/src/core/loadingComponents.js similarity index 100% rename from frontend/src/core/loading.js rename to frontend/src/core/loadingComponents.js diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index d76da15..7637b31 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -1,6 +1,6 @@ import { initializeRouter, changeUrl, parsePath } from "./router.js"; import { getCookie } from "./jwt.js"; -import { createLoadingElement, addLoadingStyles } from "./loading.js"; +import { createLoadingElement, addLoadingStyles } from "./loadingComponents.js"; const WEBSOCKET_URL = 'wss://localhost:443/ws/online/'; From 3c83f3bb05f96c54808ee8648728bfeed4993ceb Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:30:20 +0900 Subject: [PATCH 08/14] refactor: Introduce utility functions for API requests and refactor router.js to utilize them - Added `getRequest` and `postRequest` functions in utils.js for streamlined API calls. - Refactored `parsePath` function in router.js to replace direct fetch calls with the new utility functions, improving code readability and maintainability. - Enhanced error handling for API responses in `parsePath` function. --- frontend/src/core/router.js | 56 ++++++++++++++----------------------- frontend/src/utils.js | 31 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 35 deletions(-) create mode 100644 frontend/src/utils.js diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index 03e2072..ad18f28 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -13,6 +13,7 @@ import { GameTournament } from "../components/Game-Tournament.js"; import { GameMatching } from "../components/Game-matching.js"; import { Error } from "../components/Error.js"; import { GameResult } from "../components/Game-Result.js"; +import { getRequest, postRequest } from '../utils.js'; export const createRoutes = (root) => { return { @@ -89,35 +90,22 @@ export async function parsePath(path) { console.log("code:" + code); try { - const response = await fetch('https://localhost:443/api/callback/', { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ code }) - }); + const response = await postRequest('/callback/', { code }); - if (response.status === 200) { + if (response && response.status === 200) { const data = await response.json(); if (data.is_2FA) { - const mailResponse = await fetch('https://localhost:443/api/send-mail/', { - method: 'GET', - credentials: 'include' - }); + const mailResponse = await getRequest('/send-mail/'); - if (mailResponse.status === 200) { + if (mailResponse && mailResponse.status === 200) { return changeUrl("/2FA", false); } else { return changeUrl("/", false); } } else { - const langResponse = await fetch("https://localhost:443/api/language/", { - method: 'GET', - credentials: 'include', - }); + const langResponse = await getRequest('/language/'); - if (!langResponse.ok) { + if (!langResponse || !langResponse.ok) { changeUrl("/"); return null; } @@ -127,6 +115,7 @@ export async function parsePath(path) { console.log(langData.language); root.lan.value = langData.language; changeUrl('/main'); + return null; } } } else { @@ -134,14 +123,20 @@ export async function parsePath(path) { } } catch (error) { console.error('Error:', error); + return changeUrl("/", false); } } - const isAuthenticated = await checkAuth(); - if ((path === "/" || path === "/2FA") && isAuthenticated) { - return changeUrl("/main"); // /로 이동할 때 인증되어 있으면 /main으로 이동, replaceState 사용 - } else if ((path !== "/" && path !== "/2FA") && !isAuthenticated) { - return changeUrl("/"); // /를 제외한 다른 경로로 이동할 때 인증되지 않은 경우 /로 이동, replaceState 사용 + try { + const isAuthenticated = await checkAuth(); + if ((path === "/" || path === "/2FA") && isAuthenticated) { + return changeUrl("/main"); // /로 이동할 때 인증되어 있으면 /main으로 이동, replaceState 사용 + } else if ((path !== "/" && path !== "/2FA") && !isAuthenticated) { + return changeUrl("/"); // /를 제외한 다른 경로로 이동할 때 인증되지 않은 경우 /로 이동, replaceState 사용 + } + } catch (error) { + console.error('Error:', error); + return changeUrl("/error", false); } const routeKeys = Object.keys(routes); @@ -171,15 +166,6 @@ export const initializeRouter = async () => { }; async function checkAuth() { - try { - const response = await fetch('https://localhost:443/api/validate/', { - method: 'GET', - credentials: 'include', // 쿠키를 포함하여 요청 - }); - - return response.ok; // 상태가 200~299 범위에 있으면 true, 그렇지 않으면 false 반환 - } catch (error) { - console.error('Error:', error); - return false; - } + const response = await getRequest('/validate/'); + return response ? response.ok : false; } diff --git a/frontend/src/utils.js b/frontend/src/utils.js new file mode 100644 index 0000000..0f04d32 --- /dev/null +++ b/frontend/src/utils.js @@ -0,0 +1,31 @@ +export const API_BASE_URL = 'https://localhost:443/api'; + +export async function getRequest(endpoint) { + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: 'GET', + credentials: 'include', + }); + return response; + } catch (error) { + console.error('Error:', error); + return null; + } +} + +export async function postRequest(endpoint, body) { + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }); + return response; + } catch (error) { + console.error('Error:', error); + return null; + } +} \ No newline at end of file From ebdf509a5b4a985c61aac1630a74b46a6db48b84 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:35:08 +0900 Subject: [PATCH 09/14] refactor: Clean up showLoading function by removing unnecessary console logs and timeout - Eliminated console log statements to reduce clutter and improve performance. - Removed the setTimeout delay for removing the loading element, enhancing user experience by immediately hiding the loading indicator after operations complete. - Streamlined the function for better readability and maintainability. --- frontend/src/core/showLoading.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index 7637b31..623a075 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -38,18 +38,10 @@ export const showLoading = async (routes, socketList) => { document.body.appendChild(loadingElement); addLoadingStyles(); - console.log("showLoading"); - console.log("wait 1"); - await Promise.all([ initializeRouter(routes), parsePath(window.location.pathname), ]); await online(socketList); - console.log("wait 2"); - - setTimeout(() => { - console.log("wait 3"); - document.body.removeChild(loadingElement); - }, 1000); -}; + document.body.removeChild(loadingElement); +}; \ No newline at end of file From 12f53bc7f1c2bf24bcad5285e14fe39817304c6a Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:54:05 +0900 Subject: [PATCH 10/14] fix: Update URL handling in Main-Menu component to redirect to home on successful response => reduce one validate error - Added a call to change the URL to the home page ("/") after successfully closing all sockets and reloading the page. - This change improves user navigation by ensuring they are directed to the correct page following an operation. --- frontend/src/components/Main-Menu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/Main-Menu.js b/frontend/src/components/Main-Menu.js index 5064735..bfcae4a 100644 --- a/frontend/src/components/Main-Menu.js +++ b/frontend/src/components/Main-Menu.js @@ -119,6 +119,7 @@ export class Menu extends Component { .then(response => { if (response.ok) { closeAllSockets(socketList); + changeUrl("/"); location.reload(true); } else throw new Error('Network response was not ok'); From ddcf1cde1aed5976f7bcaaad0014d621c1361f93 Mon Sep 17 00:00:00 2001 From: cozy-hn Date: Sun, 29 Dec 2024 02:54:32 +0900 Subject: [PATCH 11/14] fix: Improve error handling and URL redirection in parsePath function - Updated the parsePath function to redirect to the "/error" page without using replaceState when an error occurs. - Adjusted the redirection logic for unauthenticated users to maintain the use of replaceState when navigating from non-root paths. - These changes enhance user experience by providing clearer navigation paths during error scenarios. --- frontend/src/core/router.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/core/router.js b/frontend/src/core/router.js index ad18f28..342b7a1 100644 --- a/frontend/src/core/router.js +++ b/frontend/src/core/router.js @@ -123,7 +123,7 @@ export async function parsePath(path) { } } catch (error) { console.error('Error:', error); - return changeUrl("/", false); + return changeUrl("/error"); } } @@ -132,11 +132,11 @@ export async function parsePath(path) { if ((path === "/" || path === "/2FA") && isAuthenticated) { return changeUrl("/main"); // /로 이동할 때 인증되어 있으면 /main으로 이동, replaceState 사용 } else if ((path !== "/" && path !== "/2FA") && !isAuthenticated) { - return changeUrl("/"); // /를 제외한 다른 경로로 이동할 때 인증되지 않은 경우 /로 이동, replaceState 사용 + return changeUrl("/", false); // /를 제외한 다른 경로로 이동할 때 인증되지 않은 경우 /로 이동, replaceState 사용 } } catch (error) { console.error('Error:', error); - return changeUrl("/error", false); + return changeUrl("/error"); } const routeKeys = Object.keys(routes); From a940f43c09dc7d830c631a1bced4864d921bf2e9 Mon Sep 17 00:00:00 2001 From: minseok128 Date: Sun, 29 Dec 2024 18:44:09 +0900 Subject: [PATCH 12/14] fix matching bug --- backend/game/onlineConsumers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/game/onlineConsumers.py b/backend/game/onlineConsumers.py index d538371..49be6e2 100644 --- a/backend/game/onlineConsumers.py +++ b/backend/game/onlineConsumers.py @@ -10,7 +10,7 @@ class OnlineConsumer(AsyncWebsocketConsumer): online_user_list = set([]) - matching_queue = set([]) + matching_queue = [] matching_task = None @classmethod @@ -24,9 +24,8 @@ async def start_matching_task(cls): @classmethod async def start_game(cls): - user1, user2 = random.sample(cls.matching_queue, 2) - cls.matching_queue.remove(user1) - cls.matching_queue.remove(user2) + user1 = cls.matching_queue.pop(0) + user2 = cls.matching_queue.pop(0) room_name = f"{user1.uid}_{user2.uid}" @@ -101,7 +100,7 @@ async def enter_matching(self): OnlineConsumer.matching_queue.remove(user) print(f"Replaced existing user {self.uid} in the matching queue.") break - OnlineConsumer.matching_queue.add(self) + OnlineConsumer.matching_queue.append(self) print( "Matching queue: ", [user.uid for user in OnlineConsumer.matching_queue] ) From 2b27129f699601fdaa45b14ddc0b755873efc28b Mon Sep 17 00:00:00 2001 From: minseok128 Date: Sun, 29 Dec 2024 19:12:07 +0900 Subject: [PATCH 13/14] =?UTF-8?q?fix=20=EC=9E=90=EC=97=B0=EC=8A=A4?= =?UTF-8?q?=EB=9F=AC=EC=9A=B4=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8,=20?= =?UTF-8?q?=EB=A7=A4=EC=B9=AD=20=EC=A4=91=20=ED=87=B4=EC=9E=A5=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/game/matchingConsumers.py | 5 +++++ frontend/src/components/Match-Wait.js | 2 +- frontend/src/core/showLoading.js | 9 ++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/game/matchingConsumers.py b/backend/game/matchingConsumers.py index 5f615eb..530f2b1 100644 --- a/backend/game/matchingConsumers.py +++ b/backend/game/matchingConsumers.py @@ -75,8 +75,13 @@ async def connect(self): self.authenticated = False self.start_time = timezone.now() + if self.room_group_name not in MatchingGameConsumer.game_states: + await self.close() + return + await self.accept() + async def receive(self, text_data): text_data_json = json.loads(text_data) action = text_data_json["action"] diff --git a/frontend/src/components/Match-Wait.js b/frontend/src/components/Match-Wait.js index 0e1951c..5c41f47 100644 --- a/frontend/src/components/Match-Wait.js +++ b/frontend/src/components/Match-Wait.js @@ -18,7 +18,7 @@ export class WaitForMatch extends Component { changeUrl('/game/vs/' + data.room_name); } }; - }, 1000); // 1초 지연 + }, 2000); // 1초 지연 } return {}; } diff --git a/frontend/src/core/showLoading.js b/frontend/src/core/showLoading.js index 623a075..f7f850c 100644 --- a/frontend/src/core/showLoading.js +++ b/frontend/src/core/showLoading.js @@ -37,11 +37,14 @@ export const showLoading = async (routes, socketList) => { const loadingElement = createLoadingElement(); document.body.appendChild(loadingElement); addLoadingStyles(); - + await Promise.all([ initializeRouter(routes), parsePath(window.location.pathname), + changeUrl("/main", false) ]); - await online(socketList); - document.body.removeChild(loadingElement); + setTimeout(async () => { + await online(socketList); + document.body.removeChild(loadingElement); + }, 1000); }; \ No newline at end of file From 1afd10c4d53118581bdcebba384d91976292399a Mon Sep 17 00:00:00 2001 From: minseok128 Date: Sun, 29 Dec 2024 19:44:32 +0900 Subject: [PATCH 14/14] fix game --- backend/game/matchingConsumers.py | 25 ++++++++----------------- backend/game/onlineConsumers.py | 3 ++- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/backend/game/matchingConsumers.py b/backend/game/matchingConsumers.py index 530f2b1..f9ef6e8 100644 --- a/backend/game/matchingConsumers.py +++ b/backend/game/matchingConsumers.py @@ -76,7 +76,6 @@ async def connect(self): self.start_time = timezone.now() if self.room_group_name not in MatchingGameConsumer.game_states: - await self.close() return await self.accept() @@ -90,7 +89,7 @@ async def receive(self, text_data): token = text_data_json.get("token") if not token or not self.authenticate(token): print("authentication failed") - await self.close(code=4001) + # await self.close(code=4001) return self.authenticated = True @@ -112,8 +111,6 @@ async def receive(self, text_data): MatchingGameConsumer.game_tasks[self.room_group_name] = ( asyncio.create_task(self.game_loop()) ) - elif not self.authenticated: - await self.close(code=4001) else: bar = text_data_json.get("bar") state = MatchingGameConsumer.game_states[self.room_group_name] @@ -180,19 +177,8 @@ async def disconnect(self, close_code): # send_game_result를 통해 DB에 게임결과 저장 및 # 나머지(승자)에게 게임 종료 메시지 전송 await self.send_game_result(winner_index) - - # 이후 방 정리(루프 종료 + state 정리) - # 한쪽이 승리 처리가 끝났으므로 game_loop도 종료시키는 편이 좋습니다. - if self.room_group_name in MatchingGameConsumer.game_tasks: - MatchingGameConsumer.game_tasks[self.room_group_name].cancel() - - # 방 청소 - if self.room_group_name in MatchingGameConsumer.game_states: - del MatchingGameConsumer.game_states[self.room_group_name] - if self.room_group_name in MatchingGameConsumer.client_counts: - del MatchingGameConsumer.client_counts[self.room_group_name] - if self.room_group_name in MatchingGameConsumer.game_tasks: - del MatchingGameConsumer.game_tasks[self.room_group_name] + await asyncio.sleep(0.1) + await self.close() # 아무도 안 남았다면 방을 완전히 정리 elif MatchingGameConsumer.client_counts[self.room_group_name] <= 0: @@ -281,6 +267,7 @@ async def game_loop(self): async def send_game_result(self, winner): state = MatchingGameConsumer.game_states[self.room_group_name] + print("Game result for", self.room_group_name, ":", state.score, "Winner:", winner) await self.save_game_result(state, winner) await self.channel_layer.group_send( self.room_group_name, @@ -303,6 +290,10 @@ def save_game_result(self, state, winner): user2_obj = None score1, score2 = state.score + if winner == 0: + score1 = MAX_SCORE + else: + score2 = MAX_SCORE game = Game.objects.create( game_type="PvP", diff --git a/backend/game/onlineConsumers.py b/backend/game/onlineConsumers.py index 49be6e2..c7e2319 100644 --- a/backend/game/onlineConsumers.py +++ b/backend/game/onlineConsumers.py @@ -6,6 +6,7 @@ from channels.exceptions import DenyConnection from .matchingConsumers import MatchingGameConsumer, MatchingGameState import random +import uuid class OnlineConsumer(AsyncWebsocketConsumer): @@ -27,7 +28,7 @@ async def start_game(cls): user1 = cls.matching_queue.pop(0) user2 = cls.matching_queue.pop(0) - room_name = f"{user1.uid}_{user2.uid}" + room_name = f"{user1.uid}_{user2.uid}_{str(uuid.uuid4())[:8]}" game_state = MatchingGameState() await game_state.initialize(user1.uid, user2.uid) # 비동기 초기화