mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-31 09:19:16 +02:00
we're up to date with the terrible
This commit is contained in:
@@ -28,6 +28,7 @@ var dataToSend = {
|
||||
rt_flag: '',
|
||||
ims: 0,
|
||||
eq: 0,
|
||||
agc: 0,
|
||||
ant: 0,
|
||||
txInfo: {
|
||||
tx: '',
|
||||
@@ -128,6 +129,10 @@ function handleData(wss, receivedData, rdsWss) {
|
||||
initialData.ant = receivedLine.substring(1);
|
||||
rdsReset();
|
||||
break;
|
||||
case receivedLine.startsWith('A'): // AGC
|
||||
dataToSend.agc = receivedLine.substring(1);
|
||||
initialData.agc = receivedLine.substring(1);
|
||||
break;
|
||||
case receivedLine.startsWith('G'): // EQ / iMS (RF+/IF+)
|
||||
const mapping = filterMappings[receivedLine];
|
||||
if (mapping) {
|
||||
|
||||
+45
-27
@@ -11,7 +11,8 @@ const { parseAudioDevice } = require('./stream/parser');
|
||||
const { configName, serverConfig, configUpdate, configSave, configExists, configPath } = require('./server_config');
|
||||
const helpers = require('./helpers');
|
||||
const storage = require('./storage');
|
||||
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
|
||||
const tunerProfiles = require('./tuner_profiles');
|
||||
const { logInfo, logs } = require('./console');
|
||||
const dataHandler = require('./datahandler');
|
||||
const fmdxList = require('./fmdx_list');
|
||||
const { allPluginConfigs } = require('./plugins');
|
||||
@@ -48,7 +49,13 @@ router.get('/', (req, res) => {
|
||||
isAdminAuthenticated: true,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
serialPorts: serialPorts
|
||||
serialPorts: serialPorts,
|
||||
serialPorts: serialPorts,
|
||||
tunerProfiles: tunerProfiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
label: profile.label,
|
||||
detailsHtml: helpers.parseMarkdown(profile.details || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -68,6 +75,8 @@ router.get('/', (req, res) => {
|
||||
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
|
||||
chatEnabled: serverConfig.webserver.chatEnabled,
|
||||
device: serverConfig.device,
|
||||
tunerProfiles,
|
||||
si47xxAgcControl: !!serverConfig.si47xx?.agcControl,
|
||||
noPlugins,
|
||||
plugins: serverConfig.plugins,
|
||||
fmlist_integration: serverConfig.extras.fmlistIntegration,
|
||||
@@ -101,7 +110,12 @@ router.get('/wizard', (req, res) => {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
serialPorts: serialPorts
|
||||
serialPorts: serialPorts,
|
||||
tunerProfiles: tunerProfiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
label: profile.label,
|
||||
detailsHtml: helpers.parseMarkdown(profile.details || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -135,20 +149,25 @@ router.get('/wizard', (req, res) => {
|
||||
|
||||
const updatedConfig = loadConfig(); // Reload the config every time
|
||||
res.render('setup', {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
serialPorts: serialPorts,
|
||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
|
||||
processUptime: formattedProcessUptime,
|
||||
consoleOutput: logs,
|
||||
plugins: allPluginConfigs,
|
||||
enabledPlugins: updatedConfig.plugins,
|
||||
onlineUsers: dataHandler.dataToSend.users,
|
||||
connectedUsers: storage.connectedUsers,
|
||||
device: serverConfig.device,
|
||||
banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
serialPorts: serialPorts,
|
||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
|
||||
processUptime: formattedProcessUptime,
|
||||
consoleOutput: logs,
|
||||
plugins: allPluginConfigs,
|
||||
enabledPlugins: updatedConfig.plugins,
|
||||
onlineUsers: dataHandler.dataToSend.users,
|
||||
connectedUsers: storage.connectedUsers,
|
||||
device: serverConfig.device,
|
||||
banlist: updatedConfig.webserver.banlist, // Updated banlist from the latest config
|
||||
tunerProfiles: tunerProfiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
label: profile.label,
|
||||
detailsHtml: helpers.parseMarkdown(profile.details || '')
|
||||
}))
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -164,12 +183,13 @@ router.get('/rdsspy', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/api', (req, res) => {
|
||||
const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, ...dataToSend } = dataHandler.dataToSend;
|
||||
const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, rdsMode, ...dataToSend } = dataHandler.dataToSend;
|
||||
res.json({
|
||||
...dataToSend,
|
||||
txInfo: txInfo,
|
||||
ps_errors: ps_errors,
|
||||
ant: ant
|
||||
ant: ant,
|
||||
rbds: serverConfig.webserver.rdsMode
|
||||
});
|
||||
});
|
||||
|
||||
@@ -241,6 +261,7 @@ router.get('/kick', (req, res) => {
|
||||
});
|
||||
|
||||
router.get('/addToBanlist', (req, res) => {
|
||||
if (!req.session.isAdminAuthenticated) return;
|
||||
const ipAddress = req.query.ip;
|
||||
const location = 'Unknown';
|
||||
const date = Date.now();
|
||||
@@ -252,17 +273,14 @@ router.get('/addToBanlist', (req, res) => {
|
||||
serverConfig.webserver.banlist = [];
|
||||
}
|
||||
|
||||
if (req.session.isAdminAuthenticated) {
|
||||
serverConfig.webserver.banlist.push(userBanData);
|
||||
configSave();
|
||||
res.json({ success: true, message: 'IP address added to banlist.' });
|
||||
helpers.kickClient(ipAddress);
|
||||
} else {
|
||||
res.status(403).json({ success: false, message: 'Unauthorized access.' });
|
||||
}
|
||||
serverConfig.webserver.banlist.push(userBanData);
|
||||
configSave();
|
||||
res.json({ success: true, message: 'IP address added to banlist.' });
|
||||
helpers.kickClient(ipAddress);
|
||||
});
|
||||
|
||||
router.get('/removeFromBanlist', (req, res) => {
|
||||
if (!req.session.isAdminAuthenticated) return;
|
||||
const ipAddress = req.query.ip;
|
||||
|
||||
if (typeof serverConfig.webserver.banlist !== 'object') {
|
||||
|
||||
+17
-21
@@ -27,6 +27,7 @@ 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');
|
||||
|
||||
// Function to find server files based on the plugins listed in config
|
||||
@@ -155,8 +156,9 @@ function connectToSerial() {
|
||||
return;
|
||||
}
|
||||
|
||||
logInfo('Using COM device: ' + serverConfig.xdrd.comPort);
|
||||
logInfo('Using serial port: ' + serverConfig.xdrd.comPort);
|
||||
dataHandler.state.isSerialportAlive = true;
|
||||
pluginsApi.setOutput(serialport);
|
||||
setTimeout(() => {
|
||||
serialport.write('x\n');
|
||||
}, 3000);
|
||||
@@ -175,7 +177,7 @@ function connectToSerial() {
|
||||
} else serialport.write('T87500\n');
|
||||
dataHandler.state.isSerialportRetrying = false;
|
||||
|
||||
serialport.write('A0\n');
|
||||
if (serverConfig.device === 'si47xx') serialport.write('A0\n');
|
||||
serialport.write('F-1\n');
|
||||
serialport.write('W0\n');
|
||||
serverConfig.webserver.rdsMode ? serialport.write('D1\n') : serialport.write('D0\n');
|
||||
@@ -202,6 +204,7 @@ function connectToSerial() {
|
||||
|
||||
// Handle port closure
|
||||
serialport.on('close', () => {
|
||||
pluginsApi.setOutput(null);
|
||||
logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.');
|
||||
setTimeout(() => {
|
||||
dataHandler.state.isSerialportRetrying = true;
|
||||
@@ -220,6 +223,7 @@ function connectToXdrd() {
|
||||
if (xdrd.wirelessConnection && configExists()) {
|
||||
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
|
||||
logInfo('Connection to xdrd established successfully.');
|
||||
pluginsApi.setOutput(client);
|
||||
|
||||
authFlags = {
|
||||
authMsg: false,
|
||||
@@ -277,7 +281,7 @@ client.on('data', (data) => {
|
||||
client.write(serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? 'T' + Math.round(serverConfig.defaultFreq * 1000) + '\n' : 'T87500\n');
|
||||
dataHandler.initialData.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
||||
dataHandler.dataToSend.freq = serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true ? Number(serverConfig.defaultFreq).toFixed(3) : (87.5).toFixed(3);
|
||||
client.write('A0\n');
|
||||
if (serverConfig.device === 'si47xx') serialport.write('A0\n');
|
||||
client.write(serverConfig.audio.startupVolume ? 'Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n' : 'Y100\n');
|
||||
serverConfig.webserver.rdsMode ? client.write('D1\n') : client.write('D0\n');
|
||||
return;
|
||||
@@ -287,6 +291,7 @@ client.on('data', (data) => {
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
pluginsApi.setOutput(null);
|
||||
if(serverConfig.autoShutdown === false) {
|
||||
logWarn('Disconnected from xdrd. Attempting to reconnect.');
|
||||
setTimeout(function () {
|
||||
@@ -572,7 +577,12 @@ pluginsWss.on('connection', (ws, request) => {
|
||||
});
|
||||
|
||||
// Websocket register for /text, /audio and /chat paths
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
httpServer.on('upgrade', (request, socket, head) => {
|
||||
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
if (request.url === '/text') {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
wss.handleUpgrade(request, socket, head, (ws) => {
|
||||
@@ -595,23 +605,6 @@ httpServer.on('upgrade', (request, socket, head) => {
|
||||
sessionMiddleware(request, {}, () => {
|
||||
rdsWss.handleUpgrade(request, socket, head, (ws) => {
|
||||
rdsWss.emit('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;
|
||||
}
|
||||
|
||||
// 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', 'rds');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
} else if (request.url === '/data_plugins') {
|
||||
@@ -648,3 +641,6 @@ helpers.checkIPv6Support((isIPv6Supported) => {
|
||||
startServer(ipv6Address, true); // Start on IPv6
|
||||
} else startServer(ipv4Address, false); // Start only on IPv4
|
||||
});
|
||||
|
||||
pluginsApi.registerServerContext({ wss, pluginsWss, httpServer, serverConfig });
|
||||
module.exports = { wss, pluginsWss, httpServer, serverConfig };
|
||||
@@ -0,0 +1,137 @@
|
||||
// plugins_api.js
|
||||
// Shared API for server plugins:
|
||||
// - Provides privileged/admin command access
|
||||
// - Exposes server-side hooks for inter-plugin communication
|
||||
// - Optionally broadcasts events to connected plugin WebSocket clients
|
||||
|
||||
const { EventEmitter } = require('events');
|
||||
const { logInfo, logWarn, logError } = require('./console');
|
||||
|
||||
let output = null;
|
||||
let wss = null;
|
||||
let pluginsWss = null;
|
||||
let httpServer = null;
|
||||
let serverConfig = null;
|
||||
|
||||
// ---- internal plugin event bus ----
|
||||
|
||||
const pluginEvents = new EventEmitter();
|
||||
// prevent accidental memory leak warnings
|
||||
pluginEvents.setMaxListeners(50);
|
||||
|
||||
// ---- registration server side ----
|
||||
|
||||
function registerServerContext(ctx) {
|
||||
if (ctx.wss) wss = ctx.wss;
|
||||
if (ctx.pluginsWss) pluginsWss = ctx.pluginsWss;
|
||||
if (ctx.httpServer) httpServer = ctx.httpServer;
|
||||
if (ctx.serverConfig) serverConfig = ctx.serverConfig;
|
||||
}
|
||||
|
||||
function setOutput(newOutput) {
|
||||
output = newOutput;
|
||||
}
|
||||
|
||||
function clearOutput() {
|
||||
output = null;
|
||||
}
|
||||
|
||||
// ---- accessors plugin side ----
|
||||
|
||||
function getWss() {
|
||||
return wss;
|
||||
}
|
||||
|
||||
function getPluginsWss() {
|
||||
return pluginsWss;
|
||||
}
|
||||
|
||||
function getHttpServer() {
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
function getServerConfig() {
|
||||
return serverConfig;
|
||||
}
|
||||
|
||||
// ---- privileged command path ----
|
||||
|
||||
async function sendPrivilegedCommand(command, isPluginInternal = false) {
|
||||
const maxWait = 10000;
|
||||
const interval = 500;
|
||||
let waited = 0;
|
||||
|
||||
while (!output && waited < maxWait) {
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
waited += interval;
|
||||
}
|
||||
|
||||
if (!output) {
|
||||
logError(`[Privileged Send] Timeout waiting for output (${command})`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPluginInternal) {
|
||||
output.write(`${command}\n`);
|
||||
//logInfo(`[Privileged Plugin] Command sent: ${command}`); // Debug
|
||||
return true;
|
||||
}
|
||||
|
||||
logWarn(`[Privileged Send] Rejected (not internal): ${command.slice(0, 64)}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- plugin hook API ----
|
||||
|
||||
function emitPluginEvent(event, payload, opts = {}) {
|
||||
pluginEvents.emit(event, payload);
|
||||
|
||||
// Stop here unless option to broadcast to clients if true
|
||||
if (opts.broadcast === false) return;
|
||||
|
||||
// Broadcast to connected plugin WebSocket clients if available
|
||||
if (pluginsWss) {
|
||||
const message = JSON.stringify({ type: event, value: payload });
|
||||
pluginsWss.clients.forEach((client) => {
|
||||
if (client.readyState === client.OPEN) {
|
||||
try {
|
||||
// Send event to client
|
||||
client.send(message);
|
||||
} catch (err) {
|
||||
logWarn(`[plugins_api] Failed to send ${event} to client: ${err.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onPluginEvent(event, handler) {
|
||||
pluginEvents.on(event, handler);
|
||||
}
|
||||
|
||||
function offPluginEvent(event, handler) {
|
||||
pluginEvents.off(event, handler);
|
||||
}
|
||||
|
||||
// ---- exports ----
|
||||
|
||||
module.exports = {
|
||||
// server registration
|
||||
registerServerContext,
|
||||
setOutput,
|
||||
clearOutput,
|
||||
|
||||
// server context access
|
||||
getWss,
|
||||
getPluginsWss,
|
||||
getHttpServer,
|
||||
getServerConfig,
|
||||
|
||||
// privileged control
|
||||
sendPrivilegedCommand,
|
||||
|
||||
// inter-plugin hooks
|
||||
emitPluginEvent,
|
||||
onPluginEvent,
|
||||
offPluginEvent
|
||||
};
|
||||
@@ -90,6 +90,9 @@ let serverConfig = {
|
||||
fmlistAdminOnly: false,
|
||||
fmlistOmid: "",
|
||||
},
|
||||
si47xx: {
|
||||
agcControl: false
|
||||
},
|
||||
tunnel: {
|
||||
enabled: false,
|
||||
username: "",
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
const tunerProfiles = [
|
||||
{
|
||||
id: 'tef',
|
||||
label: 'TEF668x',
|
||||
fmBandwidths: [
|
||||
{ value: 0, label: 'Auto' },
|
||||
{ value: 56000, label: '56 kHz' },
|
||||
{ value: 64000, label: '64 kHz' },
|
||||
{ value: 72000, label: '72 kHz' },
|
||||
{ value: 84000, label: '84 kHz' },
|
||||
{ value: 97000, label: '97 kHz' },
|
||||
{ value: 114000, label: '114 kHz' },
|
||||
{ value: 133000, label: '133 kHz' },
|
||||
{ value: 151000, label: '151 kHz' },
|
||||
{ value: 184000, label: '184 kHz' },
|
||||
{ value: 200000, label: '200 kHz' },
|
||||
{ value: 217000, label: '217 kHz' },
|
||||
{ value: 236000, label: '236 kHz' },
|
||||
{ value: 254000, label: '254 kHz' },
|
||||
{ value: 287000, label: '287 kHz' },
|
||||
{ value: 311000, label: '311 kHz' }
|
||||
],
|
||||
details: ''
|
||||
},
|
||||
{
|
||||
id: 'xdr',
|
||||
label: 'XDR (F1HD / S10HDiP)',
|
||||
fmBandwidths: [
|
||||
{ value: 0, value2: -1, label: 'Auto' },
|
||||
{ value: 55000, value2: 0, label: '55 kHz' },
|
||||
{ value: 73000, value2: 1, label: '73 kHz' },
|
||||
{ value: 90000, value2: 2, label: '90 kHz' },
|
||||
{ value: 108000, value2: 3, label: '108 kHz' },
|
||||
{ value: 125000, value2: 4, label: '125 kHz' },
|
||||
{ value: 142000, value2: 5, label: '142 kHz' },
|
||||
{ value: 159000, value2: 6, label: '159 kHz' },
|
||||
{ value: 177000, value2: 7, label: '177 kHz' },
|
||||
{ value: 194000, value2: 8, label: '194 kHz' },
|
||||
{ value: 211000, value2: 9, label: '211 kHz' },
|
||||
{ value: 229000, value2: 10, label: '229 kHz' },
|
||||
{ value: 246000, value2: 11, label: '246 kHz' },
|
||||
{ value: 263000, value2: 12, label: '263 kHz' },
|
||||
{ value: 281000, value2: 13, label: '281 kHz' },
|
||||
{ value: 298000, value2: 14, label: '298 kHz' },
|
||||
{ value: 309000, value2: 15, label: '309 kHz' }
|
||||
],
|
||||
details: ''
|
||||
},
|
||||
{
|
||||
id: 'sdr',
|
||||
label: 'SDR (RTL-SDR / AirSpy)',
|
||||
fmBandwidths: [
|
||||
{ value: 0, label: 'Auto' },
|
||||
{ value: 4000, label: '4 kHz' },
|
||||
{ value: 8000, label: '8 kHz' },
|
||||
{ value: 10000, label: '10 kHz' },
|
||||
{ value: 20000, label: '20 kHz' },
|
||||
{ value: 30000, label: '30 kHz' },
|
||||
{ value: 50000, label: '50 kHz' },
|
||||
{ value: 75000, label: '75 kHz' },
|
||||
{ value: 100000, label: '100 kHz' },
|
||||
{ value: 125000, label: '125 kHz' },
|
||||
{ value: 150000, label: '150 kHz' },
|
||||
{ value: 175000, label: '175 kHz' },
|
||||
{ value: 200000, label: '200 kHz' },
|
||||
{ value: 225000, label: '225 kHz' }
|
||||
],
|
||||
details: ''
|
||||
},
|
||||
{
|
||||
id: 'si47xx',
|
||||
label: 'Si47XX (Si4735 / Si4732)',
|
||||
fmBandwidths: [
|
||||
{ value: 0, label: 'Auto' },
|
||||
{ value: 40000, label: '40 kHz' },
|
||||
{ value: 60000, label: '60 kHz' },
|
||||
{ value: 84000, label: '84 kHz' },
|
||||
{ value: 110000, label: '110 kHz' }
|
||||
],
|
||||
details: ''
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = tunerProfiles;
|
||||
Reference in New Issue
Block a user