From 234f59ff137339dc7e536df51754204615f5fd44 Mon Sep 17 00:00:00 2001 From: CesarMendivil Date: Sat, 28 Feb 2026 14:52:00 -0700 Subject: [PATCH] Refine Restreamer Core address resolution and enhance proxy error handling --- src/index.js | 37 +++++++++++++---------- src/setupProxy.js | 76 +++++++++++++++++++++++++++-------------------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/src/index.js b/src/index.js index 0d2b2a1..15e8715 100644 --- a/src/index.js +++ b/src/index.js @@ -10,14 +10,15 @@ import theme from './theme'; import RestreamerUI from './RestreamerUI'; /** - * Resolve the Restreamer Core address using the following priority: + * Resolve the Restreamer Core address: * - * 1. ?address= query param (explicit override, useful for dev/testing) - * 2. window.__RESTREAMER_CONFIG__.CORE_ADDRESS (set in public/config.js — editable at runtime) - * 3. Auto-detect from window.location: - * a. If served under /ui/ path → strip it (standard production setup inside Core) - * b. Otherwise use empty string → all /api/* calls are relative, handled by - * CRA proxy (setupProxy.js) in development or the same-origin server in production. + * 1. ?address= → explicit URL param (always wins) + * 2. window.__RESTREAMER_CONFIG__ → public/config.js (Docker/production runtime) + * 3. /ui/ path detection → strip /ui/ suffix (embedded inside Core) + * 4. process.env.REACT_APP_CORE_URL → .env / .env.local (CRA injects at build time) + * Used both for the proxy (Node.js) AND as the + * browser-side address so hostname is never empty. + * 5. window.location.origin → same-origin fallback (production same host) */ const urlParams = new URLSearchParams(window.location.search.substring(1)); const runtimeConfig = window.__RESTREAMER_CONFIG__ || {}; @@ -25,17 +26,23 @@ const runtimeConfig = window.__RESTREAMER_CONFIG__ || {}; let address; if (urlParams.has('address')) { - // Priority 1: explicit ?address= param + // 1. Explicit override via URL param address = urlParams.get('address'); } else if (runtimeConfig.CORE_ADDRESS && runtimeConfig.CORE_ADDRESS.trim() !== '') { - // Priority 2: runtime config.js (Docker / production override) - address = runtimeConfig.CORE_ADDRESS.trim(); -} else if (window.location.pathname.endsWith('/ui/')) { - // Priority 3a: served inside Core at /ui/ - address = window.location.protocol + '//' + window.location.host + window.location.pathname.replace(/ui\/$/, ''); + // 2. Runtime config.js — Docker / production without /ui/ path + address = runtimeConfig.CORE_ADDRESS.trim().replace(/\/$/, ''); +} else if (window.location.pathname.includes('/ui/')) { + // 3. Embedded inside Core at /ui/ path + address = window.location.protocol + '//' + window.location.host + + window.location.pathname.replace(/\/ui\/.*$/, ''); +} else if (process.env.REACT_APP_CORE_URL && process.env.REACT_APP_CORE_URL.trim() !== '') { + // 4. .env / .env.local — CRA injects at build time. + // Use window.location.origin so all /api/* and /memfs/* calls go through + // the CRA dev proxy (setupProxy.js) which forwards them to REACT_APP_CORE_URL. + address = window.location.origin; } else { - // Priority 3b: development proxy or same-origin production - address = ''; + // 5. Same-origin production (Core and UI on the same host/port) + address = window.location.origin; } createRoot(document.getElementById('root')).render( diff --git a/src/setupProxy.js b/src/setupProxy.js index 2278644..bfb1687 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -1,44 +1,57 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); /** - * Development proxy configuration (CRA - only active with `npm start`). + * Development proxy (CRA - only active with `npm start`). * - * Set in your .env.local: - * REACT_APP_CORE_URL=https://restreamer.nextream.sytes.net <- Restreamer Core - * REACT_APP_YTDLP_URL=http://192.168.1.20:8282 <- yt-dlp stream extractor + * .env / .env.local: + * REACT_APP_CORE_URL=https://restreamer.nextream.sytes.net + * REACT_APP_YTDLP_URL=http://192.168.1.20:8282 */ const CORE_TARGET = process.env.REACT_APP_CORE_URL || 'http://localhost:8080'; const YTDLP_TARGET = process.env.REACT_APP_YTDLP_URL || 'http://localhost:8282'; -console.log(`[setupProxy] /api → ${CORE_TARGET}`); -console.log(`[setupProxy] /yt-stream → ${YTDLP_TARGET}`); +console.log('\n[setupProxy] ─────────────────────────────────────'); +console.log(`[setupProxy] Core → ${CORE_TARGET}`); +console.log(`[setupProxy] yt-dlp → ${YTDLP_TARGET}`); +console.log('[setupProxy] ─────────────────────────────────────\n'); -const coreUrl = new URL(CORE_TARGET); +let coreUrl; +try { + coreUrl = new URL(CORE_TARGET); +} catch (e) { + console.error(`[setupProxy] Invalid REACT_APP_CORE_URL: "${CORE_TARGET}"`); + coreUrl = new URL('http://localhost:8080'); +} + +// Shared proxy instance for all Core paths (/api, /memfs, /diskfs) +const coreProxy = createProxyMiddleware({ + target: CORE_TARGET, + changeOrigin: true, + secure: false, + ws: false, + onProxyReq: (proxyReq) => { + proxyReq.setHeader('Host', coreUrl.host); + }, + onError: (err, req, res) => { + console.error(`[setupProxy] Core proxy error: ${err.code} — ${err.message}`); + if (!res.headersSent) { + res.writeHead(502, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Proxy error', target: CORE_TARGET, message: err.message })); + } + }, +}); module.exports = function (app) { - // Proxy /api/* → Restreamer Core - app.use( - '/api', - createProxyMiddleware({ - target: CORE_TARGET, - changeOrigin: true, - secure: false, - ws: false, // NO WebSocket — Core uses HTTP REST only - onProxyReq: (proxyReq) => { - // Fix Host header for HTTPS targets (required by some reverse proxies) - proxyReq.setHeader('Host', coreUrl.host); - }, - onError: (err, req, res) => { - console.error(`[setupProxy] /api error: ${err.message}`); - if (!res.headersSent) { - res.writeHead(502, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Proxy error', message: err.message })); - } - }, - }), - ); + // Restreamer Core REST API + app.use('/api', coreProxy); - // Proxy /yt-stream/* → yt-dlp extractor service → /stream/{VIDEO_ID} + // Restreamer Core in-memory FS (HLS playlists, snapshots) + app.use('/memfs', coreProxy); + + // Restreamer Core disk FS + app.use('/diskfs', coreProxy); + + // yt-dlp stream extractor: /yt-stream/{VIDEO_ID} → /stream/{VIDEO_ID} app.use( '/yt-stream', createProxyMiddleware({ @@ -48,13 +61,12 @@ module.exports = function (app) { ws: false, pathRewrite: { '^/yt-stream': '/stream' }, onError: (err, req, res) => { - console.error(`[setupProxy] /yt-stream error: ${err.message}`); + console.error(`[setupProxy] yt-dlp proxy error: ${err.code} — ${err.message}`); if (!res.headersSent) { res.writeHead(502, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Proxy error', message: err.message })); + res.end(JSON.stringify({ error: 'Proxy error', target: YTDLP_TARGET, message: err.message })); } }, }), ); }; -