feat(chat): Handle user change

This commit is contained in:
Maieul BOYER 2026-01-11 13:52:00 +01:00 committed by Nigel
parent 0ec2b3007c
commit f14d618ed5
4 changed files with 363 additions and 351 deletions

View file

@ -2,6 +2,7 @@ import { type State } from "ft_state";
interface CustomEventMap { interface CustomEventMap {
"ft:pageChange": CustomEvent<string>; "ft:pageChange": CustomEvent<string>;
"ft:userChange": CustomEvent<{id: user, name: string} | null>;
} }
declare global { declare global {

View file

@ -1,7 +1,7 @@
import { showError } from "@app/toast"; import { showError } from "@app/toast";
import client from "@app/api"; import client from "@app/api";
import cookie from "js-cookie"; import cookie from "js-cookie";
import { ensureWindowState } from "@app/utils"; import { ensureWindowState, isNullish } from "@app/utils";
import { navigateTo } from "./routing"; import { navigateTo } from "./routing";
@ -40,8 +40,14 @@ export function isLogged(): boolean {
} }
export function setUser(newUser: User | null) { export function setUser(newUser: User | null) {
let sendEvent = (window.__state.user?.id !== newUser?.id);
window.__state.user = newUser; window.__state.user = newUser;
updateHeaderProfile(); updateHeaderProfile();
if (sendEvent) {
const event = new CustomEvent("ft:userChange", { detail: isNullish(newUser) ? null : { id: newUser.id, name: newUser.name } });
document.dispatchEvent(event);
}
} }
export function updateHeaderProfile() { export function updateHeaderProfile() {

View file

@ -2,16 +2,16 @@ import "./chat.css";
import io, { Socket } from "socket.io-client"; import io, { Socket } from "socket.io-client";
import type { blockedUnBlocked } from "./types_front"; import type { blockedUnBlocked } from "./types_front";
import type { import type {
ClientMessage, ClientMessage,
ClientProfil, ClientProfil,
ClientProfilPartial, ClientProfilPartial,
} from "./types_front"; } from "./types_front";
import type { User } from "@app/auth"; import type { User } from "@app/auth";
import { import {
addRoute, addRoute,
setTitle, setTitle,
type RouteHandlerParams, type RouteHandlerParams,
type RouteHandlerReturn, type RouteHandlerReturn,
} from "@app/routing"; } from "@app/routing";
import authHtml from "./chat.html?raw"; import authHtml from "./chat.html?raw";
import { getUser } from "@app/auth"; import { getUser } from "@app/auth";
@ -31,395 +31,400 @@ import { quitChat } from "./chatHelperFunctions/quitChat";
import { openMessagePopup } from "./chatHelperFunctions/openMessagePopup"; import { openMessagePopup } from "./chatHelperFunctions/openMessagePopup";
import { windowStateVisable } from "./chatHelperFunctions/windowStateVisable"; import { windowStateVisable } from "./chatHelperFunctions/windowStateVisable";
import { cmdList } from "./chatHelperFunctions/cmdList"; import { cmdList } from "./chatHelperFunctions/cmdList";
import { showInfo } from '../toast'; import { showInfo } from "../toast";
const MAX_SYSTEM_MESSAGES = 10; const MAX_SYSTEM_MESSAGES = 10;
let inviteMsgFlag: boolean = false; let inviteMsgFlag: boolean = false;
export let noGuestFlag: boolean = true; export let noGuestFlag: boolean = true;
declare module "ft_state" { declare module "ft_state" {
interface State { interface State {
chatSock?: Socket; chatSock?: Socket;
} }
} }
export function getSocket(): Socket { export function getSocket(): Socket {
if (window.__state.chatSock === undefined) if (window.__state.chatSock === undefined)
window.__state.chatSock = io(window.location.host, { window.__state.chatSock = io(window.location.host, {
path: "/api/chat/socket.io/", path: "/api/chat/socket.io/",
}) as any as Socket; }) as any as Socket;
return window.__state.chatSock; return window.__state.chatSock;
} }
const chatBox = document.getElementById("chatBox")!; const chatBox = document.getElementById("chatBox")!;
chatBox.classList.add('hidden'); chatBox.classList.add("hidden");
chatBox.innerHTML = authHtml; chatBox.innerHTML = authHtml;
let socket = getSocket(); const bquit = document.getElementById("b-quit") as HTMLDivElement;
let blockMessage: boolean; const buddies = document.getElementById("div-buddies") as HTMLDivElement;
// Listen for the 'connect' event const buttonMessage = document.getElementById("close-modal-message") ?? null;
socket.on("connect", async () => { const buttonPro = document.getElementById("close-modal") ?? null;
const systemWindow = document.getElementById("system-box") as HTMLDivElement; const chatButton = document.querySelector("#chatButton");
const sendtextbox = document.getElementById( const chatWindow = document.querySelector("#t-chatbox")!;
"t-chat-window" const clearText = document.getElementById("b-clear") as HTMLButtonElement;
) as HTMLButtonElement; const gameMessage = document.getElementById("game-modal") ?? null;
const noGuest = document.getElementById("noGuest") ?? null; const modalmessage = document.getElementById("modal-message") ?? null;
const noGuest = document.getElementById("noGuest") ?? null;
const notify = document.getElementById("notify") ?? null;
const overlay = document.querySelector("#overlay")!;
const profilList = document.getElementById("profile-modal") ?? null;
const sendButton = document.getElementById("b-send") as HTMLButtonElement;
const sendtextbox = document.getElementById(
"t-chat-window",
) as HTMLButtonElement;
const systemWindow = document.getElementById("chat-system-box") as HTMLDivElement;
await waitSocketConnected(socket); function initChatSocket() {
const user = getUser()?.name; let socket = getSocket();
const userID = getUser()?.id; let blockMessage: boolean;
// Ensure we have a user AND socket is connected // Listen for the 'connect' event
if (!user || !socket.connected || !noGuest) return; socket.on("connect", async () => {
const message = { await waitSocketConnected(socket);
command: "", const user = getUser()?.name;
destination: "system-info", const userID = getUser()?.id;
type: "chat", // Ensure we have a user AND socket is connected
user, if (!user || !socket.connected || !noGuest) return;
token: document.cookie ?? "", const message = {
text: " has Just ARRIVED in the chat", command: "",
timestamp: Date.now(), destination: "system-info",
SenderWindowID: socket.id, type: "chat",
SenderID: userID, user,
}; token: document.cookie ?? "",
socket.emit("message", JSON.stringify(message)); text: " has Just ARRIVED in the chat",
const guest = getUser()?.guest; timestamp: Date.now(),
if (guest) { SenderWindowID: socket.id,
noGuest.innerText = ""; SenderID: userID,
} else { };
noGuest.innerText = "❤️"; socket.emit("message", JSON.stringify(message));
} const guest = getUser()?.guest;
if (guest) {
noGuest.innerText = "";
} else {
noGuest.innerText = "❤️";
}
const userProfile: ClientProfil = { const userProfile: ClientProfil = {
command: "@noguest", command: "@noguest",
destination: "", destination: "",
type: "chat", type: "chat",
timestamp: Date.now(), timestamp: Date.now(),
guestmsg: true, guestmsg: true,
}; };
socket.emit("guestmsg", JSON.stringify(userProfile)); socket.emit("guestmsg", JSON.stringify(userProfile));
const messageElement = document.createElement("div"); const messageElement = document.createElement("div");
messageElement.textContent = `${user}: is connected au server`; messageElement.textContent = `${user}: is connected au server`;
systemWindow.appendChild(messageElement); systemWindow.appendChild(messageElement);
systemWindow.scrollTop = systemWindow.scrollHeight; systemWindow.scrollTop = systemWindow.scrollHeight;
}); });
// Listen for messages from the server "MsgObjectServer" // Listen for messages from the server "MsgObjectServer"
socket.on("MsgObjectServer", (data: { message: ClientMessage }) => { socket.on("MsgObjectServer", (data: { message: ClientMessage }) => {
const systemWindow = document.getElementById("system-box") as HTMLDivElement; if (socket) {
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; connected(socket);
}
if (socket) { if (chatWindow && data.message.destination === "") {
connected(socket); const messageElement = document.createElement("div");
} messageElement.textContent = `${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
if (chatWindow && data.message.destination === "") { if (chatWindow && data.message.destination === "privateMsg") {
const messageElement = document.createElement("div"); const messageElement = document.createElement("div-private");
messageElement.textContent = `${data.message.user}: ${data.message.text}`; messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement); chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight; chatWindow.scrollTop = chatWindow.scrollHeight;
} }
if (chatWindow && data.message.destination === "privateMsg") { if (chatWindow && data.message.destination === "inviteMsg") {
const messageElement = document.createElement("div-private"); const messageElement = document.createElement("div-private");
messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`; const chatWindow = document.getElementById(
chatWindow.appendChild(messageElement); "t-chatbox",
chatWindow.scrollTop = chatWindow.scrollHeight; ) as HTMLDivElement;
} messageElement.innerHTML = `🏓${data.message.SenderUserName}: ${data.message.innerHtml}`;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
if (chatWindow && data.message.destination === "inviteMsg") { if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div-private"); const messageElement = document.createElement("div");
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; messageElement.textContent = `${data.message.user}: ${data.message.text}`;
messageElement.innerHTML = `🏓${data.message.SenderUserName}: ${data.message.innerHtml}`; systemWindow.appendChild(messageElement);
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
if (systemWindow && data.message.destination === "system-info") { // keep only last 10
const messageElement = document.createElement("div"); while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
messageElement.textContent = `${data.message.user}: ${data.message.text}`; systemWindow.removeChild(systemWindow.firstChild!);
systemWindow.appendChild(messageElement); }
systemWindow.scrollTop = systemWindow.scrollHeight;
}
});
// keep only last 10 socket.on("profilMessage", (profil: ClientProfil) => {
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) { profil.SenderID = getUser()?.id ?? "";
systemWindow.removeChild(systemWindow.firstChild!); profil.SenderName = getUser()?.name ?? "";
} openProfilePopup(profil);
systemWindow.scrollTop = systemWindow.scrollHeight; socket.emit("isBlockdBtn", profil);
} socket.emit("check_Block_button", profil);
}); actionBtnPopUpInvite(profil, socket);
actionBtnPopUpBlock(profil, socket);
});
socket.on("profilMessage", (profil: ClientProfil) => { socket.on("blockUser", (blocked: ClientProfil) => {
profil.SenderID = getUser()?.id ?? ""; let icon = "⛔";
profil.SenderName = getUser()?.name ?? ""; if (inviteMsgFlag) {
openProfilePopup(profil); const messageElement = document.createElement("div");
socket.emit("isBlockdBtn", profil); if (`${blocked.text}` === "I have un-blocked you") {
socket.emit("check_Block_button", profil); icon = "💚";
actionBtnPopUpInvite(profil, socket); }
actionBtnPopUpBlock(profil, socket); messageElement.innerText = `${icon}${blocked.SenderName}: ${blocked.text}`;
}); chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
});
socket.on("blockUser", (blocked: ClientProfil) => { socket.on("blockBtn", (data: blockedUnBlocked) => {
let icon = "⛔"; const blockUserBtn = document.querySelector("#popup-b-block");
if (inviteMsgFlag) { if (blockUserBtn) {
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; let message = "";
const messageElement = document.createElement("div"); if (data.userState === "block") {
if (`${blocked.text}` === "I have un-blocked you") { ((message = "un-block"), (blockMessage = true));
icon = "💚"; } else {
} ((message = "block"), (blockMessage = false));
messageElement.innerText = `${icon}${blocked.SenderName}: ${blocked.text}`; }
chatWindow.appendChild(messageElement); blockUserBtn.textContent = message;
chatWindow.scrollTop = chatWindow.scrollHeight; }
} });
});
socket.on("blockBtn", (data: blockedUnBlocked) => { socket.on("logout", () => {
const blockUserBtn = document.querySelector("#popup-b-block"); quitChat();
if (blockUserBtn) { });
let message = "";
if (data.userState === "block") {
(message = "un-block"), (blockMessage = true);
} else {
(message = "block"), (blockMessage = false);
}
blockUserBtn.textContent = message;
}
});
socket.on("logout", () => { socket.on("privMessageCopy", (message: string) => {
quitChat(socket); addMessage(message);
}); });
socket.on("privMessageCopy", (message: string) => { //receives broadcast of the next GAME
addMessage(message); socket.on("nextGame", (message: string) => {
}); openMessagePopup(message);
});
//receives broadcast of the next GAME socket.on("listBud", async (myBuddies: string[]) => {
socket.on("nextGame", (message: string) => { const buddies = document.getElementById(
openMessagePopup(message); "div-buddies",
) as HTMLDivElement;
listBuddies(socket, buddies, myBuddies);
});
socket.once("welcome", (data) => {
const buddies = document.getElementById(
"div-buddies",
) as HTMLDivElement;
buddies.textContent = "";
buddies.innerHTML = "";
connected(socket);
addMessage(`${data.msg} ` + getUser()?.name);
});
buddies.textContent = "";
buddies.innerHTML = "";
}
if (buttonPro)
buttonPro.addEventListener("click", () => {
if (profilList) profilList.classList.add("hidden");
});
if (buttonMessage)
buttonMessage.addEventListener("click", () => {
if (gameMessage) gameMessage.classList.add("hidden");
if (modalmessage) {
modalmessage.innerHTML = "";
}
});
// Send button
sendButton?.addEventListener("click", () => {
let socket = window.__state.chatSock;
if (!socket) return;
const userId = getUser()?.id;
const userAskingToBlock = getUser()?.name;
if (sendtextbox && sendtextbox.value.trim()) {
let msgText: string = sendtextbox.value.trim();
const msgCommand = parseCmdMsg(msgText) ?? "";
connected(socket);
if (msgCommand !== "") {
switch (msgCommand[0]) {
case "@msg":
broadcastMsg(socket, msgCommand);
break;
case "@block":
if (msgCommand[1] === "") {
break;
}
if (!userAskingToBlock) return;
if (!userId) return;
const userToBlock: ClientProfil = {
command: msgCommand[0],
destination: "",
type: "chat",
user: msgCommand[1],
userID: userId,
timestamp: Date.now(),
SenderWindowID: socket.id,
SenderName: userAskingToBlock,
};
blockUser(userToBlock, socket);
break;
case "@notify":
if (notify === null) {
break;
}
if (inviteMsgFlag === false) {
notify.innerText = "🔔";
inviteMsgFlag = true;
} else {
notify.innerText = "🔕";
inviteMsgFlag = false;
}
break;
case "@guest":
if (!userId) {
return;
}
if (!userAskingToBlock) {
return;
}
if (noGuest === null) {
break;
}
const guest = getUser()?.guest;
if (noGuestFlag === false && noGuest.innerText === "💔") {
noGuest.innerText = "❤️​";
noGuestFlag = true;
} else {
noGuest.innerText = "💔";
noGuestFlag = false;
}
if (guest) {
noGuestFlag = true;
noGuest.innerText = "";
sendtextbox.value = "";
}
const userProfile: ClientProfilPartial = {
command: "@noguest",
destination: "",
type: "chat",
user: userAskingToBlock,
userID: userId,
timestamp: Date.now(),
SenderWindowID: "",
guestmsg: noGuestFlag,
};
socket.emit("guestmsg", JSON.stringify(userProfile));
break;
case "@profile":
if (msgCommand[1] === "") {
break;
}
getProfil(socket, msgCommand[1]);
break;
case "@cls":
chatWindow.innerHTML = "";
break;
case "@help":
cmdList();
break;
case "@quit":
quitChat();
break;
default:
const user: User | null = getUser();
if (!user) return;
if (!user || !socket.connected) return;
const message: ClientProfilPartial = {
command: msgCommand[0],
destination: "",
type: "chat",
user: user.name,
userID: user.id,
token: document.cookie ?? "",
text: msgCommand[1],
timestamp: Date.now(),
SenderWindowID: socket.id ?? "",
SenderID: user.id,
};
socket.emit("privMessage", JSON.stringify(message));
break;
}
// Clear the input in all cases
sendtextbox.value = "";
}
}
}); });
let toggle = false; let toggle = false;
window.addEventListener("focus", async () => { window.addEventListener("focus", async () => {
setTimeout(() => { setTimeout(() => {
connected(socket); if (window.__state.chatSock) connected(window.__state.chatSock);
}, 16); }, 16);
if (window.location.pathname === "/app/chat") { if (window.location.pathname === "/app/chat") {
if (socket.id) { if (window.__state.chatSock?.id) {
await windowStateVisable(); await windowStateVisable();
} }
toggle = true; toggle = true;
} }
}); });
window.addEventListener("blur", () => { window.addEventListener("blur", () => {
if (socket.id) windowStateHidden(); if (window.__state.chatSock?.id) windowStateHidden();
toggle = false; toggle = false;
}); });
socket.on("listBud", async (myBuddies: string[]) => {
const buddies = document.getElementById("div-buddies") as HTMLDivElement;
listBuddies(socket, buddies, myBuddies);
});
socket.once("welcome", (data) => {
const buddies = document.getElementById("div-buddies") as HTMLDivElement;
buddies.textContent = "";
buddies.innerHTML = "";
connected(socket);
addMessage(`${data.msg} ` + getUser()?.name);
});
setTitle("Chat Page");
const sendButton = document.getElementById("b-send") as HTMLButtonElement;
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
const sendtextbox = document.getElementById(
"t-chat-window"
) as HTMLButtonElement;
const clearText = document.getElementById("b-clear") as HTMLButtonElement;
const buddies = document.getElementById("div-buddies") as HTMLDivElement;
const bquit = document.getElementById("b-quit") as HTMLDivElement;
buddies.textContent = "";
buddies.innerHTML = "";
const buttonPro = document.getElementById("close-modal") ?? null;
if (buttonPro)
buttonPro.addEventListener("click", () => {
const profilList = document.getElementById("profile-modal") ?? null;
if (profilList) profilList.classList.add("hidden");
});
const buttonMessage = document.getElementById("close-modal-message") ?? null;
if (buttonMessage)
buttonMessage.addEventListener("click", () => {
const gameMessage = document.getElementById("game-modal") ?? null;
if (gameMessage) gameMessage.classList.add("hidden");
const modalmessage = document.getElementById("modal-message") ?? null;
if (modalmessage) {
modalmessage.innerHTML = "";
}
});
// Send button
sendButton?.addEventListener("click", () => {
const notify = document.getElementById("notify") ?? null;
const noGuest = document.getElementById("noGuest") ?? null;
const userId = getUser()?.id;
const userAskingToBlock = getUser()?.name;
if (sendtextbox && sendtextbox.value.trim()) {
let msgText: string = sendtextbox.value.trim();
const msgCommand = parseCmdMsg(msgText) ?? "";
connected(socket);
if (msgCommand !== "") {
switch (msgCommand[0]) {
case "@msg":
broadcastMsg(socket, msgCommand);
break;
case "@block":
if (msgCommand[1] === "") {
break;
}
if (!userAskingToBlock) return;
if (!userId) return;
const userToBlock: ClientProfil = {
command: msgCommand[0],
destination: "",
type: "chat",
user: msgCommand[1],
userID: userId,
timestamp: Date.now(),
SenderWindowID: socket.id,
SenderName: userAskingToBlock,
};
blockUser(userToBlock, socket);
break;
case "@notify":
if (notify === null) {
break;
}
if (inviteMsgFlag === false) {
notify.innerText = "🔔";
inviteMsgFlag = true;
} else {
notify.innerText = "🔕";
inviteMsgFlag = false;
}
break;
case "@guest":
if (!userId) {
return;
}
if (!userAskingToBlock) {
return;
}
if (noGuest === null) {
break;
}
const guest = getUser()?.guest;
if (noGuestFlag === false && noGuest.innerText === "💔") {
noGuest.innerText = "❤️​";
noGuestFlag = true;
} else {
noGuest.innerText = "💔";
noGuestFlag = false;
}
if (guest) {
noGuestFlag = true;
noGuest.innerText = "";
sendtextbox.value = "";
}
const userProfile: ClientProfilPartial = {
command: "@noguest",
destination: "",
type: "chat",
user: userAskingToBlock,
userID: userId,
timestamp: Date.now(),
SenderWindowID: "",
guestmsg: noGuestFlag,
};
socket.emit("guestmsg", JSON.stringify(userProfile));
break;
case "@profile":
if (msgCommand[1] === "") {
break;
}
getProfil(socket, msgCommand[1]);
break;
case "@cls":
chatWindow.innerHTML = "";
break;
case "@help":
cmdList();
break;
case "@quit":
quitChat(socket);
break;
default:
const user: User | null = getUser();
if (!user) return;
if (!user || !socket.connected) return;
const message: ClientProfilPartial = {
command: msgCommand[0],
destination: "",
type: "chat",
user: user.name,
userID: user.id,
token: document.cookie ?? "",
text: msgCommand[1],
timestamp: Date.now(),
SenderWindowID: socket.id ?? "",
SenderID: user.id,
};
socket.emit("privMessage", JSON.stringify(message));
break;
}
// Clear the input in all cases
sendtextbox.value = "";
}
}
});
// Clear Text button // Clear Text button
clearText?.addEventListener("click", () => { clearText?.addEventListener("click", () => {
if (chatWindow) { if (chatWindow) {
chatWindow.innerHTML = ""; chatWindow.innerHTML = "";
} }
}); });
bquit?.addEventListener("click", () => { bquit?.addEventListener("click", () => {
quitChat(socket); quitChat();
}); });
// Enter key to send message // Enter key to send message
sendtextbox.addEventListener("keydown", (event) => { sendtextbox.addEventListener("keydown", (event) => {
if (!sendtextbox) return; if (!sendtextbox) return;
if (event.key === "Enter") { if (event.key === "Enter") {
event.preventDefault(); event.preventDefault();
sendButton?.click(); sendButton?.click();
} }
}); });
const chatButton = document.querySelector('#chatButton');
chatButton!.addEventListener("click", () => { chatButton!.addEventListener("click", () => {
if (chatBox.classList.contains("hidden")) {
const overlay = document.querySelector('#overlay')!; chatBox.classList.toggle("hidden");
if (chatBox.classList.contains('hidden')) { overlay.classList.add("opacity-60");
chatBox.classList.toggle('hidden'); } else {
overlay.classList.add('opacity-60'); chatBox.classList.toggle("hidden");
} else { overlay.classList.remove("opacity-60");
chatBox.classList.toggle('hidden'); }
overlay.classList.remove('opacity-60');
}
}); });
document.addEventListener('click', () => { document.addEventListener("ft:userChange", (user) => {
if (socket) { let newUser: { id: string; name: string } | null = user.detail;
connected(socket); window.__state.chatSock?.disconnect();
} window.__state.chatSock = undefined;
if (newUser === null) {
quitChat();
// logged out
// hide chat button
} else {
// user has changed
initChatSocket();
}
}); });

View file

@ -10,7 +10,7 @@ import { setTitle } from "@app/routing";
* @param socket * @param socket
*/ */
export function quitChat (socket: Socket) { export function quitChat () {
const chatBox = document.getElementById("chatBox")!; const chatBox = document.getElementById("chatBox")!;
const overlay = document.querySelector('#overlay')!; const overlay = document.querySelector('#overlay')!;