diff --git a/frontend/src/chat/chat.css b/frontend/src/chat/chat.css index 5e9b65e..b308418 100644 --- a/frontend/src/chat/chat.css +++ b/frontend/src/chat/chat.css @@ -223,6 +223,40 @@ div-private { right-12 } +.popup-b-invite { + @apply + absolute + bottom-52 + right-12 +} + + +.popUpMessage { + @apply + bg-white + p-6 rounded-xl + shadow-xl + w-[800px] + h-[100px] + p-[10px] + border-1 + border-black + +} + +.gamePopup { + @apply + fixed + inset-0 + bg-black/50 + flex + justify-center + items-center; +} + + + + .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 bf06259..87b57be 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,6 +1,7 @@
+

ChatterBox


@@ -26,9 +27,6 @@

Ping Buddies

- Marks
+
diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 4b8bd2f..fb18ecb 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -22,6 +22,35 @@ export type ClientMessage = { }; +export type ClientProfil = { + command: string, + destination: string, + type: string, + user: string, + loginName: string, + userID: string, + text: string, + timestamp: number, + SenderWindowID:string, + SenderName: string, + innerHtml?: string, + +}; + +// export type inviteGame = { +// command?: string, +// destination?: string, +// type?: string, +// user?: string, +// loginName?: string, +// userID?: string, +// innerHtml?: string, +// timestamp?: number, +// SenderWindowID?:string, +// }; + + + // get the name of the machine used to connect const machineHostName = window.location.hostname; console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow); @@ -58,24 +87,47 @@ function addMessage(text: string) { return ; }; -function clearText() { +function clear(senderSocket: Socket) { const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; if (!chatWindow) return; chatWindow.innerHTML = ""; + // senderSocket.emit('nextGame'); + } function isLoggedIn() { return getUser() || null; }; -function actionBtnPopUp() { +function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) { + profil.SenderName = getUser()?.name ?? ''; + if (profil.SenderName === profil.user) return; + addMessage(`You invited to play: ${profil.user}🏓`) + senderSocket.emit('inviteGame', JSON.stringify(profil)); +}; + + + +function actionBtnPopUpClear(profil: ClientProfil, senderSocket: Socket) { setTimeout(() => { const clearTextBtn = document.querySelector("#popup-b-clear"); clearTextBtn?.addEventListener("click", () => { - clearText(); + clear(senderSocket); }); }, 0) -} +}; + + +function actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) { + setTimeout(() => { + const InvitePongBtn = document.querySelector("#popup-b-invite"); + InvitePongBtn?.addEventListener("click", () => { + inviteToPlayPong(invite, senderSocket); + }); + }, 0) +}; + + // getProfil get the profil of user function getProfil(socket: Socket, user: string) { @@ -190,16 +242,9 @@ async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: }); buddiesElement.addEventListener("dblclick", () => { - console.log("Open profile:", listBuddies); + console.log("Open profile:", listBuddies); getProfil(socket, listBuddies); - // openProfilePopup(`${profile}`); - // setTimeout(() => { - // const clearTextBtn = document.querySelector("#popup-b-clear"); - // clearTextBtn?.addEventListener("click", () => { - // clearText(); - // }); - // }, 0) - // actionBtnPopUp(); + sendtextbox.value = ""; }); buddies.appendChild(buddiesElement); @@ -274,22 +319,24 @@ function broadcastMsg (socket: Socket, msgCommand: string[]): void { 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, - }); + const buddies = document.getElementById('div-buddies') as HTMLDivElement; + const loggedIn = 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 ;} + setTimeout(() => { + oldUser = loggedIn.name ?? ""; + }, 0); + // 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'); @@ -326,18 +373,49 @@ async function whoami(socket: Socket) { } }; -async function openProfilePopup(profil: string) { +async function openProfilePopup(profil: ClientProfil) { const modalname = document.getElementById("modal-name") ?? null; if (modalname) - modalname.innerHTML = `${profil}`; - const profilList = document.getElementById("profile-modal") ?? null; + modalname.innerHTML = ` +
+ Profil of ${profil.user}
+ Login Name: '${profil.loginName ?? 'Guest'}' +
+ Login ID: '${profil.userID ?? ''}' +
+ + +
About: '${profil.text}'
+ + `; + const profilList = document.getElementById("profile-modal") ?? null; if (profilList) profilList.classList.remove("hidden"); // The popup now exists → attach the event } +async function openMessagePopup(message: string) { + + + const modalmessage = document.getElementById("modal-message") ?? null; + if(!message) return + const obj:any = JSON.parse(message); + if (modalmessage) + modalmessage.innerHTML = ` +
+
+
Next Game Message: ${obj.link}
+
`; + const gameMessage = document.getElementById("game-modal") ?? null; + if (gameMessage) + gameMessage.classList.remove("hidden"); + // The popup now exists → attach the event +} + + + function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -374,9 +452,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: { message: ClientMessage}) => { - 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; @@ -416,12 +491,19 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log("Getuser():", getUser()); }); - socket.on('profilMessage', (profil) => { + socket.on('profilMessage', (profil: ClientProfil) => { openProfilePopup(profil); - actionBtnPopUp(); + actionBtnPopUpClear(profil, socket); + actionBtnPopUpInvite(profil, socket); + }); + + socket.on('inviteGame', (invite: ClientProfil) => { + const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; + const messageElement = document.createElement("div"); + messageElement.innerHTML =`🏓${invite.SenderName}: ${invite.innerHtml}`; + chatWindow.appendChild(messageElement); }); - socket.on('logout', () => { quitChat(socket); }); @@ -430,37 +512,39 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn addMessage(message); }) - type Providers = { - name: string, - display_name: string, - icon_url?: string, - color?: { default: string, hover: string }, - }; + //receives broadcast of the next GAME + socket.on('nextGame', (message) => { + openMessagePopup(message); + // addMessage(message); + }) let toggle = false - window.addEventListener("focus", () => { + window.addEventListener("focus", async () => { //nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement; - if (window.location.pathname === '/app/chat') { + + setTimeout(() => { connected(socket); - console.log("%cWindow is focused on /chat:" + socket.id, color.green); + }, 0); + if (window.location.pathname === '/app/chat') { + console.log('%cWindow is focused on /chat:' + socket.id, color.green); if (socket.id) { - windowStateVisable(); + await windowStateVisable(); } toggle = true; } }); window.addEventListener("blur", () => { - console.log("%cWindow is not focused on /chat", color.red); + console.log('%cWindow is not focused on /chat', color.red); if (socket.id) windowStateHidden(); toggle = false; }); - // setInterval(async () => { // //connected(socket); - // },10000); // every 10 seco + // },10000); // every 10 sec + socket.on('listBud', async (myBuddies: string) => { const buddies = document.getElementById('div-buddies') as HTMLDivElement; console.log('List buddies connected ', myBuddies); @@ -493,6 +577,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const buddies = document.getElementById('div-buddies') as HTMLDivElement; const bquit = document.getElementById('b-quit') as HTMLDivElement; const systemWindow = document.getElementById('system-box') as HTMLDivElement; + const bnextGame = document.getElementById('b-nextGame') as HTMLDivElement; chatWindow.textContent = ''; chatWindow.innerHTML = ''; @@ -519,6 +604,18 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn 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"); + }); + + // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -535,21 +632,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn 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 = ''; @@ -587,6 +669,14 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn if (chatWindow) { chatWindow.innerHTML = ''; } + clear(socket); //DEV testing broadcastGames + }); + + // Dev Game message button + bnextGame?.addEventListener("click", () => { + if (chatWindow) { + socket.emit('nextGame'); + } }); bquit?.addEventListener('click', () => { @@ -608,5 +698,4 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } } }; -addRoute('/chat', handleChat, { bypass_auth: true }); - +addRoute('/chat', handleChat, { bypass_auth: true }); \ No newline at end of file diff --git a/src/@shared/src/utils/index.ts b/src/@shared/src/utils/index.ts index a79cbe9..96d02ad 100644 --- a/src/@shared/src/utils/index.ts +++ b/src/@shared/src/utils/index.ts @@ -142,3 +142,11 @@ export function typeResponse( export function isNullish(v: T | undefined | null): v is null | undefined { return v === null || v === undefined; } + +/** +/* EXPERIMENTAL: how to send a starting game link to chat +**/ +export async function sendGameLinkToChatService(link: string) :Promise { + const payload = { link }; + return JSON.stringify(payload); +} \ No newline at end of file diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 033e17c..e904b15 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -7,6 +7,7 @@ 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'; +import { sendGameLinkToChatService } from '../../@shared/src/utils/index'; // colors for console.log export const color = { @@ -42,6 +43,43 @@ export type ClientMessage = { SenderWindowID: string; }; +export type ClientProfil = { + command: string, + destination: string, + type: string, + user: string, + loginName: string, + userID: string, + text: string, + timestamp: number, + SenderWindowID:string, + SenderName: string, + innerHtml?: string, + +}; + +/** +/* TODO find the description info for profil / or profil game link and return +**/ +function createNextGame() { + return 'The next Game is Starting click here to watch'; +}; + +function setAboutPlayer(about: string): string { + if (!about) { + about = 'Player is good Shape - This is a default description'; + } + return about; +}; + +function setGameLink(link: string): string { + if (!link) { + link = 'Click me'; + } + return link; +}; + + const clientChat = new Map(); // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... @@ -85,8 +123,10 @@ declare module 'fastify' { hello: (message: string) => string; MsgObjectServer: (data: { message: ClientMessage }) => void; privMessage: (data: string) => void; - profilMessage: (data: string) => void; + profilMessage: (data: ClientProfil) => void; + inviteGame: (data: ClientProfil) => void; privMessageCopy: (msg: string) => void; + nextGame: (msg: string) => void; message: (msg: string) => void; listBud: (msg: string) => void; testend: (sock_id_client: string) => void; @@ -117,15 +157,12 @@ async function onReady(fastify: FastifyInstance) { // If we have the io instance, attempt to validate the socket is still connected if (io && typeof io.sockets?.sockets?.get === 'function') { - const s = io.sockets.sockets.get(socketId) as - | Socket - | undefined; + const s = io.sockets.sockets.get(socketId) as Socket | undefined; // If socket not found or disconnected, remove from map and skip if (!s || s.disconnected) { clientChat.delete(socketId); continue; } - // Skip duplicates (DO NOT delete them — just don't count) if (seen.has(username.user)) { continue; @@ -133,23 +170,11 @@ async function onReady(fastify: FastifyInstance) { // socket exists and is connected seen.add(username.user); count++; - // console.log(color.green,"count: ", count); - // 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, - // ); continue; } - // If no io provided, assume entries in the map are valid and count them. count++; - 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; } @@ -169,58 +194,99 @@ async function onReady(fastify: FastifyInstance) { 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'); } }); } + + async function broadcastNextGame(gameLink?: Promise) { + + const link = gameLink ? await gameLink : undefined; + const sockets = await fastify.io.fetchSockets(); + // fastify.io.fetchSockets().then((sockets) => { + for (const socket of sockets) { + // Skip sender's own socket + 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 + if (link) { + socket.emit('nextGame', link); + } + // Debug logs + // console.log(color.green, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`); + } + }; + // 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; + return users.find(user => user.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'; + async function getProfil(user: string, socket: Socket): Promise { + + let clientProfil!: ClientProfil; 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; + clientProfil = + { + command: 'getProfil', + destination: 'profilMsg', + type: 'chat' as const, + user: `${allUsers.name}`, + loginName: `${allUsers?.login ?? 'Guest'}`, + userID: `${allUsers?.id ?? ''}`, + text: setAboutPlayer(''), + timestamp: Date.now(), + SenderWindowID: socket.id, + SenderName: '', + innerHtml: '', + }; + } + return clientProfil; }; - function sendProfil(data: ClientMessage, clientProfil?: string) { - + function sendProfil(data: ClientProfil, 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}`); - } - } + if (senderSocket) { + console.log(color.yellow, 'user inFO:', data.user); + senderSocket.emit('profilMessage', data); } }); } + function sendInvite(innerHtml: string, data: ClientProfil) { + fastify.io.fetchSockets().then((sockets) => { + + let targetSocket; + for (const socket of sockets) { + const clientInfo: string = clientChat.get(socket.id)?.user || ''; + if (clientInfo === data.user) { + console.log(color.yellow, 'FOUND:', data.user); + targetSocket = socket || ''; + break; + } + } + data.innerHtml = innerHtml ?? ''; + if (targetSocket) { + targetSocket.emit('inviteGame', data); + } + }); + } + + function sendPrivMessage(data: ClientMessage, sender?: string) { fastify.io.fetchSockets().then((sockets) => { const senderSocket = sockets.find(s => s.id === sender); @@ -286,6 +352,13 @@ async function onReady(fastify: FastifyInstance) { console.log('testend received from client socket id:', sock_id_cl); }); + + socket.on('nextGame', () => { + const link = createNextGame(); + const game: Promise = sendGameLinkToChatService(link); + broadcastNextGame(game); + }); + socket.on('list', (object) => { const userFromFrontend = object || null; @@ -425,7 +498,7 @@ async function onReady(fastify: FastifyInstance) { } }); - socket.on('profilMessage', async (data) => { + socket.on('profilMessage', async (data: string) => { const clientName: string = clientChat.get(socket.id)?.user || ''; const profilMessage: ClientMessage = JSON.parse(data) || ''; const users: User[] = fastify.db.getAllUsers() ?? []; @@ -435,26 +508,29 @@ async function onReady(fastify: FastifyInstance) { `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`, profilMessage.user, ); - const profileHtml: string = await getProfil(profilMessage.user); + const profileHtml: ClientProfil = await getProfil(profilMessage.user, socket); 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); + console.log(color.yellow, 'user:', testuser?.name ?? 'Guest'); + console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', profileHtml.SenderWindowID); + sendProfil(profileHtml, profileHtml.SenderWindowID); // clientChat.delete(obj.user); } }); + socket.on('inviteGame', async (data: string) => { + const clientName: string = clientChat.get(socket.id)?.user || ''; + const profilInvite: ClientProfil = JSON.parse(data) || ''; + // const users: User[] = fastify.db.getAllUsers() ?? []; + + const inviteHtml: string = 'invites you to a game ' + setGameLink(''); + if (clientName !== null) { + // const testuser: User | null = getUserByName(users, profilInvite.user ?? ''); + // console.log(color.yellow, 'user:', testuser?.name ?? 'Guest'); + sendInvite(inviteHtml, profilInvite); + } + }); + socket.on('client_entered', (data) => { // data may be undefined (when frontend calls emit with no payload)