From 40980bedeb15ec2abfbe01cfdd145c67dfe0e270 Mon Sep 17 00:00:00 2001 From: NigeParis Date: Tue, 25 Nov 2025 17:49:49 +0100 Subject: [PATCH] Added Date.now ClientChat that stores user, socket id and date --- frontend/src/pages/chat/chat.html | 32 +++++++++---------- frontend/src/pages/chat/chat.ts | 46 ++++++++++++++++++++++++++- frontend/src/routing/index.ts | 1 + src/@shared/package.json | 2 +- src/auth/package.json | 2 +- src/chat/src/app.ts | 52 ++++++++++++++++++++----------- src/pnpm-lock.yaml | 18 +++++------ src/user/package.json | 2 +- 8 files changed, 107 insertions(+), 48 deletions(-) diff --git a/frontend/src/pages/chat/chat.html b/frontend/src/pages/chat/chat.html index 1e765fc..a0f0dde 100644 --- a/frontend/src/pages/chat/chat.html +++ b/frontend/src/pages/chat/chat.html @@ -1,19 +1,19 @@
- -

- Chat Box -


- - -

Welcome


-
-
-
- - -
-
-

From this Chat Box you can send messages to other players

+ +

+ Chat Box +


+ + +

Welcome


+
+
+
+ +
-
\ No newline at end of file +
+

From this Chat Box you can send messages to other players

+
+ \ No newline at end of file diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 7c30261..18bca7a 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -12,6 +12,33 @@ document.addEventListener('ft:pageChange', () => { __socket = undefined; console.log("Page changed"); }) + + +document.addEventListener("visibilitychange", async () => { + + // When user leaves tab + if (document.visibilityState === "hidden") { + console.log("User LEFT this tab"); + + // if (__socket) { + // __socket.close(); + // __socket = undefined; + // } + + return; + } + + // When user returns to tab → soft reload using imported HTML file + if (document.visibilityState === "visible") { + // location.reload(); + //console.log(location.replace(location.href)); + + + console.log('Chat Visible') + } +}); + + function getSocket(): Socket { if (__socket === undefined) __socket = io("wss://localhost:8888", { @@ -23,6 +50,10 @@ function getSocket(): Socket { } +async function isLoggedIn() { + return getUser() || null; +} + function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn { @@ -43,6 +74,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }; socket.emit('message', JSON.stringify(message)); }); + // Listen for messages from the server "MsgObjectServer" socket.on("MsgObjectServer", (data: any) => { console.log("Message Obj Recieved:", data.message); @@ -109,6 +141,11 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn console.log(`Added new message: ${text}`) }; + + socket.on("welcome", (data) => { + addMessage(`${data.msg}`); + }); + // Send button sendButton?.addEventListener("click", () => { if (sendtextbox && sendtextbox.value.trim()) { @@ -141,9 +178,13 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn // Help Text button bconnected?.addEventListener("click", async () => { + + const loggedIn = await isLoggedIn(); + + if (loggedIn?.name === undefined) return ; if (chatWindow) { addMessage('@list - lists all connected users in the chat'); - await socket.emit('list'); + socket.emit('list'); } }); socket.on('listObj', (list: string) => { @@ -152,6 +193,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }); + + + // Enter key to send message sendtextbox!.addEventListener('keydown', (event) => { if (event.key === 'Enter') { diff --git a/frontend/src/routing/index.ts b/frontend/src/routing/index.ts index 0c0b9a4..9e504db 100644 --- a/frontend/src/routing/index.ts +++ b/frontend/src/routing/index.ts @@ -196,6 +196,7 @@ export async function handleRoute() { return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`) const app = document.getElementById('app')!; document.dispatchEvent(new CustomEvent('ft:pageChange' as any, {} as any) as any); + document.dispatchEvent(new CustomEvent('ft:tabChange' as any, {} as any) as any); let ret = await executeRouteHandler(route_handler, window.location.pathname, args) app.innerHTML = ret.html; if (ret.postInsert) { diff --git a/src/@shared/package.json b/src/@shared/package.json index edcf6a8..dad8675 100644 --- a/src/@shared/package.json +++ b/src/@shared/package.json @@ -20,7 +20,7 @@ "fastify-plugin": "^5.1.0", "joi": "^18.0.2", "otp": "^1.1.2", - "typebox": "^1.0.55", + "typebox": "^1.0.56", "uuidv7": "^1.0.2" }, "devDependencies": { diff --git a/src/auth/package.json b/src/auth/package.json index 855e7f8..adb5e2e 100644 --- a/src/auth/package.json +++ b/src/auth/package.json @@ -27,7 +27,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.55" + "typebox": "^1.0.56" }, "devDependencies": { "@types/node": "^22.19.1", diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 4749bb9..0dbac4d 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -11,7 +11,12 @@ declare const __SERVICE_NAME: string; // Global map of clients // key = socket, value = clientname -const clientChat = new Map(); +interface ClientInfo { + user: string; + lastSeen: number; +} + +const clientChat = new Map(); // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); @@ -79,13 +84,13 @@ async function onReady(fastify: FastifyInstance) { const seen = new Set(); // <- only log/count unique usernames - for (const [socketId, username] of clientChat) { + for (const [socketId, username,] of clientChat) { // Basic sanity checks if (typeof socketId !== 'string' || socketId.length === 0) { clientChat.delete(socketId); continue; } - if (typeof username !== 'string' || username.length === 0) { + if (typeof username.user !== 'string' || username.user.length === 0) { clientChat.delete(socketId); continue; } @@ -102,17 +107,17 @@ async function onReady(fastify: FastifyInstance) { } // Skip duplicates (DO NOT delete them — just don't count) - if (seen.has(username)) { + if (seen.has(username.user)) { continue; } // socket exists and is connected - seen.add(username); + seen.add(username.user); count++; // console.log(color.green,"count: ", count); - console.log(color.yellow, 'Client:', color.reset, username); + console.log(color.yellow, 'Client:', color.reset, username.user); const targetSocketId = target; - io.to(targetSocketId!).emit('listObj', username); + io.to(targetSocketId!).emit('listObj', username.user); console.log( color.yellow, @@ -147,8 +152,8 @@ async function onReady(fastify: FastifyInstance) { for (const s of sockets) { if (s.id !== sender) { // Send REAL JSON object - const clientName = clientChat.get(s.id) || null; - if (clientName !== null) { + const clientName = clientChat.get(s.id)?.user; + if (clientName !== undefined) { s.emit('MsgObjectServer', { message: data }); } console.log(' Target window socket ID:', s.id); @@ -159,7 +164,11 @@ async function onReady(fastify: FastifyInstance) { }); } + fastify.io.on('connection', (socket: Socket) => { + + + socket.on('message', (message: string) => { console.info( color.blue, @@ -173,9 +182,9 @@ async function onReady(fastify: FastifyInstance) { color.reset, message, ); - + const obj: ClientMessage = JSON.parse(message) as ClientMessage; - clientChat.set(socket.id, obj.user); + clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); console.log( color.green, 'Message from client', @@ -191,35 +200,40 @@ async function onReady(fastify: FastifyInstance) { color.reset, ); }); - + + socket.emit("welcome", { + msg: `Welcome to the chat!`, + id: socket.id + }); + socket.on('testend', (sock_id_cl: string) => { console.log('testend received from client socket id:', sock_id_cl); }); - + socket.on('list', () => { console.log(color.red, 'list activated', color.reset, socket.id); connectedUser(fastify.io, socket.id); }); - + socket.on('disconnecting', (reason) => { const clientName = clientChat.get(socket.id) || null; console.log( color.green, - `Client disconnecting: ${clientName} (${socket.id}) reason:`, + `Client disconnecting: ${clientName?.user} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - - if (clientName !== null) { + + if (clientName?.user !== null) { const obj = { type: 'chat', - user: clientName, + user: clientName!.user, token: '', text: 'LEFT the chat', timestamp: Date.now(), SenderWindowID: socket.id, }; - + broadcast(obj, obj.SenderWindowID); // clientChat.delete(obj.user); } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index e77156a..bc84ef1 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -88,8 +88,8 @@ importers: specifier: ^1.1.2 version: 1.1.2 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 uuidv7: specifier: ^1.0.2 version: 1.0.2 @@ -131,8 +131,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 devDependencies: '@types/node': specifier: ^22.19.1 @@ -266,8 +266,8 @@ importers: specifier: ^5.1.0 version: 5.1.0 typebox: - specifier: ^1.0.55 - version: 1.0.55 + specifier: ^1.0.56 + version: 1.0.56 devDependencies: '@types/node': specifier: ^22.19.1 @@ -3025,8 +3025,8 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typebox@1.0.55: - resolution: {integrity: sha512-TP02wN0B6tDZngprrGVu/Z9s/QUyVEmR7VIg1yEOtsqyDdXXEoQPSfWdkD2PsA2lGLxu6GgwOTtGZVS9CAoERg==} + typebox@1.0.56: + resolution: {integrity: sha512-KMd1DJnIRqLUzAicpFmGqgmt+/IePCEmT/Jtywyyyn0hK6+dupQnxm7OAIn/cL/vu22jKi1XvDjDhrpatZ46kA==} typescript-eslint@8.48.0: resolution: {integrity: sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==} @@ -6156,7 +6156,7 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - typebox@1.0.55: {} + typebox@1.0.56: {} typescript-eslint@8.48.0(eslint@9.39.1)(typescript@5.9.3): dependencies: diff --git a/src/user/package.json b/src/user/package.json index 4f197a3..9efe221 100644 --- a/src/user/package.json +++ b/src/user/package.json @@ -26,7 +26,7 @@ "fastify": "^5.6.2", "fastify-cli": "^7.4.1", "fastify-plugin": "^5.1.0", - "typebox": "^1.0.55" + "typebox": "^1.0.56" }, "devDependencies": { "@types/node": "^22.19.1",