mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-31 01:09:18 +02:00
Compare commits
11
Commits
50156b5b26
...
c9dc6082dd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9dc6082dd
|
||
|
|
53f1401b88
|
||
|
|
6f4e2beabc
|
||
|
|
c5f3373770
|
||
|
|
e55e9964b0
|
||
|
|
ee7a1c9d0f
|
||
|
|
e574f326b5
|
||
|
|
88c2a3783f
|
||
|
|
11328a9fc5
|
||
|
|
72bac6e6f1
|
||
|
|
82709ea421
|
+5
-3
@@ -1,13 +1,14 @@
|
||||
const WebSocket = require('ws');
|
||||
const { serverConfig } = require('./server_config');
|
||||
const { serverConfig, configExists } = require('./server_config');
|
||||
const { logChat } = require('./console');
|
||||
const helpers = require('./helpers');
|
||||
const storage = require('./storage.js');
|
||||
|
||||
function heartbeat() { // WebSocket heartbeat helper
|
||||
this.isAlive = true;
|
||||
}
|
||||
|
||||
function createChatServer(storage) {
|
||||
function createChatServer() {
|
||||
if (!serverConfig.webserver.chatEnabled) return;
|
||||
|
||||
const chatWss = new WebSocket.Server({ noServer: true });
|
||||
@@ -119,4 +120,5 @@ function createChatServer(storage) {
|
||||
storage.websocket_delegation.set("/chat", chatWss);
|
||||
}
|
||||
|
||||
module.exports = { createChatServer };
|
||||
if(!configExists()) return;
|
||||
createChatServer();
|
||||
+1
-1
@@ -3,7 +3,7 @@ const fs = require('fs').promises;
|
||||
const verboseMode = process.argv.includes('--debug');
|
||||
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
||||
|
||||
const LOG_FILE = process.argv.includes('--config') && process.argv[process.argv.indexOf('--config') + 1]
|
||||
const LOG_FILE = process.argv.includes('--config') && process.argv[process.argv.indexOf('--config') + 1]
|
||||
? `serverlog_${process.argv[process.argv.indexOf('--config') + 1]}.txt`
|
||||
: 'serverlog.txt';
|
||||
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* Libraries / Imports */
|
||||
const { send_xrd_online } = require('./xrd_server');
|
||||
const { send_xdr_online } = require('./xdr_server');
|
||||
const RDSDecoder = require("./rds.js");
|
||||
const { serverConfig } = require('./server_config');
|
||||
|
||||
@@ -90,7 +90,7 @@ function handleData(wss, receivedData, rdsWss) {
|
||||
|
||||
let modifiedData, parsedValue;
|
||||
const receivedLines = receivedData.split('\n');
|
||||
|
||||
|
||||
for (const receivedLine of receivedLines) {
|
||||
switch (true) {
|
||||
case receivedLine.startsWith('F'): // Bandwidth
|
||||
@@ -274,7 +274,7 @@ checkSerialPortStatus();
|
||||
function showOnlineUsers(currentUsers) {
|
||||
dataToSend.users = currentUsers;
|
||||
initialData.users = currentUsers;
|
||||
send_xrd_online(currentUsers);
|
||||
send_xdr_online(currentUsers);
|
||||
}
|
||||
|
||||
let prevFreq = initialData.freq || '87.500';
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ const dataHandler = require('./datahandler');
|
||||
const storage = require('./storage');
|
||||
const consoleCmd = require('./console');
|
||||
const { serverConfig, configSave } = require('./server_config');
|
||||
const { send_to_xrd } = require("./xrd_server")
|
||||
const { send_to_xdr } = require("./xdr_server")
|
||||
|
||||
let geoip = null;
|
||||
try {
|
||||
@@ -230,7 +230,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
|
||||
.join(', ')
|
||||
: 'Unknown';
|
||||
const userLocationForLog = locationInfo?.isp ? `${userLocation} (${locationInfo.isp})` : userLocation;
|
||||
|
||||
|
||||
storage.connectedUsers.push({
|
||||
ip: clientIp,
|
||||
location: userLocation,
|
||||
@@ -276,7 +276,7 @@ function resolveDataBuffer(data, wss, rdsWss) {
|
||||
|
||||
if (receivedData.length) {
|
||||
dataHandler.handleData(wss, receivedData, rdsWss);
|
||||
send_to_xrd(receivedData);
|
||||
send_to_xdr(receivedData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-315
@@ -1,76 +1,36 @@
|
||||
const express = require('express');
|
||||
const endpoints = require('./endpoints');
|
||||
const session = require('express-session');
|
||||
const bodyParser = require('body-parser');
|
||||
const http = require('http');
|
||||
const readline = require('readline');
|
||||
const app = express();
|
||||
const httpServer = http.createServer(app);
|
||||
const WebSocket = require('ws');
|
||||
const path = require('path');
|
||||
const net = require('net');
|
||||
const { SerialPort } = require('serialport');
|
||||
const tunnel = require('./tunnel');
|
||||
const { createChatServer } = require('./chat');
|
||||
const figlet = require('figlet');
|
||||
|
||||
const helpers = require('./helpers');
|
||||
const { findServerFiles, startPluginsWithDelay } = helpers;
|
||||
|
||||
const dataHandler = require('./datahandler');
|
||||
const fmdxList = require('./fmdx_list');
|
||||
const { logError, logInfo, logWarn } = require('./console');
|
||||
const storage = require('./storage');
|
||||
const { serverConfig, configExists } = require('./server_config');
|
||||
const pluginsApi = require('./plugins_api');
|
||||
const pjson = require('../package.json');
|
||||
const { startServer, wss, rdsWss } = require("./web");
|
||||
|
||||
const client = new net.Socket();
|
||||
const wss = new WebSocket.Server({ noServer: true });
|
||||
const rdsWss = new WebSocket.Server({ noServer: true });
|
||||
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
|
||||
|
||||
storage.websocket_delegation.set("/text", wss);
|
||||
storage.websocket_delegation.set("/rds", rdsWss);
|
||||
storage.websocket_delegation.set("/rdsspy", rdsWss);
|
||||
storage.websocket_delegation.set("/data_plugins", pluginsWss);
|
||||
require('./stream/ws.js');
|
||||
const client = new (require('net')).Socket();
|
||||
|
||||
// Get all plugins from config and find corresponding server files
|
||||
const plugins = findServerFiles(serverConfig.plugins);
|
||||
const plugins = helpers.findServerFiles(serverConfig.plugins);
|
||||
|
||||
// Start the first plugin after 3 seconds, then the rest with 3 seconds delay
|
||||
if (plugins.length > 0) {
|
||||
setTimeout(() => {
|
||||
startPluginsWithDelay(plugins, 3000); // Start plugins with 3 seconds interval
|
||||
helpers.startPluginsWithDelay(plugins, 3000); // Start plugins with 3 seconds interval
|
||||
}, 4000); // Initial delay of 4 seconds for the first plugin
|
||||
}
|
||||
|
||||
const terminalWidth = readline.createInterface({input: process.stdin, output: process.stdout}).output.columns;
|
||||
const terminalWidth = require('readline').createInterface({input: process.stdin, output: process.stdout}).output.columns;
|
||||
|
||||
console.log('\x1b[32m' + figlet.textSync("FM-DX Webserver"));
|
||||
console.log('\x1b[32m' + require('figlet').textSync("FM-DX Webserver"));
|
||||
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org + KubaPro010\x1b[0m');
|
||||
console.log("v" + pjson.version)
|
||||
console.log("v" + require('../package.json').version)
|
||||
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
|
||||
|
||||
require('./plugins');
|
||||
|
||||
let currentUsers = 0;
|
||||
let serialport;
|
||||
let timeoutAntenna;
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
const sessionMiddleware = session({
|
||||
secret: 'GTce3tN6U8odMwoI', // Cool
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.json());
|
||||
createChatServer(storage);
|
||||
|
||||
tunnel.download();
|
||||
require('./stream/index');
|
||||
connectToXdrd();
|
||||
connectToSerial();
|
||||
|
||||
@@ -117,7 +77,7 @@ function connectToSerial() {
|
||||
pluginsApi.setOutput(serialport);
|
||||
setTimeout(() => {
|
||||
serialport.write('x\n');
|
||||
}, 2500);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
serialport.write('Q0\n');
|
||||
@@ -268,272 +228,7 @@ client.on('error', (err) => {
|
||||
}
|
||||
});
|
||||
|
||||
const tunerLockTracker = new WeakMap();
|
||||
const ipConnectionCounts = new Map(); // Per-IP limit variables
|
||||
const ipLogTimestamps = new Map();
|
||||
const MAX_CONNECTIONS_PER_IP = 4;
|
||||
const IP_LOG_INTERVAL_MS = 60000;
|
||||
// Remove old per-IP limit addresses
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [ip, count] of ipConnectionCounts.entries()) {
|
||||
const lastSeen = ipLogTimestamps.get(ip) || 0;
|
||||
const inactive = now - lastSeen > 60 * 60 * 1000;
|
||||
|
||||
if (count === 0 && inactive) {
|
||||
ipConnectionCounts.delete(ip);
|
||||
ipLogTimestamps.delete(ip);
|
||||
}
|
||||
}
|
||||
}, 30 * 60 * 1000); // Run every half hour
|
||||
|
||||
wss.on('connection', (ws, request) => {
|
||||
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
||||
let clientIp = helpers.getIpAddress(request);
|
||||
const userCommandHistory = {};
|
||||
|
||||
// Per-IP limit connection open
|
||||
if (clientIp) {
|
||||
const isLocalIp = (
|
||||
clientIp === '127.0.0.1' || clientIp === '::1' ||
|
||||
clientIp.startsWith('192.168.') || clientIp.startsWith('10.') || clientIp.startsWith('172.16.'));
|
||||
if (!isLocalIp) {
|
||||
if (!ipConnectionCounts.has(clientIp)) ipConnectionCounts.set(clientIp, 0);
|
||||
const currentCount = ipConnectionCounts.get(clientIp);
|
||||
if (currentCount >= MAX_CONNECTIONS_PER_IP) {
|
||||
ws.close(1008, 'Too many open connections from this IP');
|
||||
const lastLogTime = ipLogTimestamps.get(clientIp) || 0;
|
||||
const now = Date.now();
|
||||
if (now - lastLogTime > IP_LOG_INTERVAL_MS) {
|
||||
logWarn(`Web client \x1b[31mclosed: limit exceeded\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
|
||||
ipLogTimestamps.set(clientIp, now);
|
||||
}
|
||||
return;
|
||||
}
|
||||
ipConnectionCounts.set(clientIp, currentCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIp !== '127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers++;
|
||||
}
|
||||
|
||||
if (timeoutAntenna) clearTimeout(timeoutAntenna);
|
||||
|
||||
helpers.handleConnect(clientIp, currentUsers, ws, (result) => {
|
||||
if (result === "User banned") {
|
||||
ws.close(1008, 'Banned IP');
|
||||
return;
|
||||
}
|
||||
dataHandler.showOnlineUsers(currentUsers);
|
||||
|
||||
if (currentUsers === 1 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) serverConfig.xdrd.wirelessConnection ? connectToXdrd() : serialport.write('x\n');
|
||||
});
|
||||
|
||||
const userCommands = {};
|
||||
let lastWarn = { time: 0 };
|
||||
|
||||
ws.on('message', (message) => {
|
||||
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024);
|
||||
|
||||
if (!clientIp.includes("127.0.0.1")) {
|
||||
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
|
||||
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
|
||||
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command: ${command.slice(0, 64)}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.includes("\'")) return;
|
||||
|
||||
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
|
||||
|
||||
if (command.startsWith('w') && (isAdminAuthenticated || isTuneAuthenticated)) {
|
||||
switch (command) {
|
||||
case 'wL1':
|
||||
if (isAdminAuthenticated) serverConfig.lockToAdmin = true;
|
||||
break;
|
||||
case 'wL0':
|
||||
if (isAdminAuthenticated) serverConfig.lockToAdmin = false;
|
||||
break;
|
||||
case 'wT0':
|
||||
serverConfig.publicTuner = true;
|
||||
if(!isAdminAuthenticated) tunerLockTracker.delete(ws);
|
||||
break;
|
||||
case 'wT1':
|
||||
serverConfig.publicTuner = false;
|
||||
if(!isAdminAuthenticated) tunerLockTracker.set(ws, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.startsWith('T')) {
|
||||
const tuneFreq = Number(command.slice(1)) / 1000;
|
||||
const { tuningLimit, tuningLowerLimit, tuningUpperLimit } = serverConfig.webserver;
|
||||
|
||||
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) return;
|
||||
}
|
||||
|
||||
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) output.write(`${command}\n`);
|
||||
});
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
// Per-IP limit connection closed
|
||||
if (clientIp) {
|
||||
const isLocalIp = (
|
||||
clientIp === '127.0.0.1' ||
|
||||
clientIp === '::1' ||
|
||||
clientIp.startsWith('192.168.') ||
|
||||
clientIp.startsWith('10.') ||
|
||||
clientIp.startsWith('172.16.')
|
||||
);
|
||||
if (!isLocalIp) {
|
||||
const current = ipConnectionCounts.get(clientIp) || 1;
|
||||
ipConnectionCounts.set(clientIp, Math.max(0, current - 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIp !== ':127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers--;
|
||||
}
|
||||
dataHandler.showOnlineUsers(currentUsers);
|
||||
|
||||
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
|
||||
if (index !== -1) storage.connectedUsers.splice(index, 1);
|
||||
|
||||
if (currentUsers === 0) {
|
||||
storage.connectedUsers = [];
|
||||
|
||||
if (serverConfig.bwAutoNoUsers === "1") output.write("W0\n"); // Auto BW 'Enabled'
|
||||
|
||||
// cEQ and iMS combinations
|
||||
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") output.write("G00\n"); // Both Disabled
|
||||
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") output.write(`G0${dataHandler.dataToSend.ims}\n`);
|
||||
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") output.write(`G${dataHandler.dataToSend.eq}0\n`);
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") output.write(`G1${dataHandler.dataToSend.ims}\n`);
|
||||
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") output.write(`G${dataHandler.dataToSend.eq}1\n`);
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") output.write("G10\n"); // Only cEQ enabled
|
||||
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") output.write("G01\n"); // Only iMS enabled
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") output.write("G11\n"); // Both Enabled
|
||||
|
||||
// Handle stereo mode
|
||||
if (serverConfig.stereoNoUsers === "1") output.write("B0\n");
|
||||
else if (serverConfig.stereoNoUsers === "2") output.write("B1\n");
|
||||
|
||||
// Handle Antenna selection
|
||||
if (timeoutAntenna) clearTimeout(timeoutAntenna);
|
||||
timeoutAntenna = setTimeout(() => {
|
||||
if (serverConfig.antennaNoUsers === "1") output.write("Z0\n");
|
||||
else if (serverConfig.antennaNoUsers === "2") output.write("Z1\n");
|
||||
else if (serverConfig.antennaNoUsers === "3") output.write("Z2\n");
|
||||
else if (serverConfig.antennaNoUsers === "4") output.write("Z3\n");
|
||||
}, serverConfig.antennaNoUsersDelay ? 15000 : 0);
|
||||
}
|
||||
|
||||
if (tunerLockTracker.has(ws)) {
|
||||
logInfo(`User who locked the tuner left. Unlocking the tuner.`);
|
||||
output.write('wT0\n')
|
||||
tunerLockTracker.delete(ws);
|
||||
serverConfig.publicTuner = true;
|
||||
}
|
||||
|
||||
if (currentUsers === 0 && serverConfig.enableDefaultFreq === true &&
|
||||
serverConfig.autoShutdown !== true && serverConfig.xdrd.wirelessConnection === true) {
|
||||
setTimeout(function() {
|
||||
if (currentUsers === 0) {
|
||||
output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
|
||||
dataHandler.resetToDefault(dataHandler.dataToSend);
|
||||
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
|
||||
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
if (currentUsers === 0 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection === true) client.write('X\n');
|
||||
|
||||
if (code !== 1008) logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
|
||||
});
|
||||
|
||||
ws.on('error', console.error);
|
||||
});
|
||||
|
||||
// Additional web socket for using plugins
|
||||
pluginsWss.on('connection', (ws, request) => {
|
||||
const clientIp = helpers.getIpAddress(request);
|
||||
const userCommandHistory = {};
|
||||
// Anti-spam tracking for each client
|
||||
const userCommands = {};
|
||||
let lastWarn = { time: 0 };
|
||||
|
||||
ws.on('message', message => {
|
||||
// Anti-spam
|
||||
helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins');
|
||||
|
||||
try {
|
||||
let messageData = JSON.parse(message); // Attempt to parse the JSON
|
||||
|
||||
if (messageData.type === "GPS" && messageData.value) {
|
||||
const gpsData = messageData.value;
|
||||
const { status, time, lat, lon, alt, mode } = gpsData;
|
||||
|
||||
if (status === "active") {
|
||||
Latitude = parseFloat(lat);
|
||||
Longitude = parseFloat(lon);
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
// Broadcast the message to all other clients
|
||||
pluginsWss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) client.send(message); // Send the message to all clients
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('error', error => {
|
||||
logError('WebSocket Extra error: ' + error); // Use custom logError function
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
if (serverConfig.webserver.banlist?.includes(helpers.getIpAddress(request))) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const upgradeWss = storage.websocket_delegation.get(request.url);
|
||||
if(upgradeWss) {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
upgradeWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
upgradeWss.emit('connection', ws, request);
|
||||
});
|
||||
});
|
||||
} else socket.destroy();
|
||||
});
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../views'));
|
||||
app.use('/', endpoints);
|
||||
app.use(express.static(path.join(__dirname, '../web'))); // Serve the entire web folder to the user
|
||||
pluginsApi.registerServerContext({ wss, pluginsWss, httpServer, serverConfig });
|
||||
|
||||
const logServerStart = (address) => {
|
||||
logInfo(`Web server has started on address \x1b[34mhttp://${address}:${serverConfig.webserver.webserverPort}\x1b[0m.`);
|
||||
};
|
||||
|
||||
const startServer = (address) => {
|
||||
httpServer.listen(serverConfig.webserver.webserverPort, address, () => {
|
||||
if (!configExists()) logInfo(`Open your browser and proceed to \x1b[34mhttp://${address}:${serverConfig.webserver.webserverPort}\x1b[0m to continue with setup.`);
|
||||
else logServerStart(address);
|
||||
});
|
||||
};
|
||||
|
||||
require('./stream/index');
|
||||
startServer(serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp);
|
||||
tunnel.connect();
|
||||
fmdxList.update();
|
||||
require('./fmdx_list').update();
|
||||
+1
-1
@@ -38,7 +38,7 @@ function parsePluginConfig(filePath) {
|
||||
if (fs.existsSync(destinationFile)) fs.unlinkSync(destinationFile); // Remove existing file/symlink
|
||||
fs.symlinkSync(sourcePath, destinationFile);
|
||||
setTimeout(function() {
|
||||
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
|
||||
consoleCmd.logInfo(`Plugin ${pluginConfig.name} ${pluginConfig.version} initialized successfully.`);
|
||||
}, 500)
|
||||
} catch (err) {
|
||||
console.error(`Error creating symlink at ${destinationFile}: ${err.message}`);
|
||||
|
||||
+1
-1
@@ -236,7 +236,7 @@ class RDSDecoder {
|
||||
if(d_error > 2) return; // Don't risk it
|
||||
|
||||
const idx = blockB & 0x3;
|
||||
|
||||
|
||||
const last_err = this.ps_errors[idx * 2];
|
||||
const cur_err = Math.ceil(d_error * (10/3)); // They expect an error score of 0-10 with 10 being the worst. Too bad we don't have that resolution
|
||||
const character_a = decode_charset(blockD >> 8);
|
||||
|
||||
@@ -153,7 +153,7 @@ function configUpdate(newConfig) {
|
||||
serverConfig.plugins = newConfig.plugins;
|
||||
delete newConfig.webserver.banlist;
|
||||
}
|
||||
|
||||
|
||||
deepMerge(serverConfig, newConfig); // Overwrite with newConfig values
|
||||
configSave();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
const { PassThrough } = require('stream');
|
||||
const audio_pipe = new PassThrough();
|
||||
module.exports = audio_pipe; // Important
|
||||
|
||||
const { serverConfig, configExists } = require('../server_config');
|
||||
if (!configExists()) return;
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const { serverConfig } = require('../server_config');
|
||||
const { logDebug, logError, logInfo, logWarn, logFfmpeg } = require('../console');
|
||||
const checkFFmpeg = require('./checkFFmpeg');
|
||||
const { PassThrough } = require('stream');
|
||||
|
||||
const consoleLogTitle = '[Audio Stream]';
|
||||
|
||||
@@ -15,7 +20,6 @@ function connectMessage(message) {
|
||||
}
|
||||
}
|
||||
|
||||
const audio_pipe = new PassThrough();
|
||||
|
||||
checkFFmpeg().then((ffmpegPath) => {
|
||||
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
|
||||
@@ -137,6 +141,4 @@ checkFFmpeg().then((ffmpegPath) => {
|
||||
|
||||
}).catch((err) => {
|
||||
logError(`${consoleLogTitle} Error: ${err.message}`);
|
||||
});
|
||||
|
||||
module.exports = audio_pipe;
|
||||
});
|
||||
+1
-3
@@ -11,9 +11,7 @@ audio_pipe.on('data', (chunk) => {
|
||||
});
|
||||
|
||||
audio_pipe.on('end', () => {
|
||||
audioWss.clients.forEach((client) => {
|
||||
client.close(1001, "Audio stream ended");
|
||||
});
|
||||
audioWss.clients.forEach((client) => client.close(1001, "Audio stream ended"));
|
||||
});
|
||||
|
||||
storage.websocket_delegation.set("/audio", audioWss);
|
||||
+4
-4
@@ -20,7 +20,7 @@ async function download() {
|
||||
if (!await fileExists(frpcPath)) {
|
||||
logInfo('frpc binary, required for tunnel is not available. Downloading now...');
|
||||
const frpcFileName = `frpc_${os.platform}_${os.arch}` + (os.platform() === 'win32' ? '.exe' : '');
|
||||
|
||||
|
||||
try {
|
||||
const res = await fetch('https://fmtuner.org/binaries/' + frpcFileName);
|
||||
if (res.status === 404) throw new Error('404 error');
|
||||
@@ -40,7 +40,7 @@ async function connect() {
|
||||
if (serverConfig.tunnel?.enabled === true) {
|
||||
const librariesDir = path.resolve(__dirname, '../libraries');
|
||||
const frpcPath = path.resolve(librariesDir, 'frpc' + (os.platform() === 'win32' ? '.exe' : ''));
|
||||
|
||||
|
||||
const cfg = ejs.render(frpcConfigTemplate, {
|
||||
cfg: serverConfig.tunnel,
|
||||
host: serverConfig.tunnel.community.enabled ? serverConfig.tunnel.community.host : ((serverConfig.tunnel.region == "pldx") ? "pldx.duckdns.org" : (serverConfig.tunnel.region + ".fmtuner.org")),
|
||||
@@ -67,11 +67,11 @@ async function connect() {
|
||||
else if (line.includes('login to server success')) logInfo('Connection to tunnel server was successful');
|
||||
else logDebug('Tunnel log:', line);
|
||||
});
|
||||
|
||||
|
||||
child.on('error', (err) => {
|
||||
logError('Failed to start tunnel process:', err);
|
||||
});
|
||||
|
||||
|
||||
child.on('close', (code) => {
|
||||
logInfo(`Tunnel process exited with code ${code}`);
|
||||
});
|
||||
|
||||
+9
-7
@@ -1,5 +1,7 @@
|
||||
const { serverConfig, configExists } = require('./server_config');
|
||||
if(!configExists()) return;
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const { serverConfig } = require('./server_config');
|
||||
const consoleCmd = require('./console');
|
||||
|
||||
let localDb = {};
|
||||
@@ -154,10 +156,10 @@ function validPsCompare(rdsPs, stationPs) {
|
||||
|
||||
// Standardize the rdsPs string: replace spaces with underscores and convert to lowercase.
|
||||
const standardizedRdsPs = rdsPs.replace(/ /g, '_').toLowerCase();
|
||||
|
||||
|
||||
// Split stationPs into tokens (e.g., "__mdr___ _kultur_" -> ["__mdr___", "_kultur_"])
|
||||
const psTokens = stationPs.split(/\s+/).filter(token => token.length > 0).map(token => { const lower = token.toLowerCase(); return lower.length < 8 ? lower.padEnd(8, '_') : lower; });
|
||||
|
||||
|
||||
// Iterate through all tokens and check if any token yields at least three valid (non "_" ) matches.
|
||||
for (let token of psTokens) {
|
||||
// If total non "_" length of token is less than 3, allow match based on that length instead
|
||||
@@ -165,7 +167,7 @@ function validPsCompare(rdsPs, stationPs) {
|
||||
const minMatchLen = tokenLength > 2 ? 3 : tokenLength;
|
||||
// If the token's length does not match the standardized rdsPs length, skip this token.
|
||||
if (token.length !== standardizedRdsPs.length) continue;
|
||||
|
||||
|
||||
let matchCount = 0;
|
||||
for (let i = 0; i < standardizedRdsPs.length; i++) {
|
||||
// Skip this position if the character in standardizedRdsPs is an underscore.
|
||||
@@ -234,7 +236,7 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
if (filteredLocations.length > 1) {
|
||||
const extraFilteredLocations = filteredLocations.map(locData => ({
|
||||
...locData,
|
||||
stations: locData.stations.filter(station =>
|
||||
stations: locData.stations.filter(station =>
|
||||
station.ps?.toLowerCase().includes(
|
||||
rdsPs.replace(/ /g, '_').toLowerCase()
|
||||
) ?? false
|
||||
@@ -243,7 +245,7 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
|
||||
if (extraFilteredLocations.length > 0) filteredLocations = extraFilteredLocations;
|
||||
}
|
||||
|
||||
|
||||
for (let loc of filteredLocations) {
|
||||
loc = Object.assign(loc, loc.stations[0]);
|
||||
delete loc.stations;
|
||||
@@ -251,7 +253,7 @@ async function fetchTx(freq, piCode, rdsPs) {
|
||||
loc = Object.assign(loc, dist);
|
||||
loc.detectedByPireg = (loc.pireg === piCode.toUpperCase());
|
||||
}
|
||||
|
||||
|
||||
if (filteredLocations.length > 1) {
|
||||
// Check for any 10kW+ stations within 700km, and don't Es weight if any found.
|
||||
const tropoPriority = filteredLocations.some(loc => loc.distanceKm < 700 && loc.erp >= 10);
|
||||
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
const http = require('http');
|
||||
const WebSocket = require('ws');
|
||||
const storage = require('./storage.js');
|
||||
const { serverConfig, configExists } = require('./server_config');
|
||||
const path = require('path');
|
||||
const endpoints = require('./endpoints');
|
||||
const pluginsApi = require('./plugins_api');
|
||||
const { logError, logInfo, logWarn } = require('./console');
|
||||
const { send_to_xdr } = require("./xdr_server")
|
||||
const helpers = require('./helpers');
|
||||
const dataHandler = require('./datahandler');
|
||||
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const bodyParser = require('body-parser');
|
||||
const app = express();
|
||||
const httpServer = http.createServer(app);
|
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
const sessionMiddleware = session({
|
||||
secret: 'GTce3tN6U8odMwoI', // Cool
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.json());
|
||||
|
||||
const wss = new WebSocket.Server({ noServer: true });
|
||||
const rdsWss = new WebSocket.Server({ noServer: true });
|
||||
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
|
||||
require('./chat');
|
||||
|
||||
const tunerLockTracker = new WeakMap();
|
||||
const ipConnectionCounts = new Map(); // Per-IP limit variables
|
||||
const ipLogTimestamps = new Map();
|
||||
const MAX_CONNECTIONS_PER_IP = 4;
|
||||
const IP_LOG_INTERVAL_MS = 60000;
|
||||
const MAX_USERS = 12; // Server max
|
||||
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
|
||||
for (const [ip, count] of ipConnectionCounts.entries()) {
|
||||
const lastSeen = ipLogTimestamps.get(ip) || 0;
|
||||
const inactive = now - lastSeen > 60 * 60 * 1000;
|
||||
|
||||
if (count === 0 && inactive) {
|
||||
ipConnectionCounts.delete(ip);
|
||||
ipLogTimestamps.delete(ip);
|
||||
}
|
||||
}
|
||||
}, 30 * 60 * 1000); // Run every half hour (this is a stop sign ahh comment, i wrote it lol)
|
||||
|
||||
let currentUsers = 0;
|
||||
let timeoutAntenna;
|
||||
|
||||
wss.on('connection', (ws, request) => {
|
||||
const output = storage.ctl_output;
|
||||
let clientIp = helpers.getIpAddress(request);
|
||||
const userCommandHistory = {};
|
||||
|
||||
// Per-IP limit connection open
|
||||
if (clientIp) {
|
||||
const isLocalIp = (
|
||||
clientIp === '127.0.0.1' || clientIp === '::1' ||
|
||||
clientIp.startsWith('192.168.') || clientIp.startsWith('10.') || clientIp.startsWith('172.16.'));
|
||||
if (!isLocalIp) {
|
||||
if (!ipConnectionCounts.has(clientIp)) ipConnectionCounts.set(clientIp, 0);
|
||||
const currentCount = ipConnectionCounts.get(clientIp);
|
||||
if (currentCount >= MAX_CONNECTIONS_PER_IP) {
|
||||
ws.close(1008, 'Too many open connections from this IP');
|
||||
const lastLogTime = ipLogTimestamps.get(clientIp) || 0;
|
||||
const now = Date.now();
|
||||
if (now - lastLogTime > IP_LOG_INTERVAL_MS) {
|
||||
logWarn(`Web client \x1b[31mclosed: limit exceeded\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
|
||||
ipLogTimestamps.set(clientIp, now);
|
||||
} return;
|
||||
} ipConnectionCounts.set(clientIp, currentCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(currentUsers >= MAX_USERS) {
|
||||
// TODO: give a nice fucking unwelcome message on the ui (we kindly ask you to FUCK OFF?)
|
||||
ws.send("a0"); // This means not authenticated in XDR
|
||||
ws.close(1008, 'Too many users using this server at the moment.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientIp !== '127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers++;
|
||||
}
|
||||
|
||||
if (timeoutAntenna) clearTimeout(timeoutAntenna);
|
||||
|
||||
helpers.handleConnect(clientIp, currentUsers, ws, (result) => {
|
||||
if (result === "User banned") {
|
||||
ws.close(1008, 'Banned IP');
|
||||
return;
|
||||
}
|
||||
dataHandler.showOnlineUsers(currentUsers);
|
||||
|
||||
if (currentUsers === 1 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) serverConfig.xdrd.wirelessConnection ? connectToXdrd() : storage.ctl_output.write('x\n');
|
||||
});
|
||||
|
||||
const userCommands = {};
|
||||
let lastWarn = { time: 0 };
|
||||
|
||||
ws.on('message', (message) => {
|
||||
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024);
|
||||
|
||||
if (!clientIp.includes("127.0.0.1")) {
|
||||
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
|
||||
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
|
||||
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command: ${command.slice(0, 64)}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.includes("\'")) return;
|
||||
|
||||
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
|
||||
|
||||
if (command.startsWith('w') && (isAdminAuthenticated || isTuneAuthenticated)) {
|
||||
switch (command) {
|
||||
case 'wL1':
|
||||
if (isAdminAuthenticated) serverConfig.lockToAdmin = true;
|
||||
send_to_xdr("wL1\n");
|
||||
break;
|
||||
case 'wL0':
|
||||
if (isAdminAuthenticated) serverConfig.lockToAdmin = false;
|
||||
send_to_xdr("wL0\n");
|
||||
break;
|
||||
case 'wT0':
|
||||
serverConfig.publicTuner = true;
|
||||
if(!isAdminAuthenticated) tunerLockTracker.delete(ws);
|
||||
send_to_xdr("wT0\n");
|
||||
break;
|
||||
case 'wT1':
|
||||
serverConfig.publicTuner = false;
|
||||
if(!isAdminAuthenticated) tunerLockTracker.set(ws, true);
|
||||
send_to_xdr("wT1\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (command.startsWith('T')) {
|
||||
const tuneFreq = Number(command.slice(1)) / 1000;
|
||||
const { tuningLimit, tuningLowerLimit, tuningUpperLimit } = serverConfig.webserver;
|
||||
|
||||
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) return;
|
||||
}
|
||||
|
||||
if ((serverConfig.publicTuner && !serverConfig.lockToAdmin) || isAdminAuthenticated || (!serverConfig.publicTuner && !serverConfig.lockToAdmin && isTuneAuthenticated)) output.write(`${command}\n`);
|
||||
});
|
||||
|
||||
ws.on('close', (code) => {
|
||||
// Per-IP limit connection closed
|
||||
if (clientIp) {
|
||||
const isLocalIp = (
|
||||
clientIp === '127.0.0.1' ||
|
||||
clientIp === '::1' ||
|
||||
clientIp.startsWith('192.168.') ||
|
||||
clientIp.startsWith('10.') ||
|
||||
clientIp.startsWith('172.16.')
|
||||
);
|
||||
if (!isLocalIp) {
|
||||
const current = ipConnectionCounts.get(clientIp) || 1;
|
||||
ipConnectionCounts.set(clientIp, Math.max(0, current - 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (clientIp !== '127.0.0.1' ||
|
||||
(request.socket && request.socket.remoteAddress && request.socket.remoteAddress !== '::ffff:127.0.0.1') ||
|
||||
(request.headers && request.headers['origin'] && request.headers['origin'].trim() !== '')) {
|
||||
currentUsers--;
|
||||
} dataHandler.showOnlineUsers(currentUsers);
|
||||
|
||||
const index = storage.connectedUsers.findIndex(user => user.ip === clientIp);
|
||||
if (index !== -1) storage.connectedUsers.splice(index, 1);
|
||||
|
||||
if (currentUsers === 0) {
|
||||
storage.connectedUsers = [];
|
||||
|
||||
if (serverConfig.bwAutoNoUsers === "1") output.write("W0\n"); // Auto BW 'Enabled'
|
||||
|
||||
// cEQ and iMS combinations
|
||||
if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "1") output.write("G00\n"); // Both Disabled
|
||||
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "0") output.write(`G0${dataHandler.dataToSend.ims}\n`);
|
||||
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "1") output.write(`G${dataHandler.dataToSend.eq}0\n`);
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "0") output.write(`G1${dataHandler.dataToSend.ims}\n`);
|
||||
else if (serverConfig.ceqNoUsers === "0" && serverConfig.imsNoUsers === "2") output.write(`G${dataHandler.dataToSend.eq}1\n`);
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "1") output.write("G10\n"); // Only cEQ enabled
|
||||
else if (serverConfig.ceqNoUsers === "1" && serverConfig.imsNoUsers === "2") output.write("G01\n"); // Only iMS enabled
|
||||
else if (serverConfig.ceqNoUsers === "2" && serverConfig.imsNoUsers === "2") output.write("G11\n"); // Both Enabled
|
||||
|
||||
// Handle stereo mode
|
||||
if (serverConfig.stereoNoUsers === "1") output.write("B0\n");
|
||||
else if (serverConfig.stereoNoUsers === "2") output.write("B1\n");
|
||||
|
||||
// Handle Antenna selection
|
||||
if (timeoutAntenna) clearTimeout(timeoutAntenna);
|
||||
timeoutAntenna = setTimeout(() => {
|
||||
if (serverConfig.antennaNoUsers === "1") output.write("Z0\n");
|
||||
else if (serverConfig.antennaNoUsers === "2") output.write("Z1\n");
|
||||
else if (serverConfig.antennaNoUsers === "3") output.write("Z2\n");
|
||||
else if (serverConfig.antennaNoUsers === "4") output.write("Z3\n");
|
||||
}, serverConfig.antennaNoUsersDelay ? 15000 : 0);
|
||||
}
|
||||
|
||||
if (tunerLockTracker.has(ws)) {
|
||||
logInfo(`User who locked the tuner left. Unlocking the tuner.`);
|
||||
output.write('wT0\n') // Why are we telling the tuner that?
|
||||
tunerLockTracker.delete(ws);
|
||||
serverConfig.publicTuner = true;
|
||||
}
|
||||
|
||||
if (currentUsers === 0 && serverConfig.enableDefaultFreq === true &&
|
||||
serverConfig.autoShutdown !== true && serverConfig.xdrd.wirelessConnection === true) {
|
||||
setTimeout(function() {
|
||||
if (currentUsers === 0) {
|
||||
output.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
|
||||
dataHandler.resetToDefault(dataHandler.dataToSend);
|
||||
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
|
||||
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
if (currentUsers === 0 && serverConfig.autoShutdown === true && serverConfig.xdrd.wirelessConnection) client.write('X\n');
|
||||
|
||||
if (code !== 1008) logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
|
||||
});
|
||||
|
||||
ws.on('error', console.error);
|
||||
});
|
||||
|
||||
// Additional web socket for using plugins
|
||||
pluginsWss.on('connection', (ws, request) => {
|
||||
const clientIp = helpers.getIpAddress(request);
|
||||
const userCommandHistory = {};
|
||||
// Anti-spam tracking for each client
|
||||
const userCommands = {};
|
||||
let lastWarn = { time: 0 };
|
||||
|
||||
ws.on('message', message => {
|
||||
// Anti-spam
|
||||
helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins');
|
||||
|
||||
try {
|
||||
let messageData = JSON.parse(message); // Attempt to parse the JSON. This code will attemp to parse json using the JSON.parse which coincidentally actually parses the json
|
||||
|
||||
if (messageData.type === "GPS" && messageData.value) {
|
||||
const gpsData = messageData.value;
|
||||
const { status, time, lat, lon, alt, mode } = gpsData;
|
||||
|
||||
if (status === "active") {
|
||||
Latitude = parseFloat(lat);
|
||||
Longitude = parseFloat(lon);
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
// Broadcast the message to all other clients
|
||||
pluginsWss.clients.forEach(client => {
|
||||
if (client.readyState === WebSocket.OPEN) client.send(message); // Send the message to all clients
|
||||
});
|
||||
});
|
||||
|
||||
ws.on('error', error => logError('WebSocket Extra error: ' + error));
|
||||
});
|
||||
|
||||
storage.websocket_delegation.set("/text", wss);
|
||||
storage.websocket_delegation.set("/rds", rdsWss);
|
||||
storage.websocket_delegation.set("/rdsspy", rdsWss);
|
||||
storage.websocket_delegation.set("/data_plugins", pluginsWss);
|
||||
require('./stream/ws.js');
|
||||
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
if (serverConfig.webserver.banlist?.includes(helpers.getIpAddress(request))) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
const upgradeWss = storage.websocket_delegation.get(request.url);
|
||||
if(upgradeWss) {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
// Sure want to, but no
|
||||
upgradeWss.handleUpgrade(request, socket, head, (ws) => upgradeWss.emit('connection', ws, request));
|
||||
});
|
||||
} else socket.destroy();
|
||||
});
|
||||
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../views'));
|
||||
app.use('/', endpoints);
|
||||
app.use(express.static(path.join(__dirname, '../web')));
|
||||
pluginsApi.registerServerContext({ wss, pluginsWss, httpServer, serverConfig });
|
||||
|
||||
const logServerStart = (address) => {
|
||||
logInfo(`Web server has started on address \x1b[34mhttp://${address}:${serverConfig.webserver.webserverPort}\x1b[0m.`);
|
||||
};
|
||||
|
||||
const startServer = (address) => {
|
||||
httpServer.listen(serverConfig.webserver.webserverPort, address, () => {
|
||||
if (!configExists()) {
|
||||
logInfo(`Open your browser and proceed to \x1b[34mhttp://${address}:${serverConfig.webserver.webserverPort}\x1b[0m to continue with setup.`);
|
||||
logInfo("Remember to restart afterwards!");
|
||||
} else logServerStart(address);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { startServer, wss, rdsWss };
|
||||
@@ -1,20 +1,20 @@
|
||||
const storage = require('./storage');
|
||||
const WebSocket = require('ws');
|
||||
const crypto = require('crypto');
|
||||
const xrd = new WebSocket.Server({ noServer: true });
|
||||
const xdr = new WebSocket.Server({ noServer: true });
|
||||
const { serverConfig } = require('./server_config');
|
||||
|
||||
let currentUsers = 0;
|
||||
let clients = []
|
||||
|
||||
function send_to_xrd(data) {
|
||||
function send_to_xdr(data) {
|
||||
clients.forEach((client) => {
|
||||
if (client.readyState === WebSocket.OPEN) client.send(data);
|
||||
});
|
||||
}
|
||||
|
||||
function send_xrd_online(fmusers) {
|
||||
send_to_xrd(`o${currentUsers},${fmusers}\n`);
|
||||
function send_xdr_online(fmusers) {
|
||||
send_to_xdr(`o${currentUsers},${fmusers}\n`);
|
||||
}
|
||||
|
||||
function randomString(length) {
|
||||
@@ -26,7 +26,7 @@ function randomString(length) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function xrd_auth(ws, salt) {
|
||||
function xdr_auth(ws, salt) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const expected = crypto.createHash('sha1')
|
||||
.update(salt + serverConfig.password.adminPass).digest('hex');
|
||||
@@ -44,14 +44,14 @@ function xrd_auth(ws, salt) {
|
||||
});
|
||||
}
|
||||
|
||||
xrd.on('connection', async (ws, request) => {
|
||||
xdr.on('connection', async (ws, request) => {
|
||||
const { initialData } = require('./datahandler');
|
||||
|
||||
const salt = randomString(16);
|
||||
ws.send(`${salt}\n`);
|
||||
|
||||
try {
|
||||
await xrd_auth(ws, salt);
|
||||
await xdr_auth(ws, salt);
|
||||
} catch (err) {
|
||||
ws.send("a0\n");
|
||||
ws.close(1008, err.message);
|
||||
@@ -59,19 +59,35 @@ xrd.on('connection', async (ws, request) => {
|
||||
}
|
||||
|
||||
currentUsers++;
|
||||
send_xrd_online(initialData.users);
|
||||
send_xdr_online(initialData.users);
|
||||
ws.send(`T${initialData.freq * 1000}\n`);
|
||||
ws.send(`G${initialData.eq}${initialData.ims}\n`);
|
||||
ws.send(`Z${initialData.ant}\n`);
|
||||
ws.send(`A${initialData.agc}\n`);
|
||||
ws.send(`F${initialData.bw}\n`);
|
||||
ws.send(`W${initialData.bw}\n`);
|
||||
ws.send(`OK\n`);
|
||||
clients.concat(ws);
|
||||
ws.send(`wL${serverConfig.lockToAdmin ? "1" : "0"}\n`); // Don't know how XDR-GTK will handle this, but lets hope it will be fine
|
||||
ws.send(`wT${serverConfig.publicTuner ? "0" : "1"}\n`); // Again
|
||||
ws.send(`OK\n`); // Make sure dumbass clients don't need to wait long for the OK, does the comms really REQUIRE you to start the receiver for you to know you are in?
|
||||
clients.push(ws);
|
||||
|
||||
ws.on('message', (message) => {
|
||||
const data = message.toString();
|
||||
|
||||
if(data.startsWith("w")) {
|
||||
switch(data.trim()) {
|
||||
case "wL1":
|
||||
serverConfig.lockToAdmin = true;
|
||||
send_to_xdr("wL1\n")
|
||||
break;
|
||||
case "wL0":
|
||||
serverConfig.lockToAdmin = false;
|
||||
send_to_xdr("wL0\n")
|
||||
break;
|
||||
// TODO: Do the wT, but not rn because i don't feel like doing the tunerlockTracker
|
||||
}
|
||||
}
|
||||
|
||||
if (!storage.ctl_output.write(data)) {
|
||||
ws.pause();
|
||||
storage.ctl_output.once('drain', () => ws.resume());
|
||||
@@ -80,10 +96,12 @@ xrd.on('connection', async (ws, request) => {
|
||||
|
||||
ws.on('close', () => {
|
||||
currentUsers--;
|
||||
send_xrd_online(initialData.users);
|
||||
send_xdr_online(initialData.users);
|
||||
|
||||
clients = clients.filter(client => client !== ws);
|
||||
});
|
||||
});
|
||||
|
||||
storage.websocket_delegation.set("/xrd", xrd);
|
||||
storage.websocket_delegation.set("/xdr", xdr);
|
||||
|
||||
module.exports = { send_to_xrd, send_xrd_online };
|
||||
module.exports = { send_to_xdr, send_xdr_online };
|
||||
Reference in New Issue
Block a user