feat(frontend): per-user DLOB source header
- Add cookie-based source selector (mevnode|drift)\n- Proxy sets x-hasura-dlob-source for HTTP + WS\n- Include same proxy script mount in prod overlay
This commit is contained in:
@@ -37,6 +37,8 @@ const AUTH_SESSION_COOKIE = String(process.env.AUTH_SESSION_COOKIE || 'trade_ses
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const AUTH_SESSION_TTL_SECONDS = Number.parseInt(process.env.AUTH_SESSION_TTL_SECONDS || '43200', 10); // 12h
|
||||
const DLOB_SOURCE_COOKIE = String(process.env.DLOB_SOURCE_COOKIE || 'trade_dlob_source').trim() || 'trade_dlob_source';
|
||||
const DLOB_SOURCE_DEFAULT = String(process.env.DLOB_SOURCE_DEFAULT || 'mevnode').trim() || 'mevnode';
|
||||
|
||||
function readJson(filePath) {
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
@@ -143,6 +145,29 @@ function safePathFromUrlPath(urlPath) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function injectIndexHtml(html, { dlobSource, redirectPath }) {
|
||||
const src = normalizeDlobSource(dlobSource) || 'mevnode';
|
||||
const redirect = safeRedirectPath(redirectPath);
|
||||
const hrefBase = `/prefs/dlob-source?redirect=${encodeURIComponent(redirect)}&set=`;
|
||||
|
||||
const styleActive = 'font-weight:700;text-decoration:underline;';
|
||||
const styleInactive = 'font-weight:400;text-decoration:none;';
|
||||
|
||||
const snippet = `
|
||||
<!-- trade: dlob source switch -->
|
||||
<div style="position:fixed;right:12px;bottom:12px;z-index:2147483647;background:rgba(0,0,0,0.72);color:#fff;padding:8px 10px;border-radius:10px;font:12px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;backdrop-filter:blur(6px);">
|
||||
<span style="opacity:0.85;margin-right:6px;">DLOB</span>
|
||||
<a href="${hrefBase}mevnode" style="color:#fff;${src === 'mevnode' ? styleActive : styleInactive}">mevnode</a>
|
||||
<span style="opacity:0.6;margin:0 6px;">|</span>
|
||||
<a href="${hrefBase}drift" style="color:#fff;${src === 'drift' ? styleActive : styleInactive}">drift</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const bodyClose = /<\/body>/i;
|
||||
if (bodyClose.test(html)) return html.replace(bodyClose, `${snippet}</body>`);
|
||||
return `${html}\n${snippet}\n`;
|
||||
}
|
||||
|
||||
function serveStatic(req, res) {
|
||||
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
||||
send(res, 405, { 'content-type': 'text/plain; charset=utf-8' }, 'method_not_allowed');
|
||||
@@ -171,6 +196,15 @@ function serveStatic(req, res) {
|
||||
res.setHeader('content-type', contentTypeFor(filePath));
|
||||
res.setHeader('cache-control', filePath.endsWith('index.html') ? 'no-cache' : 'public, max-age=31536000');
|
||||
if (req.method === 'HEAD') return void res.end();
|
||||
if (filePath.endsWith('index.html')) {
|
||||
const html = fs.readFileSync(filePath, 'utf8');
|
||||
const injected = injectIndexHtml(html, {
|
||||
dlobSource: resolveDlobSource(req),
|
||||
redirectPath: url.pathname + url.search,
|
||||
});
|
||||
res.end(injected);
|
||||
return true;
|
||||
}
|
||||
fs.createReadStream(filePath).pipe(res);
|
||||
return true;
|
||||
} catch {
|
||||
@@ -222,6 +256,43 @@ function readCookie(req, name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeDlobSource(value) {
|
||||
const v = String(value ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (v === 'mevnode') return 'mevnode';
|
||||
if (v === 'drift') return 'drift';
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveDlobSource(req) {
|
||||
const fromCookie = normalizeDlobSource(readCookie(req, DLOB_SOURCE_COOKIE));
|
||||
if (fromCookie) return fromCookie;
|
||||
return normalizeDlobSource(DLOB_SOURCE_DEFAULT) || 'mevnode';
|
||||
}
|
||||
|
||||
function safeRedirectPath(value) {
|
||||
const s = String(value ?? '').trim();
|
||||
if (!s.startsWith('/')) return '/';
|
||||
if (s.startsWith('//')) return '/';
|
||||
return s.replace(/\r|\n/g, '');
|
||||
}
|
||||
|
||||
function setDlobSourceCookie(res, { secure, dlobSource }) {
|
||||
const src = normalizeDlobSource(dlobSource);
|
||||
if (!src) return false;
|
||||
const parts = [
|
||||
`${DLOB_SOURCE_COOKIE}=${src}`,
|
||||
'Path=/',
|
||||
'SameSite=Lax',
|
||||
'HttpOnly',
|
||||
'Max-Age=31536000',
|
||||
];
|
||||
if (secure) parts.push('Secure');
|
||||
res.setHeader('set-cookie', parts.join('; '));
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveAuthUser(req) {
|
||||
const user = readHeader(req, AUTH_USER_HEADER) || readHeader(req, 'x-webauth-user');
|
||||
const value = typeof user === 'string' ? user.trim() : '';
|
||||
@@ -423,7 +494,7 @@ function withCors(res) {
|
||||
res.setHeader('access-control-allow-methods', 'GET,POST,OPTIONS');
|
||||
res.setHeader(
|
||||
'access-control-allow-headers',
|
||||
'content-type, authorization, x-hasura-admin-secret, x-hasura-role, x-hasura-user-id'
|
||||
'content-type, authorization, x-hasura-admin-secret, x-hasura-role, x-hasura-user-id, x-hasura-dlob-source'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -440,6 +511,8 @@ function proxyGraphqlHttp(req, res) {
|
||||
|
||||
const headers = stripHopByHopHeaders(req.headers);
|
||||
headers.host = target.host;
|
||||
delete headers['x-hasura-dlob-source'];
|
||||
headers['x-hasura-dlob-source'] = resolveDlobSource(req);
|
||||
|
||||
const upstreamReq = lib.request(
|
||||
{
|
||||
@@ -499,6 +572,8 @@ function proxyGraphqlWs(req, socket, head) {
|
||||
delete headers['content-length'];
|
||||
delete headers['content-type'];
|
||||
headers.host = target.host;
|
||||
delete headers['x-hasura-dlob-source'];
|
||||
headers['x-hasura-dlob-source'] = resolveDlobSource(req);
|
||||
|
||||
const lines = [];
|
||||
lines.push(`GET ${target.pathname + target.search} HTTP/1.1`);
|
||||
@@ -570,6 +645,25 @@ async function handler(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/prefs/dlob-source') {
|
||||
const set = url.searchParams.get('set');
|
||||
if (!set) {
|
||||
sendJson(res, 200, { ok: true, dlobSource: resolveDlobSource(req) });
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = setDlobSourceCookie(res, { secure: isHttpsRequest(req), dlobSource: set });
|
||||
if (!ok) {
|
||||
sendJson(res, 400, { ok: false, error: 'invalid_dlob_source' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.statusCode = 302;
|
||||
res.setHeader('location', safeRedirectPath(url.searchParams.get('redirect') || '/'));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && url.pathname === '/auth/login') {
|
||||
if (AUTH_MODE === 'off' || AUTH_MODE === 'none' || AUTH_MODE === 'disabled') {
|
||||
sendJson(res, 400, { ok: false, error: 'auth_disabled' });
|
||||
|
||||
Reference in New Issue
Block a user