From 7886b3a6b8e63c17b35287cbe22a7215513792c4 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 3 Jul 2026 19:16:41 +0200 Subject: [PATCH] fix rusenie hier ked vsetkych odpoji --- api/__init__.py | 39 +++++++++++++++++++++++++++--------- frontend/src/lib/socket.ts | 3 +++ frontend/src/pages/Lobby.tsx | 21 +++++++++++++++++-- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/api/__init__.py b/api/__init__.py index 48934f9..2600388 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -1,3 +1,4 @@ +import asyncio import hmac import json import os @@ -273,19 +274,37 @@ async def send_error(sid: str, message: str): await sio.emit("error", {"error": message}, to=sid) +# Ako dlho prezije nezacata hra, ked su vsetci hraci naraz offline. Na mobile +# sa socket bezne strati uz pri zamknuti obrazovky, takze okamzite zmazanie +# hry rusilo lobby, v ktorom hraci len cakali so zhasnutym telefonom. +LOBBY_ABANDON_GRACE_SECONDS = 10 * 60 + + +async def _cleanup_abandoned_lobby(gid: str): + """Po grace periode zmaz nezacatu hru, ak sa medzitym nikto nevratil. + Podmienka sa overuje az po uplynuti casu, takze pri navrate hraca je + task neskodny no-op (netreba nic rusit).""" + await asyncio.sleep(LOBBY_ABANDON_GRACE_SECONDS) + game = games.get(gid) + if game is not None and not game.started and not any(p.connected for p in game.players): + del games[gid] + await broadcast_lobby() + + async def _mark_player_offline(game: "Game", player: "Player"): - """Mark player disconnected. An unstarted game with nobody left is cleaned - up; a started game is kept in memory so it stays in the lobby and can be - resumed (it's torn down only by end_game).""" + """Mark player disconnected. An unstarted game with nobody left gets a + delayed cleanup (mobile sockets drop on screen lock, so an immediate + delete would kill lobbies where everyone is just waiting); a started game + is kept in memory so it stays in the lobby and can be resumed (it's torn + down only by end_game).""" player.connected = False if not any(p.connected for p in game.players) and not game.started: - del games[game.gid] - else: - await sio.emit( - "player_connection", - {"order": player.order, "connected": False}, - room=game.gid, - ) + asyncio.create_task(_cleanup_abandoned_lobby(game.gid)) + await sio.emit( + "player_connection", + {"order": player.order, "connected": False}, + room=game.gid, + ) def _active_game(sid: str) -> "tuple[Game, dict] | None": diff --git a/frontend/src/lib/socket.ts b/frontend/src/lib/socket.ts index 51bbb8d..641eb66 100644 --- a/frontend/src/lib/socket.ts +++ b/frontend/src/lib/socket.ts @@ -132,6 +132,9 @@ export function setupSocketListeners() { // Surface connection failures instead of silently buffering emits. socket.on('connect_error', (err: Error) => { + // `active` = klient sa automaticky pokusi znova (typicky prebudeny mobil, + // kym sa siet zobudi) -- prechodne, netreba strasit toastom. + if (socket.active) return; useGameStore.getState().setError(`Spojenie so serverom zlyhalo: ${err.message}`); }); } diff --git a/frontend/src/pages/Lobby.tsx b/frontend/src/pages/Lobby.tsx index f5f8ae8..1ee79f2 100644 --- a/frontend/src/pages/Lobby.tsx +++ b/frontend/src/pages/Lobby.tsx @@ -1,3 +1,4 @@ +import { useEffect, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useGameStore } from '../store/gameStore'; import { emit } from '../lib/socket'; @@ -8,6 +9,8 @@ export default function Lobby() { const navigate = useNavigate(); const myPlayer = useGameStore((s) => s.myPlayer); const games = useGameStore((s) => s.games); + const [showCopied, setShowCopied] = useState(false); + const copiedTimeout = useRef>(); const game = games.find((g) => g.gid === gid); const players = game?.players ?? []; @@ -17,11 +20,25 @@ export default function Lobby() { const handleLeave = () => leaveGame(navigate); const handleCopyCode = () => { - if (gid) navigator.clipboard.writeText(`${window.location.origin}/lobby/${gid}`); + if (!gid) return; + navigator.clipboard.writeText(`${window.location.origin}/lobby/${gid}`); + setShowCopied(true); + clearTimeout(copiedTimeout.current); + copiedTimeout.current = setTimeout(() => setShowCopied(false), 1800); }; + useEffect(() => () => clearTimeout(copiedTimeout.current), []); + return (
+
+ Odkaz na hru skopírovaný +
+

{game?.name ?? 'Hra'}