better plugin configs (add backend path)

This commit is contained in:
2026-04-06 20:22:56 +02:00
parent 0bfe77f4ef
commit 9ef820d699
12 changed files with 256 additions and 258 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"webserver": "node index.js"
},
"author": "",
"license": "ISC",
"license": "GPL-3.0-only",
"dependencies": {
"@mapbox/node-pre-gyp": "2.0.3",
"body-parser": "2.2.2",
+4 -3
View File
@@ -7,10 +7,10 @@ const LOG_FILE = process.argv.includes('--config') && process.argv[process.argv.
? `serverlog_${process.argv[process.argv.indexOf('--config') + 1]}.txt`
: 'serverlog.txt';
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
const MAX_LOG_LINES = 3500;
const MAX_LOG_LINES = 2500;
const FLUSH_INTERVAL = 60000;
const logs = [];
const maxConsoleLogLines = 250;
const maxConsoleLogLines = 230;
let logBuffer = [];
// Message prefixes with ANSI codes
@@ -27,7 +27,7 @@ const MESSAGE_PREFIX = {
const getCurrentTime = () => {
const currentTime = new Date();
const date = currentTime.toLocaleDateString().replace(/\ /g, '');
const time = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const time = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
return `\x1b[90m[${date} ${time}]\x1b[0m`;
};
@@ -57,6 +57,7 @@ function appendLogToBuffer(logMessage) {
const cleanLogMessage = removeANSIEscapeCodes(logMessage);
logBuffer.push(cleanLogMessage + '\n');
}
appendLogToBuffer("Server started.");
async function flushLogBuffer() {
if (logBuffer.length === 0) return;
+198
View File
@@ -0,0 +1,198 @@
const { SerialPort } = require('serialport');
const { serverConfig, configExists } = require('./server_config');
const { logError, logInfo, logWarn } = require('./console');
const pluginsApi = require('./plugins_api');
const dataHandler = require('./datahandler');
const client = new (require('net')).Socket();
const helpers = require('./helpers');
let serialport;
connectToXdrd();
connectToSerial();
dataHandler.state.isSerialportRetrying = false;
setInterval(() => {
if (!dataHandler.state.isSerialportAlive && serverConfig.xdrd.wirelessConnection === false) {
dataHandler.state.isSerialportAlive = true;
dataHandler.state.isSerialportRetrying = true;
if (serialport && serialport.isOpen) {
logWarn('Communication lost from ' + serverConfig.xdrd.comPort + ', force closing serialport.');
setTimeout(() => {
serialport.close((err) => {
if (err) logError('Error closing serialport: ', err.message);
});
}, 1000);
} else logWarn('Communication lost from ' + serverConfig.xdrd.comPort + '.');
}
}, 2000);
// Serial Connection
function connectToSerial() {
if (serverConfig.xdrd.wirelessConnection === true) return;
serialport = new SerialPort({
path: serverConfig.xdrd.comPort,
baudRate: 115200,
autoOpen: false,
dtr: false, rts: false
});
serialport.open((err) => {
if (err) {
logError('Error opening port: ' + err.message);
setTimeout(() => {
connectToSerial();
}, 5000);
return;
}
logInfo('Using serial port: ' + serverConfig.xdrd.comPort);
dataHandler.state.isSerialportAlive = true;
pluginsApi.setOutput(serialport);
setTimeout(() => {
serialport.write('x\n');
}, 1000);
setTimeout(() => {
serialport.write('Q0\n');
serialport.write('M0\n');
serialport.write(`Z${serverConfig.antennaStartup}\n`); // Antenna on startup
if (serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true) {
serialport.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
} else if (dataHandler.state.lastFrequencyAlive && dataHandler.state.isSerialportRetrying) { // Serialport retry code when port is open but communication is lost
serialport.write('T' + (dataHandler.state.lastFrequencyAlive * 1000) + '\n');
} else serialport.write('T87500\n');
dataHandler.state.isSerialportRetrying = false;
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');
serialport.write(`G${serverConfig.ceqStartup}${serverConfig.imsStartup}\n`);
serialport.write(`B${serverConfig.stereoStartup}\n`);
serverConfig.audio.startupVolume
? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n')
: serialport.write('Y100\n');
}, 6000);
serialport.on('data', helpers.resolveDataBuffer);
serialport.on('error', (error) => logError(error.message));
});
// Handle port closure
serialport.on('close', () => {
pluginsApi.setOutput(null);
logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.');
setTimeout(() => {
dataHandler.state.isSerialportRetrying = true;
connectToSerial();
}, 5000);
});
return serialport;
}
// xdrd connection
let authFlags = {};
function connectToXdrd() {
const { xdrd } = serverConfig;
if (xdrd.wirelessConnection && configExists()) {
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
logInfo('Connection to xdrd established successfully.');
pluginsApi.setOutput(client);
authFlags = {
authMsg: false,
firstClient: false,
receivedSalt: '',
receivedPassword: false,
messageCount: 0,
};
});
}
}
client.on('data', (data) => {
const { xdrd } = serverConfig;
helpers.resolveDataBuffer(data);
if (authFlags.authMsg == true && authFlags.messageCount > 1) return;
authFlags.messageCount++;
const receivedData = data.toString();
const lines = receivedData.split('\n');
for (const line of lines) {
if (authFlags.receivedPassword === false) {
authFlags.receivedSalt = line.trim();
authFlags.receivedPassword = true;
helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
} else {
if (line.startsWith('a')) {
authFlags.authMsg = true;
logWarn('Authentication with xdrd failed. Is your password set correctly?');
} else if (line.startsWith('o1,')) authFlags.firstClient = true;
else if (line.startsWith('T') && line.length <= 7) {
const freq = line.slice(1) / 1000;
dataHandler.dataToSend.freq = freq.toFixed(3);
} else if (line.startsWith('OK')) {
authFlags.authMsg = true;
logInfo('Authentication with xdrd successful.');
} else if (line.startsWith('G')) {
dataHandler.initialData.eq = line.charAt(1);
dataHandler.dataToSend.eq = line.charAt(1);
dataHandler.initialData.ims = line.charAt(2);
dataHandler.dataToSend.ims = line.charAt(2);
} else if (line.startsWith('Z')) {
let modifiedLine = line.slice(1);
dataHandler.initialData.ant = modifiedLine;
dataHandler.dataToSend.ant = modifiedLine;
}
if (authFlags.authMsg === true && authFlags.firstClient === true) {
client.write('x\n');
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);
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;
}
}
}
});
client.on('close', () => {
pluginsApi.setOutput(null);
if(serverConfig.autoShutdown === false) {
logWarn('Disconnected from xdrd. Attempting to reconnect.');
setTimeout(connectToXdrd, 2000);
} else logWarn('Disconnected from xdrd.');
});
client.on('error', (err) => {
switch (true) {
case err.message.includes("ECONNRESET"):
logError("Connection to xdrd lost. Reconnecting...");
break;
case err.message.includes("ETIMEDOUT"):
logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " timed out.");
break;
case err.message.includes("ECONNREFUSED"):
logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " failed. Is xdrd running?");
break;
case err.message.includes("EINVAL"):
logError("Attempts to reconnect are failing repeatedly. Consider checking your settings or restarting xdrd.");
break;
default:
logError("Unhandled error: ", err.message);
break;
}
});
+4 -6
View File
@@ -14,8 +14,7 @@ const storage = require('./storage');
const tunerProfiles = require('./tuner_profiles');
const { logInfo, logs } = require('./console');
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const allPluginConfigs = require('./plugins');
const serverList = require('./server_list');
// Endpoints
router.get('/', (req, res) => {
@@ -141,8 +140,7 @@ router.get('/setup', (req, res) => {
return;
}
SerialPort.list()
.then((deviceList) => {
SerialPort.list().then((deviceList) => {
serialPorts = deviceList.map(port => ({
path: port.path,
friendlyName: port.friendlyName,
@@ -162,7 +160,7 @@ router.get('/setup', (req, res) => {
memoryHeap: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(1) + ' MB',
processUptime: formattedProcessUptime,
consoleOutput: logs,
plugins: allPluginConfigs,
plugins: require('./plugins'),
enabledPlugins: updatedConfig.plugins,
onlineUsers: dataHandler.dataToSend.users,
connectedUsers: storage.connectedUsers,
@@ -308,7 +306,7 @@ router.post('/saveData', (req, res) => {
let firstSetup;
if(req.session.isAdminAuthenticated || !configExists()) {
configUpdate(data);
fmdxList.update();
serverList.update();
if(!configExists()) firstSetup = true;
logInfo('Server config changed successfully.');
+7 -1
View File
@@ -400,12 +400,18 @@ function startPluginsWithDelay(plugins, delay) {
function findServerFiles(plugins) {
let results = [];
plugins.forEach(plugin => {
// Remove .js extension if present
if (plugin.endsWith('.js')) plugin = plugin.slice(0, -3);
const pluginPath = path.join(__dirname, '..', 'plugins', `${plugin}_server.js`);
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) results.push(pluginPath);
});
require("./plugins").forEach(config => {
if(config.backEndPath) {
const pluginPath = config.backEndPath;
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) results.push(pluginPath);
}
});
return results;
}
+12 -215
View File
@@ -1,229 +1,26 @@
const { SerialPort } = require('serialport');
const tunnel = require('./tunnel');
const helpers = require('./helpers');
const dataHandler = require('./datahandler');
const { logError, logInfo, logWarn } = require('./console');
const { serverConfig, configExists } = require('./server_config');
const pluginsApi = require('./plugins_api');
const startServer = require("./web");
const client = new (require('net')).Socket();
// Get all plugins from config and find corresponding server files
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(() => {
helpers.startPluginsWithDelay(plugins, 3000); // Start plugins with 3 seconds interval
}, 4000); // Initial delay of 4 seconds for the first plugin
}
const terminalWidth = require('readline').createInterface({input: process.stdin, output: process.stdout}).output.columns;
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('\x1b[2mby (Noobish @ \x1b[4mFMDX.org\x1b[0m\x1b[32m\x1b[2m) + KubaPro010\x1b[0m');
console.log("v" + require('../package.json').version)
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
require('./plugins');
let serialport;
const { serverConfig } = require('./server_config');
const tunnel = require('./tunnel');
tunnel.download();
connectToXdrd();
connectToSerial();
// Serialport retry code when port is open but communication is lost (additional code in datahandler.js)
dataHandler.state.isSerialportRetrying = false;
require("./device");
setInterval(() => {
if (!dataHandler.state.isSerialportAlive && serverConfig.xdrd.wirelessConnection === false) {
dataHandler.state.isSerialportAlive = true;
dataHandler.state.isSerialportRetrying = true;
if (serialport && serialport.isOpen) {
logWarn('Communication lost from ' + serverConfig.xdrd.comPort + ', force closing serialport.');
setTimeout(() => {
serialport.close((err) => {
if (err) logError('Error closing serialport: ', err.message);
});
}, 1000);
} else logWarn('Communication lost from ' + serverConfig.xdrd.comPort + '.');
}
}, 2000);
// Serial Connection
function connectToSerial() {
if (serverConfig.xdrd.wirelessConnection === true) return;
serialport = new SerialPort({
path: serverConfig.xdrd.comPort,
baudRate: 115200,
autoOpen: false,
dtr: false, rts: false
});
serialport.open((err) => {
if (err) {
logError('Error opening port: ' + err.message);
setTimeout(() => {
connectToSerial();
}, 5000);
return;
}
logInfo('Using serial port: ' + serverConfig.xdrd.comPort);
dataHandler.state.isSerialportAlive = true;
pluginsApi.setOutput(serialport);
setTimeout(() => {
serialport.write('x\n');
}, 1000);
setTimeout(() => {
serialport.write('Q0\n');
serialport.write('M0\n');
serialport.write(`Z${serverConfig.antennaStartup}\n`); // Antenna on startup
if (serverConfig.defaultFreq && serverConfig.enableDefaultFreq === true) {
serialport.write('T' + Math.round(serverConfig.defaultFreq * 1000) + '\n');
dataHandler.initialData.freq = Number(serverConfig.defaultFreq).toFixed(3);
dataHandler.dataToSend.freq = Number(serverConfig.defaultFreq).toFixed(3);
} else if (dataHandler.state.lastFrequencyAlive && dataHandler.state.isSerialportRetrying) { // Serialport retry code when port is open but communication is lost
serialport.write('T' + (dataHandler.state.lastFrequencyAlive * 1000) + '\n');
} else serialport.write('T87500\n');
dataHandler.state.isSerialportRetrying = false;
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');
serialport.write(`G${serverConfig.ceqStartup}${serverConfig.imsStartup}\n`);
serialport.write(`B${serverConfig.stereoStartup}\n`); // Mono
serverConfig.audio.startupVolume
? serialport.write('Y' + (serverConfig.audio.startupVolume * 100).toFixed(0) + '\n')
: serialport.write('Y100\n');
}, 6000);
serialport.on('data', (data) => helpers.resolveDataBuffer(data));
serialport.on('error', (error) => logError(error.message));
});
// Handle port closure
serialport.on('close', () => {
pluginsApi.setOutput(null);
logWarn('Disconnected from ' + serverConfig.xdrd.comPort + '. Attempting to reconnect.');
setTimeout(() => {
dataHandler.state.isSerialportRetrying = true;
connectToSerial();
}, 5000);
});
return serialport;
{
const helpers = require('./helpers');
const plugins = helpers.findServerFiles(serverConfig.plugins);
if (plugins.length > 0) setTimeout(helpers.startPluginsWithDelay, 4000, plugins, 3000);
}
// xdrd connection
let authFlags = {};
function connectToXdrd() {
const { xdrd } = serverConfig;
if (xdrd.wirelessConnection && configExists()) {
client.connect(xdrd.xdrdPort, xdrd.xdrdIp, () => {
logInfo('Connection to xdrd established successfully.');
pluginsApi.setOutput(client);
authFlags = {
authMsg: false,
firstClient: false,
receivedSalt: '',
receivedPassword: false,
messageCount: 0,
};
});
}
}
client.on('data', (data) => {
const { xdrd } = serverConfig;
helpers.resolveDataBuffer(data);
if (authFlags.authMsg == true && authFlags.messageCount > 1) return;
authFlags.messageCount++;
const receivedData = data.toString();
const lines = receivedData.split('\n');
for (const line of lines) {
if (authFlags.receivedPassword === false) {
authFlags.receivedSalt = line.trim();
authFlags.receivedPassword = true;
helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
} else {
if (line.startsWith('a')) {
authFlags.authMsg = true;
logWarn('Authentication with xdrd failed. Is your password set correctly?');
} else if (line.startsWith('o1,')) authFlags.firstClient = true;
else if (line.startsWith('T') && line.length <= 7) {
const freq = line.slice(1) / 1000;
dataHandler.dataToSend.freq = freq.toFixed(3);
} else if (line.startsWith('OK')) {
authFlags.authMsg = true;
logInfo('Authentication with xdrd successful.');
} else if (line.startsWith('G')) {
dataHandler.initialData.eq = line.charAt(1);
dataHandler.dataToSend.eq = line.charAt(1);
dataHandler.initialData.ims = line.charAt(2);
dataHandler.dataToSend.ims = line.charAt(2);
} else if (line.startsWith('Z')) {
let modifiedLine = line.slice(1);
dataHandler.initialData.ant = modifiedLine;
dataHandler.dataToSend.ant = modifiedLine;
}
if (authFlags.authMsg === true && authFlags.firstClient === true) {
client.write('x\n');
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);
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;
}
}
}
});
client.on('close', () => {
pluginsApi.setOutput(null);
if(serverConfig.autoShutdown === false) {
logWarn('Disconnected from xdrd. Attempting to reconnect.');
setTimeout(function () {
connectToXdrd();
}, 2000)
} else logWarn('Disconnected from xdrd.');
});
client.on('error', (err) => {
switch (true) {
case err.message.includes("ECONNRESET"):
logError("Connection to xdrd lost. Reconnecting...");
break;
case err.message.includes("ETIMEDOUT"):
logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " timed out.");
break;
case err.message.includes("ECONNREFUSED"):
logError("Connection to xdrd @ " + serverConfig.xdrd.xdrdIp + ":" + serverConfig.xdrd.xdrdPort + " failed. Is xdrd running?");
break;
case err.message.includes("EINVAL"):
logError("Attempts to reconnect are failing repeatedly. Consider checking your settings or restarting xdrd.");
break;
default:
logError("Unhandled error: ", err.message);
break;
}
});
require('./stream/index');
const startServer = require("./web");
startServer(serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp);
tunnel.connect();
require('./fmdx_list').update();
require('./server_list').update();
+12 -20
View File
@@ -3,8 +3,7 @@ const path = require('path');
const consoleCmd = require('./console');
function readJSFiles(dir) {
const files = fs.readdirSync(dir);
return files.filter(file => file.endsWith('.js'));
return fs.readdirSync(dir).filter(file => file.endsWith('.js'));
}
function parsePluginConfig(filePath) {
@@ -44,7 +43,7 @@ function parsePluginConfig(filePath) {
console.error(`Error creating symlink at ${destinationFile}: ${err.message}`);
}
}
} else console.error(`Error: frontEndPath is not defined in ${filePath}`);
}
} catch (err) {
console.error(`Error parsing plugin config from ${filePath}: ${err.message}`);
}
@@ -52,27 +51,13 @@ function parsePluginConfig(filePath) {
return pluginConfig;
}
// Main function to collect plugin configs from all .js files in the 'plugins' directory
function collectPluginConfigs() {
const pluginsDir = path.join(__dirname, '../plugins');
const jsFiles = readJSFiles(pluginsDir);
const pluginConfigs = [];
jsFiles.forEach(file => {
const filePath = path.join(pluginsDir, file);
const config = parsePluginConfig(filePath);
if (Object.keys(config).length > 0) pluginConfigs.push(config);
});
return pluginConfigs;
}
// Ensure the web/js/plugins directory exists
const webJsPluginsDir = path.join(__dirname, '../web/js/plugins');
if (!fs.existsSync(webJsPluginsDir)) fs.mkdirSync(webJsPluginsDir, { recursive: true });
const pluginsDir = path.join(__dirname, '../plugins');
if (process.platform === 'win32') {
const pluginsDir = path.join(__dirname, '../plugins');
const destinationPluginsDir = path.join(__dirname, '../web/js/plugins');
// On Windows, create a junction
@@ -84,4 +69,11 @@ if (process.platform === 'win32') {
}
}
module.exports = collectPluginConfigs();
const pluginConfigs = [];
readJSFiles(pluginsDir).forEach(file => {
const filePath = path.join(pluginsDir, file);
const config = parsePluginConfig(filePath);
if (Object.keys(config).length > 0) pluginConfigs.push(config);
});
module.exports = pluginConfigs;
@@ -8,8 +8,6 @@ var os = require('os');
let timeoutID = null;
function send(request) {
const url = "https://servers.fmdx.org/api/";
const options = {
method: 'POST',
headers: {
@@ -19,7 +17,7 @@ function send(request) {
body: JSON.stringify(request)
};
fetch(url, options)
fetch("https://servers.fmdx.org/api/", options)
.then(response => response.json())
.then(data => {
if (data.success && data.token) {
+11 -4
View File
@@ -44,7 +44,7 @@ function xdr_auth(ws, salt) {
});
}
xdr.on('connection', async (ws, request) => {
xdr.on('connection', async (ws) => {
const { initialData } = require('./datahandler');
const salt = randomString(16);
@@ -79,12 +79,19 @@ xdr.on('connection', async (ws, request) => {
case "wL1":
serverConfig.lockToAdmin = true;
send_to_xdr("wL1\n")
break;
return;
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
return;
case "wT1":
serverConfig.publicTuner = false;
send_to_xdr("wT1\n")
return;
case "wT0":
serverConfig.publicTuner = true;
send_to_xdr("wT0\n")
return;
}
}
+4 -2
View File
@@ -420,11 +420,13 @@
<p>Any compatible <strong>.js</strong> plugin, which is in the "plugins" folder, will be listed here.<br>
Click on the individual plugins to enable/disable them.</p>
<select id="plugins" class="multiselect" multiple>
<% plugins.forEach(function(plugin) { %>
<% plugins.forEach(function(plugin) {
if (plugin.frontEndPath) { %>
<option data-name="<%= plugin.frontEndPath %>" title="<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]">
<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]
</option>
<% }); %>
<% }
}); %>
</select><br><br>
<a href="https://github.com/NoobishSVK/fm-dx-webserver/wiki/Plugin-List" target="_blank">Download new plugins here!</a>
</div>
+1 -1
View File
@@ -21,7 +21,7 @@ class WebSocketAudioPlayer {
const liveEdge = buffered.end(buffered.length - 1);
const lag = liveEdge - this.audio.currentTime;
if (lag > .9) this.audio.currentTime = liveEdge - 0.15; // snap to near live edge
if (lag > .9) this.audio.currentTime = liveEdge - 0.2; // snap to near live edge
}
Start() {
+1 -2
View File
@@ -815,8 +815,7 @@ function findOnMaps() {
frequency > 74 ? frequency = frequency.toFixed(1) : null;
var url = `https://maps.fmdx.org/#qth=${longitude},${latitude}&freq=${frequency}&findPi=${pi}`;
window.open(url, "_blank");
window.open(`https://maps.fmdx.org/#qth=${longitude},${latitude}&freq=${frequency}&findPi=${pi}`, "_blank");
}
function updateSignalUnits(parsedData, averageSignal) {