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
-
+
+
@@ -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) => {