Refine Restreamer Core address resolution and enhance proxy error handling

This commit is contained in:
CesarMendivil 2026-02-28 14:52:00 -07:00
parent 0ee2475a1e
commit 234f59ff13
2 changed files with 66 additions and 47 deletions

View File

@ -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(

View File

@ -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 }));
}
},
}),
);
};