Nigel/block user (#60)
* WIP block users * Add test to check if the person for user X is blocked or not * Added function isBlocked return true if user x is blocked by user y * Added block ans un block messages * Route / broadcast added ny Maix * notification block and un block with icon now works, started clean up * cleaning up the code * cleaning up the code - WIP * cleaning up the code - WIP * clean up code - WIP * clean up code - WIP * WIP block users * Add test to check if the person for user X is blocked or not * Added function isBlocked return true if user x is blocked by user y * Added block ans un block messages * REBASE -trying * REBASE trying * REBASE problems * REBASE problems * REBASE problems * MERGE problems * REBASE problem * REBASE problem * clean up code - WIP * MERGED master into nigel/blockUser * linter OK * TTT problem with package * Deletes tic tac and icons
This commit is contained in:
parent
64a820c0f0
commit
85eca5d301
60 changed files with 2613 additions and 1891 deletions
|
|
@ -11,15 +11,15 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.2.7",
|
"vite": "^7.3.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"tailwindcss": "^4.1.17"
|
"tailwindcss": "^4.1.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
572
frontend/pnpm-lock.yaml
generated
572
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,8 +9,6 @@ models/ChangePassword400Response.ts
|
||||||
models/ChangePassword401Response.ts
|
models/ChangePassword401Response.ts
|
||||||
models/ChangePassword500Response.ts
|
models/ChangePassword500Response.ts
|
||||||
models/ChangePasswordRequest.ts
|
models/ChangePasswordRequest.ts
|
||||||
models/ChatTest200Response.ts
|
|
||||||
models/ChatTest200ResponsePayload.ts
|
|
||||||
models/DisableOtp200Response.ts
|
models/DisableOtp200Response.ts
|
||||||
models/DisableOtp400Response.ts
|
models/DisableOtp400Response.ts
|
||||||
models/DisableOtp500Response.ts
|
models/DisableOtp500Response.ts
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import type {
|
||||||
ChangePassword401Response,
|
ChangePassword401Response,
|
||||||
ChangePassword500Response,
|
ChangePassword500Response,
|
||||||
ChangePasswordRequest,
|
ChangePasswordRequest,
|
||||||
ChatTest200Response,
|
|
||||||
DisableOtp200Response,
|
DisableOtp200Response,
|
||||||
DisableOtp400Response,
|
DisableOtp400Response,
|
||||||
DisableOtp500Response,
|
DisableOtp500Response,
|
||||||
|
|
@ -74,8 +73,6 @@ import {
|
||||||
ChangePassword500ResponseToJSON,
|
ChangePassword500ResponseToJSON,
|
||||||
ChangePasswordRequestFromJSON,
|
ChangePasswordRequestFromJSON,
|
||||||
ChangePasswordRequestToJSON,
|
ChangePasswordRequestToJSON,
|
||||||
ChatTest200ResponseFromJSON,
|
|
||||||
ChatTest200ResponseToJSON,
|
|
||||||
DisableOtp200ResponseFromJSON,
|
DisableOtp200ResponseFromJSON,
|
||||||
DisableOtp200ResponseToJSON,
|
DisableOtp200ResponseToJSON,
|
||||||
DisableOtp400ResponseFromJSON,
|
DisableOtp400ResponseFromJSON,
|
||||||
|
|
@ -291,48 +288,6 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
|
||||||
return await response.value();
|
return await response.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async chatTestRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ChatTest200Response | StatusOtp401Response>> {
|
|
||||||
const queryParameters: any = {};
|
|
||||||
|
|
||||||
const headerParameters: runtime.HTTPHeaders = {};
|
|
||||||
|
|
||||||
|
|
||||||
let urlPath = `/api/chat/test`;
|
|
||||||
|
|
||||||
const response = await this.request({
|
|
||||||
path: urlPath,
|
|
||||||
method: 'GET',
|
|
||||||
headers: headerParameters,
|
|
||||||
query: queryParameters,
|
|
||||||
}, initOverrides);
|
|
||||||
|
|
||||||
// CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses
|
|
||||||
// This allows typed access to error responses (4xx, 5xx) and other status codes.
|
|
||||||
// The code routes responses based on the actual HTTP status code and returns
|
|
||||||
// appropriately typed ApiResponse wrappers for each status code.
|
|
||||||
if (response.status === 200) {
|
|
||||||
// Object response for status 200
|
|
||||||
return new runtime.JSONApiResponse(response, (jsonValue) => ChatTest200ResponseFromJSON(jsonValue));
|
|
||||||
}
|
|
||||||
if (response.status === 401) {
|
|
||||||
// Object response for status 401
|
|
||||||
return new runtime.JSONApiResponse(response, (jsonValue) => StatusOtp401ResponseFromJSON(jsonValue));
|
|
||||||
}
|
|
||||||
// CHANGED: Throw error if status code is not handled by any of the defined responses
|
|
||||||
// This ensures all code paths return a value and provides clear error messages for unexpected status codes
|
|
||||||
// Only throw if responses were defined but none matched the actual status code
|
|
||||||
throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 401`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async chatTest(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ChatTest200Response | StatusOtp401Response> {
|
|
||||||
const response = await this.chatTestRaw(initOverrides);
|
|
||||||
return await response.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<DisableOtp200Response | DisableOtp400Response | ChangePassword401Response | DisableOtp500Response>> {
|
async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<DisableOtp200Response | DisableOtp400Response | ChangePassword401Response | DisableOtp500Response>> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* @fastify/swagger
|
||||||
|
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 9.6.1
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mapValues } from '../runtime';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface ApiChatBroadcastPostRequest
|
||||||
|
*/
|
||||||
|
export interface ApiChatBroadcastPostRequest {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ApiChatBroadcastPostRequest
|
||||||
|
*/
|
||||||
|
nextGame: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given object implements the ApiChatBroadcastPostRequest interface.
|
||||||
|
*/
|
||||||
|
export function instanceOfApiChatBroadcastPostRequest(value: object): value is ApiChatBroadcastPostRequest {
|
||||||
|
if (!('nextGame' in value) || value['nextGame'] === undefined) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApiChatBroadcastPostRequestFromJSON(json: any): ApiChatBroadcastPostRequest {
|
||||||
|
return ApiChatBroadcastPostRequestFromJSONTyped(json, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApiChatBroadcastPostRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): ApiChatBroadcastPostRequest {
|
||||||
|
if (json == null) {
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
|
||||||
|
'nextGame': json['nextGame'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApiChatBroadcastPostRequestToJSON(json: any): ApiChatBroadcastPostRequest {
|
||||||
|
return ApiChatBroadcastPostRequestToJSONTyped(json, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApiChatBroadcastPostRequestToJSONTyped(value?: ApiChatBroadcastPostRequest | null, ignoreDiscriminator: boolean = false): any {
|
||||||
|
if (value == null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
'nextGame': value['nextGame'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -8,8 +8,6 @@ export * from './ChangePassword400Response';
|
||||||
export * from './ChangePassword401Response';
|
export * from './ChangePassword401Response';
|
||||||
export * from './ChangePassword500Response';
|
export * from './ChangePassword500Response';
|
||||||
export * from './ChangePasswordRequest';
|
export * from './ChangePasswordRequest';
|
||||||
export * from './ChatTest200Response';
|
|
||||||
export * from './ChatTest200ResponsePayload';
|
|
||||||
export * from './DisableOtp200Response';
|
export * from './DisableOtp200Response';
|
||||||
export * from './DisableOtp400Response';
|
export * from './DisableOtp400Response';
|
||||||
export * from './DisableOtp500Response';
|
export * from './DisableOtp500Response';
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,23 @@
|
||||||
relative; /* needed for overlay */
|
relative; /* needed for overlay */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-messages {
|
||||||
|
@apply
|
||||||
|
h-[80px]
|
||||||
|
bg-white
|
||||||
|
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 {
|
.text-info {
|
||||||
@apply
|
@apply
|
||||||
|
|
@ -230,6 +247,12 @@ div-private {
|
||||||
right-12
|
right-12
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.popup-b-block {
|
||||||
|
@apply
|
||||||
|
absolute
|
||||||
|
bottom-62
|
||||||
|
right-12
|
||||||
|
}
|
||||||
|
|
||||||
.popUpMessage {
|
.popUpMessage {
|
||||||
@apply
|
@apply
|
||||||
|
|
|
||||||
13
frontend/src/pages/chat/actionBtnPopUpBlock.ts
Normal file
13
frontend/src/pages/chat/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/chat/actionBtnPopUpClear.ts
Normal file
13
frontend/src/pages/chat/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/chat/addMessage.ts
Normal file
18
frontend/src/pages/chat/addMessage.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { color } from './chat';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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/chat/blockUser.ts
Normal file
11
frontend/src/pages/chat/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/chat/broadcastMsg.ts
Normal file
28
frontend/src/pages/chat/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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -37,14 +37,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="game-modal" class="gamePopup hidden">
|
<div id="game-modal" class="gamePopup hidden">
|
||||||
<div class="popUpMessage">
|
<div class="popUpMessage">
|
||||||
<p class="" id="modal-message"></p>
|
<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>
|
<button id="close-modal-message" class="btn-style absolute bottom-67 right-12">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,18 @@ 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 { 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';
|
||||||
|
|
||||||
const color = {
|
export const color = {
|
||||||
red: 'color: red;',
|
red: 'color: red;',
|
||||||
green: 'color: green;',
|
green: 'color: green;',
|
||||||
yellow: 'color: orange;',
|
yellow: 'color: orange;',
|
||||||
|
|
@ -13,49 +23,11 @@ const color = {
|
||||||
reset: '',
|
reset: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
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,
|
|
||||||
innerHtml?: string,
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// export type inviteGame = {
|
|
||||||
// command?: string,
|
|
||||||
// destination?: string,
|
|
||||||
// type?: string,
|
|
||||||
// user?: string,
|
|
||||||
// loginName?: string,
|
|
||||||
// userID?: string,
|
|
||||||
// innerHtml?: string,
|
|
||||||
// timestamp?: number,
|
|
||||||
// SenderWindowID?:string,
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// get the name of the machine used to connect
|
// get the name of the machine used to connect
|
||||||
const machineHostName = window.location.hostname;
|
const machineHostName = window.location.hostname;
|
||||||
console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow);
|
console.log('connect to login at %chttps://' + machineHostName + ':8888/app/login',color.yellow);
|
||||||
|
|
||||||
let __socket: Socket | undefined = undefined;
|
export let __socket: Socket | undefined = undefined;
|
||||||
document.addEventListener('ft:pageChange', () => {
|
document.addEventListener('ft:pageChange', () => {
|
||||||
if (__socket !== undefined)
|
if (__socket !== undefined)
|
||||||
__socket.close();
|
__socket.close();
|
||||||
|
|
@ -63,7 +35,7 @@ document.addEventListener('ft:pageChange', () => {
|
||||||
console.log("Page changed");
|
console.log("Page changed");
|
||||||
});
|
});
|
||||||
|
|
||||||
function getSocket(): Socket {
|
export function getSocket(): Socket {
|
||||||
let addressHost = `wss://${machineHostName}:8888`;
|
let addressHost = `wss://${machineHostName}:8888`;
|
||||||
// let addressHost = `wss://localhost:8888`;
|
// let addressHost = `wss://localhost:8888`;
|
||||||
if (__socket === undefined)
|
if (__socket === undefined)
|
||||||
|
|
@ -76,29 +48,6 @@ function getSocket(): Socket {
|
||||||
return __socket;
|
return __socket;
|
||||||
};
|
};
|
||||||
|
|
||||||
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(`Added new message: ${text}`)
|
|
||||||
return ;
|
|
||||||
};
|
|
||||||
|
|
||||||
function clear(senderSocket: Socket) {
|
|
||||||
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
|
|
||||||
if (!chatWindow) return;
|
|
||||||
chatWindow.innerHTML = "";
|
|
||||||
// senderSocket.emit('nextGame');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLoggedIn() {
|
|
||||||
return getUser() || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) {
|
function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) {
|
||||||
profil.SenderName = getUser()?.name ?? '';
|
profil.SenderName = getUser()?.name ?? '';
|
||||||
if (profil.SenderName === profil.user) return;
|
if (profil.SenderName === profil.user) return;
|
||||||
|
|
@ -106,18 +55,6 @@ function inviteToPlayPong(profil: ClientProfil, senderSocket: Socket) {
|
||||||
senderSocket.emit('inviteGame', JSON.stringify(profil));
|
senderSocket.emit('inviteGame', JSON.stringify(profil));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function actionBtnPopUpClear(profil: ClientProfil, senderSocket: Socket) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const clearTextBtn = document.querySelector("#popup-b-clear");
|
|
||||||
clearTextBtn?.addEventListener("click", () => {
|
|
||||||
clear(senderSocket);
|
|
||||||
});
|
|
||||||
}, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) {
|
function actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const InvitePongBtn = document.querySelector("#popup-b-invite");
|
const InvitePongBtn = document.querySelector("#popup-b-invite");
|
||||||
|
|
@ -127,40 +64,21 @@ function actionBtnPopUpInvite(invite: ClientProfil, senderSocket: Socket) {
|
||||||
}, 0)
|
}, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// async function windowStateHidden() {
|
||||||
|
// const socketId = __socket || undefined;
|
||||||
// getProfil get the profil of user
|
// // let oldName = localStorage.getItem("oldName") ?? undefined;
|
||||||
function getProfil(socket: Socket, user: string) {
|
// let oldName: string;
|
||||||
if (!socket.connected) return;
|
// if (socketId === undefined) return;
|
||||||
const profil = {
|
// let userName = await updateUser();
|
||||||
command: '@profil',
|
// oldName = userName?.name ?? "";
|
||||||
destination: 'profilMessage',
|
// if (oldName === "") return;
|
||||||
type: "chat",
|
// localStorage.setItem('oldName', oldName);
|
||||||
user: user,
|
// socketId.emit('client_left', {
|
||||||
token: document.cookie ?? "",
|
// user: userName?.name,
|
||||||
text: user,
|
// why: 'tab window hidden - socket not dead',
|
||||||
timestamp: Date.now(),
|
// });
|
||||||
SenderWindowID: socket.id,
|
// return;
|
||||||
};
|
// };
|
||||||
// addMessage(JSON.stringify(profil));
|
|
||||||
socket.emit('profilMessage', JSON.stringify(profil));
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
async function windowStateVisable() {
|
||||||
|
|
||||||
|
|
@ -223,34 +141,34 @@ function parseCmdMsg(msgText: string): string[] | undefined {
|
||||||
return command;
|
return command;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: string) {
|
// async function listBuddies(socket: Socket, buddies: HTMLDivElement, listBuddies: string) {
|
||||||
|
|
||||||
if (!buddies) return;
|
// if (!buddies) return;
|
||||||
const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement;
|
// const sendtextbox = document.getElementById('t-chat-window') as HTMLButtonElement;
|
||||||
const buddiesElement = document.createElement("div-buddies-list");
|
// const buddiesElement = document.createElement("div-buddies-list");
|
||||||
buddiesElement.textContent = listBuddies + '\n';
|
// buddiesElement.textContent = listBuddies + '\n';
|
||||||
const user = getUser()?.name ?? "";
|
// const user = getUser()?.name ?? "";
|
||||||
|
// buddies.appendChild(buddiesElement);
|
||||||
|
// buddies.scrollTop = buddies.scrollHeight;
|
||||||
|
// console.log(`Added buddies: ${listBuddies}`);
|
||||||
|
|
||||||
buddiesElement.style.cursor = "pointer";
|
// buddiesElement.style.cursor = "pointer";
|
||||||
buddiesElement.addEventListener("click", () => {
|
// buddiesElement.addEventListener("click", () => {
|
||||||
navigator.clipboard.writeText(listBuddies);
|
// navigator.clipboard.writeText(listBuddies);
|
||||||
if (listBuddies !== user && user !== "") {
|
// if (listBuddies !== user && user !== "") {
|
||||||
sendtextbox.value = `@${listBuddies}: `;
|
// sendtextbox.value = `@${listBuddies}: `;
|
||||||
console.log("Copied to clipboard:", listBuddies);
|
// console.log("Copied to clipboard:", listBuddies);
|
||||||
sendtextbox.focus();
|
// sendtextbox.focus();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
|
|
||||||
buddiesElement.addEventListener("dblclick", () => {
|
// buddiesElement.addEventListener("dblclick", () => {
|
||||||
console.log("Open profile:", listBuddies);
|
// console.log("Open profile:", listBuddies);
|
||||||
getProfil(socket, listBuddies);
|
// getProfil(socket, listBuddies);
|
||||||
sendtextbox.value = "";
|
// sendtextbox.value = "";
|
||||||
});
|
// });
|
||||||
|
|
||||||
buddies.appendChild(buddiesElement);
|
// }
|
||||||
buddies.scrollTop = buddies.scrollHeight;
|
|
||||||
console.log(`Added buddies: ${listBuddies}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function waitSocketConnected(socket: Socket): Promise<void> {
|
function waitSocketConnected(socket: Socket): Promise<void> {
|
||||||
|
|
@ -295,32 +213,13 @@ function logout(socket: Socket) {
|
||||||
// window.location.href = "/login";
|
// window.location.href = "/login";
|
||||||
};
|
};
|
||||||
|
|
||||||
function broadcastMsg (socket: Socket, msgCommand: string[]): void {
|
|
||||||
let msgText = msgCommand[1] ?? "";
|
|
||||||
console.log('%cmsgText:', color.red, msgText);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
async function connected(socket: Socket): Promise<void> {
|
async function connected(socket: Socket): Promise<void> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
||||||
const loggedIn = isLoggedIn();
|
const loggedIn = isLoggedIn();
|
||||||
|
if (!loggedIn) throw('Not Logged in');
|
||||||
console.log('%cloggedIn:',color.blue, loggedIn?.name);
|
console.log('%cloggedIn:',color.blue, loggedIn?.name);
|
||||||
let oldUser = localStorage.getItem("oldName") ?? "";
|
let oldUser = localStorage.getItem("oldName") ?? "";
|
||||||
console.log('%coldUser:',color.yellow, oldUser);
|
console.log('%coldUser:',color.yellow, oldUser);
|
||||||
|
|
@ -348,9 +247,9 @@ async function whoami(socket: Socket) {
|
||||||
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
|
const chatWindow = document.getElementById("t-chatbox") as HTMLDivElement;
|
||||||
const loggedIn = isLoggedIn();
|
const loggedIn = isLoggedIn();
|
||||||
|
|
||||||
const res = await client.guestLogin();
|
const res = (getUser());
|
||||||
switch (res.kind) {
|
console.log('loginGuest():', res?.name);
|
||||||
case 'success': {
|
if (res) {
|
||||||
let user = await updateUser();
|
let user = await updateUser();
|
||||||
if (chatWindow) {
|
if (chatWindow) {
|
||||||
socket.emit('updateClientName', {
|
socket.emit('updateClientName', {
|
||||||
|
|
@ -361,53 +260,61 @@ async function whoami(socket: Socket) {
|
||||||
if (user === null)
|
if (user === null)
|
||||||
return showError('Failed to get user: no user ?');
|
return showError('Failed to get user: no user ?');
|
||||||
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
|
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
|
||||||
break;
|
} else {
|
||||||
}
|
showError(`Failed to login: ${res}`);
|
||||||
case 'failed': {
|
|
||||||
showError(`Failed to login: ${res.msg}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Login error:", e);
|
console.error("Login error:", e);
|
||||||
showError('Failed to login: Unknown error');
|
showError('Failed to login: Unknown error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function openProfilePopup(profil: ClientProfil) {
|
// async function openProfilePopup(profil: ClientProfil) {
|
||||||
|
|
||||||
|
|
||||||
const modalname = document.getElementById("modal-name") ?? null;
|
// const modalname = document.getElementById("modal-name") ?? null;
|
||||||
if (modalname)
|
// if (modalname)
|
||||||
modalname.innerHTML = `
|
// modalname.innerHTML = `
|
||||||
<div class="profile-info">
|
// <div class="profile-info">
|
||||||
<div-profil-name id="profilName"> Profil of ${profil.user} </div>
|
// <div-profil-name id="profilName"> Profil of ${profil.user} </div>
|
||||||
<div-login-name id="loginName"> Login Name: '${profil.loginName ?? 'Guest'}' </div>
|
// <div-login-name id="loginName"> Login Name: '${profil.loginName ?? 'Guest'}' </div>
|
||||||
</br>
|
// </br>
|
||||||
<div-login-name id="loginName"> Login ID: '${profil.userID ?? ''}' </div>
|
// <div-login-name id="loginName"> Login ID: '${profil.userID ?? ''}' </div>
|
||||||
</br>
|
// </br>
|
||||||
<button id="popup-b-clear" class="btn-style popup-b-clear">Clear Text</button>
|
// <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-invite" class="btn-style popup-b-invite">U Game ?</button>
|
||||||
<div id="profile-about">About: '${profil.text}' </div>
|
// <button id="popup-b-block" class="btn-style popup-b-block">Block User</button>
|
||||||
</div>
|
// <div id="profile-about">About: '${profil.text}' </div>
|
||||||
`;
|
// </div>
|
||||||
const profilList = document.getElementById("profile-modal") ?? null;
|
// `;
|
||||||
if (profilList)
|
// const profilList = document.getElementById("profile-modal") ?? null;
|
||||||
profilList.classList.remove("hidden");
|
// if (profilList)
|
||||||
// The popup now exists → attach the event
|
// 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) {
|
async function openMessagePopup(message: string) {
|
||||||
|
|
||||||
|
|
||||||
const modalmessage = document.getElementById("modal-message") ?? null;
|
const modalmessage = document.getElementById("modal-message") ?? null;
|
||||||
if(!message) return
|
if(!message) return
|
||||||
const obj:any = JSON.parse(message);
|
const obj:any = JSON.parse(message);
|
||||||
if (modalmessage)
|
if (modalmessage) {
|
||||||
modalmessage.innerHTML = `
|
const messageElement = document.createElement("div");
|
||||||
|
messageElement.innerHTML = `
|
||||||
<div class="profile-info">
|
<div class="profile-info">
|
||||||
</br>
|
<div id="profile-about">Next Game Message ${incrementCounter()}: ${obj.link}</div>
|
||||||
<div id="profile-about">Next Game Message: ${obj.link}</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
|
modalmessage.appendChild(messageElement);
|
||||||
|
modalmessage.scrollTop = modalmessage.scrollHeight;
|
||||||
|
|
||||||
|
}
|
||||||
const gameMessage = document.getElementById("game-modal") ?? null;
|
const gameMessage = document.getElementById("game-modal") ?? null;
|
||||||
if (gameMessage)
|
if (gameMessage)
|
||||||
gameMessage.classList.remove("hidden");
|
gameMessage.classList.remove("hidden");
|
||||||
|
|
@ -422,8 +329,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
|
|
||||||
let socket = getSocket();
|
let socket = getSocket();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Listen for the 'connect' event
|
// Listen for the 'connect' event
|
||||||
socket.on("connect", async () => {
|
socket.on("connect", async () => {
|
||||||
|
|
||||||
|
|
@ -495,6 +400,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
openProfilePopup(profil);
|
openProfilePopup(profil);
|
||||||
actionBtnPopUpClear(profil, socket);
|
actionBtnPopUpClear(profil, socket);
|
||||||
actionBtnPopUpInvite(profil, socket);
|
actionBtnPopUpInvite(profil, socket);
|
||||||
|
actionBtnPopUpBlock(profil, socket);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('inviteGame', (invite: ClientProfil) => {
|
socket.on('inviteGame', (invite: ClientProfil) => {
|
||||||
|
|
@ -502,8 +408,23 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
const messageElement = document.createElement("div");
|
const messageElement = document.createElement("div");
|
||||||
messageElement.innerHTML =`🏓${invite.SenderName}: ${invite.innerHtml}`;
|
messageElement.innerHTML =`🏓${invite.SenderName}: ${invite.innerHtml}`;
|
||||||
chatWindow.appendChild(messageElement);
|
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', () => {
|
socket.on('logout', () => {
|
||||||
quitChat(socket);
|
quitChat(socket);
|
||||||
});
|
});
|
||||||
|
|
@ -513,7 +434,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
})
|
})
|
||||||
|
|
||||||
//receives broadcast of the next GAME
|
//receives broadcast of the next GAME
|
||||||
socket.on('nextGame', (message) => {
|
socket.on('nextGame', (message: string) => {
|
||||||
openMessagePopup(message);
|
openMessagePopup(message);
|
||||||
// addMessage(message);
|
// addMessage(message);
|
||||||
})
|
})
|
||||||
|
|
@ -547,7 +468,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
|
|
||||||
socket.on('listBud', async (myBuddies: string) => {
|
socket.on('listBud', async (myBuddies: string) => {
|
||||||
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
const buddies = document.getElementById('div-buddies') as HTMLDivElement;
|
||||||
console.log('List buddies connected ', myBuddies);
|
console.log('%cList buddies connected ',color.yellow, myBuddies);
|
||||||
listBuddies(socket, buddies, myBuddies);
|
listBuddies(socket, buddies, myBuddies);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -584,18 +505,6 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
buddies.textContent = '';
|
buddies.textContent = '';
|
||||||
buddies.innerHTML = '';
|
buddies.innerHTML = '';
|
||||||
|
|
||||||
|
|
||||||
const value = await client.chatTest();
|
|
||||||
if (value.kind === "success") {
|
|
||||||
console.log(value.payload);
|
|
||||||
} else if (value.kind === "notLoggedIn") {
|
|
||||||
console.log('not logged in');
|
|
||||||
} else {
|
|
||||||
console.log('unknown response: ', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const buttonPro = document.getElementById("close-modal") ?? null;
|
const buttonPro = document.getElementById("close-modal") ?? null;
|
||||||
|
|
||||||
if (buttonPro)
|
if (buttonPro)
|
||||||
|
|
@ -613,6 +522,9 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
buttonMessage.addEventListener("click", () => {
|
buttonMessage.addEventListener("click", () => {
|
||||||
const gameMessage = document.getElementById("game-modal") ?? null;
|
const gameMessage = document.getElementById("game-modal") ?? null;
|
||||||
if (gameMessage) gameMessage.classList.add("hidden");
|
if (gameMessage) gameMessage.classList.add("hidden");
|
||||||
|
const modalmessage = document.getElementById("modal-message") ?? null;
|
||||||
|
if (modalmessage) {modalmessage.innerHTML = "";}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -669,7 +581,7 @@ function handleChat(_url: string, _args: RouteHandlerParams): RouteHandlerReturn
|
||||||
if (chatWindow) {
|
if (chatWindow) {
|
||||||
chatWindow.innerHTML = '';
|
chatWindow.innerHTML = '';
|
||||||
}
|
}
|
||||||
clear(socket); //DEV testing broadcastGames
|
//clearChatWindow(socket); //DEV testing broadcastGames
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dev Game message button
|
// Dev Game message button
|
||||||
|
|
|
||||||
13
frontend/src/pages/chat/clearChatWindow.ts
Normal file
13
frontend/src/pages/chat/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/chat/getProfil.ts
Normal file
24
frontend/src/pages/chat/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/chat/isLoggedIn.ts
Normal file
9
frontend/src/pages/chat/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/chat/listBuddies.ts
Normal file
45
frontend/src/pages/chat/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/chat/openProfilePopup.ts
Normal file
24
frontend/src/pages/chat/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
|
||||||
|
}
|
||||||
23
frontend/src/pages/chat/types_front.ts
Normal file
23
frontend/src/pages/chat/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/chat/windowStateHidden.ts
Normal file
18
frontend/src/pages/chat/windowStateHidden.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { __socket } from './chat';
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"packageManager": "pnpm@10",
|
"packageManager": "pnpm@10",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@redocly/cli": "^2.11.1",
|
"@redocly/cli": "^2.12.7",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"vite": "^7.2.4"
|
"vite": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"joi": "^18.0.2",
|
"joi": "^18.0.2",
|
||||||
"otp": "^1.1.2",
|
"otp": "^1.1.2",
|
||||||
"typebox": "^1.0.61",
|
"typebox": "^1.0.63",
|
||||||
"uuidv7": "^1.1.0"
|
"uuidv7": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
"@types/node": "^22.19.2"
|
"@types/node": "^22.19.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,11 +142,3 @@ export function typeResponse<K extends string, T extends TProperties>(
|
||||||
export function isNullish<T>(v: T | undefined | null): v is null | undefined {
|
export function isNullish<T>(v: T | undefined | null): v is null | undefined {
|
||||||
return v === null || v === undefined;
|
return v === null || v === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
/* 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);
|
|
||||||
}
|
|
||||||
|
|
@ -27,12 +27,13 @@
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"fastify-cli": "^7.4.1",
|
"fastify-cli": "^7.4.1",
|
||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"typebox": "^1.0.61"
|
"socket.io-client": "^4.8.1",
|
||||||
|
"typebox": "^1.0.63"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.19.2",
|
"@types/node": "^22.19.3",
|
||||||
"rollup-plugin-node-externals": "^8.1.2",
|
"rollup-plugin-node-externals": "^8.1.2",
|
||||||
"vite": "^7.2.7",
|
"vite": "^7.3.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,116 +7,7 @@
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {}
|
"schemas": {}
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {},
|
||||||
"/api/chat/test": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "chatTest",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Default Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg",
|
|
||||||
"payload"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"success"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"chat.success"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
"id",
|
|
||||||
"guest"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"guest": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Default Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"notLoggedIn"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"auth.noCookie",
|
|
||||||
"auth.invalidKind",
|
|
||||||
"auth.noUser",
|
|
||||||
"auth.invalid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"notLoggedIn"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"auth.noCookie",
|
|
||||||
"auth.invalidKind",
|
|
||||||
"auth.noUser",
|
|
||||||
"auth.invalid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://local.maix.me:8888",
|
"url": "https://local.maix.me:8888",
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"typebox": "^1.0.61"
|
"typebox": "^1.0.63"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.19.2",
|
"@types/node": "^22.19.3",
|
||||||
"rollup-plugin-node-externals": "^8.1.2",
|
"rollup-plugin-node-externals": "^8.1.2",
|
||||||
"vite": "^7.2.7",
|
"vite": "^7.3.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,19 @@ import * as swagger from '@shared/swagger';
|
||||||
import * as utils from '@shared/utils';
|
import * as utils from '@shared/utils';
|
||||||
import { Server, Socket } from 'socket.io';
|
import { Server, Socket } from 'socket.io';
|
||||||
import type { User } from '@shared/database/mixin/user';
|
import type { User } from '@shared/database/mixin/user';
|
||||||
import { sendGameLinkToChatService } from '../../@shared/src/utils/index';
|
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
|
// colors for console.log
|
||||||
export const color = {
|
export const color = {
|
||||||
|
|
@ -18,14 +30,6 @@ export const color = {
|
||||||
reset: '\x1b[0m',
|
reset: '\x1b[0m',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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');
|
|
||||||
}
|
|
||||||
|
|
||||||
declare const __SERVICE_NAME: string;
|
declare const __SERVICE_NAME: string;
|
||||||
|
|
||||||
// Global map of clients
|
// Global map of clients
|
||||||
|
|
@ -35,52 +39,21 @@ interface ClientInfo {
|
||||||
lastSeen: number;
|
lastSeen: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientMessage = {
|
// function setAboutPlayer(about: string): string {
|
||||||
command: string
|
// if (!about) {
|
||||||
destination: string;
|
// about = 'Player is good Shape - This is a default description';
|
||||||
user: string;
|
// }
|
||||||
text: string;
|
// return about;
|
||||||
SenderWindowID: string;
|
// };
|
||||||
};
|
|
||||||
|
|
||||||
export type ClientProfil = {
|
// function setGameLink(link: string): string {
|
||||||
command: string,
|
// if (!link) {
|
||||||
destination: string,
|
// link = '<a href=\'https://google.com\' style=\'color: blue; text-decoration: underline; cursor: pointer;\'>Click me</a>';
|
||||||
type: string,
|
// }
|
||||||
user: string,
|
// return link;
|
||||||
loginName: string,
|
// };
|
||||||
userID: string,
|
|
||||||
text: string,
|
|
||||||
timestamp: number,
|
|
||||||
SenderWindowID:string,
|
|
||||||
SenderName: string,
|
|
||||||
innerHtml?: string,
|
|
||||||
|
|
||||||
};
|
export const clientChat = new Map<string, ClientInfo>();
|
||||||
|
|
||||||
/**
|
|
||||||
/* TODO find the description info for profil / or profil game link and return
|
|
||||||
**/
|
|
||||||
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>';
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
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...
|
||||||
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
|
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
|
||||||
|
|
@ -125,11 +98,11 @@ declare module 'fastify' {
|
||||||
privMessage: (data: string) => void;
|
privMessage: (data: string) => void;
|
||||||
profilMessage: (data: ClientProfil) => void;
|
profilMessage: (data: ClientProfil) => void;
|
||||||
inviteGame: (data: ClientProfil) => void;
|
inviteGame: (data: ClientProfil) => void;
|
||||||
|
blockUser: (data: ClientProfil) => void;
|
||||||
privMessageCopy: (msg: string) => void;
|
privMessageCopy: (msg: string) => void;
|
||||||
nextGame: (msg: string) => void;
|
nextGame: (nextGame: string) => void;
|
||||||
message: (msg: string) => void;
|
message: (msg: string) => void;
|
||||||
listBud: (msg: string) => void;
|
listBud: (msg: string) => void;
|
||||||
testend: (sock_id_client: string) => void;
|
|
||||||
client_entered: (userName: string, user: string) => void;
|
client_entered: (userName: string, user: string) => void;
|
||||||
client_left: (userName: string, why: string) => void;
|
client_left: (userName: string, why: string) => void;
|
||||||
list: (oldUser: string, user: string) => void;
|
list: (oldUser: string, user: string) => void;
|
||||||
|
|
@ -139,259 +112,68 @@ declare module 'fastify' {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onReady(fastify: FastifyInstance) {
|
async function onReady(fastify: FastifyInstance) {
|
||||||
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 sanity 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 s = io.sockets.sockets.get(socketId) as Socket | undefined;
|
|
||||||
// If socket not found or disconnected, remove from map and skip
|
|
||||||
if (!s || s.disconnected) {
|
|
||||||
clientChat.delete(socketId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Skip duplicates (DO NOT delete them — just don't count)
|
|
||||||
if (seen.has(username.user)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// socket exists and is connected
|
|
||||||
seen.add(username.user);
|
|
||||||
count++;
|
|
||||||
const targetSocketId = target;
|
|
||||||
io.to(targetSocketId!).emit('listBud', username.user);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
function broadcast(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, `'Broadcast to:', ${data.command} message: ${data.text}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function broadcastNextGame(gameLink?: Promise<string>) {
|
// shows address for connection au server transcendance
|
||||||
|
const session = process.env.SESSION_MANAGER ?? '';
|
||||||
const link = gameLink ? await gameLink : undefined;
|
if (session) {
|
||||||
const sockets = await fastify.io.fetchSockets();
|
const part = session.split('/')[1];
|
||||||
// fastify.io.fetchSockets().then((sockets) => {
|
const machineName = part.split('.')[0];
|
||||||
for (const socket of sockets) {
|
console.log(color.yellow, 'Connect at : https://' + machineName + ':8888/app/login');
|
||||||
// Skip sender's own socket
|
|
||||||
const clientInfo = clientChat.get(socket.id);
|
|
||||||
if (!clientInfo?.user) {
|
|
||||||
console.log(color.yellow, `Skipping socket ${socket.id} (no user found)`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Emit structured JSON object
|
|
||||||
if (link) {
|
|
||||||
socket.emit('nextGame', link);
|
|
||||||
}
|
|
||||||
// Debug logs
|
|
||||||
// console.log(color.green, `'DEBUG LOG: Broadcast to:', ${data.command} message: ${data.text}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// function formatTimestamp(ms: number) {
|
|
||||||
// const d = new Date(ms);
|
|
||||||
// return d.toLocaleString('fr-FR', { timeZone: 'Europe/Paris' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
function getUserByName(users: User[], name: string) {
|
|
||||||
return users.find(user => user.name === name) || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function returns html the profil pop up in CHAT of a user 'nickname unique' TODO ....
|
|
||||||
async function getProfil(user: string, socket: Socket): Promise <ClientProfil> {
|
|
||||||
|
|
||||||
let clientProfil!: ClientProfil;
|
|
||||||
const users: User[] = fastify.db.getAllUsers() ?? [];
|
|
||||||
const allUsers: User | null = getUserByName(users, user);
|
|
||||||
console.log(color.yellow, `'userFound is:'${allUsers?.name}`);
|
|
||||||
if (user === allUsers?.name) {
|
|
||||||
console.log(color.yellow, `'login Name: '${allUsers.login}' user: '${user}'`);
|
|
||||||
|
|
||||||
clientProfil =
|
|
||||||
{
|
|
||||||
command: 'getProfil',
|
|
||||||
destination: 'profilMsg',
|
|
||||||
type: 'chat' as const,
|
|
||||||
user: `${allUsers.name}`,
|
|
||||||
loginName: `${allUsers?.login ?? 'Guest'}`,
|
|
||||||
userID: `${allUsers?.id ?? ''}`,
|
|
||||||
text: setAboutPlayer(''),
|
|
||||||
timestamp: Date.now(),
|
|
||||||
SenderWindowID: socket.id,
|
|
||||||
SenderName: '',
|
|
||||||
innerHtml: '',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return clientProfil;
|
|
||||||
};
|
|
||||||
|
|
||||||
function sendProfil(data: ClientProfil, clientProfil?: string) {
|
|
||||||
fastify.io.fetchSockets().then((sockets) => {
|
|
||||||
const senderSocket = sockets.find(socket => socket.id === clientProfil);
|
|
||||||
if (senderSocket) {
|
|
||||||
console.log(color.yellow, 'user inFO:', data.user);
|
|
||||||
senderSocket.emit('profilMessage', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendInvite(innerHtml: string, data: ClientProfil) {
|
|
||||||
fastify.io.fetchSockets().then((sockets) => {
|
|
||||||
|
|
||||||
let targetSocket;
|
|
||||||
for (const socket of sockets) {
|
|
||||||
const clientInfo: string = clientChat.get(socket.id)?.user || '';
|
|
||||||
if (clientInfo === data.user) {
|
|
||||||
console.log(color.yellow, 'FOUND:', data.user);
|
|
||||||
targetSocket = socket || '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.innerHtml = innerHtml ?? '';
|
|
||||||
if (targetSocket) {
|
|
||||||
targetSocket.emit('inviteGame', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function sendPrivMessage(data: ClientMessage, sender?: string) {
|
|
||||||
fastify.io.fetchSockets().then((sockets) => {
|
|
||||||
const senderSocket = sockets.find(s => s.id === sender);
|
|
||||||
for (const s of sockets) {
|
|
||||||
if (s.id === sender) continue;
|
|
||||||
const clientInfo = clientChat.get(s.id);
|
|
||||||
if (!clientInfo?.user) {
|
|
||||||
console.log(color.yellow, `Skipping socket ${s.id} (no user found)`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const user: string = clientChat.get(s.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 !== '') {
|
|
||||||
s.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}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fastify.io.on('connection', (socket: Socket) => {
|
fastify.io.on('connection', (socket: Socket) => {
|
||||||
|
|
||||||
socket.on('message', (message: string) => {
|
socket.on('message', (message: string) => {
|
||||||
console.info(
|
// console.info(color.blue, 'DEBUG LOG: Socket connected!', color.reset, socket.id);
|
||||||
color.blue,
|
// console.log( color.blue, 'DEBUG LOG: Received message from client', color.reset, message);
|
||||||
'Socket connected!',
|
|
||||||
color.reset,
|
|
||||||
socket.id,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
color.blue,
|
|
||||||
'Received message from client',
|
|
||||||
color.reset,
|
|
||||||
message,
|
|
||||||
);
|
|
||||||
|
|
||||||
const obj: ClientMessage = JSON.parse(message) as ClientMessage;
|
const obj: ClientMessage = JSON.parse(message) as ClientMessage;
|
||||||
clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() });
|
clientChat.set(socket.id, { user: obj.user, lastSeen: Date.now() });
|
||||||
console.log(
|
// console.log(color.green, 'DEBUG LOG: Message from client', color.reset, `Sender: login name: ${obj.user} - windowID ${obj.SenderWindowID} - text message: ${obj.text}`);
|
||||||
color.green,
|
socket.emit('welcome', { msg: 'Welcome to the chat! : ' });
|
||||||
'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
|
// Send object directly — DO NOT wrap it in a string
|
||||||
broadcast(obj, obj.SenderWindowID);
|
broadcast(fastify, obj, obj.SenderWindowID);
|
||||||
console.log(color.red, 'DEBUG LOG: connected in the Chat :', connectedUser(fastify.io), color.reset);
|
// console.log(color.red, 'DEBUG LOG: connected in the Chat :', connectedUser(fastify.io), color.reset);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('testend', (sock_id_cl: string) => {
|
nextGame_SocketListener(fastify, socket);
|
||||||
console.log('testend received from client socket id:', sock_id_cl);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
list_SocketListener(fastify, socket);
|
||||||
|
|
||||||
socket.on('nextGame', () => {
|
// socket.on('list', (object) => {
|
||||||
const link = createNextGame();
|
|
||||||
const game: Promise<string> = sendGameLinkToChatService(link);
|
|
||||||
broadcastNextGame(game);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('list', (object) => {
|
// const userFromFrontend = object || null;
|
||||||
|
// const client = clientChat.get(socket.id) || null;
|
||||||
|
|
||||||
const userFromFrontend = object || null;
|
// //console.log(color.red, 'DEBUG LOG: list activated', userFromFrontend, color.reset, socket.id);
|
||||||
const client = clientChat.get(socket.id) || null;
|
|
||||||
|
|
||||||
console.log(color.red, '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 (userFromFrontend.oldUser !== userFromFrontend.user) {
|
// // if (client?.user === null) {
|
||||||
console.log(color.red, 'list activated', userFromFrontend.oldUser, color.reset);
|
// // console.log('ERROR: clientName is NULL');
|
||||||
// if (client?.user === null) {
|
// // return;
|
||||||
// console.log('ERROR: clientName is NULL');
|
// // };
|
||||||
// return;
|
// if (client) {
|
||||||
// };
|
// client.user = userFromFrontend.user;
|
||||||
if (client) {
|
// }
|
||||||
client.user = userFromFrontend.user;
|
// }
|
||||||
}
|
// connectedUser(fastify.io, socket.id);
|
||||||
}
|
// });
|
||||||
connectedUser(fastify.io, socket.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('updateClientName', (object) => {
|
socket.on('updateClientName', (object) => {
|
||||||
const userFromFrontend = object || null;
|
const userFromFrontend = object || null;
|
||||||
const client = clientChat.get(socket.id) || null;
|
const client = clientChat.get(socket.id) || null;
|
||||||
console.log(color.red, 'whoAMi activated', userFromFrontend, color.reset, socket.id);
|
// console.log(color.red, 'DEBUG LOG: whoAMi activated', userFromFrontend, color.reset, socket.id);
|
||||||
if (userFromFrontend.oldUser !== userFromFrontend.user) {
|
if (userFromFrontend.oldUser !== userFromFrontend.user) {
|
||||||
console.log(color.red, 'whoAMi activated', userFromFrontend.oldUser, color.reset);
|
// console.log(color.red, 'DEBUG LOG: whoAMi activated', userFromFrontend.oldUser, color.reset);
|
||||||
// if (client === null) {
|
// if (client === null) {
|
||||||
// console.log('ERROR: clientName is NULL');
|
// console.log('ERROR: clientName is NULL');
|
||||||
// return;
|
// return;
|
||||||
// };
|
// };
|
||||||
if (client) {
|
if (client) {
|
||||||
client.user = userFromFrontend.user;
|
client.user = userFromFrontend.user;
|
||||||
console.log(color.green, `'DEBUG LOG: client.user is, '${client.user}'`);
|
console.log(color.yellow, `'DEBUG LOG: client.user is, '${client.user}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -412,7 +194,7 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
SenderWindowID: socket.id,
|
SenderWindowID: socket.id,
|
||||||
};
|
};
|
||||||
broadcast(obj, socket.id);
|
broadcast(fastify, obj, socket.id);
|
||||||
// Optional: remove from map
|
// Optional: remove from map
|
||||||
clientChat.delete(socket.id);
|
clientChat.delete(socket.id);
|
||||||
// Ensure socket is fully disconnected
|
// Ensure socket is fully disconnected
|
||||||
|
|
@ -440,7 +222,7 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
SenderWindowID: socket.id,
|
SenderWindowID: socket.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
broadcast(obj, obj.SenderWindowID);
|
broadcast(fastify, obj, obj.SenderWindowID);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -464,9 +246,9 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
SenderWindowID: socket.id,
|
SenderWindowID: socket.id,
|
||||||
};
|
};
|
||||||
console.log(color.blue, 'BROADCASTS OUT :', obj.SenderWindowID);
|
// console.log(color.blue, 'DEBUG LOG: BROADCASTS OUT :', obj.SenderWindowID);
|
||||||
|
|
||||||
broadcast(obj, obj.SenderWindowID);
|
broadcast(fastify, obj, obj.SenderWindowID);
|
||||||
// clientChat.delete(obj.user);
|
// clientChat.delete(obj.user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -492,8 +274,8 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
SenderWindowID: socket.id,
|
SenderWindowID: socket.id,
|
||||||
};
|
};
|
||||||
console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID);
|
// console.log(color.blue, 'DEBUG LOG: PRIV MESSAGE OUT :', obj.SenderWindowID);
|
||||||
sendPrivMessage(obj, obj.SenderWindowID);
|
sendPrivMessage(fastify, obj, obj.SenderWindowID);
|
||||||
// clientChat.delete(obj.user);
|
// clientChat.delete(obj.user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -502,18 +284,14 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
const clientName: string = clientChat.get(socket.id)?.user || '';
|
const clientName: string = clientChat.get(socket.id)?.user || '';
|
||||||
const profilMessage: ClientMessage = JSON.parse(data) || '';
|
const profilMessage: ClientMessage = JSON.parse(data) || '';
|
||||||
const users: User[] = fastify.db.getAllUsers() ?? [];
|
const users: User[] = fastify.db.getAllUsers() ?? [];
|
||||||
console.log(color.yellow, 'DEBUG LOG: ALL USERS EVER CONNECTED:', users);
|
// console.log(color.yellow, 'DEBUG LOG: ALL USERS EVER CONNECTED:', users);
|
||||||
console.log(
|
// console.log(color.blue, `DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`, profilMessage.user);
|
||||||
color.blue,
|
const profile: ClientProfil = await makeProfil(fastify, profilMessage.user, socket);
|
||||||
`DEBUG LOG: ClientName: '${clientName}' id Socket: '${socket.id}' target profil:`,
|
|
||||||
profilMessage.user,
|
|
||||||
);
|
|
||||||
const profileHtml: ClientProfil = await getProfil(profilMessage.user, socket);
|
|
||||||
if (clientName !== null) {
|
if (clientName !== null) {
|
||||||
const testuser: User | null = getUserByName(users, profilMessage.user);
|
const testuser: User | null = getUserByName(users, profilMessage.user);
|
||||||
console.log(color.yellow, 'user:', testuser?.name ?? 'Guest');
|
console.log(color.yellow, 'user:', testuser?.name ?? 'Guest');
|
||||||
console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', profileHtml.SenderWindowID);
|
console.log(color.blue, 'DEBUG - profil message MESSAGE OUT :', profile.SenderWindowID);
|
||||||
sendProfil(profileHtml, profileHtml.SenderWindowID);
|
sendProfil(fastify, profile, profile.SenderWindowID);
|
||||||
// clientChat.delete(obj.user);
|
// clientChat.delete(obj.user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -527,10 +305,87 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
if (clientName !== null) {
|
if (clientName !== null) {
|
||||||
// const testuser: User | null = getUserByName(users, profilInvite.user ?? '');
|
// const testuser: User | null = getUserByName(users, profilInvite.user ?? '');
|
||||||
// console.log(color.yellow, 'user:', testuser?.name ?? 'Guest');
|
// console.log(color.yellow, 'user:', testuser?.name ?? 'Guest');
|
||||||
sendInvite(inviteHtml, profilInvite);
|
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) => {
|
socket.on('client_entered', (data) => {
|
||||||
|
|
||||||
// data may be undefined (when frontend calls emit with no payload)
|
// data may be undefined (when frontend calls emit with no payload)
|
||||||
|
|
@ -573,7 +428,7 @@ async function onReady(fastify: FastifyInstance) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
SenderWindowID: socket.id,
|
SenderWindowID: socket.id,
|
||||||
};
|
};
|
||||||
broadcast(obj, obj.SenderWindowID);
|
broadcast(fastify, obj, obj.SenderWindowID);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
22
src/chat/src/broadcast.ts
Normal file
22
src/chat/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/chat/src/broadcastNextGame.ts
Normal file
25
src/chat/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/chat/src/chat_types.ts
Normal file
23
src/chat/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/chat/src/connectedUser.ts
Normal file
48
src/chat/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/chat/src/createNextGame.ts
Normal file
6
src/chat/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>';
|
||||||
|
};
|
||||||
10
src/chat/src/getUserByName.ts
Normal file
10
src/chat/src/getUserByName.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
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/chat/src/isBlocked.ts
Normal file
17
src/chat/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);
|
||||||
|
}
|
||||||
27
src/chat/src/list_SocketListener.ts
Normal file
27
src/chat/src/list_SocketListener.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
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/chat/src/makeProfil.ts
Normal file
41
src/chat/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/chat/src/nextGame_SocketListener.ts
Normal file
20
src/chat/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);
|
||||||
|
});
|
||||||
|
}
|
||||||
53
src/chat/src/routes/broadcast.ts
Normal file
53
src/chat/src/routes/broadcast.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { FastifyPluginAsync } from 'fastify';
|
||||||
|
import { Static, Type } from 'typebox';
|
||||||
|
import { broadcast } from '../broadcast';
|
||||||
|
|
||||||
|
export const ChatReq = Type.Object({
|
||||||
|
message: Type.String(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ChatReq = Static<typeof ChatReq>;
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
||||||
|
fastify.post<{ Body: ChatReq }>(
|
||||||
|
'/api/chat/broadcast',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
body: ChatReq,
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import { FastifyPluginAsync } from 'fastify';
|
|
||||||
import { MakeStaticResponse, typeResponse } from '@shared/utils';
|
|
||||||
import { Type } from 'typebox';
|
|
||||||
// import { UserId } from '@shared/database/mixin/user';
|
|
||||||
// import { Server } from 'socket.io';
|
|
||||||
|
|
||||||
// colors for console.log
|
|
||||||
export const color = {
|
|
||||||
red: '\x1b[31m',
|
|
||||||
green: '\x1b[32m',
|
|
||||||
yellow: '\x1b[33m',
|
|
||||||
blue: '\x1b[34m',
|
|
||||||
reset: '\x1b[0m',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChatRes = {
|
|
||||||
200: typeResponse('success', 'chat.success', {
|
|
||||||
name: Type.String(),
|
|
||||||
id: Type.String(),
|
|
||||||
guest: Type.Boolean(),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ChatResType = MakeStaticResponse<typeof ChatRes>;
|
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|
||||||
fastify.get(
|
|
||||||
'/api/chat/test',
|
|
||||||
{
|
|
||||||
schema: {
|
|
||||||
response: ChatRes,
|
|
||||||
operationId: 'chatTest',
|
|
||||||
},
|
|
||||||
config: { requireAuth: true },
|
|
||||||
},
|
|
||||||
async (req, res) => {
|
|
||||||
|
|
||||||
// const users = fastify.db.getAllUsers();
|
|
||||||
// console.log('ALL USERS EVER CONNECTED:', users);
|
|
||||||
// if (!users) return;
|
|
||||||
// for (const user of users) {
|
|
||||||
// console.log(color.yellow, 'USER:', user.name);
|
|
||||||
// }
|
|
||||||
// // const usersBlocked = fastify.db.getAllBlockedUsers();
|
|
||||||
// // console.log(color.red, "ALL BLOCKED USERS:", usersBlocked);
|
|
||||||
// fastify.db.addBlockedUserFor(users[0].id, users[1].id);
|
|
||||||
// const usersBlocked2 = fastify.db.getAllBlockedUsers();
|
|
||||||
// console.log(color.green, 'ALL BLOCKED USERS:', usersBlocked2);
|
|
||||||
res.makeResponse(200, 'success', 'CCChat.success', { name: 'name', 'id': req.authUser!.id, guest: false });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default route;
|
|
||||||
29
src/chat/src/sendBlocked.ts
Normal file
29
src/chat/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/chat/src/sendGameLinkToChatService.ts
Normal file
7
src/chat/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/chat/src/sendInvite.ts
Normal file
30
src/chat/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/chat/src/sendPrivMessage.ts
Normal file
39
src/chat/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/chat/src/sendProfil.ts
Normal file
19
src/chat/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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
6
src/chat/src/setGameLink.ts
Normal file
6
src/chat/src/setGameLink.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
set -x
|
set -x
|
||||||
|
# do anything here
|
||||||
|
|
||||||
# run the CMD [ ... ] from the dockerfile
|
# run the CMD [ ... ] from the dockerfile
|
||||||
exec "$@"
|
exec "$@"
|
||||||
21
src/icons/openapi.json
Normal file
21
src/icons/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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
15
src/icons/tsconfig.json
Normal file
15
src/icons/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"]
|
||||||
|
}
|
||||||
111
src/openapi.json
111
src/openapi.json
|
|
@ -1543,117 +1543,6 @@
|
||||||
"openapi_other"
|
"openapi_other"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/api/chat/test": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "chatTest",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Default Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg",
|
|
||||||
"payload"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"success"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"chat.success"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"payload": {
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
"id",
|
|
||||||
"guest"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"guest": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"401": {
|
|
||||||
"description": "Default Response",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"notLoggedIn"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"auth.noCookie",
|
|
||||||
"auth.invalidKind",
|
|
||||||
"auth.noUser",
|
|
||||||
"auth.invalid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"msg"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"enum": [
|
|
||||||
"notLoggedIn"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"msg": {
|
|
||||||
"enum": [
|
|
||||||
"auth.noCookie",
|
|
||||||
"auth.invalidKind",
|
|
||||||
"auth.noUser",
|
|
||||||
"auth.invalid"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"openapi_other"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,19 @@
|
||||||
"dev:prepare": "husky"
|
"dev:prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
"@openapitools/openapi-generator-cli": "^2.25.2",
|
||||||
"@typescript-eslint/parser": "^8.49.0",
|
"@typescript-eslint/eslint-plugin": "^8.50.0",
|
||||||
"eslint": "^9.39.1",
|
"@typescript-eslint/parser": "^8.50.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.49.0",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.7"
|
"vite": "^7.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redocly/cli": "^2.12.5",
|
"@redocly/cli": "^2.12.7",
|
||||||
"bindings": "^1.5.0"
|
"bindings": "^1.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1632
src/pnpm-lock.yaml
generated
1632
src/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,2 +0,0 @@
|
||||||
/dist
|
|
||||||
/node_modules
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
# Directory layout
|
|
||||||
```plaintext
|
|
||||||
./src/tic-tac-toe
|
|
||||||
├── entrypoint.sh
|
|
||||||
├── package.json
|
|
||||||
├── README.md
|
|
||||||
├── src
|
|
||||||
│ ├── app.ts # The microservice app file, where the major part of the backend code lives.
|
|
||||||
│ └── run.ts # Equivalent of server.ts, it is the entrypoint for our service.
|
|
||||||
├── tsconfig.json
|
|
||||||
└── vite.config.js
|
|
||||||
```
|
|
||||||
|
|
||||||
# Anatomy of a microservice
|
|
||||||
|
|
||||||
# Backend
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "tic-tac-toe",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "npm run build && node dist/run.js",
|
|
||||||
"build": "vite build",
|
|
||||||
"build:prod": "vite build --outDir=/dist --minify=true --sourcemap=false",
|
|
||||||
"REMOVEME-build:openapi": "VITE_ENTRYPOINT=src/openapi.ts vite build && node dist/openapi.cjs >openapi.json",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"packageManager": "pnpm@10.24.0"
|
|
||||||
}
|
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
// import fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
|
||||||
// // TODO: Import Fastify formbody
|
|
||||||
// // TODO: Import Fastify multipart
|
|
||||||
// // TODO: Import shared database
|
|
||||||
// // TODO: Import shared auth
|
|
||||||
// // TODO: Import shared swagger
|
|
||||||
// // TODO: Import shared utils
|
|
||||||
// // TODO: Import socketio
|
|
||||||
|
|
||||||
// // @brief ???
|
|
||||||
// declare const __SERVICE_NAME: string;
|
|
||||||
|
|
||||||
// // TODO: Import the plugins defined for this microservice
|
|
||||||
// // TODO: Import the routes defined for this microservice
|
|
||||||
|
|
||||||
// // @brief The microservice app (as a plugin for Fastify), kinda like a main function I guess ???
|
|
||||||
// // @param fastify
|
|
||||||
// // @param opts
|
|
||||||
// export const app: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
|
||||||
// // Register all the fastify plugins that this app will use
|
|
||||||
|
|
||||||
// // Once it is done:
|
|
||||||
// fastify.ready((err) => {
|
|
||||||
// if (err) {
|
|
||||||
// throw err;
|
|
||||||
// }
|
|
||||||
// // TODO: Supposedly, something should be there I guess
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// // Export it as the default for this file.
|
|
||||||
// export default app;
|
|
||||||
|
|
||||||
// // TODO: Understand what is this for in /src/chat/src/app.ts
|
|
||||||
// // declare module 'fastify' {
|
|
||||||
// // interface FastifyInstance {
|
|
||||||
// // io: Server<{
|
|
||||||
// // hello: (message: string) => string;
|
|
||||||
// // MsgObjectServer: (data: { message: ClientMessage }) => void;
|
|
||||||
// // message: (msg: string) => void;
|
|
||||||
// // testend: (sock_id_client: string) => void;
|
|
||||||
// // }>;
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // TODO: Same for this, also in /src/chat/src/app.ts
|
|
||||||
// // async function onReady(fastify: FastifyInstance) {
|
|
||||||
// // 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 sanity checks
|
|
||||||
// // if (typeof socketId !== 'string' || socketId.length === 0) {
|
|
||||||
// // clientChat.delete(socketId);
|
|
||||||
// // continue;
|
|
||||||
// // }
|
|
||||||
// // if (typeof username !== 'string' || username.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 s = io.sockets.sockets.get(socketId) as
|
|
||||||
// // | Socket
|
|
||||||
// // | undefined;
|
|
||||||
// // // If socket not found or disconnected, remove from map and skip
|
|
||||||
// // if (!s || s.disconnected) {
|
|
||||||
// // clientChat.delete(socketId);
|
|
||||||
// // continue;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // // Skip duplicates (DO NOT delete them — just don't count)
|
|
||||||
// // if (seen.has(username)) {
|
|
||||||
// // continue;
|
|
||||||
// // }
|
|
||||||
// // // socket exists and is connected
|
|
||||||
// // seen.add(username);
|
|
||||||
// // count++;
|
|
||||||
// // // console.log(color.green,"count: ", count);
|
|
||||||
// // console.log(color.yellow, 'Client:', color.reset, username);
|
|
||||||
|
|
||||||
// // const targetSocketId = target;
|
|
||||||
// // io.to(targetSocketId!).emit('listObj', username);
|
|
||||||
|
|
||||||
// // console.log(
|
|
||||||
// // color.yellow,
|
|
||||||
// // 'Chat Socket ID:',
|
|
||||||
// // color.reset,
|
|
||||||
// // socketId,
|
|
||||||
// // );
|
|
||||||
// // continue;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // // If no io provided, assume entries in the map are valid and count them.
|
|
||||||
// // count++;
|
|
||||||
// // console.log(
|
|
||||||
// // color.red,
|
|
||||||
// // 'Client (unverified):',
|
|
||||||
// // color.reset,
|
|
||||||
// // username,
|
|
||||||
// // );
|
|
||||||
// // console.log(
|
|
||||||
// // color.red,
|
|
||||||
// // 'Chat Socket ID (unverified):',
|
|
||||||
// // color.reset,
|
|
||||||
// // socketId,
|
|
||||||
// // );
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // return count;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // function broadcast(data: ClientMessage, sender?: string) {
|
|
||||||
// // fastify.io.fetchSockets().then((sockets) => {
|
|
||||||
// // for (const s of sockets) {
|
|
||||||
// // if (s.id !== sender) {
|
|
||||||
// // // Send REAL JSON object
|
|
||||||
// // const clientName = clientChat.get(s.id) || null;
|
|
||||||
// // if (clientName !== null) {
|
|
||||||
// // s.emit('MsgObjectServer', { message: data });
|
|
||||||
// // }
|
|
||||||
// // console.log(' Target window socket ID:', s.id);
|
|
||||||
// // console.log(' Target window ID:', [...s.rooms]);
|
|
||||||
// // console.log(' Sender window ID:', sender ? sender : 'none');
|
|
||||||
// // }
|
|
||||||
// // }
|
|
||||||
// // });
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // fastify.io.on('connection', (socket: Socket) => {
|
|
||||||
// // socket.on('message', (message: string) => {
|
|
||||||
// // console.info(
|
|
||||||
// // color.blue,
|
|
||||||
// // 'Socket connected!',
|
|
||||||
// // color.reset,
|
|
||||||
// // socket.id,
|
|
||||||
// // );
|
|
||||||
// // console.log(
|
|
||||||
// // color.blue,
|
|
||||||
// // 'Received message from client',
|
|
||||||
// // color.reset,
|
|
||||||
// // message,
|
|
||||||
// // );
|
|
||||||
|
|
||||||
// // const obj: ClientMessage = JSON.parse(message) as ClientMessage;
|
|
||||||
// // clientChat.set(socket.id, obj.user);
|
|
||||||
// // console.log(
|
|
||||||
// // color.green,
|
|
||||||
// // 'Message from client',
|
|
||||||
// // color.reset,
|
|
||||||
// // `Sender: login name: "${obj.user}" - windowID "${obj.SenderWindowID}" - text message: "${obj.text}"`,
|
|
||||||
// // );
|
|
||||||
// // // Send object directly — DO NOT wrap it in a string
|
|
||||||
// // broadcast(obj, obj.SenderWindowID);
|
|
||||||
// // console.log(
|
|
||||||
// // color.red,
|
|
||||||
// // 'connected in the Chat :',
|
|
||||||
// // connectedUser(fastify.io),
|
|
||||||
// // color.reset,
|
|
||||||
// // );
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // socket.on('testend', (sock_id_cl: string) => {
|
|
||||||
// // console.log('testend received from client socket id:', sock_id_cl);
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // socket.on('list', () => {
|
|
||||||
// // console.log(color.red, 'list activated', color.reset, socket.id);
|
|
||||||
// // connectedUser(fastify.io, socket.id);
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // socket.on('disconnecting', (reason) => {
|
|
||||||
// // const clientName = clientChat.get(socket.id) || null;
|
|
||||||
// // console.log(
|
|
||||||
// // color.green,
|
|
||||||
// // `Client disconnecting: ${clientName} (${socket.id}) reason:`,
|
|
||||||
// // reason,
|
|
||||||
// // );
|
|
||||||
// // if (reason === 'transport error') return;
|
|
||||||
|
|
||||||
// // if (clientName !== null) {
|
|
||||||
// // const obj = {
|
|
||||||
// // type: 'chat',
|
|
||||||
// // user: clientName,
|
|
||||||
// // token: '',
|
|
||||||
// // text: 'LEFT the chat',
|
|
||||||
// // timestamp: Date.now(),
|
|
||||||
// // SenderWindowID: socket.id,
|
|
||||||
// // };
|
|
||||||
|
|
||||||
// // broadcast(obj, obj.SenderWindowID);
|
|
||||||
// // // clientChat.delete(obj.user);
|
|
||||||
// // }
|
|
||||||
// // });
|
|
||||||
// // });
|
|
||||||
// // }
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// @file run.ts
|
|
||||||
// @brief The entrypoint to the service.
|
|
||||||
|
|
||||||
// Entry point of the microservice, ran by the Dockerfile.
|
|
||||||
|
|
||||||
import fastify, { FastifyInstance } from 'fastify';
|
|
||||||
import app from './app';
|
|
||||||
// TODO: Import the microservice app
|
|
||||||
|
|
||||||
// @brief Entrypoint for the microservice's backend.
|
|
||||||
const start = async () => {
|
|
||||||
// TODO: Thingies to send to log service (if I understood that correctly from /src/chat/src/run.ts)
|
|
||||||
|
|
||||||
// TODO: Add the logging thingy to the call to fastify()
|
|
||||||
const fastInst: FastifyInstance = fastify();
|
|
||||||
try {
|
|
||||||
process.on('SIGTERM', () => {
|
|
||||||
fastInst.log.info('Requested to shutdown');
|
|
||||||
process.exit(143);
|
|
||||||
});
|
|
||||||
// TODO: Uncomment when app.ts will be import-able.
|
|
||||||
await fastInst.register(app);
|
|
||||||
await fastInst.listen({ port: 80, host: '0.0.0.0' });
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
fastInst.log.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../tsconfig.base.json",
|
|
||||||
"compilerOptions": {},
|
|
||||||
"include": ["src/**/*.ts"]
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
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: '"tic-tac-toe"',
|
|
||||||
},
|
|
||||||
// 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
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
@ -26,12 +26,12 @@
|
||||||
"fastify": "^5.6.2",
|
"fastify": "^5.6.2",
|
||||||
"fastify-cli": "^7.4.1",
|
"fastify-cli": "^7.4.1",
|
||||||
"fastify-plugin": "^5.1.0",
|
"fastify-plugin": "^5.1.0",
|
||||||
"typebox": "^1.0.61"
|
"typebox": "^1.0.63"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.19.2",
|
"@types/node": "^22.19.3",
|
||||||
"rollup-plugin-node-externals": "^8.1.2",
|
"rollup-plugin-node-externals": "^8.1.2",
|
||||||
"vite": "^7.2.7",
|
"vite": "^7.3.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4"
|
"vite-tsconfig-paths": "^5.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue