diff --git a/frontend/src/auth.ts b/frontend/src/auth.ts index c7a5d57..5c97eab 100644 --- a/frontend/src/auth.ts +++ b/frontend/src/auth.ts @@ -4,7 +4,6 @@ import cookie from "js-cookie"; import { ensureWindowState, isNullish } from "@app/utils"; import { handleRoute, navigateTo } from "./routing"; - cookie.remove("pkce"); const headerProfile = document.querySelector("#header-profile")!; @@ -29,6 +28,7 @@ declare module "ft_state" { user: User | null; _headerProfile: boolean; _reloadOnAuthChange: boolean; + lastAuthChange: number; } } @@ -41,12 +41,16 @@ export function isLogged(): boolean { } export function setUser(newUser: User | null) { - let sendEvent = (window.__state.user?.id !== newUser?.id); + let sendEvent = window.__state.user?.id !== newUser?.id; window.__state.user = newUser; updateHeaderProfile(); if (sendEvent) { - const event = new CustomEvent("ft:userChange", { detail: isNullish(newUser) ? null : { id: newUser.id, name: newUser.name } }); + const event = new CustomEvent("ft:userChange", { + detail: isNullish(newUser) + ? null + : { id: newUser.id, name: newUser.name }, + }); document.dispatchEvent(event); } } @@ -89,7 +93,9 @@ window.__state._headerProfile ??= false; if (!window.__state._headerProfile) { headerProfile.addEventListener("click", () => { if (window.__state.user === null) { - navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`); + navigateTo( + `/login?returnTo=${encodeURIComponent(window.location.pathname)}`, + ); } else { navigateTo("/profile"); } @@ -99,7 +105,12 @@ if (!window.__state._headerProfile) { window.__state._reloadOnAuthChange ??= false; if (!window.__state._reloadOnAuthChange) { - document.addEventListener("ft:userChange", () => handleRoute()); + document.addEventListener("ft:userChange", () => { + // if the last forced auth change is less than 1000 sec old -> we do nothing + if (Date.now() - (window.__state.lastAuthChange ?? Date.now()) < 1000) + return; + handleRoute(); + }); window.__state._reloadOnAuthChange = true; } updateHeaderProfile(); diff --git a/frontend/src/pages/login/login.ts b/frontend/src/pages/login/login.ts index f0fb8e1..af72074 100644 --- a/frontend/src/pages/login/login.ts +++ b/frontend/src/pages/login/login.ts @@ -11,11 +11,13 @@ import client from "@app/api"; import cuteCat from "./cuteCat.png"; import loggedInHtml from "./alreadyLoggedin.html?raw"; import totpHtml from "./totp.html?raw"; -import { isNullish } from "@app/utils"; +import { ensureWindowState, isNullish } from "@app/utils"; import { showError, showInfo, showSuccess } from "@app/toast"; import { updateUser } from "@app/auth"; const TOTP_LENGTH = 6; +ensureWindowState(); +window.__state.lastAuthChange = Date.now(); async function handleOtp( app: HTMLElement, @@ -86,6 +88,7 @@ async function handleOtp( }); if (res.kind === "success") { + window.__state.lastAuthChange = Date.now(); Cookie.set("token", res.payload.token, { path: "/", sameSite: "lax", @@ -178,6 +181,7 @@ async function handleLogin( }); switch (res.kind) { case "success": { + window.__state.lastAuthChange = Date.now(); Cookie.set("token", res.payload.token, { path: "/", sameSite: "lax", @@ -219,6 +223,7 @@ async function handleLogin( }); switch (res.kind) { case "success": { + window.__state.lastAuthChange = Date.now(); Cookie.set("token", res.payload.token, { path: "/", sameSite: "lax", diff --git a/frontend/src/pages/pong/pong.ts b/frontend/src/pages/pong/pong.ts index 5733f54..c83410a 100644 --- a/frontend/src/pages/pong/pong.ts +++ b/frontend/src/pages/pong/pong.ts @@ -8,7 +8,13 @@ import { import authHtml from "./pong.html?raw"; import tourScoresHtml from "./tourTable.html?raw"; import io from "socket.io-client"; -import { JoinRes, type CSocket, type GameMove, type GameUpdate, type TourInfo } from "./socket"; +import { + JoinRes, + type CSocket, + type GameMove, + type GameUpdate, + type TourInfo, +} from "./socket"; import { showError, showInfo, showSuccess } from "@app/toast"; import { getUser as getSelfUser, type User } from "@app/auth"; import { isNullish } from "@app/utils"; @@ -86,6 +92,7 @@ function pongClient( // [ ] shape sock // - [ ] joinGame (guid) -> ["ok"|"no, dont ever talk to my kid or me ever again you creep"]; // - [ ] launch newgame evt? + let inTournament = false; return { html: authHtml, @@ -245,21 +252,19 @@ function pongClient( // join game // --- if (game_req_join != null) { - socket.emit('joinGame', game_req_join, - (res : JoinRes) => { - switch (res) { - case JoinRes.yes : - showInfo('JoinRes = yes'); - quitChat(); - break; - case JoinRes.no : - showInfo('JoinRes = no'); - break; - default: - showError('JoinRes switch fail:' + res); - } + socket.emit("joinGame", game_req_join, (res: JoinRes) => { + switch (res) { + case JoinRes.yes: + showInfo("JoinRes = yes"); + quitChat(); + break; + case JoinRes.no: + showInfo("JoinRes = no"); + break; + default: + showError("JoinRes switch fail:" + res); } - ) + }); game_req_join = null; } // --- @@ -300,20 +305,33 @@ function pongClient( playerL.innerText = ""; currentGame = null; } + let render_tour_score_once = false; const renderTournamentScores = (info: TourInfo) => { let players = info.players.sort((l, r) => r.score - l.score); const medals = ["🥇", "🥈", "🥉"]; - tour_scores.innerHTML = tourScoresHtml; + if (!render_tour_score_once) + { + tour_scores.innerHTML = tourScoresHtml; + render_tour_score_once = true; + } let table = tour_scores.querySelector("#tour-score-body"); - if (table) - table.innerHTML = players.map((player, idx) => - ` - ${idx < medals.length ? `${medals[idx]}` : ''}${player.name} - ${player.score} - `) + let table_shadow = document.createElement("tbody"); + if (table) { + table_shadow.innerHTML = players + .map( + (player, idx) => + ` + ${idx < medals.length ? medals[idx] : ""}${player.name} + ${player.score} + `, + ) .join(""); + if (table_shadow.innerHTML !== table.innerHTML) { + table.innerHTML = table_shadow.innerHTML; + } + } }; // TODO: REMOVE THIS @@ -371,6 +389,10 @@ function pongClient( // btn setup queueBtn.addEventListener("click", () => { + if (inTournament) { + showError("You can't queue up currently !"); + return; + } if (queueBtn.innerText !== QueueState.Iddle) { if (queueBtn.innerText === QueueState.InQueu) { socket.emit("dequeue"); @@ -384,11 +406,10 @@ function pongClient( LocalGameBtn.addEventListener("click", () => { if ( queueBtn.innerText !== QueueState.Iddle || - currentGame !== null + currentGame !== null || + inTournament ) { - showError( - "cant launch a local game while in queue/in game", - ); + showError("cant launch a game currently"); return; } socket.emit("localGame"); @@ -467,6 +488,7 @@ function pongClient( updateCurrentGame(state); render(state); + tour_scores.classList.add("hidden"); queueBtn.innerText = QueueState.InGame; queueBtn.style.color = "red"; batLeft.style.backgroundColor = DEFAULT_COLOR; @@ -487,14 +509,17 @@ function pongClient( queueBtn.style.color = "white"; if (!isNullish(currentGame)) { - let end_txt: string = ''; - if ((user.id === currentGame.game.left.id && winner === 'left') || - (user.id === currentGame.game.right.id && winner === 'right')) - end_txt = 'you won! #yippe'; - else - end_txt = 'you lost #sadge'; + let end_txt: string = ""; + if ( + (user.id === currentGame.game.left.id && + winner === "left") || + (user.id === currentGame.game.right.id && + winner === "right") + ) + end_txt = "you won! #yippe"; + else end_txt = "you lost #sadge"; if (currentGame.spectating) - end_txt = `${winner === 'left' ? currentGame.playerL.name : currentGame.playerR.name} won #gg`; + end_txt = `${winner === "left" ? currentGame.playerL.name : currentGame.playerR.name} won #gg`; end_scr.innerText = end_txt; end_scr.classList.remove("hidden"); setTimeout(() => { @@ -528,13 +553,16 @@ function pongClient( let imOwner = s.ownerId === user.id; switch (s.state) { case "ended": + inTournament = false; tournamentBtn.innerText = TourBtnState.AbleToCreate; break; case "playing": + inTournament = true; tournamentBtn.innerText = TourBtnState.Started; tour_infos.innerText = `${TourInfoState.Running} ${s.players.length}👤 ${s.remainingMatches ?? "?"}▮•▮`; break; case "prestart": + inTournament = true; tour_infos.innerText = `${imOwner ? TourInfoState.Owner : weIn ? TourInfoState.Registered : TourInfoState.NotRegisted} ${s.players.length}👤 ?▮•▮`; if (imOwner) { tournamentBtn.innerText = TourBtnState.AbleToStart; diff --git a/frontend/src/pages/pong/tourTable.html b/frontend/src/pages/pong/tourTable.html index e1be5a8..f9caeba 100644 --- a/frontend/src/pages/pong/tourTable.html +++ b/frontend/src/pages/pong/tourTable.html @@ -5,10 +5,10 @@ - - diff --git a/frontend/src/pages/signin/signin.ts b/frontend/src/pages/signin/signin.ts index 8e60837..ea3032f 100644 --- a/frontend/src/pages/signin/signin.ts +++ b/frontend/src/pages/signin/signin.ts @@ -1,26 +1,38 @@ -import { addRoute, setTitle, navigateTo, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing"; +import { + addRoute, + setTitle, + navigateTo, + type RouteHandlerParams, + type RouteHandlerReturn, +} from "@app/routing"; import { showError, showInfo, showSuccess } from "@app/toast"; -import page from './signin.html?raw'; -import client from '@app/api' +import page from "./signin.html?raw"; +import client from "@app/api"; import { updateUser } from "@app/auth"; -import Cookie from 'js-cookie'; -import loggedInHtml from './alreadyLoggedin.html?raw'; -import { isNullish } from "@app/utils"; -import cuteCat from './cuteCat.png'; +import Cookie from "js-cookie"; +import loggedInHtml from "./alreadyLoggedin.html?raw"; +import { ensureWindowState, isNullish } from "@app/utils"; +import cuteCat from "./cuteCat.png"; const MSG_KEY_TO_STRING = { - 'signin.failed.username.existing': 'Username already exists', - 'signin.failed.username.toolong': 'Username is too short', - 'signin.failed.username.tooshort': 'Username is too long', - 'signin.failed.username.invalid': 'Username is invalid', - 'signin.failed.password.toolong': 'Password is too long', - 'signin.failed.password.tooshort': 'Password is too short', - 'signin.failed.password.invalid': 'Password is invalid', - 'signin.failed.generic': 'Unknown Error', + "signin.failed.username.existing": "Username already exists", + "signin.failed.username.toolong": "Username is too short", + "signin.failed.username.tooshort": "Username is too long", + "signin.failed.username.invalid": "Username is invalid", + "signin.failed.password.toolong": "Password is too long", + "signin.failed.password.tooshort": "Password is too short", + "signin.failed.password.invalid": "Password is invalid", + "signin.failed.generic": "Unknown Error", }; -async function handleSignin(_url: string, _args: RouteHandlerParams): Promise { - setTitle('Signin') +ensureWindowState(); +window.__state.lastAuthChange = Date.now(); + +async function handleSignin( + _url: string, + _args: RouteHandlerParams, +): Promise { + setTitle("Signin"); let user = await updateUser(); const urlParams = new URLSearchParams(window.location.search); const returnTo = urlParams.get("returnTo"); @@ -57,43 +69,67 @@ async function handleSignin(_url: string, _args: RouteHandlerParams): Promise { - const fSignin = document.querySelector('form#signin-form'); + html: page, + postInsert: async (app) => { + const fSignin = + document.querySelector("form#signin-form"); if (fSignin === null) - return showError('Error while rendering the page: no form found'); - fSignin.addEventListener('submit', async function(e: SubmitEvent) { + return showError( + "Error while rendering the page: no form found", + ); + fSignin.addEventListener("submit", async function(e: SubmitEvent) { e.preventDefault(); - let form = e.target as (HTMLFormElement | null); - if (form === null) - return showError('Failed to send form...'); - let formData = Object.fromEntries((new FormData(form)).entries()); - if (!('login' in formData) || typeof formData['login'] !== 'string' || (formData['login'] as string).length === 0) - return showError('Please enter a Login'); - if (!('password' in formData) || typeof formData['password'] !== 'string' || (formData['password'] as string).length === 0) - return showError('Please enter a Password'); + let form = e.target as HTMLFormElement | null; + if (form === null) return showError("Failed to send form..."); + let formData = Object.fromEntries(new FormData(form).entries()); + if ( + !("login" in formData) || + typeof formData["login"] !== "string" || + (formData["login"] as string).length === 0 + ) + return showError("Please enter a Login"); + if ( + !("password" in formData) || + typeof formData["password"] !== "string" || + (formData["password"] as string).length === 0 + ) + return showError("Please enter a Password"); try { - const res = await client.signin({ loginRequest: { name: formData.login, password: formData.password } }); + const res = await client.signin({ + loginRequest: { + name: formData.login, + password: formData.password, + }, + }); switch (res.kind) { - case 'success': { - Cookie.set('token', res.payload.token, { path: '/', sameSite: 'lax' }); + case "success": { + window.__state.lastAuthChange = Date.now(); + Cookie.set("token", res.payload.token, { + path: "/", + sameSite: "lax", + }); let user = await updateUser(); if (user === null) - return showError('Failed to get user: no user ?'); - navigateTo(returnTo !== null ? returnTo : '/') + return showError( + "Failed to get user: no user ?", + ); + navigateTo(returnTo !== null ? returnTo : "/"); break; } - case 'failed': { - showError(`Failed to signin: ${MSG_KEY_TO_STRING[res.msg]}`); + case "failed": { + showError( + `Failed to signin: ${MSG_KEY_TO_STRING[res.msg]}`, + ); } } } catch (e) { console.error("Signin error:", e); - showError('Failed to signin: Unknown error'); + showError("Failed to signin: Unknown error"); } }); - } - } -}; + }, + }; +} +addRoute("/signin", handleSignin, { bypass_auth: true }); -addRoute('/signin', handleSignin, { bypass_auth: true }) diff --git a/src/pong/src/routes/tournamentData.ts b/src/pong/src/routes/tournamentData.ts index 7a4b105..8d41306 100644 --- a/src/pong/src/routes/tournamentData.ts +++ b/src/pong/src/routes/tournamentData.ts @@ -82,12 +82,12 @@ const route: FastifyPluginAsync = async (fastify): Promise => { left: { score: v.left.score, id: v.left.id, - name: `${v.nameL}-left`, + name: `${v.nameL}`, }, right: { score: v.right.score, id: v.right.id, - name: `${v.nameR}-right`, + name: `${v.nameR}`, }, local: v.local, date: v.time.toString(), diff --git a/src/pong/src/state.ts b/src/pong/src/state.ts index 0aba22f..75c0a02 100644 --- a/src/pong/src/state.ts +++ b/src/pong/src/state.ts @@ -77,23 +77,21 @@ class StateI { const u1 = this.users.get(id1); const u2 = this.users.get(id2); - if (isNullish(u1) || isNullish(u2)) return null; - this.fastify.log.info({ msg: 'init new game', - user1: u1.id, - user2: u2.id, + user1: id1, + user2: id2, }); - if (g === null) g = new Pong(u1.id, u2.id); + if (g === null) g = new Pong(id1, id2); const iState: GameUpdate = StateI.getGameUpdateData(gameId, g); - u1.socket.emit('newGame', iState); - u2.socket.emit('newGame', iState); + u1?.socket.emit('newGame', iState); + u2?.socket.emit('newGame', iState); g.rdy_timer = Date.now(); this.games.set(gameId, g); - u1.currentGame = gameId; - u2.currentGame = gameId; + if (u1) { u1.currentGame = gameId; } + if (u2) { u2.currentGame = gameId; } g.gameUpdate = setInterval(() => { g.tick(); @@ -102,13 +100,21 @@ class StateI { g.ready_checks[0] === true && g.ready_checks[1] === true ) { - u1.socket.emit('rdyEnd'); - u2.socket.emit('rdyEnd'); + if (u1) { + u1.socket.emit('rdyEnd'); + } + if (u2) { + u2.socket.emit('rdyEnd'); + } g.sendSig = true; } if (g.ready_checks[0] === true && g.ready_checks[1] === true) { - this.gameUpdate(gameId, u1.socket); - this.gameUpdate(gameId, u2.socket); + if (u1) { + this.gameUpdate(gameId, u1.socket); + } + if (u2) { + this.gameUpdate(gameId, u2.socket); + } } if (g.checkWinner() !== null) { this.cleanupGame(gameId, g); @@ -376,14 +382,14 @@ class StateI { this.cleanupUser(sock); } - private tryJoinGame(g_id : string, sock : SSocket) : JoinRes { - const game_id : PongGameId = g_id as PongGameId; + private tryJoinGame(g_id: string, sock: SSocket): JoinRes { + const game_id: PongGameId = g_id as PongGameId; if (this.games.has(game_id) === false) { this.fastify.log.warn('gameId:' + g_id + ' is unknown!'); return (JoinRes.no); } - const game : Pong = this.games.get(game_id)!; + const game: Pong = this.games.get(game_id)!; if (game.local === true || (game.userLeft !== sock.authUser.id && game.userRight !== sock.authUser.id)) { this.fastify.log.warn('user trying to connect to a game he\'s not part of: gameId:' + g_id + ' userId:' + sock.authUser.id); return (JoinRes.no); @@ -426,7 +432,7 @@ class StateI { socket.on('gameMove', (e) => this.gameMove(socket, e)); socket.on('localGame', () => this.newLocalGame(socket)); - socket.on('joinGame', (g_id, ack) => {return (ack(this.tryJoinGame(g_id, socket)));}); + socket.on('joinGame', (g_id, ack) => { return (ack(this.tryJoinGame(g_id, socket))); }); // todo: allow passing nickname socket.on('tourRegister', () => this.registerForTournament(socket, null), @@ -587,4 +593,4 @@ export let State: StateI = undefined as unknown as StateI; export function newState(f: FastifyInstance) { State = new StateI(f); -} \ No newline at end of file +}
+ Name + Score