From 422c0b26c4b1e2a37bde221311a3c908e24c978e Mon Sep 17 00:00:00 2001 From: NigeParis Date: Wed, 26 Nov 2025 14:41:21 +0100 Subject: [PATCH] added tab control to chat, When user leaves tab open on chat --- .../api/generated/.openapi-generator/FILES | 1 - .../src/api/generated/apis/OpenapiOtherApi.ts | 13 +-- frontend/src/api/generated/models/index.ts | 1 - frontend/src/pages/chat/chat.ts | 60 ++++++++---- src/chat/openapi.json | 26 ----- src/chat/src/app.ts | 95 ++++++++++++++++--- src/openapi.json | 26 ----- src/package.json | 2 +- src/pnpm-lock.yaml | 46 ++++----- 9 files changed, 151 insertions(+), 119 deletions(-) diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES index b02736b..af12e66 100644 --- a/frontend/src/api/generated/.openapi-generator/FILES +++ b/frontend/src/api/generated/.openapi-generator/FILES @@ -2,7 +2,6 @@ apis/OpenapiOtherApi.ts apis/index.ts index.ts models/ChatTest200Response.ts -models/ChatTest500Response.ts models/DisableOtp200Response.ts models/DisableOtp401Response.ts models/DisableOtp500Response.ts diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts index f03d19e..fda4f60 100644 --- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts +++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts @@ -16,7 +16,6 @@ import * as runtime from '../runtime'; import type { ChatTest200Response, - ChatTest500Response, DisableOtp200Response, DisableOtp401Response, DisableOtp500Response, @@ -50,8 +49,6 @@ import type { import { ChatTest200ResponseFromJSON, ChatTest200ResponseToJSON, - ChatTest500ResponseFromJSON, - ChatTest500ResponseToJSON, DisableOtp200ResponseFromJSON, DisableOtp200ResponseToJSON, DisableOtp401ResponseFromJSON, @@ -135,7 +132,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI { /** */ - async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; @@ -162,19 +159,15 @@ export class OpenapiOtherApi extends runtime.BaseAPI { // Object response for status 401 return new runtime.JSONApiResponse(response, (jsonValue) => StatusOtp401ResponseFromJSON(jsonValue)); } - if (response.status === 500) { - // Object response for status 500 - return new runtime.JSONApiResponse(response, (jsonValue) => ChatTest500ResponseFromJSON(jsonValue)); - } // CHANGED: Throw error if status code is not handled by any of the defined responses // This ensures all code paths return a value and provides clear error messages for unexpected status codes // Only throw if responses were defined but none matched the actual status code - throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401, 500`); + throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401`); } /** */ - async chatTest(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + async chatTest(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { const response = await this.chatTestRaw(initOverrides); return await response.value(); } diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts index b4d967b..a4059b0 100644 --- a/frontend/src/api/generated/models/index.ts +++ b/frontend/src/api/generated/models/index.ts @@ -1,7 +1,6 @@ /* tslint:disable */ /* eslint-disable */ export * from './ChatTest200Response'; -export * from './ChatTest500Response'; export * from './DisableOtp200Response'; export * from './DisableOtp401Response'; export * from './DisableOtp500Response'; diff --git a/frontend/src/pages/chat/chat.ts b/frontend/src/pages/chat/chat.ts index 18bca7a..1e0d53f 100644 --- a/frontend/src/pages/chat/chat.ts +++ b/frontend/src/pages/chat/chat.ts @@ -16,29 +16,49 @@ document.addEventListener('ft:pageChange', () => { document.addEventListener("visibilitychange", async () => { - // When user leaves tab + const socketId = __socket || undefined; + let oldName = localStorage.getItem("oldName") || undefined; + if (socketId == undefined) return; if (document.visibilityState === "hidden") { - console.log("User LEFT this tab"); - - // if (__socket) { - // __socket.close(); - // __socket = undefined; - // } - + let userName = await updateUser(); + oldName = userName?.name || "undefined"; + localStorage.setItem("oldName", oldName); + socketId.emit("client_left"); 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') + const res = await client.guestLogin(); + let user = await updateUser(); + socketId.emit('client_entered', { + userName: oldName, + user: user?.name, + }); + setTitle('Chat Page'); + return; } }); + +async function getUserName(): Promise { + try { + const res = await client.guestLogin(); + + if (res.kind !== "success") { + console.error("Login failed:", res.msg); + return null; + } + + const user = await updateUser(); + if (!user) return null; + + return user.name; // <-- return the username + } catch (err) { + console.error("getUserName error:", err); + return null; + } +} + function getSocket(): Socket { if (__socket === undefined) __socket = io("wss://localhost:8888", { @@ -142,8 +162,8 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn }; - socket.on("welcome", (data) => { - addMessage(`${data.msg}`); + socket.once('welcome', (data) => { + addMessage (`${data.msg} ` + getUser()?.name); }); // Send button @@ -161,7 +181,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn timestamp: Date.now(), SenderWindowID: socket.id, }; - socket.send(JSON.stringify(message)); + socket.emit('message', JSON.stringify(message)); } sendtextbox.value = ""; } @@ -182,6 +202,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn const loggedIn = await isLoggedIn(); if (loggedIn?.name === undefined) return ; + const res = await client.guestLogin(); + let user = await updateUser(); + console.log('USER ', user?.name); if (chatWindow) { addMessage('@list - lists all connected users in the chat'); socket.emit('list'); @@ -228,3 +251,4 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn } }; addRoute('/chat', handleChat, { bypass_auth: true }); +addRoute('/chat/', handleChat, { bypass_auth: true }); diff --git a/src/chat/openapi.json b/src/chat/openapi.json index 8afe3d2..bcebf94 100644 --- a/src/chat/openapi.json +++ b/src/chat/openapi.json @@ -112,32 +112,6 @@ } } } - }, - "500": { - "description": "Default Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "msg" - ], - "properties": { - "kind": { - "enum": [ - "failed" - ] - }, - "msg": { - "enum": [ - "chat.failed.generic" - ] - } - } - } - } - } } } } diff --git a/src/chat/src/app.ts b/src/chat/src/app.ts index 0dbac4d..abc7a53 100644 --- a/src/chat/src/app.ts +++ b/src/chat/src/app.ts @@ -163,12 +163,10 @@ async function onReady(fastify: FastifyInstance) { } }); } - + fastify.io.on('connection', (socket: Socket) => { - - - + socket.on('message', (message: string) => { console.info( color.blue, @@ -191,6 +189,10 @@ async function onReady(fastify: FastifyInstance) { color.reset, `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`, ); + socket.emit('welcome', { + msg: `Welcome to the chat! `, + }); + // Send object directly — DO NOT wrap it in a string broadcast(obj, obj.SenderWindowID); console.log( @@ -201,33 +203,35 @@ async function onReady(fastify: FastifyInstance) { ); }); - 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('wakeup', (message: string) => { + const obj: ClientMessage = JSON.parse(message) as ClientMessage; + clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() }); + connectedUser(fastify.io), + console.log('Wakeup: ', message); + }); + 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; + const clientName = clientChat.get(socket.id)?.user|| null; console.log( color.green, - `Client disconnecting: ${clientName?.user} (${socket.id}) reason:`, + `Client disconnecting: ${clientName} (${socket.id}) reason:`, reason, ); if (reason === 'transport error') return; - if (clientName?.user !== null) { + if (clientName !== null) { const obj = { type: 'chat', - user: clientName!.user, + user: clientName, token: '', text: 'LEFT the chat', timestamp: Date.now(), @@ -238,5 +242,70 @@ async function onReady(fastify: FastifyInstance) { // clientChat.delete(obj.user); } }); + + socket.on('client_left', (reason) => { + const clientName = clientChat.get(socket.id)?.user|| null; + console.log( + color.green, + `Client left the Chat: ${clientName} (${socket.id}) reason:`, + reason, + ); + if (reason === 'transport error') return; + + if (clientName !== null) { + const obj = { + type: 'chat', + user: clientName, + token: '', + text: 'LEFT the chat but the window is still open', + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + console.log(obj.SenderWindowID); + broadcast(obj, obj.SenderWindowID); + // clientChat.delete(obj.user); + } + }); + + + socket.on('client_entered', (data) => { + + // data may be undefined (when frontend calls emit with no payload) + const userNameFromFrontend = data?.userName || null; + const userFromFrontend = data?.user || null; + let clientName = clientChat.get(socket.id)?.user || null; + const client = clientChat.get(socket.id) || null; + let text = 'is back in the chat'; + + // connectedUser(fastify.io, socket.id); + if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; + if(client === null) {console.log('ERROR: client is NULL'); return;}; + + if (userNameFromFrontend !== userFromFrontend) { + text = `'is back in the chat, I used to be called '${userNameFromFrontend}`; + clientName = userFromFrontend; + if(clientName === null) {console.log('ERROR: clientName is NULL'); return;}; + if (client) { + client.user = clientName; + } + } + console.log( + color.green, + `Client entered the Chat: ${clientName} (${socket.id})` + ); + if (clientName !== null) { + const obj = { + type: 'chat', + user: clientName, // server-side stored name + frontendUserName: userNameFromFrontend, // from frontend + frontendUser: userFromFrontend, // from frontend + token: '', + text: text, + timestamp: Date.now(), + SenderWindowID: socket.id, + }; + broadcast(obj, obj.SenderWindowID); + } + }); }); } diff --git a/src/openapi.json b/src/openapi.json index f370df1..4327973 100644 --- a/src/openapi.json +++ b/src/openapi.json @@ -1294,32 +1294,6 @@ } } } - }, - "500": { - "description": "Default Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "kind", - "msg" - ], - "properties": { - "kind": { - "enum": [ - "failed" - ] - }, - "msg": { - "enum": [ - "chat.failed.generic" - ] - } - } - } - } - } } }, "tags": [ diff --git a/src/package.json b/src/package.json index 22edb07..c833a12 100644 --- a/src/package.json +++ b/src/package.json @@ -36,7 +36,7 @@ "vite": "^7.2.4" }, "dependencies": { - "@redocly/cli": "^2.11.1", + "@redocly/cli": "^2.12.0", "bindings": "^1.5.0" } } diff --git a/src/pnpm-lock.yaml b/src/pnpm-lock.yaml index bc84ef1..c152612 100644 --- a/src/pnpm-lock.yaml +++ b/src/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@redocly/cli': - specifier: ^2.11.1 - version: 2.11.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) + specifier: ^2.12.0 + version: 2.12.0(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0) bindings: specifier: ^1.5.0 version: 1.5.0 @@ -946,27 +946,27 @@ packages: '@redocly/ajv@8.17.1': resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} - '@redocly/cli@2.11.1': - resolution: {integrity: sha512-doNs+sdrFzzXmyf1yIeJbPh8OChacHWkvTE9N0QbuCmnYQ4k0v1IMP20qsitkwR+fK8O1hXSnFnSTVvIunMVVw==} + '@redocly/cli@2.12.0': + resolution: {integrity: sha512-/q8RnBe+Duo+XYFCG8LnaD0kroGZ8MoS6575Xq59tCgjaCL16F+pZZ75xNBU2oXfEypJClNz/6ilc2G0q1+tlw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.38.0': - resolution: {integrity: sha512-kSgMG3rRzgXIP/6gWMRuWbu9/ms0Cyuphcx19dPR9qlgc1tt9IKYPsFQ+KhJuEtqd3bcY/+Uflysf33dQkZWVQ==} + '@redocly/config@0.40.0': + resolution: {integrity: sha512-MZQZs7QEGnue3rVN9Q9QvDbcGjesxbpKXUvDeckS69R1xjtgsnT9B39VA25zmwSJtgUeA9ST+sMf9GxIqixNbw==} '@redocly/openapi-core@1.34.5': resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.11.1': - resolution: {integrity: sha512-FVCDnZxaoUJwLQxfW4inCojxUO56J3ntu7dDAE2qyWd6tJBK45CnXMQQUxpqeRTeXROr3jYQoApAw+GCEnyBeg==} + '@redocly/openapi-core@2.12.0': + resolution: {integrity: sha512-RsVwmRD0KhyJbR8acIeU98ce6N+/YCuLJf6IGN+2SOsbwnDhnI5MG0TFV9D7URK/ukEewaNA701dVYsoP1VtRQ==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.11.1': - resolution: {integrity: sha512-jSMJvCJeo5gmhQfg82AhuwCG0h8gbW5vqHyRITBu8KHVsBiQTgvfhXepu8SKHeJu0OexYtEc0nUnGLJlefevYw==} + '@redocly/respect-core@2.12.0': + resolution: {integrity: sha512-mrYrfE81shSRS96ygXaRiSithV4Fe4Y7XlSYLSTfM8Lo3YAz7Geirg7HZ5fNFsI+hdW05ZuQewqpKL8XLwaAeA==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -1483,8 +1483,8 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.0: + resolution: {integrity: sha512-vXiThu1/rlos7EGu8TuNZQEg2e9TvhH9dmS4T4ZVzB7Ao1agEZ6EG3sn5n+hZRYUgduISd1HpngFzAZiDGm5vQ==} engines: {node: '>=18'} core-js@3.47.0: @@ -3436,7 +3436,7 @@ snapshots: '@fastify/cookie@11.0.2': dependencies: - cookie: 1.0.2 + cookie: 1.1.0 fastify-plugin: 5.1.0 '@fastify/deepmerge@3.1.0': {} @@ -3873,14 +3873,14 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.11.1(@opentelemetry/api@1.9.0)(ajv@8.17.1)(core-js@3.47.0)': + '@redocly/cli@2.12.0(@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.11.1(ajv@8.17.1) - '@redocly/respect-core': 2.11.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.0(ajv@8.17.1) + '@redocly/respect-core': 2.12.0(ajv@8.17.1) abort-controller: 3.0.0 chokidar: 3.6.0 colorette: 1.4.0 @@ -3913,7 +3913,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.38.0': + '@redocly/config@0.40.0': dependencies: json-schema-to-ts: 2.7.2 @@ -3931,10 +3931,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.11.1(ajv@8.17.1)': + '@redocly/openapi-core@2.12.0(ajv@8.17.1)': dependencies: '@redocly/ajv': 8.17.1 - '@redocly/config': 0.38.0 + '@redocly/config': 0.40.0 ajv-formats: 2.1.1(ajv@8.17.1) colorette: 1.4.0 js-levenshtein: 1.1.6 @@ -3945,12 +3945,12 @@ snapshots: transitivePeerDependencies: - ajv - '@redocly/respect-core@2.11.1(ajv@8.17.1)': + '@redocly/respect-core@2.12.0(ajv@8.17.1)': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.11.4 - '@redocly/openapi-core': 2.11.1(ajv@8.17.1) + '@redocly/openapi-core': 2.12.0(ajv@8.17.1) better-ajv-errors: 1.2.0(ajv@8.17.1) colorette: 2.0.20 json-pointer: 0.6.2 @@ -4448,7 +4448,7 @@ snapshots: cookie@0.7.2: {} - cookie@1.0.2: {} + cookie@1.1.0: {} core-js@3.47.0: {} @@ -5167,7 +5167,7 @@ snapshots: light-my-request@6.6.0: dependencies: - cookie: 1.0.2 + cookie: 1.1.0 process-warning: 4.0.1 set-cookie-parser: 2.7.2