auth reload and better tour handling
This commit is contained in:
parent
deb391807a
commit
30540bfe92
7 changed files with 188 additions and 102 deletions
|
|
@ -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<HTMLDivElement>("#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();
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
socket.emit("joinGame", game_req_join, (res: JoinRes) => {
|
||||
switch (res) {
|
||||
case JoinRes.yes:
|
||||
showInfo('JoinRes = yes');
|
||||
showInfo("JoinRes = yes");
|
||||
quitChat();
|
||||
break;
|
||||
case JoinRes.no:
|
||||
showInfo('JoinRes = no');
|
||||
showInfo("JoinRes = no");
|
||||
break;
|
||||
default:
|
||||
showError('JoinRes switch fail:' + res);
|
||||
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 = ["🥇", "🥈", "🥉"];
|
||||
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) =>
|
||||
`<tr class="${player.id === user.id ? "bg-amber-400 hover:bg-amber-500" : "hover:bg-gray-50"}" key="${player.id}">
|
||||
<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-semibold min-w-100px">${idx < medals.length ? `<span class="font-lg">${medals[idx]}</span>` : ''}${player.name}</td>
|
||||
let table_shadow = document.createElement("tbody");
|
||||
if (table) {
|
||||
table_shadow.innerHTML = players
|
||||
.map(
|
||||
(player, idx) =>
|
||||
`<tr class="${player.id === user.id ? "bg-amber-400 hover:bg-amber-500" : "hover:bg-gray-50"}" data-id="${player.id}">
|
||||
<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-semibold min-w-100px"><span class="font-lg medal">${idx < medals.length ? medals[idx] : ""}</span>${player.name}</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-bold min-w-100px">${player.score}</td>
|
||||
</tr>`)
|
||||
</tr>`,
|
||||
)
|
||||
.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;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
<table class="min-w-full border-collapse table-fixed">
|
||||
<thead class="sticky top-0 z-10 bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-right text-sm font-semibold text-gray-700 border-b">
|
||||
<th class="px-4 py-2 text-center text-sm font-semibold text-gray-700 border-b">
|
||||
Name
|
||||
</th>
|
||||
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 border-b">
|
||||
<th class="px-4 py-2 text-center text-sm font-semibold text-gray-700 border-b">
|
||||
Score
|
||||
</th>
|
||||
</tr>
|
||||
|
|
|
|||
|
|
@ -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<RouteHandlerReturn> {
|
||||
setTitle('Signin')
|
||||
ensureWindowState();
|
||||
window.__state.lastAuthChange = Date.now();
|
||||
|
||||
async function handleSignin(
|
||||
_url: string,
|
||||
_args: RouteHandlerParams,
|
||||
): Promise<RouteHandlerReturn> {
|
||||
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<Ro
|
|||
}
|
||||
|
||||
return {
|
||||
html: page, postInsert: async (app) => {
|
||||
const fSignin = document.querySelector<HTMLFormElement>('form#signin-form');
|
||||
html: page,
|
||||
postInsert: async (app) => {
|
||||
const fSignin =
|
||||
document.querySelector<HTMLFormElement>("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 })
|
||||
|
|
|
|||
|
|
@ -82,12 +82,12 @@ const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||
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(),
|
||||
|
|
|
|||
|
|
@ -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,14 +100,22 @@ class StateI {
|
|||
g.ready_checks[0] === true &&
|
||||
g.ready_checks[1] === true
|
||||
) {
|
||||
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) {
|
||||
if (u1) {
|
||||
this.gameUpdate(gameId, u1.socket);
|
||||
}
|
||||
if (u2) {
|
||||
this.gameUpdate(gameId, u2.socket);
|
||||
}
|
||||
}
|
||||
if (g.checkWinner() !== null) {
|
||||
this.cleanupGame(gameId, g);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue