diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index eb43dce..5e9b65e 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -1,8 +1,10 @@ @import "tailwindcss"; -@font-face { +/* @font-face { font-family: "Nimbus Mono L"; src: url("/fonts/NimbusMonoL.woff2") format("woff2"); -} +} */ + +@tailwind utilities; .btn-style { @apply @@ -159,8 +161,11 @@ div-buddies-list { @apply text-black - whitespace-pre-wrap; - + whitespace-pre-wrap + cursor-pointer + hover:text-blue-500 + transition-colors + duration-150; } p { @@ -180,4 +185,44 @@ div-notlog { text-red-800 text-3xl text-center; +} + +div-private { + @apply + text-blue-800; + +} + +.popUpBox { + @apply + bg-white + p-6 rounded-xl + shadow-xl + w-[800px] + h-[350px] + p-[10px] + border-1 + border-black + +} + +.profilPopup { + @apply + fixed + inset-0 + bg-black/50 + flex + justify-center + items-center; +} + +.popup-b-clear { + @apply + absolute + bottom-42 + right-12 +} + +.hidden{ + display: none; } \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 2882ae5..bf06259 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -28,7 +28,13 @@
+

Charlie

-->Marks +
+ + diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 26dd177..4b8bd2f 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -4,7 +4,6 @@ import authHtml from './chat.html?raw'; import client from '@app/api' import { getUser, updateUser } from "@app/auth"; import io, { Socket } from 'socket.io-client'; -// import type { ClientMessage } from "@app/@types/dom"; const color = { red: 'color: red;', @@ -15,6 +14,7 @@ const color = { }; export type ClientMessage = { + command: string destination: string; user: string; text: string; @@ -32,11 +32,11 @@ document.addEventListener('ft:pageChange', () => { __socket.close(); __socket = undefined; console.log("Page changed"); -}) +}); function getSocket(): Socket { - // let addressHost = `wss://${machineHostName}:8888`; - let addressHost = `wss://localhost:8888`; + let addressHost = `wss://${machineHostName}:8888`; + // let addressHost = `wss://localhost:8888`; if (__socket === undefined) __socket = io(addressHost, { @@ -45,12 +45,54 @@ function getSocket(): Socket { transports: ["websocket"], }); return __socket; +}; + +function addMessage(text: string) { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (!chatWindow) return; + const messageElement = document.createElement("div-test"); + messageElement.textContent = text; + chatWindow.appendChild(messageElement); + chatWindow.scrollTop = chatWindow.scrollHeight; + console.log(`Added new message: ${text}`) + return ; +}; + +function clearText() { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (!chatWindow) return; + chatWindow.innerHTML = ""; } - -async function isLoggedIn() { +function isLoggedIn() { return getUser() || null; -} +}; + +function actionBtnPopUp() { + setTimeout(() => { + const clearTextBtn = document.querySelector("#popup-b-clear"); + clearTextBtn?.addEventListener("click", () => { + clearText(); + }); + }, 0) +} + +// getProfil get the profil of user +function getProfil(socket: Socket, user: string) { + if (!socket.connected) return; + const profil = { + command: '@profil', + destination: 'profilMessage', + type: "chat", + user: user, + token: document.cookie ?? "", + text: user, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + // addMessage(JSON.stringify(profil)); + socket.emit('profilMessage', JSON.stringify(profil)); +} async function windowStateHidden() { const socketId = __socket || undefined; @@ -64,18 +106,18 @@ async function windowStateHidden() { socketId.emit('client_left', { user: userName?.name, why: 'tab window hidden - socket not dead', - }); + }); return; -} +}; - -async function windowStateVisable() { +async function windowStateVisable() { + + const buddies = document.getElementById('div-buddies') as HTMLDivElement; const socketId = __socket || undefined; let oldName = localStorage.getItem("oldName") || undefined; console.log("%c WINDOW VISIBLE - oldName :'" + oldName + "'", color.green); if (socketId === undefined || oldName === undefined) {console.log("%SOCKET ID", color.red); return;} - // const res = await client.guestLogin(); let user = await updateUser(); if(user === null) return; console.log("%cUserName :'" + user?.name + "'", color.green); @@ -83,33 +125,121 @@ async function windowStateVisable() { userName: oldName, user: user?.name, }); + buddies.innerHTML = ''; + buddies.textContent = ''; + //connected(socketId); setTitle('Chat Page'); return; +}; + +function parseCmdMsg(msgText: string): string[] | undefined { + + if (!msgText?.trim()) return; + msgText = msgText.trim(); + const command: string[] = ['', '']; + if (!msgText.startsWith('@')) { + command[0] = '@msg'; + command[1] = msgText; + return command; + } + const noArgCommands = ['@quit', '@who', '@cls']; + if (noArgCommands.includes(msgText)) { + command[0] = msgText; + command[1] = ''; + return command; + } + + const ArgCommands = ['@profil', '@block']; + const userName = msgText.indexOf(" "); + const cmd2 = msgText.slice(0, userName).trim() ?? ""; + const user = msgText.slice(userName + 1).trim(); + if (ArgCommands.includes(cmd2)) { + command[0] = cmd2; + command[1] = user; + return command; + } + const colonIndex = msgText.indexOf(":"); + if (colonIndex === -1) { + command[0] = msgText; + command[1] = ''; + return command; + } + const cmd = msgText.slice(0, colonIndex).trim(); + const rest = msgText.slice(colonIndex + 1).trim(); + command[0] = cmd; + command[1] = rest; + return command; } - -async function listBuddies(buddies: HTMLDivElement, listBuddies: string ) { +async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: string) { if (!buddies) return; - const messageElement = document.createElement("div-buddies-list"); - messageElement.textContent = listBuddies + '\n'; - buddies.appendChild(messageElement); - buddies.scrollTop = buddies.scrollHeight; - console.log(`Added buddies: ${listBuddies}`) - return ; + const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement; + const buddiesElement = document.createElement("div-buddies-list"); + buddiesElement.textContent = listBuddies + '\n'; + const user = getUser()?.name ?? ""; + buddiesElement.style.cursor = "pointer"; + buddiesElement.addEventListener("click", () => { + navigator.clipboard.writeText(listBuddies); + if (listBuddies !== user && user !== "") { + sendtextbox.value = `@${listBuddies}: `; + console.log("Copied to clipboard:", listBuddies); + sendtextbox.focus(); + } + }); + + buddiesElement.addEventListener("dblclick", () => { + console.log("Open profile:", listBuddies); + getProfil(socket, listBuddies); + // openProfilePopup(`${profile}`); + // setTimeout(() => { + // const clearTextBtn = document.querySelector("#popup-b-clear"); + // clearTextBtn?.addEventListener("click", () => { + // clearText(); + // }); + // }, 0) + // actionBtnPopUp(); + }); + + buddies.appendChild(buddiesElement); + buddies.scrollTop = buddies.scrollHeight; + console.log(`Added buddies: ${listBuddies}`); } + function waitSocketConnected(socket: Socket): Promise { return new Promise(resolve => { - if (socket.connected) return resolve(); // already connected + if (socket.connected) return resolve(); socket.on("connect", () => resolve()); }); -} -const bconnected = document.getElementById('b-help') as HTMLButtonElement; -if (bconnected) { - bconnected.click(); -} +}; + +function quitChat (socket: Socket) { + + try { + const systemWindow = document.getElementById('system-box') as HTMLDivElement; + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + if (socket) { + logout(socket); + setTitle('Chat Page'); + systemWindow.innerHTML = ""; + chatWindow.textContent = ""; + connected(socket); + } else { + getSocket(); + } + } catch (e) { + console.error("Quit Chat error:", e); + showError('Failed to Quit Chat: Unknown error'); + } + +}; + +// const bconnected = document.getElementById('b-help') as HTMLButtonElement; +// if (bconnected) { +// bconnected.click(); +// } function logout(socket: Socket) { socket.emit("logout"); // notify server @@ -118,6 +248,94 @@ function logout(socket: Socket) { if (__socket !== undefined) __socket.close(); // window.location.href = "/login"; +}; + +function broadcastMsg (socket: Socket, msgCommand: string[]): void { + let msgText = msgCommand[1] ?? ""; + console.log('%cmsgText:', color.red, msgText); + addMessage(msgText); + const user = getUser(); + if (user && socket?.connected) { + const message = { + command: msgCommand, + destination: '', + type: "chat", + user: user.name, + token: document.cookie, + text: msgText, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + socket.emit('message', JSON.stringify(message)); + } +}; + + +async function connected(socket: Socket): Promise { + + try { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const loggedIn = await isLoggedIn(); + console.log('%cloggedIn:',color.blue, loggedIn?.name); + let oldUser = localStorage.getItem("oldName") ?? ""; + console.log('%coldUser:',color.yellow, oldUser); + if (loggedIn?.name === undefined) {console.log('');return ;} + oldUser = loggedIn.name ?? ""; + // const res = await client.guestLogin(); + let user = await updateUser(); + console.log('%cUser?name:',color.yellow, user?.name); + localStorage.setItem("oldName", oldUser); + buddies.textContent = ""; + socket.emit('list', { + oldUser: oldUser, + user: user?.name, + }); + } catch (e) { + console.error("Login error:", e); + showError('Failed to login: Unknown error'); + } +}; + +async function whoami(socket: Socket) { + try { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + const loggedIn = isLoggedIn(); + + const res = await client.guestLogin(); + switch (res.kind) { + case 'success': { + let user = await updateUser(); + if (chatWindow) { + socket.emit('updateClientName', { + oldUser: '', + user: user?.name + }); + } + if (user === null) + return showError('Failed to get user: no user ?'); + setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`); + break; + } + case 'failed': { + showError(`Failed to login: ${res.msg}`); + } + } + } catch (e) { + console.error("Login error:", e); + showError('Failed to login: Unknown error'); + } +}; + +async function openProfilePopup(profil: string) { + + + const modalname = document.getElementById("modal-name") ?? null; + if (modalname) + modalname.innerHTML = `${profil}`; + const profilList = document.getElementById("profile-modal") ?? null; + if (profilList) + profilList.classList.remove("hidden"); + // The popup now exists → attach the event } @@ -130,13 +348,16 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Listen for the 'connect' event socket.on("connect", async () => { - + + const systemWindow = document.getElementById('system-box') as HTMLDivElement; await waitSocketConnected(socket); console.log("I AM Connected to the server:", socket.id); const user = getUser()?.name; // Ensure we have a user AND socket is connected if (!user || !socket.connected) return; const message = { + command: "", + destination: 'system-info', type: "chat", user, token: document.cookie ?? "", @@ -145,19 +366,24 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn SenderWindowID: socket.id, }; socket.emit('message', JSON.stringify(message)); + const messageElement = document.createElement("div"); + messageElement.textContent = `${user}: is connected au server`; + systemWindow.appendChild(messageElement); + systemWindow.scrollTop = systemWindow.scrollHeight; }); // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: { message: ClientMessage}) => { - console.log("Message Obj Recieved:", data); - console.log("%cRecieved data.message.text: ", color.blue, data.message.text); - console.log("%cRecieved data.message.user: ", color.blue, data.message.user); + console.log("%cDEBUG LOGS - Message Obj Recieved:", color.green, data); + console.log("%cDEBUG LOGS - Recieved data.message.text: ", color.green, data.message.text); + console.log("%cDEBUG LOGS - Recieved data.message.user: ", color.green, data.message.user); // Display the message in the chat window const systemWindow = document.getElementById('system-box') as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; const bconnected = document.getElementById('b-help') as HTMLButtonElement; + if (bconnected) { - bconnected.click(); + connected(socket); } if (chatWindow && data.message.destination === "") { @@ -166,6 +392,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn chatWindow.appendChild(messageElement); chatWindow.scrollTop = chatWindow.scrollHeight; } + if (chatWindow && data.message.destination === "privateMsg") { + const messageElement = document.createElement("div-private"); + messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`; + chatWindow.appendChild(messageElement); + chatWindow.scrollTop = chatWindow.scrollHeight; + } + + const MAX_SYSTEM_MESSAGES = 10; if (systemWindow && data.message.destination === "system-info") { @@ -177,23 +411,25 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) { systemWindow.removeChild(systemWindow.firstChild!); } - systemWindow.scrollTop = systemWindow.scrollHeight; } - console.log("Getuser():", getUser()); }); - - - - socket.on('logout', () => { - const bquit = document.getElementById('b-quit') as HTMLDivElement | null; - if (bquit instanceof HTMLDivElement) { - bquit.click(); - } + socket.on('profilMessage', (profil) => { + openProfilePopup(profil); + actionBtnPopUp(); }); + + socket.on('logout', () => { + quitChat(socket); + }); + + socket.on('privMessageCopy', (message) => { + addMessage(message); + }) + type Providers = { name: string, display_name: string, @@ -201,25 +437,46 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn color?: { default: string, hover: string }, }; - - let toggle = false - window.addEventListener("focus", () => { - const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; - if (window.location.pathname === '/app/chat') { - console.log("%cWindow is focused on /chat:" + socket.id, color.green); - if (socket.id) - windowStateVisable(); - bwhoami.click(); - toggle = true; + let toggle = false + window.addEventListener("focus", () => { + //nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; + if (window.location.pathname === '/app/chat') { + connected(socket); + console.log("%cWindow is focused on /chat:" + socket.id, color.green); + if (socket.id) { + windowStateVisable(); } - }); + toggle = true; + } + }); + + window.addEventListener("blur", () => { + console.log("%cWindow is not focused on /chat", color.red); + if (socket.id) + windowStateHidden(); + toggle = false; + }); + + + // setInterval(async () => { + // //connected(socket); + // },10000); // every 10 seco + socket.on('listBud', async (myBuddies: string) => { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + console.log('List buddies connected ', myBuddies); + listBuddies(socket, buddies, myBuddies); + }); + + socket.once('welcome', (data) => { + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const chatWindow = document.getElementById('t-chatbox') as HTMLDivElement; + chatWindow.innerHTML = ''; + buddies.textContent = ''; + buddies.innerHTML = ''; + connected(socket); + addMessage (`${data.msg} ` + getUser()?.name); + }); - window.addEventListener("blur", () => { - console.log("%cWindow is not focused on /chat", color.red); - if (socket.id) - windowStateHidden(); - toggle = false; - }); setTitle('Chat Page'); // Listen for the 'connect' event @@ -235,6 +492,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const username = document.getElementById('username') as HTMLDivElement; const buddies = document.getElementById('div-buddies') as HTMLDivElement; const bquit = document.getElementById('b-quit') as HTMLDivElement; + const systemWindow = document.getElementById('system-box') as HTMLDivElement; chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -251,105 +509,90 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log('unknown response: ', value); } - const addMessage = (text: string) => { - if (!chatWindow) return; - const messageElement = document.createElement("div-test"); - messageElement.textContent = text; - chatWindow.appendChild(messageElement); - chatWindow.scrollTop = chatWindow.scrollHeight; - console.log(`Added new message: ${text}`) - return ; - }; - socket.once('welcome', (data) => { - chatWindow.textContent = ''; - chatWindow.innerHTML = ''; - buddies.textContent = ''; - buddies.innerHTML = ''; - bconnected.click(); - addMessage (`${data.msg} ` + getUser()?.name); - }); + + 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"); + }); // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { - const msgText = sendtextbox.value.trim(); - bconnected.click(); - addMessage(msgText); - const user = getUser(); - if (user && socket?.connected) { - const message = { - destination: "", - type: "chat", - user: user.name, - token: document.cookie, - text: msgText, - timestamp: Date.now(), - SenderWindowID: socket.id, - }; - socket.emit('message', JSON.stringify(message)); + let msgText: string = sendtextbox.value.trim(); + const msgCommand = parseCmdMsg(msgText) ?? ""; + connected(socket); + if (msgCommand !== "") { + switch (msgCommand[0]) { + case '@msg': + broadcastMsg(socket, msgCommand); + break; + case '@who': + whoami(socket); + break; + case '@profil': + getProfil(socket, msgCommand[1]); + // Ensure we have a user AND socket is connected + // if (!socket.connected) return; + // const profil = { + // command: msgCommand[0], + // destination: 'profil', + // type: "chat", + // user: msgCommand[1], + // token: document.cookie ?? "", + // text: msgCommand[1], + // timestamp: Date.now(), + // SenderWindowID: socket.id, + // }; + // //socket.emit('MsgObjectServer', message); + // addMessage(JSON.stringify(profil)); + // socket.emit('profilMessage', JSON.stringify(profil)); + break; + case '@cls': + chatWindow.innerHTML = ''; + break; + case '@quit': + quitChat(socket); + break; + default: + const user = getUser()?.name; + // Ensure we have a user AND socket is connected + if (!user || !socket.connected) return; + const message = { + command: msgCommand[0], + destination: '', + type: "chat", + user: user, + token: document.cookie ?? "", + text: msgCommand[1], + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + //socket.emit('MsgObjectServer', message); + socket.emit('privMessage', JSON.stringify(message)); + // addMessage(JSON.stringify(message)); + break; + } + // Clear the input in all cases + sendtextbox.value = ""; } - sendtextbox.value = ""; } }); - - - // Clear Text button clearText?.addEventListener("click", () => { - if (chatWindow) { - bconnected.click(); chatWindow.innerHTML = ''; } }); bquit?.addEventListener('click', () => { - if (socket) { - logout(socket); - setTitle('Chat Page'); - bconnected.click(); - } else { - getSocket(); - } + quitChat(socket); }); - - setInterval(async () => { - //bconnected.click(); - }, 10000); // every 10 second - - // Help Text button - bconnected?.addEventListener("click", async () => { - - const loggedIn = await isLoggedIn(); - console.log('%cloggedIn:',color.blue, loggedIn?.name); - let oldUser = localStorage.getItem("oldName") ?? ""; - console.log('%coldUser:',color.yellow, oldUser); - if (loggedIn?.name === undefined) {console.log('');return ;} - oldUser = loggedIn.name || "undefined"; - const res = await client.guestLogin(); - let user = await updateUser(); - console.log('%cUser?name:',color.yellow, user?.name); - localStorage.setItem("oldName", oldUser); - buddies.textContent = ""; - // if (chatWindow) { - // addMessage('@list - lists all connected users in the chat'); - socket.emit('list', { - oldUser: oldUser, - user: user?.name, - }); - // } - - }); - - socket.on('listBud', (myBuddies: string) => { - console.log('List buddies connected ', myBuddies); - listBuddies(buddies,myBuddies); - }); - - // Enter key to send message sendtextbox!.addEventListener('keydown', (event) => { if (event.key === 'Enter') { @@ -357,34 +600,10 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }); - // Whoami button to display user name + // Whoami button to display user name addMessage(msgCommand[0]); + bwhoami?.addEventListener('click', async () => { - try { - const loggedIn = await isLoggedIn(); - - const res = await client.guestLogin(); - switch (res.kind) { - case 'success': { - let user = await updateUser(); - if (chatWindow) { - socket.emit('updateClientName', { - oldUser: '', - user: user?.name - }); - } - if (user === null) - return showError('Failed to get user: no user ?'); - setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`); - break; - } - case 'failed': { - showError(`Failed to login: ${res.msg}`); - } - } - } catch (e) { - console.error("Login error:", e); - showError('Failed to login: Unknown error'); - } + whoami(socket); }); } } diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 4ac2c96..033e17c 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -6,6 +6,7 @@ import * as auth from '@shared/auth'; import * as swagger from '@shared/swagger'; import * as utils from '@shared/utils'; import { Server, Socket } from 'socket.io'; +import type { User } from '@shared/database/mixin/user'; // colors for console.log export const color = { @@ -34,6 +35,7 @@ interface ClientInfo { } export type ClientMessage = { + command: string destination: string; user: string; text: string; @@ -82,6 +84,9 @@ declare module 'fastify' { io: Server<{ hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; + privMessage: (data: string) => void; + profilMessage: (data: string) => void; + privMessageCopy: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -129,55 +134,117 @@ async function onReady(fastify: FastifyInstance) { seen.add(username.user); count++; // console.log(color.green,"count: ", count); - console.log(color.yellow, 'Client:', color.reset, username.user); + // console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; io.to(targetSocketId!).emit('listBud', username.user); - console.log( - color.yellow, - 'Chat Socket ID:', - color.reset, - socketId, - ); + // console.log( + // color.yellow, + // 'Chat Socket ID:', + // color.reset, + // socketId, + // ); continue; } - // If no io provided, assume entries in the map are valid and count them. count++; - console.log( - color.red, - 'Client (unverified):', - color.reset, - username, - ); - console.log( - color.red, - 'Chat Socket ID (unverified):', - color.reset, - socketId, - ); + console.log(color.red, 'DEBUG LOG: - Client (unverified):', color.reset, username); + console.log(color.red, 'DEBUG LOG: - Chat Socket ID (unverified):', color.reset, socketId); } return count; } function broadcast(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { - for (const s of sockets) { + for (const socket of sockets) { // Skip sender's own socket - if (s.id === sender) continue; + if (socket.id === sender) continue; // Get client name from map + const clientInfo = clientChat.get(socket.id); + if (!clientInfo?.user) { + console.log(color.yellow, `Skipping socket ${socket.id} (no user found)`); + continue; + } + // Emit structured JSON object + socket.emit('MsgObjectServer', { message: data }); + // Debug logs + console.log(color.green, `'Broadcast to:', ${data.command} message: ${data.text}`); + // console.log('DEBUG - Target socket ID:', s.id); + // console.log('DEBUG - Target rooms:', [...s.rooms]); + // console.log('DEBUG - Sender socket ID:', sender ?? 'none'); + } + }); + } + + // function formatTimestamp(ms: number) { + // const d = new Date(ms); + // return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' }); + // } + + function getUserByName(users: User[], name: string) { + return users.find(u => u.name === name) || null; + } + + + // this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO .... + async function getProfil(user: string): Promise { + let profilHtmlPopup = '404: Error: Profil not found'; + const users: User[] = fastify.db.getAllUsers() ?? []; + const allUsers: User | null = getUserByName(users, user); + console.log(color.yellow, `'userFound is:'${allUsers?.name}`); + if (user === allUsers?.name) { + console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`); + profilHtmlPopup = `
+ Profil of ${allUsers.name}
+ Login Name: '${allUsers?.login ?? 'Guest'}' +
+ +
About: No description
+ `; + } + + return profilHtmlPopup; + }; + + function sendProfil(data: ClientMessage, clientProfil?: string) { + + fastify.io.fetchSockets().then((sockets) => { + const senderSocket = sockets.find(socket => socket.id === clientProfil); + for (const socket of sockets) { + const clientInfo = clientChat.get(socket.id); + if (clientInfo?.user === data.user) { + if (senderSocket) { + socket.emit('profilMessage', `${data.text}`); + } + } + } + }); + } + + function sendPrivMessage(data: ClientMessage, sender?: string) { + fastify.io.fetchSockets().then((sockets) => { + const senderSocket = sockets.find(s => s.id === sender); + for (const s of sockets) { + if (s.id === sender) continue; const clientInfo = clientChat.get(s.id); if (!clientInfo?.user) { console.log(color.yellow, `Skipping socket ${s.id} (no user found)`); continue; } - // Emit structured JSON object - s.emit('MsgObjectServer', { message: data }); - // Debug logs - console.log(color.green, 'Broadcast to:', clientInfo.user); - console.log(' Target socket ID:', s.id); - console.log(' Target rooms:', [...s.rooms]); - console.log(' Sender socket ID:', sender ?? 'none'); + const user: string = clientChat.get(s.id)?.user ?? ''; + const atUser = `@${user}`; + if (atUser !== data.command || atUser === '') { + console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command NOT FOUND: '${data.command[0]}' `); + continue; + } + if (data.text !== '') { + s.emit('MsgObjectServer', { message: data }); + console.log(color.yellow, `DEBUG LOG: User: '${atUser}' command FOUND: '${data.command}' `); + if (senderSocket) { + senderSocket.emit('privMessageCopy', `${data.command}: ${data.text}🔒`); + } + } + console.log(color.green, `DEBUG LOG: 'Priv to:', ${data.command} message: ${data.text}`); } }); } @@ -212,12 +279,7 @@ async function onReady(fastify: FastifyInstance) { // Send object directly — DO NOT wrap it in a string broadcast(obj, obj.SenderWindowID); - console.log( - color.red, - 'connected in the Chat :', - connectedUser(fastify.io), - color.reset, - ); + console.log(color.red, 'DEBUG LOG: connected in the Chat :', connectedUser(fastify.io), color.reset); }); socket.on('testend', (sock_id_cl: string) => { @@ -256,8 +318,7 @@ async function onReady(fastify: FastifyInstance) { // }; if (client) { client.user = userFromFrontend.user; - console.log(color.green, 'client.user is: ', client.user); - + console.log(color.green, `'DEBUG LOG: client.user is, '${client.user}'`); } } }); @@ -269,6 +330,7 @@ async function onReady(fastify: FastifyInstance) { if (!clientName) return; console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); const obj = { + command: '', destination: 'system-info', type: 'chat' as const, user: clientName, @@ -295,6 +357,7 @@ async function onReady(fastify: FastifyInstance) { if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, @@ -319,6 +382,7 @@ async function onReady(fastify: FastifyInstance) { if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, @@ -328,11 +392,69 @@ async function onReady(fastify: FastifyInstance) { SenderWindowID: socket.id, }; console.log(color.blue, 'BROADCASTS OUT :', obj.SenderWindowID); + broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } }); + + socket.on('privMessage', (data) => { + const clientName: string = clientChat.get(socket.id)?.user || ''; + const prvMessage: ClientMessage = JSON.parse(data) || ''; + console.log( + color.blue, + `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target Name:`, + prvMessage.command, + ); + + if (clientName !== null) { + const obj = { + command: prvMessage.command, + destination: 'privateMsg', + type: 'chat', + user: clientName, + token: '', + text: prvMessage.text, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID); + sendPrivMessage(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + + socket.on('profilMessage', async (data) => { + const clientName: string = clientChat.get(socket.id)?.user || ''; + const profilMessage: ClientMessage = JSON.parse(data) || ''; + const users: User[] = fastify.db.getAllUsers() ?? []; + console.log(color.yellow, 'DEBUG LOG: ALL USERS EVER CONNECTED:', users); + console.log( + color.blue, + `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`, + profilMessage.user, + ); + const profileHtml: string = await getProfil(profilMessage.user); + if (clientName !== null) { + const testuser: User | null = getUserByName(users, profilMessage.user); + console.log(color.yellow, 'user:', testuser?.login ?? 'Guest'); + const obj = { + command: profilMessage.command, + destination: 'profilMsg', + type: 'chat', + user: clientName, + token: '', + text: profileHtml, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', obj.SenderWindowID); + sendProfil(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload) @@ -364,6 +486,7 @@ async function onReady(fastify: FastifyInstance) { ); if (clientName !== null) { const obj = { + command: '', destination: 'system-info', type: 'chat', user: clientName, diff --git a/src/package.json b/src/package.json index 9fa1e9c..45b7a6d 100644 --- a/src/package.json +++ b/src/package.json @@ -36,7 +36,7 @@ "vite": "^7.2.6" }, "dependencies": { - "@redocly/cli": "^2.12.1", + "@redocly/cli": "^2.12.3", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index ad74292..a50dc94 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.12.1 - version: 2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.3 + version: 2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -943,8 +943,8 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.12.1': - resolution: {integrity: sha512-XGD28QjjZEzN+J9WOROzw4fHNi+Fyw/gCyDZDgI4nX4j9gEBT1PcxN75wWpMoDGHKAUj8ghrhMHtfQoUuR90zg==} + '@redocly/cli@2.12.3': + resolution: {integrity: sha512-1SDW551scNdb4HmNpzyUf4gjsK89KkRUeXF91VVMRkQ5+lFEq1Nj259jN1M25uOd/cg1QjKE3kIbnN1dxPa3ng==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true @@ -958,12 +958,12 @@ packages: resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.12.1': - resolution: {integrity: sha512-xMlKf4dnZsxP3JYBNZFsMNBJqVxWlwLuyGLhGc36hXw50YOla1UjrVZ5psIyzLXgUPI3QJDA1XmGcJ8rcex/ow==} + '@redocly/openapi-core@2.12.3': + resolution: {integrity: sha512-3gdSRftIeUbzXvwDi/tBjO0uj9PzR0XzbWjNwuu3HlVXJ1ElB+K31AnzQ2iA6mjIHq9uvmLRXAs9MsP/0Hbzug==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.12.1': - resolution: {integrity: sha512-ADm+JMHWGYeOwzdGEQ8CYKjmMBLU0ycZTwJbCkQsUulXSNkNA7GzA8lrMM2+I8cPMRk25G5PmtfAR7U+a0o1ew==} + '@redocly/respect-core@2.12.3': + resolution: {integrity: sha512-ZYqrLBlRVVHwgPawOjo94sKmeuuien77xtkXluTa6+y/wkQ8c5oYY7OqWbasMv0IoxSPehwVMa0AL0OCQP3uCQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -2613,10 +2613,10 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: - react: ^19.2.0 + react: ^19.2.1 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2626,8 +2626,8 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} engines: {node: '>=0.10.0'} readable-stream@3.6.2: @@ -3054,6 +3054,10 @@ packages: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} + ulid@3.0.2: + resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} + hasBin: true + undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} @@ -3864,14 +3868,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.12.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.3(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.12.1(ajv@8.17.1) - '@redocly/respect-core': 2.12.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.3(ajv@8.17.1) + '@redocly/respect-core': 2.12.3(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3883,13 +3887,14 @@ snapshots: https-proxy-agent: 7.0.6(supports-color@10.2.2) mobx: 6.15.0 pluralize: 8.0.0 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - redoc: 2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + redoc: 2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)) semver: 7.7.3 set-cookie-parser: 2.7.2 simple-websocket: 9.1.0 - styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + styled-components: 6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + ulid: 3.0.2 undici: 6.22.0 yargs: 17.0.1 transitivePeerDependencies: @@ -3922,7 +3927,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.12.1(ajv@8.17.1)': + '@redocly/openapi-core@2.12.3(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 '@redocly/config': 0.40.0 @@ -3936,12 +3941,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.12.1(ajv@8.17.1)': + '@redocly/respect-core@2.12.3(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.17.1 - '@redocly/openapi-core': 2.12.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.3(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -5282,21 +5287,21 @@ snapshots: dependencies: obliterator: 2.0.5 - mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + mobx-react-lite@4.1.1(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: mobx: 6.15.0 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.1(react@19.2.1) - mobx-react@9.2.0(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + mobx-react@9.2.0(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: mobx: 6.15.0 - mobx-react-lite: 4.1.1(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 + mobx-react-lite: 4.1.1(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.1(react@19.2.1) mobx@6.15.0: {} @@ -5661,20 +5666,20 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.0(react@19.2.0): + react-dom@19.2.1(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 scheduler: 0.27.0 react-is@16.13.1: {} - react-tabs@6.1.0(react@19.2.0): + react-tabs@6.1.0(react@19.2.1): dependencies: clsx: 2.1.1 prop-types: 15.8.1 - react: 19.2.0 + react: 19.2.1 - react@19.2.0: {} + react@19.2.1: {} readable-stream@3.6.2: dependencies: @@ -5690,7 +5695,7 @@ snapshots: real-require@0.2.0: {} - redoc@2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)): + redoc@2.5.1(core-js@3.47.0)(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1)): dependencies: '@redocly/openapi-core': 1.34.5(supports-color@10.2.2) classnames: 2.5.1 @@ -5703,19 +5708,19 @@ snapshots: mark.js: 8.11.1 marked: 4.3.0 mobx: 6.15.0 - mobx-react: 9.2.0(mobx@6.15.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + mobx-react: 9.2.0(mobx@6.15.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) openapi-sampler: 1.6.2 path-browserify: 1.0.1 perfect-scrollbar: 1.5.6 polished: 4.3.1 prismjs: 1.30.0 prop-types: 15.8.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-tabs: 6.1.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-tabs: 6.1.0(react@19.2.1) slugify: 1.4.7 stickyfill: 1.1.1 - styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + styled-components: 6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1) swagger2openapi: 7.0.8 url-template: 2.0.8 transitivePeerDependencies: @@ -6034,7 +6039,7 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 - styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + styled-components@6.1.19(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -6042,8 +6047,8 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.49 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 @@ -6178,6 +6183,8 @@ snapshots: uint8array-extras@1.5.0: {} + ulid@3.0.2: {} + undici-types@6.21.0: {} undici-types@7.16.0: {} @@ -6194,9 +6201,9 @@ snapshots: url-template@2.0.8: {} - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@19.2.1): dependencies: - react: 19.2.0 + react: 19.2.1 util-deprecate@1.0.2: {}