From 42fead82aa3867c0a4825ffcae571de60e4fd352 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Mon, 6 Apr 2026 22:02:14 -0400 Subject: [PATCH] fix: split relay URL into internal + public for browser clients The relay container is reached internally via docker service name (http://dragon-wind-relay:3001) but browsers need the public address. Previously the internal URL was sent to clients causing UDP uploads to fail. - Add publicRelayUrl field to relay config (server + admin UI) - /api/udp/session now returns publicRelayUrl to browser clients - Internal relayUrl still used for server-side health checks - Falls back to internal URL if public not set Co-Authored-By: Claude Sonnet 4.6 --- public/index.html | 12 +++++++++--- server.js | 13 ++++++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/public/index.html b/public/index.html index 6609ff6..2ac1a36 100644 --- a/public/index.html +++ b/public/index.html @@ -552,7 +552,8 @@ body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellips
UDP Relay Configuration
Not checked
-
Base URL of your Dragon Wind UDP Relay container
+
Internal URL the server uses to reach the relay container (Docker service name or localhost)
+
The externally reachable URL for the relay β€” sent to uploaders' browsers. Must be reachable on port 3001 from the internet.
@@ -1208,7 +1209,12 @@ async function saveS3() { // RELAY ADMIN // ============================================================ async function loadRelayConfig() { - try { const d=await api('GET','/api/relay/config'); if(!d.success)return; document.getElementById('relay-url').value=d.config.relayUrl||''; document.getElementById('relay-udp-port').value=d.config.udpPort||5000; } catch(_){} + try { + const d=await api('GET','/api/relay/config'); if(!d.success)return; + document.getElementById('relay-url').value=d.config.relayUrl||''; + document.getElementById('relay-public-url').value=d.config.publicRelayUrl||''; + document.getElementById('relay-udp-port').value=d.config.udpPort||5000; + } catch(_){} } async function testRelay() { const s=document.getElementById('relay-status'); @@ -1223,7 +1229,7 @@ async function saveRelay() { const s=document.getElementById('relay-status'); s.className='status-msg loading'; s.textContent='πŸ’Ύ Saving…'; try { - const d=await api('PUT','/api/relay/config',{relayUrl:document.getElementById('relay-url').value.trim(),udpPort:parseInt(document.getElementById('relay-udp-port').value)}); + const d=await api('PUT','/api/relay/config',{relayUrl:document.getElementById('relay-url').value.trim(),publicRelayUrl:document.getElementById('relay-public-url').value.trim(),udpPort:parseInt(document.getElementById('relay-udp-port').value)}); s.className=`status-msg ${d.success?'success':'error'}`; s.textContent=d.success?'βœ… Relay configuration saved':`❌ ${d.error}`; } catch(e){s.className='status-msg error';s.textContent=`❌ ${e.message}`;} } diff --git a/server.js b/server.js index efaa605..f8a1c31 100644 --- a/server.js +++ b/server.js @@ -483,13 +483,18 @@ app.post("/api/s3/test", requireAdmin, async (req, res) => { // ==================== RELAY CONFIG (Admin) ==================== app.get("/api/relay/config", requireAdmin, (req, res) => { const cfg = db.relayConfig || {}; - res.json({ success: true, config: { relayUrl: cfg.relayUrl || "", udpPort: cfg.udpPort || 5000 } }); + res.json({ success: true, config: { + relayUrl: cfg.relayUrl || "", + publicRelayUrl: cfg.publicRelayUrl || "", + udpPort: cfg.udpPort || 5000 + }}); }); app.put("/api/relay/config", requireAdmin, (req, res) => { - const { relayUrl, udpPort } = req.body; + const { relayUrl, publicRelayUrl, udpPort } = req.body; if (!db.relayConfig) db.relayConfig = {}; db.relayConfig.relayUrl = (relayUrl || "").trim(); + db.relayConfig.publicRelayUrl = (publicRelayUrl || "").trim(); db.relayConfig.udpPort = parseInt(udpPort) || 5000; saveData(db); res.json({ success: true, message: "Relay configuration saved" }); @@ -588,6 +593,8 @@ app.post("/api/udp/session", requireAuth, (req, res) => { const relayUrl = db.relayConfig?.relayUrl || ""; const udpPort = db.relayConfig?.udpPort || 5000; if (!relayUrl) return res.status(503).json({ success: false, error: "UDP relay not configured. Go to Admin β†’ Relay Settings." }); + // Public URL is what the browser connects to β€” falls back to internal if not set + const publicRelayUrl = (db.relayConfig?.publicRelayUrl || "").trim() || relayUrl; const sessionId = crypto.randomBytes(16).toString("hex"); const key = (prefix ? `${prefix.replace(/[-\/]+$/, "")}--${filename}` : filename); udpSessions.set(sessionId, { @@ -598,7 +605,7 @@ app.post("/api/udp/session", requireAuth, (req, res) => { }); // Cleanup after 2 hours setTimeout(() => udpSessions.delete(sessionId), 2 * 60 * 60 * 1000); - res.json({ success: true, sessionId, relayUrl, udpPort, key, s3Bucket: db.s3Config?.bucket || "" }); + res.json({ success: true, sessionId, relayUrl: publicRelayUrl, udpPort, key, s3Bucket: db.s3Config?.bucket || "" }); }); app.get("/api/udp/session/:id", requireAuth, (req, res) => {