From 531a0f23e6b0912d247d43bd13235530cc5d156e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Farka=C5=A1?= Date: Sun, 30 Nov 2025 22:49:00 +0100 Subject: [PATCH] community tunnel, minor bugfixes --- package-lock.json | 2 +- package.json | 2 +- server/endpoints.js | 28 ++++++++++++++++++++-------- server/helpers.js | 19 ++++++++++++++++++- server/server_config.js | 5 +++++ server/tunnel.js | 3 ++- web/js/setup.js | 32 ++++++++++++++++++++++++++++++++ web/js/webserver.js | 5 +---- web/setup.ejs | 35 ++++++++++++++++++++++++++++------- web/wizard.ejs | 1 - 10 files changed, 108 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5eab2c7..2caec72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.3.11", + "version": "1.3.12", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 3b3207e..989e5ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.3.11", + "version": "1.3.12", "description": "FM DX Webserver", "main": "index.js", "scripts": { diff --git a/server/endpoints.js b/server/endpoints.js index 87eb480..ffdf365 100644 --- a/server/endpoints.js +++ b/server/endpoints.js @@ -155,14 +155,6 @@ router.get('/wizard', (req, res) => { }); -router.get('/rds', (req, res) => { - res.send('Please connect using a WebSocket compatible app to obtain RDS stream.'); -}); - -router.get('/rdsspy', (req, res) => { - res.send('Please connect using a WebSocket compatible app to obtain RDS stream.'); -}); - router.get('/rds', (req, res) => { res.send('Please connect using a WebSocket compatible app to obtain RDS stream.'); }); @@ -466,4 +458,24 @@ router.get('/log_fmlist', (req, res) => { request.end(); }); +router.get('/tunnelservers', async (req, res) => { + const servers = [ + { value: "eu", host: "eu.fmtuner.org", label: "Europe" }, + { value: "us", host: "us.fmtuner.org", label: "Americas" }, + { value: "sg", host: "sg.fmtuner.org", label: "Asia & Oceania" }, + ]; + + const results = await Promise.all( + servers.map(async s => { + const latency = await helpers.checkLatency(s.host); + return { + value: s.value, + label: `${s.label} (${latency ? latency + ' ms' : 'offline'})` // From my tests, the latency via HTTP ping is roughly 2x higher than regular ping + }; + }) + ); + + res.json(results); + }); + module.exports = router; diff --git a/server/helpers.js b/server/helpers.js index 9544023..2c8dd8c 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -240,6 +240,23 @@ function checkIPv6Support(callback) { }); } +function checkLatency(host) { + return new Promise(resolve => { + const start = Date.now(); + + const req = http.get({ host, timeout: 2000 }, res => { + res.resume(); // discard body + resolve(Date.now() - start); + }); + + req.on("error", () => resolve(null)); // server offline + req.on("timeout", () => { + req.destroy(); + resolve(null); + }); + }); +} + function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName) { const command = message.toString(); const now = Date.now(); @@ -313,5 +330,5 @@ const escapeHtml = (unsafe) => { module.exports = { - authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, antispamProtection, escapeHtml + authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, checkLatency, antispamProtection, escapeHtml } \ No newline at end of file diff --git a/server/server_config.js b/server/server_config.js index f5d6a0e..62466bc 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -94,10 +94,15 @@ let serverConfig = { enabled: false, username: "", token: "", + region: "eu", lowLatencyMode: false, subdomain: "", httpName: "", httpPassword: "", + community: { + enabled: false, + host: "" + } }, plugins: [], device: 'tef', diff --git a/server/tunnel.js b/server/tunnel.js index d2341ab..74f00b6 100644 --- a/server/tunnel.js +++ b/server/tunnel.js @@ -41,6 +41,7 @@ async function connect() { } const cfg = ejs.render(frpcConfigTemplate, { cfg: serverConfig.tunnel, + host: serverConfig.tunnel.community.enabled ? serverConfig.tunnel.community.host : serverConfig.tunnel.region + ".fmtuner.org", server: { port: serverConfig.webserver.webserverPort } @@ -84,7 +85,7 @@ async function connect() { } const frpcConfigTemplate = ` -serverAddr = "fmtuner.org" +serverAddr = "<%= host %>" serverPort = 7000 loginFailExit = false log.disablePrintColor = true diff --git a/web/js/setup.js b/web/js/setup.js index 806b607..231106d 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -10,6 +10,9 @@ $(document).ready(function() { showPanelFromHash(); initNav(); initBanlist(); + + checkTunnelServers(); + setInterval(checkTunnelServers, 10000); }); /** @@ -253,3 +256,32 @@ async function loadConsoleLogs() { }); $("#console-output").length ? $("#console-output").scrollTop($("#console-output")[0].scrollHeight) : null; } + +function checkTunnelServers() { + $.ajax({ + url: '/tunnelservers', + method: 'GET', + success: function(servers) { + const $options = $('#tunnel-server ul.options'); + const $input = $('#tunnel-serverSelect'); + const selectedValue = $input.val(); // currently selected value (label or value?) + + servers.forEach(server => { + const $li = $options.find(`li[data-value="${server.value}"]`); + + if ($li.length) { + $li.text(server.label); + + // If this li is the currently selected one, update input text too + // Note: input.val() holds the label, so match by label is safer + if ($li.text() === selectedValue || server.value === selectedValue) { + $input.val(server.label); + } + } + }); + }, + error: function() { + console.error('Failed to load server latency data'); + } + }); +} diff --git a/web/js/webserver.js b/web/js/webserver.js index 951b086..2a210a1 100644 --- a/web/js/webserver.js +++ b/web/js/webserver.js @@ -1,7 +1,3 @@ -const versionDate = new Date('Sep 11, 2025 14:00:00'); -const currentVersion = `v1.3.11 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`; - - function loadScript(src) { return new Promise((resolve, reject) => { const script = document.createElement('script'); @@ -13,6 +9,7 @@ function loadScript(src) { } async function loadScriptsInOrder() { + await loadScript('./js/ver.js'); await loadScript('./js/api.js'); await loadScript('./js/main.js'); await loadScript('./js/dropdown.js'); diff --git a/web/setup.ejs b/web/setup.ejs index 00ee44e..dc7a5e0 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -129,7 +129,7 @@

Quick settings

- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Public Tuner', id: 'publicTuner'}) %> + <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Unlocked Tuner', id: 'publicTuner'}) %> <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Admin lock', id: 'lockToAdmin'}) %>
@@ -483,7 +483,7 @@ <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Broadcast to map', id: 'identification-broadcastTuner'}) %>
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Your e-mail or Discord...', label: 'Owner contact', id: 'identification-contact'}) %> - <%- include('_components', {component: 'text', cssClass: 'br-15', label: 'Broadcast address (if using a proxy)', id: 'identification-proxyIp'}) %> + <%- include('_components', {component: 'text', cssClass: 'br-15', label: 'Proxy address', id: 'identification-proxyIp'}) %>

Check your tuner at servers.fmdx.org.

By activating the Broadcast to map option,
you agree to the Terms of Service.

@@ -700,20 +700,31 @@

Tunnel

When you become an FMDX.org supporter, you can host your webserver without the need of a public IP address & port forwarding.
When you become a supporter, you can message the Founders on Discord for your login details.

- +

Main tunnel settings

<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %>
+ <%- include('_components', { component: 'dropdown', id: 'tunnel-server', inputId: 'tunnel-serverSelect', label: 'Official server region', cssClass: '', placeholder: 'Europe', + options: [ + { value: 'eu', label: 'Europe' }, + { value: 'us', label: 'Americas' }, + { value: 'sg', label: 'Asia & Oceania' }, + ] + }) %> <%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Username', id: 'tunnel-username'}) %> <%- include('_components', {component: 'text', cssClass: 'w-250 br-15', password: true, placeholder: '', label: 'Token', id: 'tunnel-token'}) %> - <%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %>.fmtuner.org - -

Enabling low latency mode may provide better experience, however it will also use more bandwidth.

+ <%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %>
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Low latency mode', id: 'tunnel-lowLatencyMode'}) %>
+

Enabling low latency mode may provide better experience, however it will also use more bandwidth.

+ +

Community tunnel settings

+

You can also self-host or ask other people to provide you a token. In this case, the server owner is responsible for any potential security issues.

+ <%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Use a community tunnel', id: 'tunnel-community-enabled'}) %>
+ <%- include('_components', {component: 'text', cssClass: 'w-250 br-15', placeholder: '', label: 'Community Tunnel host (IP or hostname)', id: 'tunnel-community-host'}) %>
- + @@ -722,5 +733,15 @@ <% enabledPlugins?.forEach(function(plugin) { %> <% }); %> + + diff --git a/web/wizard.ejs b/web/wizard.ejs index 0f4b87f..3e57144 100644 --- a/web/wizard.ejs +++ b/web/wizard.ejs @@ -220,7 +220,6 @@ -