Started Pong simple copy Chat - WIP simplification first job
This commit is contained in:
parent
c39e7b9e04
commit
7c20066b63
48 changed files with 2080 additions and 0 deletions
|
|
@ -138,6 +138,32 @@ services:
|
|||
gelf-address: "udp://127.0.0.1:12201"
|
||||
tag: "{{.Name}}"
|
||||
|
||||
###############
|
||||
# PONG #
|
||||
###############
|
||||
pong:
|
||||
build:
|
||||
context: ./src/
|
||||
args:
|
||||
- SERVICE=pong
|
||||
- EXTRA_FILES=pong/extra
|
||||
container_name: app-pong
|
||||
restart: always
|
||||
networks:
|
||||
- transcendance-network
|
||||
volumes:
|
||||
- sqlite-volume:/volumes/database
|
||||
- static-volume:/volumes/static
|
||||
environment:
|
||||
- JWT_SECRET=KRUGKIDROVUWG2ZAMJZG653OEBTG66BANJ2W24DTEBXXMZLSEB2GQZJANRQXU6JA
|
||||
- DATABASE_DIR=/volumes/database
|
||||
- PROVIDER_FILE=/extra/providers.toml
|
||||
- SESSION_MANAGER=${SESSION_MANAGER}
|
||||
|
||||
|
||||
###############
|
||||
# USER #
|
||||
###############
|
||||
user:
|
||||
build:
|
||||
context: ./src/
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { setTitle, handleRoute } from '@app/routing';
|
||||
import './root/root.ts'
|
||||
import './chat/chat.ts'
|
||||
import './pong/pong.ts'
|
||||
import './login/login.ts'
|
||||
import './signin/signin.ts'
|
||||
import './ttt/ttt.ts'
|
||||
|
|
|
|||
13
frontend/src/pages/pong/actionBtnPopUpBlock.ts
Normal file
13
frontend/src/pages/pong/actionBtnPopUpBlock.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Socket } from 'socket.io-client';
|
||||
import type { ClientProfil } from './types_front';
|
||||
import { blockUser } from './blockUser';
|
||||
|
||||
export function actionBtnPopUpBlock(block: ClientProfil, senderSocket: Socket) {
|
||||
setTimeout(() => {
|
||||
const blockUserBtn = document.querySelector("#popup-b-block");
|
||||
blockUserBtn?.addEventListener("click", () => {
|
||||
block.text = '';
|
||||
blockUser(block, senderSocket);
|
||||
});
|
||||
}, 0)
|
||||
};
|
||||
13
frontend/src/pages/pong/actionBtnPopUpClear.ts
Normal file
13
frontend/src/pages/pong/actionBtnPopUpClear.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { clearChatWindow } from './clearChatWindow';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import type { ClientProfil } from './types_front';
|
||||
|
||||
|
||||
export function actionBtnPopUpClear(profil: ClientProfil, senderSocket: Socket) {
|
||||
setTimeout(() => {
|
||||
const clearTextBtn = document.querySelector("#popup-b-clear");
|
||||
clearTextBtn?.addEventListener("click", () => {
|
||||
clearChatWindow(senderSocket);
|
||||
});
|
||||
}, 0)
|
||||
};
|
||||
18
frontend/src/pages/pong/addMessage.ts
Normal file
18
frontend/src/pages/pong/addMessage.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { color } from './pong';
|
||||
|
||||
/**
|
||||
* function adds a message to the frontend chatWindow
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export 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(`%c DEBUG LOG: Added new message:%c ${text}`, color.red, color.reset);
|
||||
return ;
|
||||
};
|
||||
11
frontend/src/pages/pong/blockUser.ts
Normal file
11
frontend/src/pages/pong/blockUser.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { Socket } from 'socket.io-client';
|
||||
import type { ClientProfil } from './types_front';
|
||||
import { getUser } from "@app/auth";
|
||||
|
||||
|
||||
export function blockUser(profil: ClientProfil, senderSocket: Socket) {
|
||||
profil.SenderName = getUser()?.name ?? '';
|
||||
if (profil.SenderName === profil.user) return;
|
||||
// addMessage(`${profil.Sendertext}: ${profil.user}⛔`)
|
||||
senderSocket.emit('blockUser', JSON.stringify(profil));
|
||||
};
|
||||
28
frontend/src/pages/pong/broadcastMsg.ts
Normal file
28
frontend/src/pages/pong/broadcastMsg.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { addMessage } from "./addMessage";
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { getUser } from "@app/auth";
|
||||
|
||||
/**
|
||||
* function sends socket.emit to the backend to active and broadcast a message to all sockets
|
||||
* echos the message with addMessage to the sender
|
||||
* @param socket
|
||||
* @param msgCommand
|
||||
*/
|
||||
export function broadcastMsg (socket: Socket, msgCommand: string[]): void {
|
||||
let msgText = msgCommand[1] ?? "";
|
||||
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));
|
||||
}
|
||||
};
|
||||
13
frontend/src/pages/pong/clearChatWindow.ts
Normal file
13
frontend/src/pages/pong/clearChatWindow.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import io, { Socket } from 'socket.io-client';
|
||||
|
||||
/**
|
||||
* function clears all messages in the chat window
|
||||
* @param senderSocket
|
||||
* @returns
|
||||
*/
|
||||
export function clearChatWindow(senderSocket: Socket) {
|
||||
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
|
||||
if (!chatWindow) return;
|
||||
chatWindow.innerHTML = "";
|
||||
// senderSocket.emit('nextGame');
|
||||
}
|
||||
24
frontend/src/pages/pong/getProfil.ts
Normal file
24
frontend/src/pages/pong/getProfil.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Socket } from 'socket.io-client';
|
||||
|
||||
/**
|
||||
* getProfil of a user
|
||||
* @param socket
|
||||
* @param user
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export 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));
|
||||
}
|
||||
9
frontend/src/pages/pong/isLoggedIn.ts
Normal file
9
frontend/src/pages/pong/isLoggedIn.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { getUser } from "@app/auth";
|
||||
import type { User } from '@app/auth'
|
||||
/**
|
||||
* function checks if logged in
|
||||
* @returns either user | null
|
||||
*/
|
||||
export function isLoggedIn(): User | null {
|
||||
return getUser() || null;
|
||||
};
|
||||
45
frontend/src/pages/pong/listBuddies.ts
Normal file
45
frontend/src/pages/pong/listBuddies.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { getUser } from "@app/auth";
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { getProfil } from './getProfil';
|
||||
|
||||
/**
|
||||
* function adds a user to the ping Buddies window\
|
||||
* it also acts as click or double click\
|
||||
* activates two possible actions:\
|
||||
* click => private Mag\
|
||||
* dbl click => get Profil of the name\
|
||||
* collected in the clipBoard
|
||||
* @param socket
|
||||
* @param buddies
|
||||
* @param listBuddies
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: string) {
|
||||
|
||||
if (!buddies) 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 ?? "";
|
||||
buddies.appendChild(buddiesElement);
|
||||
buddies.scrollTop = buddies.scrollHeight;
|
||||
console.log(`Added buddies: ${listBuddies}`);
|
||||
|
||||
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);
|
||||
sendtextbox.value = "";
|
||||
});
|
||||
|
||||
}
|
||||
24
frontend/src/pages/pong/openProfilePopup.ts
Normal file
24
frontend/src/pages/pong/openProfilePopup.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type { ClientProfil } from './types_front';
|
||||
|
||||
export async function openProfilePopup(profil: ClientProfil) {
|
||||
const modalname = document.getElementById("modal-name") ?? null;
|
||||
if (modalname)
|
||||
modalname.innerHTML =
|
||||
`
|
||||
<div class="profile-info">
|
||||
<div-profil-name id="profilName"> Profil of ${profil.user} </div>
|
||||
<div-login-name id="loginName"> Login Name: '${profil.loginName ?? 'Guest'}' </div>
|
||||
</br>
|
||||
<div-login-name id="loginName"> Login ID: '${profil.userID ?? ''}' </div>
|
||||
</br>
|
||||
<button id="popup-b-clear" class="btn-style popup-b-clear">Clear Text</button>
|
||||
<button id="popup-b-invite" class="btn-style popup-b-invite">U Game ?</button>
|
||||
<button id="popup-b-block" class="btn-style popup-b-block">Block User</button>
|
||||
<div id="profile-about">About: '${profil.text}' </div>
|
||||
</div>
|
||||
`;
|
||||
const profilList = document.getElementById("profile-modal") ?? null;
|
||||
if (profilList)
|
||||
profilList.classList.remove("hidden");
|
||||
// The popup now exists → attach the event
|
||||
}
|
||||
54
frontend/src/pages/pong/pong.html
Normal file
54
frontend/src/pages/pong/pong.html
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<div class="displaybox">
|
||||
<div id="mainbox" class="mainboxDisplay">
|
||||
<button id="b-whoami" class="btn-style absolute top-4 left-6">Who am i</button>
|
||||
<button id="b-nextGame" class="btn-style absolute top-4 left-34">nextGame</button>
|
||||
<h1 class="text-3xl font-bold text-gray-800">
|
||||
ChatterBox<span id="t-username"></span>
|
||||
</h1><br>
|
||||
<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-help" class="btn-style absolute top-14 left-6">Connected</button>
|
||||
<!-- Horizontal Message Box -->
|
||||
<div id="system-box" class="system-info">System: connecting ... </div>
|
||||
<div class="flex justify-center mt-2">
|
||||
<!-- Center wrapper for chat + vertical box -->
|
||||
<!-- Groupe Chat + vertical box container -->
|
||||
<div id = "g-boxes" class="flex gap-2">
|
||||
<!-- Text Chat box panel + send -->
|
||||
<div id="g-textBoxes" class="flex flex-col">
|
||||
<div id="t-chatbox" class="chatbox-style"></div>
|
||||
<div id="t-input-send" class="flex gap-1 mt-2">
|
||||
<input id="t-chat-window" placeholder="Type your message..." class="chat-window-style" />
|
||||
<button id="b-send" class="send-btn-style">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Vertical Ping Buddies box panel-->
|
||||
<div id="ping-box" class="ping-box">
|
||||
<p id="ping-title" class="ping-title">Ping Buddies</p>
|
||||
<div id="ping-list" class="flex-1 overflow-y-auto">
|
||||
<div id = "div-buddies">
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-modal" class="profilPopup hidden">
|
||||
<div class="popUpBox">
|
||||
<p class="" id="modal-name"></p>
|
||||
<button id="close-modal" class="btn-style absolute bottom-32 right-12">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="game-modal" class="gamePopup hidden">
|
||||
<div class="popUpMessage">
|
||||
<div id="game-info">
|
||||
<p class="modal-messages " id="modal-message"></p>
|
||||
</div>
|
||||
<button id="close-modal-message" class="btn-style absolute bottom-67 right-12">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
615
frontend/src/pages/pong/pong.ts
Normal file
615
frontend/src/pages/pong/pong.ts
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
import { addRoute, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
|
||||
import { showError } from "@app/toast";
|
||||
import authHtml from './pong.html?raw';
|
||||
import client from '@app/api'
|
||||
import { getUser, updateUser } from "@app/auth";
|
||||
import io, { Socket } from 'socket.io-client';
|
||||
import { listBuddies } from './listBuddies';
|
||||
import { getProfil } from './getProfil';
|
||||
import { addMessage } from './addMessage';
|
||||
import { broadcastMsg } from './broadcastMsg';
|
||||
import { isLoggedIn } from './isLoggedIn';
|
||||
import type { ClientMessage, ClientProfil } from './types_front';
|
||||
import { openProfilePopup } from './openProfilePopup';
|
||||
import { actionBtnPopUpClear } from './actionBtnPopUpClear';
|
||||
import { actionBtnPopUpBlock } from './actionBtnPopUpBlock';
|
||||
import { windowStateHidden } from './windowStateHidden';
|
||||
|
||||
export const color = {
|
||||
red: 'color: red;',
|
||||
green: 'color: green;',
|
||||
yellow: 'color: orange;',
|
||||
blue: 'color: blue;',
|
||||
reset: '',
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
export let __socket: Socket | undefined = undefined;
|
||||
document.addEventListener('ft:pageChange', () => {
|
||||
if (__socket !== undefined)
|
||||
__socket.close();
|
||||
__socket = undefined;
|
||||
console.log("Page changed");
|
||||
});
|
||||
|
||||
export function getSocket(): Socket {
|
||||
let addressHost = `wss://${machineHostName}:8888`;
|
||||
// let addressHost = `wss://localhost:8888`;
|
||||
if (__socket === undefined)
|
||||
|
||||
__socket = io(addressHost, {
|
||||
path: "/api/pong/socket.io/",
|
||||
secure: false,
|
||||
transports: ["websocket"],
|
||||
});
|
||||
return __socket;
|
||||
};
|
||||
|
||||
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 actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) {
|
||||
setTimeout(() => {
|
||||
const InvitePongBtn = document.querySelector("#popup-b-invite");
|
||||
InvitePongBtn?.addEventListener("click", () => {
|
||||
inviteToPlayPong(invite, senderSocket);
|
||||
});
|
||||
}, 0)
|
||||
};
|
||||
|
||||
// async function windowStateHidden() {
|
||||
// const socketId = __socket || undefined;
|
||||
// // let oldName = localStorage.getItem("oldName") ?? undefined;
|
||||
// let oldName: string;
|
||||
// if (socketId === undefined) return;
|
||||
// let userName = await updateUser();
|
||||
// oldName = userName?.name ?? "";
|
||||
// if (oldName === "") return;
|
||||
// localStorage.setItem('oldName', oldName);
|
||||
// socketId.emit('client_left', {
|
||||
// user: userName?.name,
|
||||
// why: 'tab window hidden - socket not dead',
|
||||
// });
|
||||
// return;
|
||||
// };
|
||||
|
||||
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;}
|
||||
let user = await updateUser();
|
||||
if(user === null) return;
|
||||
console.log("%cUserName :'" + user?.name + "'", color.green);
|
||||
socketId.emit('client_entered', {
|
||||
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(socket: Socket, buddies: HTMLDivElement, listBuddies: string) {
|
||||
|
||||
// if (!buddies) 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 ?? "";
|
||||
// buddies.appendChild(buddiesElement);
|
||||
// buddies.scrollTop = buddies.scrollHeight;
|
||||
// console.log(`Added buddies: ${listBuddies}`);
|
||||
|
||||
// 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);
|
||||
// sendtextbox.value = "";
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
|
||||
function waitSocketConnected(socket: Socket): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (socket.connected) return resolve();
|
||||
socket.on("connect", () => resolve());
|
||||
});
|
||||
};
|
||||
|
||||
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('Pong 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
|
||||
socket.disconnect(); // actually close the socket
|
||||
localStorage.clear();
|
||||
if (__socket !== undefined)
|
||||
__socket.close();
|
||||
// window.location.href = "/login";
|
||||
};
|
||||
|
||||
|
||||
async function connected(socket: Socket): Promise<void> {
|
||||
|
||||
try {
|
||||
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
||||
const loggedIn = isLoggedIn();
|
||||
if (!loggedIn) throw('Not Logged in');
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
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: ClientProfil) {
|
||||
|
||||
|
||||
// const modalname = document.getElementById("modal-name") ?? null;
|
||||
// if (modalname)
|
||||
// modalname.innerHTML = `
|
||||
// <div class="profile-info">
|
||||
// <div-profil-name id="profilName"> Profil of ${profil.user} </div>
|
||||
// <div-login-name id="loginName"> Login Name: '${profil.loginName ?? 'Guest'}' </div>
|
||||
// </br>
|
||||
// <div-login-name id="loginName"> Login ID: '${profil.userID ?? ''}' </div>
|
||||
// </br>
|
||||
// <button id="popup-b-clear" class="btn-style popup-b-clear">Clear Text</button>
|
||||
// <button id="popup-b-invite" class="btn-style popup-b-invite">U Game ?</button>
|
||||
// <button id="popup-b-block" class="btn-style popup-b-block">Block User</button>
|
||||
// <div id="profile-about">About: '${profil.text}' </div>
|
||||
// </div>
|
||||
// `;
|
||||
// const profilList = document.getElementById("profile-modal") ?? null;
|
||||
// if (profilList)
|
||||
// profilList.classList.remove("hidden");
|
||||
// // The popup now exists → attach the event
|
||||
// }
|
||||
|
||||
let count = 0;
|
||||
function incrementCounter(): number {
|
||||
count += 1;
|
||||
return count;
|
||||
}
|
||||
|
||||
async function openMessagePopup(message: string) {
|
||||
|
||||
const modalmessage = document.getElementById("modal-message") ?? null;
|
||||
if(!message) return
|
||||
const obj:any = JSON.parse(message);
|
||||
if (modalmessage) {
|
||||
const messageElement = document.createElement("div");
|
||||
messageElement.innerHTML = `
|
||||
<div class="profile-info">
|
||||
<div id="profile-about">Next Game Message ${incrementCounter()}: ${obj.link}</div>
|
||||
</div>`;
|
||||
modalmessage.appendChild(messageElement);
|
||||
modalmessage.scrollTop = modalmessage.scrollHeight;
|
||||
|
||||
}
|
||||
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 {
|
||||
|
||||
|
||||
let socket = getSocket();
|
||||
|
||||
// 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 ?? "",
|
||||
text: " has Just ARRIVED in the chat",
|
||||
timestamp: Date.now(),
|
||||
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}) => {
|
||||
// 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) {
|
||||
connected(socket);
|
||||
}
|
||||
|
||||
if (chatWindow && data.message.destination === "") {
|
||||
const messageElement = document.createElement("div");
|
||||
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
|
||||
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") {
|
||||
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());
|
||||
});
|
||||
|
||||
socket.on('profilMessage', (profil: ClientProfil) => {
|
||||
openProfilePopup(profil);
|
||||
actionBtnPopUpClear(profil, socket);
|
||||
actionBtnPopUpInvite(profil, socket);
|
||||
actionBtnPopUpBlock(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);
|
||||
chatWindow.scrollTop = chatWindow.scrollHeight;
|
||||
});
|
||||
|
||||
|
||||
socket.on('blockUser', (blocked: ClientProfil) => {
|
||||
let icon = '⛔';
|
||||
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
|
||||
const messageElement = document.createElement("div");
|
||||
if (`${blocked.text}` === '\'I have un-blocked you\'' ) { icon = '💚'};
|
||||
messageElement.innerText =`${icon}${blocked.SenderName}: ${blocked.text}`;
|
||||
chatWindow.appendChild(messageElement);
|
||||
chatWindow.scrollTop = chatWindow.scrollHeight;
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
socket.on('logout', () => {
|
||||
quitChat(socket);
|
||||
});
|
||||
|
||||
socket.on('privMessageCopy', (message) => {
|
||||
addMessage(message);
|
||||
})
|
||||
|
||||
//receives broadcast of the next GAME
|
||||
socket.on('nextGame', (message: string) => {
|
||||
openMessagePopup(message);
|
||||
// addMessage(message);
|
||||
})
|
||||
|
||||
let toggle = false
|
||||
window.addEventListener("focus", async () => {
|
||||
//nst bwhoami = document.getElementById('b-whoami') as HTMLButtonElement;
|
||||
|
||||
setTimeout(() => {
|
||||
connected(socket);
|
||||
}, 0);
|
||||
if (window.location.pathname === '/app/chat') {
|
||||
console.log('%cWindow is focused on /chat:' + socket.id, color.green);
|
||||
if (socket.id) {
|
||||
await 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 sec
|
||||
|
||||
socket.on('listBud', async (myBuddies: string) => {
|
||||
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
||||
console.log('%cList buddies connected ',color.yellow, 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);
|
||||
});
|
||||
|
||||
|
||||
setTitle('Chat Page');
|
||||
// Listen for the 'connect' event
|
||||
return {
|
||||
|
||||
html: authHtml, postInsert: async (app) => {
|
||||
const sendButton = document.getElementById('b-send') as HTMLButtonElement;
|
||||
const chatWindow = document.getElementById('t-chatbox') as HTMLDivElement;
|
||||
const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement;
|
||||
const clearText = document.getElementById('b-clear') as HTMLButtonElement;
|
||||
const bwhoami = document.getElementById('b-whoami') as HTMLButtonElement;
|
||||
const bconnected = document.getElementById('b-help') as HTMLButtonElement;
|
||||
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;
|
||||
const bnextGame = document.getElementById('b-nextGame') as HTMLDivElement;
|
||||
|
||||
chatWindow.textContent = '';
|
||||
chatWindow.innerHTML = '';
|
||||
buddies.textContent = '';
|
||||
buddies.innerHTML = '';
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
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");
|
||||
const modalmessage = document.getElementById("modal-message") ?? null;
|
||||
if (modalmessage) {modalmessage.innerHTML = "";}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Send button
|
||||
sendButton?.addEventListener("click", () => {
|
||||
if (sendtextbox && sendtextbox.value.trim()) {
|
||||
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]);
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Clear Text button
|
||||
clearText?.addEventListener("click", () => {
|
||||
if (chatWindow) {
|
||||
chatWindow.innerHTML = '';
|
||||
}
|
||||
//clearChatWindow(socket); //DEV testing broadcastGames
|
||||
});
|
||||
|
||||
// Dev Game message button
|
||||
bnextGame?.addEventListener("click", () => {
|
||||
if (chatWindow) {
|
||||
socket.emit('nextGame');
|
||||
}
|
||||
});
|
||||
|
||||
bquit?.addEventListener('click', () => {
|
||||
quitChat(socket);
|
||||
});
|
||||
|
||||
// Enter key to send message
|
||||
sendtextbox!.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
sendButton?.click();
|
||||
}
|
||||
});
|
||||
|
||||
// Whoami button to display user name addMessage(msgCommand[0]);
|
||||
|
||||
bwhoami?.addEventListener('click', async () => {
|
||||
whoami(socket);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
addRoute('/pong', handleChat, { bypass_auth: true });
|
||||
23
frontend/src/pages/pong/types_front.ts
Normal file
23
frontend/src/pages/pong/types_front.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export type ClientMessage = {
|
||||
command: string
|
||||
destination: string;
|
||||
user: string;
|
||||
text: string;
|
||||
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,
|
||||
Sendertext: string,
|
||||
innerHtml?: string,
|
||||
};
|
||||
18
frontend/src/pages/pong/windowStateHidden.ts
Normal file
18
frontend/src/pages/pong/windowStateHidden.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { __socket } from './pong';
|
||||
import { updateUser } from "@app/auth";
|
||||
|
||||
export async function windowStateHidden() {
|
||||
const socketId = __socket || undefined;
|
||||
// let oldName = localStorage.getItem("oldName") ?? undefined;
|
||||
let oldName: string;
|
||||
if (socketId === undefined) return;
|
||||
let userName = await updateUser();
|
||||
oldName = userName?.name ?? "";
|
||||
if (oldName === "") return;
|
||||
localStorage.setItem('oldName', oldName);
|
||||
socketId.emit('client_left', {
|
||||
user: userName?.name,
|
||||
why: 'tab window hidden - socket not dead',
|
||||
});
|
||||
return;
|
||||
};
|
||||
14
nginx/conf/locations/pong.conf
Normal file
14
nginx/conf/locations/pong.conf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#forward the post request to the microservice
|
||||
location /api/pong/ {
|
||||
proxy_pass http://pong;
|
||||
}
|
||||
|
||||
location /api/pong/socket.io/ {
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_pass http://pong;
|
||||
}
|
||||
2
src/pong/.dockerignore
Normal file
2
src/pong/.dockerignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/dist
|
||||
/node_modules
|
||||
8
src/pong/README.md
Normal file
8
src/pong/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Nginx Configuration
|
||||
|
||||
You want to have a new microservice ?
|
||||
|
||||
Edit/add a file in `conf/locations/`
|
||||
take example on `conf/locations/icons.conf` on how to make a reverse proxy and on how to serve static files
|
||||
|
||||
# Good Luck Have Fun
|
||||
8
src/pong/entrypoint.sh
Normal file
8
src/pong/entrypoint.sh
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -x
|
||||
# do anything here
|
||||
|
||||
# run the CMD [ ... ] from the dockerfile
|
||||
exec "$@"
|
||||
0
src/pong/extra/.gitkeep
Normal file
0
src/pong/extra/.gitkeep
Normal file
21
src/pong/openapi.json
Normal file
21
src/pong/openapi.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"version": "9.6.1",
|
||||
"title": "@fastify/swagger"
|
||||
},
|
||||
"components": {
|
||||
"schemas": {}
|
||||
},
|
||||
"paths": {},
|
||||
"servers": [
|
||||
{
|
||||
"url": "https://local.maix.me:8888",
|
||||
"description": "direct from docker"
|
||||
},
|
||||
{
|
||||
"url": "https://local.maix.me:8000",
|
||||
"description": "using fnginx"
|
||||
}
|
||||
]
|
||||
}
|
||||
38
src/pong/package.json
Normal file
38
src/pong/package.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"type": "module",
|
||||
"private": false,
|
||||
"name": "pong",
|
||||
"version": "1.0.0",
|
||||
"description": "This project was bootstrapped with Fastify-CLI.",
|
||||
"main": "app.ts",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run build && node dist/run.js",
|
||||
"build": "vite build",
|
||||
"build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false",
|
||||
"build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && node dist/openapi.cjs >openapi.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fastify/autoload": "^6.3.1",
|
||||
"@fastify/formbody": "^8.0.2",
|
||||
"@fastify/multipart": "^9.3.0",
|
||||
"@fastify/sensible": "^6.0.4",
|
||||
"@fastify/static": "^8.3.0",
|
||||
"@fastify/websocket": "^11.2.0",
|
||||
"fastify": "^5.6.2",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"typebox": "^1.0.62"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.19.2",
|
||||
"rollup-plugin-node-externals": "^8.1.2",
|
||||
"vite": "^7.2.7",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
441
src/pong/src/app.ts
Normal file
441
src/pong/src/app.ts
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
||||
import fastifyFormBody from '@fastify/formbody';
|
||||
import fastifyMultipart from '@fastify/multipart';
|
||||
import * as db from '@shared/database';
|
||||
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';
|
||||
import type { BlockedData } from '@shared/database/mixin/blocked';
|
||||
import { broadcast } from './broadcast';
|
||||
import type { ClientProfil, ClientMessage } from './chat_types';
|
||||
import { sendPrivMessage } from './sendPrivMessage';
|
||||
import { sendBlocked } from './sendBlocked';
|
||||
import { sendInvite } from './sendInvite';
|
||||
import { getUserByName } from './getUserByName';
|
||||
import { makeProfil } from './makeProfil';
|
||||
import { isBlocked } from './isBlocked';
|
||||
import { sendProfil } from './sendProfil';
|
||||
import { setGameLink } from './setGameLink';
|
||||
import { nextGame_SocketListener } from './nextGame_SocketListener';
|
||||
import { list_SocketListener } from './list_SocketListener';
|
||||
|
||||
// colors for console.log
|
||||
export const color = {
|
||||
red: '\x1b[31m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
|
||||
|
||||
declare const __SERVICE_NAME: string;
|
||||
|
||||
// Global map of clients
|
||||
// key = socket, value = clientname
|
||||
interface ClientInfo {
|
||||
user: string;
|
||||
lastSeen: number;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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 = '<a href=\'https://google.com\' style=\'color: blue; text-decoration: underline; cursor: pointer;\'>Click me</a>';
|
||||
// }
|
||||
// return link;
|
||||
// };
|
||||
|
||||
export const clientChat = new Map<string, ClientInfo>();
|
||||
|
||||
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
|
||||
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||
const routes = import.meta.glob('./routes/**/*.ts', { eager: true });
|
||||
|
||||
const app: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
void opts;
|
||||
|
||||
await fastify.register(utils.useMonitoring);
|
||||
await fastify.register(utils.useMakeResponse);
|
||||
await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME });
|
||||
await fastify.register(db.useDatabase as FastifyPluginAsync, {});
|
||||
await fastify.register(auth.jwtPlugin as FastifyPluginAsync, {});
|
||||
await fastify.register(auth.authPlugin as FastifyPluginAsync, {});
|
||||
|
||||
// Place here your custom code!
|
||||
for (const plugin of Object.values(plugins)) {
|
||||
void fastify.register(plugin as FastifyPluginAsync, {});
|
||||
}
|
||||
for (const route of Object.values(routes)) {
|
||||
void fastify.register(route as FastifyPluginAsync, {});
|
||||
}
|
||||
|
||||
void fastify.register(fastifyFormBody, {});
|
||||
void fastify.register(fastifyMultipart, {});
|
||||
|
||||
fastify.ready((err) => {
|
||||
if (err) throw err;
|
||||
onReady(fastify);
|
||||
});
|
||||
};
|
||||
export default app;
|
||||
export { app };
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
io: Server<{
|
||||
hello: (message: string) => string;
|
||||
MsgObjectServer: (data: { message: ClientMessage }) => void;
|
||||
privMessage: (data: string) => void;
|
||||
profilMessage: (data: ClientProfil) => void;
|
||||
inviteGame: (data: ClientProfil) => void;
|
||||
blockUser: (data: ClientProfil) => void;
|
||||
privMessageCopy: (msg: string) => void;
|
||||
nextGame: (nextGame: string) => void;
|
||||
message: (msg: string) => void;
|
||||
listBud: (msg: string) => void;
|
||||
client_entered: (userName: string, user: string) => void;
|
||||
client_left: (userName: string, why: string) => void;
|
||||
list: (oldUser: string, user: string) => void;
|
||||
updateClientName: (oldUser: string, user: string) => void;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
|
||||
async function onReady(fastify: FastifyInstance) {
|
||||
|
||||
|
||||
// shows address for connection au server transcendance
|
||||
const session = process.env.SESSION_MANAGER ?? '';
|
||||
if (session) {
|
||||
const part = session.split('/')[1];
|
||||
const machineName = part.split('.')[0];
|
||||
console.log(color.yellow, 'Connect at : https://' + machineName + ':8888/app/login');
|
||||
}
|
||||
|
||||
|
||||
fastify.io.on('connection', (socket: Socket) => {
|
||||
|
||||
socket.on('message', (message: string) => {
|
||||
//console.info(color.blue, 'DEBUG LOG: Socket connected!', color.reset, socket.id);
|
||||
// console.log( color.blue, 'DEBUG LOG: Received message from client', color.reset, message);
|
||||
const obj: ClientMessage = JSON.parse(message) as ClientMessage;
|
||||
clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() });
|
||||
// console.log(color.green, 'DEBUG LOG: Message from client', 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(fastify, obj, obj.SenderWindowID);
|
||||
// console.log(color.red, 'DEBUG LOG: connected in the Chat :', connectedUser(fastify.io), color.reset);
|
||||
});
|
||||
|
||||
nextGame_SocketListener(fastify, socket);
|
||||
|
||||
list_SocketListener(fastify, socket);
|
||||
|
||||
// socket.on('list', (object) => {
|
||||
|
||||
// const userFromFrontend = object || null;
|
||||
// const client = clientChat.get(socket.id) || null;
|
||||
|
||||
// //console.log(color.red, 'DEBUG LOG: list activated', userFromFrontend, color.reset, socket.id);
|
||||
|
||||
// if (userFromFrontend.oldUser !== userFromFrontend.user) {
|
||||
// //console.log(color.red, 'DEBUG LOG: list activated', userFromFrontend.oldUser, color.reset);
|
||||
// // if (client?.user === null) {
|
||||
// // console.log('ERROR: clientName is NULL');
|
||||
// // return;
|
||||
// // };
|
||||
// if (client) {
|
||||
// client.user = userFromFrontend.user;
|
||||
// }
|
||||
// }
|
||||
// connectedUser(fastify.io, socket.id);
|
||||
// });
|
||||
|
||||
socket.on('updateClientName', (object) => {
|
||||
const userFromFrontend = object || null;
|
||||
const client = clientChat.get(socket.id) || null;
|
||||
// console.log(color.red, 'DEBUG LOG: whoAMi activated', userFromFrontend, color.reset, socket.id);
|
||||
if (userFromFrontend.oldUser !== userFromFrontend.user) {
|
||||
// console.log(color.red, 'DEBUG LOG: whoAMi activated', userFromFrontend.oldUser, color.reset);
|
||||
// if (client === null) {
|
||||
// console.log('ERROR: clientName is NULL');
|
||||
// return;
|
||||
// };
|
||||
if (client) {
|
||||
client.user = userFromFrontend.user;
|
||||
console.log(color.green, `'DEBUG LOG: client.user is, '${client.user}'`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('logout', () => {
|
||||
const clientInfo = clientChat.get(socket.id);
|
||||
const clientName = clientInfo?.user;
|
||||
|
||||
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,
|
||||
token: '',
|
||||
text: 'LEFT the chat',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
};
|
||||
broadcast(fastify, obj, socket.id);
|
||||
// Optional: remove from map
|
||||
clientChat.delete(socket.id);
|
||||
// Ensure socket is fully disconnected
|
||||
if (socket.connected) socket.disconnect(true);
|
||||
});
|
||||
|
||||
socket.on('disconnecting', (reason) => {
|
||||
const clientName = clientChat.get(socket.id)?.user || null;
|
||||
console.log(
|
||||
color.green,
|
||||
`Client disconnecting: ${clientName} (${socket.id}) reason:`,
|
||||
reason,
|
||||
);
|
||||
if (reason === 'transport error') return;
|
||||
|
||||
if (clientName !== null) {
|
||||
const obj = {
|
||||
command: '',
|
||||
destination: 'system-info',
|
||||
type: 'chat',
|
||||
user: clientName,
|
||||
token: '',
|
||||
text: 'LEFT the chat',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
};
|
||||
|
||||
broadcast(fastify, obj, obj.SenderWindowID);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('client_left', (data) => {
|
||||
const clientName = clientChat.get(socket.id)?.user || null;
|
||||
const leftChat = data || null;
|
||||
console.log(
|
||||
color.green,
|
||||
`Left the Chat User: ${clientName} id Socket: ${socket.id} reason:`,
|
||||
leftChat.why,
|
||||
);
|
||||
|
||||
if (clientName !== null) {
|
||||
const obj = {
|
||||
command: '',
|
||||
destination: 'system-info',
|
||||
type: 'chat',
|
||||
user: clientName,
|
||||
token: '',
|
||||
text: 'LEFT the chat but the window is still open',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
};
|
||||
//console.log(color.blue, 'DEBUG LOG: BROADCASTS OUT :', obj.SenderWindowID);
|
||||
|
||||
broadcast(fastify, 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(fastify, obj, obj.SenderWindowID);
|
||||
// clientChat.delete(obj.user);
|
||||
}
|
||||
});
|
||||
|
||||
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() ?? [];
|
||||
// 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 profile: ClientProfil = await makeProfil(fastify, profilMessage.user, socket);
|
||||
if (clientName !== null) {
|
||||
const testuser: User | null = getUserByName(users, profilMessage.user);
|
||||
console.log(color.yellow, 'user:', testuser?.name ?? 'Guest');
|
||||
console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', profile.SenderWindowID);
|
||||
sendProfil(fastify, profile, profile.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(fastify, inviteHtml, profilInvite);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
socket.on('blockUser', async (data: string) => {
|
||||
const clientName: string = clientChat.get(socket.id)?.user || '';
|
||||
const profilBlock: ClientProfil = JSON.parse(data) || '';
|
||||
const users: User[] = fastify.db.getAllUsers() ?? [];
|
||||
const UserToBlock: User | null = getUserByName(users, `${profilBlock.user}`);
|
||||
const UserAskingToBlock: User | null = getUserByName(users, `${profilBlock.SenderName}`);
|
||||
|
||||
console.log(color.yellow, `user to block: ${profilBlock.user}`);
|
||||
console.log(color.yellow, UserToBlock);
|
||||
console.log(color.yellow, `user Asking to block: ${profilBlock.SenderName}`);
|
||||
console.log(color.yellow, UserAskingToBlock);
|
||||
|
||||
const usersBlocked: BlockedData[] = fastify.db.getAllBlockedUsers() ?? [];
|
||||
if (!UserAskingToBlock || !UserToBlock || !usersBlocked) return;
|
||||
const userAreBlocked: boolean = isBlocked(UserAskingToBlock, UserToBlock, usersBlocked);
|
||||
|
||||
if (userAreBlocked) {
|
||||
console.log(color.green, 'Both users are blocked as requested');
|
||||
// return true; // or any other action you need to take
|
||||
|
||||
|
||||
console.log(color.red, "ALL BLOCKED USERS:", usersBlocked);
|
||||
fastify.db.removeBlockedUserFor(UserAskingToBlock!.id, UserToBlock!.id);
|
||||
const usersBlocked2 = fastify.db.getAllBlockedUsers();
|
||||
console.log(color.green, 'remove ALL BLOCKED USERS:', usersBlocked2);
|
||||
if (clientName !== null) {
|
||||
const blockedMessage = `'I have un-blocked you'`;
|
||||
if (clientName !== null) {
|
||||
const obj = {
|
||||
command: 'message',
|
||||
destination: 'privateMsg',
|
||||
type: 'chat',
|
||||
user: clientName,
|
||||
token: '',
|
||||
text: '',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
Sendertext: 'You have un-blocked',
|
||||
};
|
||||
// console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID);
|
||||
socket.emit('privMessageCopy', `${obj.Sendertext}: ${UserToBlock.name}💚`);
|
||||
// clientChat.delete(obj.user);
|
||||
}
|
||||
// profilBlock.Sendertext = `'You have un-blocked '`;
|
||||
sendBlocked(fastify, blockedMessage, profilBlock);
|
||||
}
|
||||
} else {
|
||||
console.log(color.red, 'The users are not blocked in this way');
|
||||
console.log(color.red, "ALL BLOCKED USERS:", usersBlocked);
|
||||
fastify.db.addBlockedUserFor(UserAskingToBlock!.id, UserToBlock!.id);
|
||||
const usersBlocked2 = fastify.db.getAllBlockedUsers();
|
||||
console.log(color.green, 'ALL BLOCKED USERS:', usersBlocked2);
|
||||
if (clientName !== null) {
|
||||
const blockedMessage = `'I have blocked you'`;
|
||||
profilBlock.Sendertext = `'You have blocked '`;
|
||||
if (clientName !== null) {
|
||||
const obj = {
|
||||
command: 'message',
|
||||
destination: 'privateMsg',
|
||||
type: 'chat',
|
||||
user: clientName,
|
||||
token: '',
|
||||
text: '',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
Sendertext: 'You have blocked',
|
||||
};
|
||||
// console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID);
|
||||
socket.emit('privMessageCopy', `${obj.Sendertext}: ${UserToBlock.name}⛔`);
|
||||
// clientChat.delete(obj.user);
|
||||
}
|
||||
sendBlocked(fastify, blockedMessage, profilBlock);
|
||||
}
|
||||
}
|
||||
});
|
||||
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';
|
||||
|
||||
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 = {
|
||||
command: '',
|
||||
destination: 'system-info',
|
||||
type: 'chat',
|
||||
user: clientName,
|
||||
frontendUserName: userNameFromFrontend,
|
||||
frontendUser: userFromFrontend,
|
||||
token: '',
|
||||
text: text,
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
};
|
||||
broadcast(fastify, obj, obj.SenderWindowID);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
22
src/pong/src/broadcast.ts
Normal file
22
src/pong/src/broadcast.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import type { ClientMessage } from './chat_types';
|
||||
import { clientChat, color } from './app';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
export function broadcast(fastify: FastifyInstance, data: ClientMessage, sender?: string) {
|
||||
fastify.io.fetchSockets().then((sockets) => {
|
||||
for (const socket of sockets) {
|
||||
// Skip sender's own socket
|
||||
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, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
25
src/pong/src/broadcastNextGame.ts
Normal file
25
src/pong/src/broadcastNextGame.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import { clientChat, color } from './app';
|
||||
|
||||
/**
|
||||
* function broadcast a clickable link
|
||||
* @param fastify
|
||||
* @param gameLink
|
||||
*/
|
||||
export async function broadcastNextGame(fastify: FastifyInstance, gameLink?: Promise<string>) {
|
||||
const link = gameLink ? await gameLink : undefined;
|
||||
console.log(color.green, 'link===========> ', link);
|
||||
const sockets = await fastify.io.fetchSockets();
|
||||
// fastify.io.fetchSockets().then((sockets) => {
|
||||
for (const socket of sockets) {
|
||||
const clientInfo = clientChat.get(socket.id);
|
||||
if (!clientInfo?.user) {
|
||||
console.log(color.yellow, `DEBUG LOG: Skipping socket ${socket.id} (no user found)`);
|
||||
continue;
|
||||
}
|
||||
if (link) {
|
||||
socket.emit('nextGame', link);
|
||||
}
|
||||
// console.log(color.green, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`);
|
||||
}
|
||||
};
|
||||
23
src/pong/src/chat_types.ts
Normal file
23
src/pong/src/chat_types.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export type ClientMessage = {
|
||||
command: string
|
||||
destination: string;
|
||||
user: string;
|
||||
text: string;
|
||||
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,
|
||||
Sendertext: string,
|
||||
innerHtml?: string,
|
||||
|
||||
};
|
||||
48
src/pong/src/connectedUser.ts
Normal file
48
src/pong/src/connectedUser.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { clientChat } from './app';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
|
||||
/**
|
||||
* function check users connected to the chat with a socket and makes a seen list
|
||||
* calls listBud socket listener to update Ping Buddies List and calls listBuddies()
|
||||
* @param io
|
||||
* @param target
|
||||
* @returns the number connected
|
||||
*/
|
||||
|
||||
export function connectedUser(io?: Server, target?: string): number {
|
||||
let count = 0;
|
||||
const seen = new Set<string>();
|
||||
// <- only log/count unique usernames
|
||||
for (const [socketId, username] of clientChat) {
|
||||
// Basic checks
|
||||
if (typeof socketId !== 'string' || socketId.length === 0) {
|
||||
clientChat.delete(socketId);
|
||||
continue;
|
||||
}
|
||||
if (typeof username.user !== 'string' || username.user.length === 0) {
|
||||
clientChat.delete(socketId);
|
||||
continue;
|
||||
}
|
||||
// If we have the io instance, attempt to validate the socket is still connected
|
||||
if (io && typeof io.sockets?.sockets?.get === 'function') {
|
||||
const socket = io.sockets.sockets.get(socketId) as Socket | undefined;
|
||||
// If socket not found or disconnected, remove from map and skip
|
||||
if (!socket || socket.disconnected) {
|
||||
clientChat.delete(socketId);
|
||||
continue;
|
||||
}
|
||||
// Skip duplicates (DO NOT delete them — just don't count)
|
||||
if (seen.has(username.user)) {
|
||||
continue;
|
||||
}
|
||||
// socket exists and is connected
|
||||
seen.add(username.user);
|
||||
count++;
|
||||
const targetSocketId = target;
|
||||
io.to(targetSocketId!).emit('listBud', username.user);
|
||||
continue;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
6
src/pong/src/createNextGame.ts
Normal file
6
src/pong/src/createNextGame.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
/* TODO find the description info for profil / or profil game link and return
|
||||
**/
|
||||
export function createNextGame() {
|
||||
return '<a href=\'https://localhost:8888/app/\' style=\'color: blue; text-decoration: underline; cursor: pointer;\'>The next Game is Starting click here to watch</a>';
|
||||
};
|
||||
12
src/pong/src/getUserByName.ts
Normal file
12
src/pong/src/getUserByName.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import type { User } from '@shared/database/mixin/user';
|
||||
|
||||
/**
|
||||
* function get the object user in an array of users[] by name
|
||||
* @param users
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export function getUserByName(users: User[], name: string) {
|
||||
return users.find(user => user.name === name) || null;
|
||||
}
|
||||
17
src/pong/src/isBlocked.ts
Normal file
17
src/pong/src/isBlocked.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { User } from '@shared/database/mixin/user';
|
||||
import type { BlockedData } from '@shared/database/mixin/blocked';
|
||||
|
||||
/**
|
||||
* function compares the four ids of two users and returns true if
|
||||
* UserA1 = UserB1 and UserB1 = UserB2
|
||||
* @param UserAskingToBlock
|
||||
* @param UserToBlock
|
||||
* @param usersBlocked
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export function isBlocked(UserAskingToBlock: User, UserToBlock: User, usersBlocked: BlockedData[]): boolean {
|
||||
return usersBlocked.some(blocked =>
|
||||
blocked.blocked === UserToBlock?.id &&
|
||||
blocked.user === UserAskingToBlock?.id);
|
||||
}
|
||||
29
src/pong/src/list_SocketListener.ts
Normal file
29
src/pong/src/list_SocketListener.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { FastifyInstance } from 'fastify';
|
||||
import { Socket } from 'socket.io';
|
||||
import { clientChat } from './app';
|
||||
import { connectedUser } from './connectedUser';
|
||||
// import { color } from './app';
|
||||
|
||||
|
||||
|
||||
export function list_SocketListener(fastify: FastifyInstance, socket: Socket) {
|
||||
|
||||
socket.on('list', (object) => {
|
||||
|
||||
const userFromFrontend = object || null;
|
||||
const client = clientChat.get(socket.id) || null
|
||||
//console.log(color.red, 'DEBUG LOG: list activated', userFromFrontend, color.reset, socket.id)
|
||||
if (userFromFrontend.oldUser !== userFromFrontend.user) {
|
||||
//console.log(color.red, 'DEBUG LOG: list activated', userFromFrontend.oldUser, color.reset);
|
||||
if (client?.user === null) {
|
||||
console.log('ERROR: clientName is NULL');
|
||||
return;
|
||||
};
|
||||
if (client) {
|
||||
client.user = userFromFrontend.user;
|
||||
}
|
||||
}
|
||||
connectedUser(fastify.io, socket.id);
|
||||
});
|
||||
|
||||
}
|
||||
41
src/pong/src/makeProfil.ts
Normal file
41
src/pong/src/makeProfil.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import type { ClientProfil } from './chat_types';
|
||||
import type { User } from '@shared/database/mixin/user';
|
||||
import { getUserByName } from './getUserByName';
|
||||
import { Socket } from 'socket.io';
|
||||
|
||||
/**
|
||||
* function makeProfil - translates the Users[] to a one user looking by name
|
||||
* and puts it into ClientProfil format
|
||||
* @param fastify
|
||||
* @param user
|
||||
* @param socket
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export async function makeProfil(fastify: FastifyInstance, user: string, socket: Socket): Promise <ClientProfil> {
|
||||
|
||||
let clientProfil!: ClientProfil;
|
||||
const users: User[] = fastify.db.getAllUsers() ?? [];
|
||||
const allUsers: User | null = getUserByName(users, user);
|
||||
// console.log(color.yellow, `DEBUG LOG: 'userFound is:'${allUsers?.name}`);
|
||||
if (user === allUsers?.name) {
|
||||
// console.log(color.yellow, `DEBUG LOG: 'login Name: '${allUsers.login}' user: '${user}'`);
|
||||
clientProfil =
|
||||
{
|
||||
command: 'makeProfil',
|
||||
destination: 'profilMsg',
|
||||
type: 'chat' as const,
|
||||
user: `${allUsers.name}`,
|
||||
loginName: `${allUsers?.login ?? 'Guest'}`,
|
||||
userID: `${allUsers?.id ?? ''}`,
|
||||
text: '',
|
||||
timestamp: Date.now(),
|
||||
SenderWindowID: socket.id,
|
||||
SenderName: '',
|
||||
Sendertext: '',
|
||||
innerHtml: '',
|
||||
};
|
||||
}
|
||||
return clientProfil;
|
||||
};
|
||||
20
src/pong/src/nextGame_SocketListener.ts
Normal file
20
src/pong/src/nextGame_SocketListener.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { FastifyInstance } from 'fastify';
|
||||
import { broadcastNextGame } from './broadcastNextGame';
|
||||
import { Socket } from 'socket.io';
|
||||
import { createNextGame } from './createNextGame';
|
||||
import { sendGameLinkToChatService } from './sendGameLinkToChatService';
|
||||
|
||||
/**
|
||||
* function listens to the socket for a nextGame emit
|
||||
* once triggered it broadcasts the pop up
|
||||
* TODO plug this into backend of the game Chat
|
||||
* @param fastify
|
||||
* @param socket
|
||||
*/
|
||||
export function nextGame_SocketListener(fastify: FastifyInstance, socket: Socket) {
|
||||
socket.on('nextGame', () => {
|
||||
const link = createNextGame();
|
||||
const game: Promise<string> = sendGameLinkToChatService(link);
|
||||
broadcastNextGame(fastify, game);
|
||||
});
|
||||
}
|
||||
21
src/pong/src/openapi.ts
Normal file
21
src/pong/src/openapi.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import f, { FastifyPluginAsync } from 'fastify';
|
||||
import * as swagger from '@shared/swagger';
|
||||
import * as auth from '@shared/auth';
|
||||
|
||||
declare const __SERVICE_NAME: string;
|
||||
|
||||
// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||
const routes = import.meta.glob('./routes/**/*.ts', { eager: true });
|
||||
|
||||
async function start() {
|
||||
const fastify = f({ logger: false });
|
||||
await fastify.register(auth.authPlugin, { onlySchema: true });
|
||||
await fastify.register(swagger.useSwagger, { service: __SERVICE_NAME });
|
||||
|
||||
for (const route of Object.values(routes)) {
|
||||
await fastify.register(route as FastifyPluginAsync, {});
|
||||
}
|
||||
await fastify.ready();
|
||||
console.log(JSON.stringify(fastify.swagger(), undefined, 4));
|
||||
}
|
||||
start();
|
||||
16
src/pong/src/plugins/README.md
Normal file
16
src/pong/src/plugins/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Plugins Folder
|
||||
|
||||
Plugins define behavior that is common to all the routes in your
|
||||
application. Authentication, caching, templates, and all the other cross
|
||||
cutting concerns should be handled by plugins placed in this folder.
|
||||
|
||||
Files in this folder are typically defined through the
|
||||
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
|
||||
making them non-encapsulated. They can define decorators and set hooks
|
||||
that will then be used in the rest of your application.
|
||||
|
||||
Check out:
|
||||
|
||||
* [The hitchhiker's guide to plugins](https://fastify.dev/docs/latest/Guides/Plugins-Guide/)
|
||||
* [Fastify decorators](https://fastify.dev/docs/latest/Reference/Decorators/).
|
||||
* [Fastify lifecycle](https://fastify.dev/docs/latest/Reference/Lifecycle/).
|
||||
10
src/pong/src/plugins/sensible.ts
Normal file
10
src/pong/src/plugins/sensible.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import fp from 'fastify-plugin';
|
||||
import sensible, { FastifySensibleOptions } from '@fastify/sensible';
|
||||
/**
|
||||
* This plugins adds some utilities to handle http errors
|
||||
*
|
||||
* @see https://github.com/fastify/fastify-sensible
|
||||
*/
|
||||
export default fp<FastifySensibleOptions>(async (fastify) => {
|
||||
fastify.register(sensible);
|
||||
});
|
||||
31
src/pong/src/plugins/socket.ts
Normal file
31
src/pong/src/plugins/socket.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import type {
|
||||
FastifyInstance,
|
||||
FastifyPluginAsync,
|
||||
HookHandlerDoneFunction,
|
||||
} from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
import { Server } from 'socket.io';
|
||||
|
||||
const F: (
|
||||
f: FastifyInstance,
|
||||
) => Omit<FastifyInstance, 'io'> & { io: Server } = (f) =>
|
||||
f as Omit<FastifyInstance, 'io'> & { io: Server };
|
||||
|
||||
const fastifySocketIO: FastifyPluginAsync = fp(async (fastify) => {
|
||||
function defaultPreClose(done: HookHandlerDoneFunction) {
|
||||
F(fastify).io.local.disconnectSockets(true);
|
||||
done();
|
||||
}
|
||||
fastify.decorate(
|
||||
'io',
|
||||
new Server(fastify.server, { path: '/api/pong/socket.io' }),
|
||||
);
|
||||
fastify.addHook('preClose', defaultPreClose);
|
||||
fastify.addHook('onClose', (instance: FastifyInstance, done) => {
|
||||
F(instance).io.close();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
export default fastifySocketIO;
|
||||
|
||||
57
src/pong/src/routes/broadcast.ts
Normal file
57
src/pong/src/routes/broadcast.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { FastifyPluginAsync } from 'fastify';
|
||||
import { Static, Type } from 'typebox';
|
||||
import { broadcast } from '../broadcast';
|
||||
|
||||
|
||||
|
||||
export const PongReq = Type.Object({
|
||||
message: Type.String(),
|
||||
});
|
||||
|
||||
export type PongReq = Static<typeof PongReq>;
|
||||
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
fastify.post<{ Body: PongReq }>(
|
||||
'/api/pong/broadcast',
|
||||
{
|
||||
schema: {
|
||||
body: PongReq,
|
||||
hide: true,
|
||||
},
|
||||
config: { requireAuth: false },
|
||||
},
|
||||
async function(req, res) {
|
||||
broadcast(this, { command: '', destination: '', user: 'CMwaLeSever!!', text: req.body.message, SenderWindowID: 'server' });
|
||||
void res;
|
||||
},
|
||||
);
|
||||
};
|
||||
export default route;
|
||||
|
||||
|
||||
// const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||
// fastify.post('/api/chat/broadcast', {
|
||||
// schema: {
|
||||
// body: {
|
||||
// type: 'object',
|
||||
// required: ['nextGame'],
|
||||
// properties: {
|
||||
// nextGame: { type: 'string' }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }, async (req, reply) => {
|
||||
|
||||
// // Body only contains nextGame now
|
||||
// const gameLink: Promise<string> = Promise.resolve(req.body as string );
|
||||
|
||||
// // Broadcast nextGame
|
||||
// if (gameLink)
|
||||
// broadcastNextGame(fastify, gameLink);
|
||||
|
||||
// return reply.send({ status: 'ok' });
|
||||
// });
|
||||
// };
|
||||
// export default route;
|
||||
|
||||
36
src/pong/src/run.ts
Normal file
36
src/pong/src/run.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// this sould only be used by the docker file !
|
||||
|
||||
import fastify, { FastifyInstance } from 'fastify';
|
||||
import app from './app';
|
||||
|
||||
const start = async () => {
|
||||
const envToLogger = {
|
||||
development: {
|
||||
transport: {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
translateTime: 'HH:MM:ss Z',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
},
|
||||
},
|
||||
production: true,
|
||||
test: false,
|
||||
};
|
||||
|
||||
const f: FastifyInstance = fastify({ logger: envToLogger.development });
|
||||
try {
|
||||
process.on('SIGTERM', () => {
|
||||
f.log.info('Requested to shutdown');
|
||||
process.exit(134);
|
||||
});
|
||||
console.log('-------->Serving static files from:');
|
||||
await f.register(app, {});
|
||||
await f.listen({ port: 80, host: '0.0.0.0' });
|
||||
}
|
||||
catch (err) {
|
||||
f.log.error(err);
|
||||
process.exit(1);
|
||||
};
|
||||
};
|
||||
start();
|
||||
29
src/pong/src/sendBlocked.ts
Normal file
29
src/pong/src/sendBlocked.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { ClientProfil } from './chat_types';
|
||||
import { clientChat, color } from './app';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
/**
|
||||
* function looks for the online (socket) for user to block, when found send ordre to block or unblock user
|
||||
* @param fastify
|
||||
* @param blockedMessage
|
||||
* @param profil
|
||||
*/
|
||||
|
||||
export function sendBlocked(fastify: FastifyInstance, blockedMessage: string, profil: ClientProfil) {
|
||||
fastify.io.fetchSockets().then((sockets) => {
|
||||
let targetSocket;
|
||||
for (const socket of sockets) {
|
||||
const clientInfo: string = clientChat.get(socket.id)?.user || '';
|
||||
if (clientInfo === profil.user) {
|
||||
console.log(color.yellow, 'DEBUG LOG: User found online to block:', profil.user);
|
||||
targetSocket = socket || '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
profil.text = blockedMessage ?? '';
|
||||
// console.log(color.red, 'DEBUG LOG:',profil.Sendertext);
|
||||
if (targetSocket) {
|
||||
targetSocket.emit('blockUser', profil);
|
||||
}
|
||||
});
|
||||
}
|
||||
7
src/pong/src/sendGameLinkToChatService.ts
Normal file
7
src/pong/src/sendGameLinkToChatService.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
/* EXPERIMENTAL: how to send a starting game link to chat
|
||||
**/
|
||||
export async function sendGameLinkToChatService(link: string) :Promise<string> {
|
||||
const payload = { link };
|
||||
return JSON.stringify(payload);
|
||||
}
|
||||
30
src/pong/src/sendInvite.ts
Normal file
30
src/pong/src/sendInvite.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { ClientProfil } from './chat_types';
|
||||
import { clientChat, color } from './app';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
/**
|
||||
* function looks for the user online in the chat
|
||||
* and sends emit to invite - format HTML to make clickable
|
||||
* message appears in chat window text area
|
||||
* @param fastify
|
||||
* @param innerHtml
|
||||
* @param profil
|
||||
*/
|
||||
|
||||
export function sendInvite(fastify: FastifyInstance, innerHtml: string, profil: ClientProfil) {
|
||||
fastify.io.fetchSockets().then((sockets) => {
|
||||
let targetSocket;
|
||||
for (const socket of sockets) {
|
||||
const clientInfo: string = clientChat.get(socket.id)?.user || '';
|
||||
if (clientInfo === profil.user) {
|
||||
console.log(color.yellow, 'DEBUG LOG: user online found', profil.user);
|
||||
targetSocket = socket || '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
profil.innerHtml = innerHtml ?? '';
|
||||
if (targetSocket) {
|
||||
targetSocket.emit('inviteGame', profil);
|
||||
}
|
||||
});
|
||||
}
|
||||
39
src/pong/src/sendPrivMessage.ts
Normal file
39
src/pong/src/sendPrivMessage.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import type { ClientMessage } from './chat_types';
|
||||
import { clientChat, color } from './app';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
/**
|
||||
* function looks up the socket of a user online in the chat and sends a message
|
||||
* it also sends a copy of the message to the sender
|
||||
* @param fastify
|
||||
* @param data
|
||||
* @param sender
|
||||
*/
|
||||
|
||||
export function sendPrivMessage(fastify: FastifyInstance, data: ClientMessage, sender?: string) {
|
||||
fastify.io.fetchSockets().then((sockets) => {
|
||||
const senderSocket = sockets.find(socket => socket.id === sender);
|
||||
for (const socket of sockets) {
|
||||
if (socket.id === sender) continue;
|
||||
const clientInfo = clientChat.get(socket.id);
|
||||
if (!clientInfo?.user) {
|
||||
console.log(color.yellow, `DEBUG LOG: Skipping socket ${socket.id} (no user found)`);
|
||||
continue;
|
||||
}
|
||||
const user: string = clientChat.get(socket.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 !== '') {
|
||||
socket.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}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
19
src/pong/src/sendProfil.ts
Normal file
19
src/pong/src/sendProfil.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import type { ClientProfil } from './chat_types';
|
||||
|
||||
/**
|
||||
* function takes a user profil and sends it to the asker by window id
|
||||
* @param fastify
|
||||
* @param profil
|
||||
* @param SenderWindowID
|
||||
*/
|
||||
|
||||
export function sendProfil(fastify: FastifyInstance, profil: ClientProfil, SenderWindowID?: string) {
|
||||
fastify.io.fetchSockets().then((sockets) => {
|
||||
const senderSocket = sockets.find(socket => socket.id === SenderWindowID);
|
||||
if (senderSocket) {
|
||||
// console.log(color.yellow, 'DEBUG LOG: profil.info:', profil.user);
|
||||
senderSocket.emit('profilMessage', profil);
|
||||
}
|
||||
});
|
||||
}
|
||||
7
src/pong/src/setGameLink.ts
Normal file
7
src/pong/src/setGameLink.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
export function setGameLink(link: string): string {
|
||||
if (!link) {
|
||||
link = '<a href=\'https://google.com\' style=\'color: blue; text-decoration: underline; cursor: pointer;\'>Click me</a>';
|
||||
}
|
||||
return link;
|
||||
};
|
||||
15
src/pong/tsconfig.json
Normal file
15
src/pong/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// {
|
||||
// "extends": "../tsconfig.base.json",
|
||||
// "compilerOptions": {
|
||||
// "skipLibCheck": true, // skips type checking for all .d.ts files
|
||||
// "moduleResolution": "node",
|
||||
// "esModuleInterop": true,
|
||||
// "types": ["node"] },
|
||||
// "include": ["src/**/*.ts"]
|
||||
// }
|
||||
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
53
src/pong/vite.config.js
Normal file
53
src/pong/vite.config.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
import nodeExternals from 'rollup-plugin-node-externals';
|
||||
import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
|
||||
function collectDeps(...pkgJsonPaths) {
|
||||
const allDeps = new Set();
|
||||
for (const pkgPath of pkgJsonPaths) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
||||
for (const dep of Object.keys(pkg.dependencies || {})) {
|
||||
allDeps.add(dep);
|
||||
}
|
||||
for (const peer of Object.keys(pkg.peerDependencies || {})) {
|
||||
allDeps.add(peer);
|
||||
}
|
||||
}
|
||||
return Array.from(allDeps);
|
||||
};
|
||||
|
||||
const externals = collectDeps(
|
||||
'./package.json',
|
||||
'../@shared/package.json',
|
||||
);
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
define: {
|
||||
__SERVICE_NAME: '"pong"',
|
||||
},
|
||||
// service root
|
||||
plugins: [tsconfigPaths(), nodeExternals()],
|
||||
build: {
|
||||
ssr: true,
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, process.env.VITE_ENTRYPOINT ?? 'src/run.ts'),
|
||||
// adjust main entry
|
||||
formats: ['cjs'],
|
||||
// CommonJS for Node.js
|
||||
fileName: () => 'index.js',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: externals,
|
||||
},
|
||||
target: 'node22',
|
||||
// or whatever Node version you use
|
||||
sourcemap: true,
|
||||
minify: false,
|
||||
// for easier debugging
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue