1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-27 06:23:53 +01:00

chat modularization

This commit is contained in:
Marek Farkaš
2026-02-08 20:54:23 +01:00
parent 34e73a9e7f
commit 32782d6704
2 changed files with 125 additions and 80 deletions

121
server/chat.js Normal file
View File

@@ -0,0 +1,121 @@
const WebSocket = require('ws');
const { serverConfig } = require('./server_config');
const { logChat } = require('./console');
const helpers = require('./helpers');
function createChatServer(storage) {
if (!serverConfig.webserver.chatEnabled) {
return null;
}
const chatWss = new WebSocket.Server({ noServer: true });
chatWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
return;
}
// Send chat history safely
storage.chatHistory.forEach((message) => {
const historyMessage = { ...message, history: true };
if (!request.session?.isAdminAuthenticated) {
delete historyMessage.ip;
}
ws.send(JSON.stringify(historyMessage));
});
const ipMessage = {
type: 'clientIp',
ip: clientIp,
admin: request.session?.isAdminAuthenticated
};
ws.send(JSON.stringify(ipMessage));
const userCommands = {};
let lastWarn = { time: 0 };
ws.on('message', (message) => {
helpers.antispamProtection(
message,
clientIp,
ws,
userCommands,
lastWarn,
userCommandHistory,
'5',
'chat'
);
let messageData;
try {
messageData = JSON.parse(message);
} catch {
ws.send(JSON.stringify({ error: "Invalid message format" }));
return;
}
console.log("Chat message:", messageData);
delete messageData.admin;
delete messageData.ip;
delete messageData.time;
if (messageData.nickname != null) {
messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
}
messageData.ip = clientIp;
const now = new Date();
messageData.time =
String(now.getHours()).padStart(2, '0') +
":" +
String(now.getMinutes()).padStart(2, '0');
if (serverConfig.webserver.banlist?.includes(clientIp)) return;
if (request.session?.isAdminAuthenticated === true) {
messageData.admin = true;
}
if (messageData.nickname?.length > 32) {
messageData.nickname = messageData.nickname.substring(0, 32);
}
if (messageData.message?.length > 255) {
messageData.message = messageData.message.substring(0, 255);
}
storage.chatHistory.push(messageData);
if (storage.chatHistory.length > 50) {
storage.chatHistory.shift();
}
logChat(messageData);
chatWss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
const responseMessage = { ...messageData };
if (!request.session?.isAdminAuthenticated) {
delete responseMessage.ip;
}
client.send(JSON.stringify(responseMessage));
}
});
});
});
return chatWss; // ← VERY IMPORTANT
}
module.exports = { createChatServer };

View File

@@ -9,7 +9,6 @@ const app = express();
const httpServer = http.createServer(app); const httpServer = http.createServer(app);
const WebSocket = require('ws'); const WebSocket = require('ws');
const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true }); const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const chatWss = new WebSocket.Server({ noServer: true });
const rdsWss = new WebSocket.Server({ noServer: true }); const rdsWss = new WebSocket.Server({ noServer: true });
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true }); const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const fs = require('fs'); const fs = require('fs');
@@ -19,6 +18,7 @@ const client = new net.Socket();
const { SerialPort } = require('serialport'); const { SerialPort } = require('serialport');
const audioServer = require('./stream/3las.server'); const audioServer = require('./stream/3las.server');
const tunnel = require('./tunnel'); const tunnel = require('./tunnel');
const { createChatServer } = require('./chat');
// File imports // File imports
const helpers = require('./helpers'); const helpers = require('./helpers');
@@ -90,6 +90,8 @@ console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
console.log("v" + pjson.version) console.log("v" + pjson.version)
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m'); console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
const chatWss = createChatServer(storage);
// Start ffmpeg // Start ffmpeg
require('./stream/index'); require('./stream/index');
require('./plugins'); require('./plugins');
@@ -580,84 +582,6 @@ wss.on('connection', (ws, request) => {
ws.on('error', console.error); ws.on('error', console.error);
}); });
// CHAT WEBSOCKET BLOCK
chatWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
const userCommandHistory = {};
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
return;
}
// Send chat history to the newly connected client
storage.chatHistory.forEach(function(message) {
message.history = true;
!request.session.isAdminAuthenticated ? delete message.ip : null;
ws.send(JSON.stringify(message));
});
const ipMessage = {
type: 'clientIp',
ip: clientIp,
admin: request.session.isAdminAuthenticated
};
ws.send(JSON.stringify(ipMessage));
// Anti-spam tracking for each client
const userCommands = {};
let lastWarn = { time: 0 };
ws.on('message', function incoming(message) {
// Anti-spam
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'chat');
let messageData;
try {
messageData = JSON.parse(message);
} catch (error) {
ws.send(JSON.stringify({ error: "Invalid message format" }));
return;
}
// Escape nickname and other potentially unsafe fields
if (messageData.nickname) {
messageData.nickname = helpers.escapeHtml(messageData.nickname);
}
messageData.ip = clientIp;
const currentTime = new Date();
const hours = String(currentTime.getHours()).padStart(2, '0');
const minutes = String(currentTime.getMinutes()).padStart(2, '0');
messageData.time = `${hours}:${minutes}`; // Adding current time to the message object in hours:minutes format
if (serverConfig.webserver.banlist?.includes(clientIp)) { return; }
if (request.session.isAdminAuthenticated === true) { messageData.admin = true; }
if (messageData.message.length > 255) { messageData.message = messageData.message.substring(0, 255); }
storage.chatHistory.push(messageData);
if (storage.chatHistory.length > 50) { storage.chatHistory.shift(); }
logChat(messageData);
chatWss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
// Only include IP for admin clients
let responseMessage = { ...messageData };
if (request.session.isAdminAuthenticated !== true) {
delete responseMessage.ip;
}
const modifiedMessage = JSON.stringify(responseMessage);
client.send(modifiedMessage);
}
});
});
ws.on('close', function close() {});
});
// Additional web socket for using plugins // Additional web socket for using plugins
pluginsWss.on('connection', (ws, request) => { pluginsWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress; const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
@@ -739,7 +663,7 @@ httpServer.on('upgrade', (request, socket, head) => {
logWarn('[Audio WebSocket] Audio server not ready — dropping client connection.'); logWarn('[Audio WebSocket] Audio server not ready — dropping client connection.');
socket.destroy(); socket.destroy();
} }
} else if (request.url === '/chat') { } else if (request.url === '/chat' && serverConfig.webserver.chatEnabled === true) {
sessionMiddleware(request, {}, () => { sessionMiddleware(request, {}, () => {
chatWss.handleUpgrade(request, socket, head, (ws) => { chatWss.handleUpgrade(request, socket, head, (ws) => {
chatWss.emit('connection', ws, request); chatWss.emit('connection', ws, request);