ft_transcendence/frontend/src/chat/chat.ts

559 lines
16 KiB
TypeScript

import "./chat.css";
import io, { Socket } from "socket.io-client";
import type { blockedUnBlocked } from "./types_front";
import type {
ClientMessage,
ClientProfil,
ClientProfilPartial,
} from "./types_front";
import type { User } from "@app/auth";
import { navigateTo } from "@app/routing";
import authHtml from "./chat.html?raw";
import { getUser } from "@app/auth";
import { listBuddies } from "./chatHelperFunctions/listBuddies";
import { getProfil } from "./chatHelperFunctions/getProfil";
import { addInviteMessage, addMessage } from "./chatHelperFunctions/addMessage";
import { broadcastMsg } from "./chatHelperFunctions/broadcastMsg";
import { openProfilePopup } from "./chatHelperFunctions/openProfilePopup";
import { actionBtnPopUpBlock } from "./chatHelperFunctions/actionBtnPopUpBlock";
import { actionBtnPongGames } from "./chatHelperFunctions/actionBtnPongGames";
import { windowStateHidden } from "./chatHelperFunctions/windowStateHidden";
import { blockUser } from "./chatHelperFunctions/blockUser";
import { parseCmdMsg } from "./chatHelperFunctions/parseCmdMsg";
import { actionBtnPopUpInvite } from "./chatHelperFunctions/actionBtnPopUpInvite";
import { waitSocketConnected } from "./chatHelperFunctions/waitSocketConnected";
import { connected } from "./chatHelperFunctions/connected";
import { quitChat } from "./chatHelperFunctions/quitChat";
import { openMessagePopup } from "./chatHelperFunctions/openMessagePopup";
import { windowStateVisable } from "./chatHelperFunctions/windowStateVisable";
import { cmdList } from "./chatHelperFunctions/cmdList";
import { actionBtnTTTGames } from "./chatHelperFunctions/actionBtnTTTGames";
import { showError } from "@app/toast";
import { actionBtnFriend } from "./chatHelperFunctions/actionBtnFriend";
const MAX_SYSTEM_MESSAGES = 10;
let inviteMsgFlag: boolean = false;
export let noGuestFlag: boolean = true;
let keysPressed: Record<string, boolean> = {};
declare module "ft_state" {
interface State {
chatSock?: Socket;
}
}
class DivPrivate extends HTMLElement {
constructor() {
super();
}
}
customElements.define("div-private", DivPrivate);
export function getSocket(): Socket {
if (window.__state.chatSock === undefined)
window.__state.chatSock = io(window.location.host, {
path: "/api/chat/socket.io/",
}) as any as Socket;
return window.__state.chatSock;
}
const chatBox = document.getElementById("chatBox")!;
chatBox.classList.add("hidden");
chatBox.innerHTML = authHtml;
const bquit = document.getElementById("b-quit") as HTMLDivElement;
const buddies = document.getElementById("div-buddies") as HTMLDivElement;
const buttonMessage = document.getElementById("close-modal-message") ?? null;
const buttonPro = document.getElementById("close-modal") ?? null;
const chatButton = document.querySelector("#chatButton");
const chatMessageIn = document.querySelector("#chatMessageIn");
const chatWindow = document.querySelector("#t-chatbox")!;
const clearText = document.getElementById("b-clear") as HTMLButtonElement;
const gameMessage = document.getElementById("game-modal") ?? null;
const myGames = document.getElementById("b-hGame") ?? null;
const myTTTGames = document.getElementById("b-hTGame") ?? 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;
function chatKeyToggle() {
let anti_flicker_control = false;
const chat_hide_key = 'escape';
const chat_display_key = 'f2';
const home_display_key = 'f8';
document.addEventListener("keydown", (event) => {
if (event.repeat && keysPressed[chat_hide_key] === true) {
anti_flicker_control = true;
return ;
};
keysPressed[event.key.toLowerCase()] = true;
});
document.addEventListener("keyup", (event) => {
keysPressed[event.key.toLowerCase()] = false;
if (event.key.toLowerCase() === chat_hide_key) {
anti_flicker_control = false;
}
});
setInterval( () => {
if(keysPressed[chat_hide_key] === true) {
overlay.classList.remove("opacity-60");
chatBox.classList.add("hidden");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
profilList?.classList.add("hidden");
windowStateHidden();
}
if (keysPressed[chat_display_key] === true) {
anti_flicker_control = false;
chatBox.classList.remove("hidden");
overlay.classList.add("opacity-60");
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
let socket = window.__state.chatSock;
if (!socket) return;
connected(socket);
sendtextbox.focus();
windowStateVisable();
}
if (keysPressed[home_display_key] === true) {
navigateTo('/app/');
quitChat();
}
}, 1000/10);
};
function initChatSocket() {
let socket = getSocket();
if (
!chatBox ||
!chatMessageIn ||
!bquit ||
!buttonMessage ||
!buttonPro ||
!chatButton ||
!chatWindow ||
!clearText ||
!gameMessage ||
!myGames ||
!myTTTGames ||
!modalmessage ||
!noGuest ||
!notify ||
!overlay ||
!profilList ||
!sendButton ||
!sendtextbox ||
!systemWindow
) return showError("fatal error");
// Listen for the 'connect' event
socket.on("connect", async () => {
await waitSocketConnected(socket);
const user = getUser()?.name;
const userID = getUser()?.id;
// Ensure we have a user AND socket is connected
if (!user || !socket.connected || !noGuest) return;
const message = {
command: "",
destination: "system-info",
type: "chat",
user,
token: document.cookie ?? "",
text: " has Just ARRIVED in the chat",
timestamp: Date.now(),
SenderWindowID: socket.id,
SenderID: userID,
};
socket.emit("message", JSON.stringify(message));
const guest = getUser()?.guest;
if (guest) {
noGuest.innerText = "";
} else {
noGuest.innerText = "❤️";
}
const userProfile: ClientProfil = {
command: "@noguest",
destination: "",
type: "chat",
timestamp: Date.now(),
guestmsg: true,
};
socket.emit("guestmsg", JSON.stringify(userProfile));
const messageElement = document.createElement("div");
messageElement.textContent = `${user}: is connected au server`;
systemWindow.appendChild(messageElement);
systemWindow.lastElementChild?.scrollIntoView({ block: "end" });
});
chatMessageIn?.classList.add("hidden");
chatKeyToggle();
// Listen for messages from the server "MsgObjectServer"
socket.on("MsgObjectServer", (data: { message: ClientMessage }) => {
if (socket) {
connected(socket);
}
if (chatWindow && data.message.destination === "") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🔵';
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 === "privateMsg") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🔴';
const messageElement = document.createElement("div-private");
messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
if (chatWindow && data.message.destination === "inviteMsg") {
chatMessageIn?.classList.remove("hidden");
chatMessageIn!.textContent = '🟢';
const messageElement = document.createElement("div-private");
const chatWindow = document.getElementById(
"t-chatbox",
) as HTMLDivElement;
messageElement.innerHTML = `🏓${data.message.SenderUserName}: ${data.message.innerHtml}`;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.appendChild(messageElement);
systemWindow.lastElementChild?.scrollIntoView({ block: "end" });
}
});
socket.on("profilMessage", (profil: ClientProfil) => {
profil.SenderID = getUser()?.id ?? "";
profil.SenderName = getUser()?.name ?? "";
openProfilePopup(profil);
socket.emit("isBlockdBtn", profil);
socket.emit("check_Block_button", profil);
actionBtnPopUpInvite(profil, socket);
actionBtnPopUpBlock(profil, socket);
actionBtnPongGames(profil, socket);
actionBtnTTTGames(profil, socket);
actionBtnFriend(profil, socket);
});
socket.on("blockUser", (blocked: ClientProfil) => {
let icon = "⛔";
if (inviteMsgFlag) {
const messageElement = document.createElement("div");
if (`${blocked.text}` === "I have un-blocked you") {
icon = "💚";
}
messageElement.innerText = `${icon}${blocked.SenderName}: ${blocked.text}`;
chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight;
}
});
socket.on("blockBtn", (data: blockedUnBlocked) => {
const blockUserBtn = document.querySelector("#popup-b-block");
if (blockUserBtn) {
let message = "";
if (data.userState === "block") {
(message = "un-block");
} else {
(message = "block");
}
blockUserBtn.textContent = message;
}
});
socket.on("logout", () => {
quitChat();
});
socket.on("privMessageCopy", (message: string) => {
const htmlBaliseRegex = /<a\b[^>]*>[\s\S]*?<\/a>/;
const htmlBaliseMatch = message.match(htmlBaliseRegex);
if (htmlBaliseMatch)
addInviteMessage(message);
else
addMessage(message);
});
//receives broadcast of the next GAME
socket.on("nextGame", (message: string) => {
openMessagePopup(message);
});
//receives broadcast of the next GAME
socket.on("tourStatus", (message: string) => {
openMessagePopup(message);
});
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);
});
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 "@pong":
if (msgCommand[1] === "") {
navigateTo("/app/pong/games");
quitChat();
}
break;
case "@ttt":
if (msgCommand[1] === "") {
navigateTo("/app/ttt/games");
quitChat();
}
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;
window.addEventListener("focus", async () => {
setTimeout(() => {
if (window.__state.chatSock) connected(window.__state.chatSock);
}, 16);
await windowStateVisable();
toggle = true;
});
window.addEventListener("blur", () => {
if (window.__state.chatSock?.id) windowStateHidden();
toggle = false;
});
// Clear Text button
clearText?.addEventListener("click", () => {
if (chatWindow) {
chatWindow.innerHTML = "";
}
});
bquit?.addEventListener("click", () => {
quitChat();
});
myGames?.addEventListener("click", () => {
navigateTo("/app/pong/games");
quitChat();
});
myTTTGames?.addEventListener("click", () => {
navigateTo("/app/ttt/games");
quitChat();
});
// Enter key to send message
sendtextbox.addEventListener("keydown", (event) => {
if (!sendtextbox) return;
if (event.key === "Enter") {
event.preventDefault();
sendButton?.click();
}
});
chatButton!.addEventListener("click",() => {
if (chatBox.classList.contains("hidden")) {
chatBox.classList.toggle("hidden");
overlay.classList.add("opacity-60");
windowStateVisable();
let socket = window.__state.chatSock;
if (!socket) return;
connected(socket);
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
sendtextbox.focus();
} else {
chatBox.classList.toggle("hidden");
overlay.classList.remove("opacity-60");
windowStateHidden();
chatMessageIn?.classList.add("hidden");
chatMessageIn!.textContent = '';
}
});
document.addEventListener("ft:userChange", (user) => {
let newUser: { id: string; name: string } | null = user.detail;
window.__state.chatSock?.disconnect();
window.__state.chatSock = undefined;
if (newUser === null) {
quitChat();
} else {
initChatSocket();
}
});