chat general broadcast done with now notifications system separated in different space on the chat windo

This commit is contained in:
NigeParis 2025-12-02 16:52:41 +01:00
parent 05130e5b8a
commit c086c27c4a
6 changed files with 81 additions and 78 deletions

View file

@ -9,4 +9,6 @@ declare global {
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void; dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
} }
} }
export { }; //keep that for TS compiler. export { }; //keep that for TS compiler.

View file

@ -40,7 +40,6 @@
active:shadow-[0_2px_0_0_black];; active:shadow-[0_2px_0_0_black];;
} }
.chatbox-style { .chatbox-style {
@apply @apply
w-[650px] w-[650px]
@ -60,12 +59,37 @@
mx-auto; mx-auto;
} }
.system-info {
@apply
h-[40px]
bg-gray-200
text-gray-700
p-3
rounded-3xl
mb-2 border
border-gray-200
text-center
shadow
overflow-y-auto
justify-end /* 👈 forces text to bottom */
relative; /* needed for overlay */
}
.text-info {
@apply
text-blue-800
}
.chat-window-style { .chat-window-style {
@apply @apply
w-[400px] w-[400px]
h-[50px] h-[50px]
p-[10px] p-[10px]
border-1 border-black border-1
border-black
shadow-sm shadow-sm
flex-1 flex-1
rounded-3xl rounded-3xl

View file

@ -7,9 +7,10 @@
<button id="b-clear" class="btn-style absolute top-4 right-6">Clear Text</button> <button id="b-clear" class="btn-style absolute top-4 right-6">Clear Text</button>
<button id="b-quit" class="btn-style absolute top-14 right-6">Quit Chat</button> <button id="b-quit" class="btn-style absolute top-14 right-6">Quit Chat</button>
<button id="b-help" class="btn-style absolute top-14 left-6">Connected</button> <button id="b-help" class="btn-style absolute top-14 left-6">Connected</button>
<!-- Horizontal Message Box -->
<!-- Center wrapper for chat + vertical box --> <div id="system-box" class="system-info">System: connecting ... </div>
<div class="flex justify-center mt-2"> <div class="flex justify-center mt-2">
<!-- Center wrapper for chat + vertical box -->
<!-- Groupe Chat + vertical box container --> <!-- Groupe Chat + vertical box container -->
<div id = "g-boxes" class="flex gap-2"> <div id = "g-boxes" class="flex gap-2">
<!-- Text Chat box panel + send --> <!-- Text Chat box panel + send -->
@ -20,7 +21,6 @@
<button id="b-send" class="send-btn-style">Send</button> <button id="b-send" class="send-btn-style">Send</button>
</div> </div>
</div> </div>
<!-- Vertical Ping Buddies box panel--> <!-- Vertical Ping Buddies box panel-->
<div id="ping-box" class="ping-box"> <div id="ping-box" class="ping-box">
<p id="ping-title" class="ping-title">Ping Buddies</p> <p id="ping-title" class="ping-title">Ping Buddies</p>
@ -34,7 +34,9 @@
</div> </div>
</div> </div>
</div> </div>
<p class="text-gray-400 mt-2">From this Chat Box you can send messages to other players</p> <p class="text-gray-400 mt-2">From this Chat Box you can send messages to other players</p>
</div> </div>
</div> </div>

View file

@ -4,17 +4,22 @@ import authHtml from './chat.html?raw';
import client from '@app/api' import client from '@app/api'
import { getUser, updateUser } from "@app/auth"; import { getUser, updateUser } from "@app/auth";
import io, { Socket } from 'socket.io-client'; import io, { Socket } from 'socket.io-client';
// import type { ClientMessage } from "@app/@types/dom";
const color = { const color = {
red: 'color: red; font-weight: bold;', red: 'color: red;',
green: 'color: green; font-weight: bold;', green: 'color: green;',
yellow: 'color: orange; font-weight: bold;', yellow: 'color: orange;',
blue: 'color: blue; font-weight: bold;', blue: 'color: blue;',
reset: '', reset: '',
}; };
export type ClientMessage = {
destination: string;
user: string;
text: string;
SenderWindowID: string;
};
// get the name of the machine used to connect // get the name of the machine used to connect
@ -143,26 +148,39 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
}); });
// Listen for messages from the server "MsgObjectServer" // Listen for messages from the server "MsgObjectServer"
socket.on("MsgObjectServer", (data: any) => { socket.on("MsgObjectServer", (data: { message: ClientMessage}) => {
console.log("Message Obj Recieved:", data.message); console.log("Message Obj Recieved:", data);
console.log("Recieved data.message.text: ", data.message.text); console.log("%cRecieved data.message.text: ", color.blue, data.message.text);
console.log("Recieved data.message.user: ", data.message.user); console.log("%cRecieved data.message.user: ", color.blue, data.message.user);
console.log("Recieved data.message.type: ", data.message.type);
console.log("Recieved data.message.token: ", data.message.token);
console.log("Recieved data.message.timestamp: ", data.message.timestamp);
// Display the message in the chat window // Display the message in the chat window
const systemWindow = document.getElementById('system-box') as HTMLDivElement;
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement; const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
const bconnected = document.getElementById('b-help') as HTMLButtonElement; const bconnected = document.getElementById('b-help') as HTMLButtonElement;
if (bconnected) { if (bconnected) {
bconnected.click(); bconnected.click();
} }
if (chatWindow) { if (chatWindow && data.message.destination === "") {
const messageElement = document.createElement("div"); const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`; messageElement.textContent = `${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement); chatWindow.appendChild(messageElement);
chatWindow.scrollTop = chatWindow.scrollHeight; chatWindow.scrollTop = chatWindow.scrollHeight;
} }
const MAX_SYSTEM_MESSAGES = 10;
if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
systemWindow.appendChild(messageElement);
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.scrollTop = systemWindow.scrollHeight;
}
console.log("Getuser():", getUser()); console.log("Getuser():", getUser());
}); });
@ -261,6 +279,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
const user = getUser(); const user = getUser();
if (user && socket?.connected) { if (user && socket?.connected) {
const message = { const message = {
destination: "",
type: "chat", type: "chat",
user: user.name, user: user.name,
token: document.cookie, token: document.cookie,
@ -298,7 +317,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
setInterval(async () => { setInterval(async () => {
bconnected.click(); //bconnected.click();
}, 10000); // every 10 second }, 10000); // every 10 second
// Help Text button // Help Text button
@ -312,7 +331,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
oldUser = loggedIn.name || "undefined"; oldUser = loggedIn.name || "undefined";
const res = await client.guestLogin(); const res = await client.guestLogin();
let user = await updateUser(); let user = await updateUser();
console.log('%User?name:',color.yellow, user?.name); console.log('%cUser?name:',color.yellow, user?.name);
localStorage.setItem("oldName", oldUser); localStorage.setItem("oldName", oldUser);
buddies.textContent = ""; buddies.textContent = "";
// if (chatWindow) { // if (chatWindow) {
@ -347,8 +366,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
switch (res.kind) { switch (res.kind) {
case 'success': { case 'success': {
let user = await updateUser(); let user = await updateUser();
console.log('%cUSER_NAME ',color.yellow, user?.name);
console.log('%cGET_user ',color.yellow, getUser()?.name || null);
if (chatWindow) { if (chatWindow) {
socket.emit('updateClientName', { socket.emit('updateClientName', {
oldUser: '', oldUser: '',

View file

@ -32,6 +32,13 @@ interface ClientInfo {
lastSeen: number; lastSeen: number;
} }
export type ClientMessage = {
destination: string;
user: string;
text: string;
SenderWindowID: string;
};
const clientChat = new Map<string, ClientInfo>(); const clientChat = new Map<string, ClientInfo>();
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... // @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
@ -69,18 +76,13 @@ export default app;
export { app }; export { app };
type ClientMessage = {
user: string;
text: string;
SenderWindowID: string;
};
// When using .decorate you have to specify added properties for Typescript // When using .decorate you have to specify added properties for Typescript
declare module 'fastify' { declare module 'fastify' {
interface FastifyInstance { interface FastifyInstance {
io: Server<{ io: Server<{
hello: (message: string) => string; hello: (message: string) => string;
MsgObjectServer: (data: { message: ClientMessage }) => void; MsgObjectServer: (data: { message: ClientMessage } ) => void;
message: (msg: string) => void; message: (msg: string) => void;
listBud: (msg: string) => void; listBud: (msg: string) => void;
testend: (sock_id_client: string) => void; testend: (sock_id_client: string) => void;
@ -273,6 +275,7 @@ function broadcast(data: ClientMessage, sender?: string) {
if (!clientName) return; if (!clientName) return;
console.log(color.green, `Client logging out: ${clientName} (${socket.id})`); console.log(color.green, `Client logging out: ${clientName} (${socket.id})`);
const obj = { const obj = {
destination: "system-info",
type: "chat" as const, type: "chat" as const,
user: clientName, user: clientName,
token: "", token: "",
@ -300,6 +303,7 @@ function broadcast(data: ClientMessage, sender?: string) {
if (clientName !== null) { if (clientName !== null) {
const obj = { const obj = {
destination: "system-info",
type: 'chat', type: 'chat',
user: clientName, user: clientName,
token: '', token: '',
@ -323,6 +327,7 @@ function broadcast(data: ClientMessage, sender?: string) {
if (clientName !== null) { if (clientName !== null) {
const obj = { const obj = {
destination: "system-info",
type: 'chat', type: 'chat',
user: clientName, user: clientName,
token: '', token: '',
@ -367,6 +372,7 @@ function broadcast(data: ClientMessage, sender?: string) {
); );
if (clientName !== null) { if (clientName !== null) {
const obj = { const obj = {
destination: "system-info",
type: 'chat', type: 'chat',
user: clientName, user: clientName,
frontendUserName: userNameFromFrontend, frontendUserName: userNameFromFrontend,

View file

@ -34,54 +34,6 @@ export const ChatRes = {
export type ChatResType = MakeStaticResponse<typeof ChatRes>; export type ChatResType = MakeStaticResponse<typeof ChatRes>;
function connectedUser(io: Server | undefined, targetSocketId?: string): number {
let count = 0;
// Track unique usernames (avoid duplicates)
const seenUsers = new Set<string>();
for (const [socketId, info] of clientChat) {
// Validate entry
if (!info || typeof info.user !== "string" || info.user.trim() === "") {
clientChat.delete(socketId);
continue;
}
const username = info.user;
// Validate socket exists if io is passed
if (io) {
const socket = io.sockets.sockets.get(socketId);
// Remove disconnected sockets
if (!socket || socket.disconnected) {
clientChat.delete(socketId);
continue;
}
}
// Skip duplicates
if (seenUsers.has(username))
continue;
seenUsers.add(username);
count++;
// Send to target only
if (io && targetSocketId) {
io.to(targetSocketId).emit("listBud", username);
}
console.log(color.yellow, "Client:", color.reset, username);
console.log(color.yellow, "Socket ID:", color.reset, socketId);
}
return count;
}
const route: FastifyPluginAsync = async (fastify): Promise<void> => { const route: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get( fastify.get(