docs: stage browser alias + UX backlog (P0–P2)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Cursor-browser alias for stage UI.
|
||||
*
|
||||
* Why: Chromium treats *.local as mDNS and often ignores unicast DNS / hosts.
|
||||
* This proxy serves http://ui.stage.eventhub.test:8787 → https://stage
|
||||
* with Host/SNI ui.stage.eventhub.local (Traefik + existing cert).
|
||||
*
|
||||
* Usage:
|
||||
* node stage-browser-alias-proxy.mjs
|
||||
* open http://ui.stage.eventhub.test:8787/login
|
||||
*
|
||||
* Requires hosts: 127.0.0.1 ui.stage.eventhub.test
|
||||
*
|
||||
* Docs: EventHubSpec/STAGE-BROWSER.md
|
||||
*/
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
|
||||
const LISTEN_HOST = process.env.ALIAS_LISTEN_HOST || '127.0.0.1';
|
||||
const LISTEN_PORT = Number(process.env.ALIAS_LISTEN_PORT || 8787);
|
||||
const UPSTREAM_IP = process.env.STAGE_IP || '195.208.119.190';
|
||||
const UPSTREAM_HOST = process.env.STAGE_UI_HOST || 'ui.stage.eventhub.local';
|
||||
const ALIAS_HOST = process.env.ALIAS_HOST || 'ui.stage.eventhub.test';
|
||||
|
||||
const agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
servername: UPSTREAM_HOST,
|
||||
keepAlive: true,
|
||||
});
|
||||
|
||||
function rewriteLocation(value) {
|
||||
if (!value) return value;
|
||||
return value
|
||||
.replaceAll(`https://${UPSTREAM_HOST}`, `http://${ALIAS_HOST}:${LISTEN_PORT}`)
|
||||
.replaceAll(`http://${UPSTREAM_HOST}`, `http://${ALIAS_HOST}:${LISTEN_PORT}`);
|
||||
}
|
||||
|
||||
function scrubHeaders(headers) {
|
||||
const out = { ...headers, host: UPSTREAM_HOST };
|
||||
delete out['content-length'];
|
||||
// Avoid compression surprises while piping
|
||||
delete out['accept-encoding'];
|
||||
return out;
|
||||
}
|
||||
|
||||
function proxyHttp(req, res) {
|
||||
const opts = {
|
||||
hostname: UPSTREAM_IP,
|
||||
port: 443,
|
||||
path: req.url,
|
||||
method: req.method,
|
||||
headers: scrubHeaders(req.headers),
|
||||
agent,
|
||||
};
|
||||
const up = https.request(opts, (upRes) => {
|
||||
const headers = { ...upRes.headers };
|
||||
if (headers.location) headers.location = rewriteLocation(headers.location);
|
||||
res.writeHead(upRes.statusCode || 502, headers);
|
||||
upRes.pipe(res);
|
||||
});
|
||||
up.on('error', (err) => {
|
||||
if (!res.headersSent) res.writeHead(502, { 'content-type': 'text/plain' });
|
||||
res.end(`alias-proxy upstream error: ${err.message}`);
|
||||
});
|
||||
req.pipe(up);
|
||||
}
|
||||
|
||||
function proxyUpgrade(req, socket, head) {
|
||||
const opts = {
|
||||
hostname: UPSTREAM_IP,
|
||||
port: 443,
|
||||
path: req.url,
|
||||
method: 'GET',
|
||||
headers: scrubHeaders(req.headers),
|
||||
agent,
|
||||
};
|
||||
const up = https.request(opts);
|
||||
up.on('upgrade', (upRes, upSocket, upHead) => {
|
||||
const lines = [`HTTP/1.1 ${upRes.statusCode} Switching Protocols`];
|
||||
for (const [k, v] of Object.entries(upRes.headers)) {
|
||||
if (Array.isArray(v)) v.forEach((x) => lines.push(`${k}: ${x}`));
|
||||
else lines.push(`${k}: ${v}`);
|
||||
}
|
||||
lines.push('', '');
|
||||
socket.write(lines.join('\r\n'));
|
||||
if (upHead?.length) socket.write(upHead);
|
||||
upSocket.pipe(socket);
|
||||
socket.pipe(upSocket);
|
||||
});
|
||||
up.on('error', () => socket.destroy());
|
||||
up.end(head);
|
||||
}
|
||||
|
||||
const server = http.createServer(proxyHttp);
|
||||
server.on('upgrade', proxyUpgrade);
|
||||
server.listen(LISTEN_PORT, LISTEN_HOST, () => {
|
||||
console.log(
|
||||
`stage browser alias: http://${ALIAS_HOST}:${LISTEN_PORT} → https://${UPSTREAM_HOST} (${UPSTREAM_IP})`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user