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 <noreply@anthropic.com>
This commit is contained in:
parent
b3f669afe4
commit
42fead82aa
2 changed files with 19 additions and 6 deletions
|
|
@ -552,7 +552,8 @@ body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellips
|
||||||
<div class="admin-panel" id="admin-relay">
|
<div class="admin-panel" id="admin-relay">
|
||||||
<div class="section-title">UDP Relay Configuration</div>
|
<div class="section-title">UDP Relay Configuration</div>
|
||||||
<div class="relay-status-indicator"><div class="relay-dot grey" id="relay-dot"></div><span id="relay-status-text">Not checked</span></div>
|
<div class="relay-status-indicator"><div class="relay-dot grey" id="relay-dot"></div><span id="relay-status-text">Not checked</span></div>
|
||||||
<div class="form-group"><label class="form-label">Relay Server URL</label><input class="form-input" id="relay-url" type="url" placeholder="https://relay.yourdomain.com"/><div class="form-hint">Base URL of your Dragon Wind UDP Relay container</div></div>
|
<div class="form-group"><label class="form-label">Internal Relay URL</label><input class="form-input" id="relay-url" type="url" placeholder="http://dragon-wind-relay:3001"/><div class="form-hint">Internal URL the server uses to reach the relay container (Docker service name or localhost)</div></div>
|
||||||
|
<div class="form-group"><label class="form-label">Public Relay URL <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim)">(what browsers connect to)</span></label><input class="form-input" id="relay-public-url" type="url" placeholder="http://vpm.broadcastmgmt.cloud:3001"/><div class="form-hint">The externally reachable URL for the relay — sent to uploaders' browsers. Must be reachable on port 3001 from the internet.</div></div>
|
||||||
<div class="form-group"><label class="form-label">UDP Data Port</label><input class="form-input" id="relay-udp-port" type="number" placeholder="5000" min="1024" max="65535"/></div>
|
<div class="form-group"><label class="form-label">UDP Data Port</label><input class="form-input" id="relay-udp-port" type="number" placeholder="5000" min="1024" max="65535"/></div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-secondary" onclick="testRelay()">🔍 Test Relay</button>
|
<button class="btn-secondary" onclick="testRelay()">🔍 Test Relay</button>
|
||||||
|
|
@ -1208,7 +1209,12 @@ async function saveS3() {
|
||||||
// RELAY ADMIN
|
// RELAY ADMIN
|
||||||
// ============================================================
|
// ============================================================
|
||||||
async function loadRelayConfig() {
|
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() {
|
async function testRelay() {
|
||||||
const s=document.getElementById('relay-status');
|
const s=document.getElementById('relay-status');
|
||||||
|
|
@ -1223,7 +1229,7 @@ async function saveRelay() {
|
||||||
const s=document.getElementById('relay-status');
|
const s=document.getElementById('relay-status');
|
||||||
s.className='status-msg loading'; s.textContent='💾 Saving…';
|
s.className='status-msg loading'; s.textContent='💾 Saving…';
|
||||||
try {
|
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}`;
|
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}`;}
|
} catch(e){s.className='status-msg error';s.textContent=`❌ ${e.message}`;}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
server.js
13
server.js
|
|
@ -483,13 +483,18 @@ app.post("/api/s3/test", requireAdmin, async (req, res) => {
|
||||||
// ==================== RELAY CONFIG (Admin) ====================
|
// ==================== RELAY CONFIG (Admin) ====================
|
||||||
app.get("/api/relay/config", requireAdmin, (req, res) => {
|
app.get("/api/relay/config", requireAdmin, (req, res) => {
|
||||||
const cfg = db.relayConfig || {};
|
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) => {
|
app.put("/api/relay/config", requireAdmin, (req, res) => {
|
||||||
const { relayUrl, udpPort } = req.body;
|
const { relayUrl, publicRelayUrl, udpPort } = req.body;
|
||||||
if (!db.relayConfig) db.relayConfig = {};
|
if (!db.relayConfig) db.relayConfig = {};
|
||||||
db.relayConfig.relayUrl = (relayUrl || "").trim();
|
db.relayConfig.relayUrl = (relayUrl || "").trim();
|
||||||
|
db.relayConfig.publicRelayUrl = (publicRelayUrl || "").trim();
|
||||||
db.relayConfig.udpPort = parseInt(udpPort) || 5000;
|
db.relayConfig.udpPort = parseInt(udpPort) || 5000;
|
||||||
saveData(db);
|
saveData(db);
|
||||||
res.json({ success: true, message: "Relay configuration saved" });
|
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 relayUrl = db.relayConfig?.relayUrl || "";
|
||||||
const udpPort = db.relayConfig?.udpPort || 5000;
|
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." });
|
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 sessionId = crypto.randomBytes(16).toString("hex");
|
||||||
const key = (prefix ? `${prefix.replace(/[-\/]+$/, "")}--${filename}` : filename);
|
const key = (prefix ? `${prefix.replace(/[-\/]+$/, "")}--${filename}` : filename);
|
||||||
udpSessions.set(sessionId, {
|
udpSessions.set(sessionId, {
|
||||||
|
|
@ -598,7 +605,7 @@ app.post("/api/udp/session", requireAuth, (req, res) => {
|
||||||
});
|
});
|
||||||
// Cleanup after 2 hours
|
// Cleanup after 2 hours
|
||||||
setTimeout(() => udpSessions.delete(sessionId), 2 * 60 * 60 * 1000);
|
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) => {
|
app.get("/api/udp/session/:id", requireAuth, (req, res) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue