mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 08:01:46 -03:00
Fix community link order: TG Group > TG Channel > Chat
Consistent order across all files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
169
local-dev/debug-middleware.js
Normal file
169
local-dev/debug-middleware.js
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Debug Middleware for Local Development
|
||||
* Captures and logs all requests for research
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || '/app/data';
|
||||
const LOG_FILE = path.join(DATA_DIR, 'debug-requests.jsonl');
|
||||
const capturedRequests = [];
|
||||
const MAX_CAPTURED = 500;
|
||||
|
||||
function extractSubdomain(host) {
|
||||
if (!host) return null;
|
||||
const hostname = host.split(':')[0];
|
||||
const match = hostname.match(/^(sessions|account-data|telemetry|tools|oauth\.accounts|accounts)\./);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug middleware - logs requests (skip internal paths)
|
||||
*/
|
||||
function debugMiddleware(req, res, next) {
|
||||
// Skip debug/health/admin endpoints
|
||||
if (req.path.startsWith('/debug') || req.path === '/health' || req.path === '/favicon.ico') {
|
||||
return next();
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
const subdomain = extractSubdomain(req.headers.host);
|
||||
|
||||
// Color logging
|
||||
const colors = { reset: '\x1b[0m', green: '\x1b[32m', blue: '\x1b[34m', yellow: '\x1b[33m', magenta: '\x1b[35m', cyan: '\x1b[36m', red: '\x1b[31m' };
|
||||
const subColor = { 'sessions': colors.green, 'account-data': colors.blue, 'telemetry': colors.yellow, 'tools': colors.magenta }[subdomain] || colors.cyan;
|
||||
|
||||
console.log(`${subColor}[${subdomain || 'main'}]${colors.reset} ${req.method} ${req.url}`);
|
||||
|
||||
// Capture response
|
||||
const origSend = res.send.bind(res);
|
||||
const origJson = res.json.bind(res);
|
||||
let responseBody = null;
|
||||
|
||||
res.send = function(body) {
|
||||
responseBody = typeof body === 'string' && body.length > 2000 ? body.substring(0, 2000) + '...' : body;
|
||||
return origSend(body);
|
||||
};
|
||||
|
||||
res.json = function(body) {
|
||||
try {
|
||||
const str = JSON.stringify(body);
|
||||
responseBody = str.length > 2000 ? { _truncated: true, _len: str.length } : body;
|
||||
} catch (e) {
|
||||
responseBody = { _error: 'serialize failed' };
|
||||
}
|
||||
return origJson(body);
|
||||
};
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - startTime;
|
||||
const statusColor = res.statusCode >= 400 ? colors.red : colors.green;
|
||||
console.log(` ${statusColor}-> ${res.statusCode}${colors.reset} (${duration}ms)`);
|
||||
|
||||
const entry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
host: req.headers.host,
|
||||
subdomain,
|
||||
query: req.query,
|
||||
body: req.body,
|
||||
response: { statusCode: res.statusCode, duration, body: responseBody }
|
||||
};
|
||||
|
||||
capturedRequests.unshift(entry);
|
||||
if (capturedRequests.length > MAX_CAPTURED) capturedRequests.pop();
|
||||
|
||||
try { fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n'); } catch (e) {}
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup debug routes
|
||||
*/
|
||||
function setupDebugRoutes(app) {
|
||||
// Debug dashboard
|
||||
app.get('/debug', (req, res) => {
|
||||
res.send(`<!DOCTYPE html>
|
||||
<html><head><title>Debug</title>
|
||||
<style>
|
||||
body{font-family:monospace;background:#1a1a2e;color:#eee;padding:20px}
|
||||
h1{color:#00d4ff}.section{background:#16213e;padding:15px;margin:10px 0;border-radius:8px}
|
||||
.req{background:#0f3460;padding:8px;margin:4px 0;border-radius:4px;font-size:12px}
|
||||
.GET{color:#4ade80}.POST{color:#60a5fa}.DELETE{color:#f87171}
|
||||
.sub{color:#c084fc;font-weight:bold}pre{background:#0a0a1a;padding:8px;overflow-x:auto;font-size:11px}
|
||||
button{background:#00d4ff;color:#000;border:none;padding:6px 12px;cursor:pointer;margin:4px;border-radius:4px}
|
||||
select{padding:6px;background:#0f3460;border:1px solid #00d4ff;color:#fff;border-radius:4px}
|
||||
details summary{cursor:pointer;color:#60a5fa}
|
||||
</style></head><body>
|
||||
<h1>Debug Dashboard</h1>
|
||||
<div class="section"><button onclick="refresh()">Refresh</button><button onclick="clearReqs()">Clear</button>
|
||||
<select id="filter" onchange="refresh()"><option value="">All</option><option value="sessions">sessions</option>
|
||||
<option value="account-data">account-data</option><option value="telemetry">telemetry</option><option value="tools">tools</option></select>
|
||||
<span id="count"></span></div>
|
||||
<div class="section"><h2>Subdomains</h2><div id="subs"></div></div>
|
||||
<div class="section"><h2>Requests</h2><div id="reqs" style="max-height:500px;overflow-y:auto"></div></div>
|
||||
<script>
|
||||
async function refresh(){
|
||||
const f=document.getElementById('filter').value;
|
||||
const r=await fetch('/debug/requests'+(f?'?subdomain='+f:''));
|
||||
const d=await r.json();
|
||||
document.getElementById('count').textContent=d.total+' total';
|
||||
document.getElementById('reqs').innerHTML=d.requests.map(r=>\`<div class="req">
|
||||
<span class="\${r.method}">\${r.method}</span> <span class="sub">\${r.subdomain||'main'}</span> \${r.path}
|
||||
<span style="color:\${r.response?.statusCode<400?'#4ade80':'#f87171'}">\${r.response?.statusCode}</span>
|
||||
<details><summary>Details</summary><pre>\${JSON.stringify(r,null,2)}</pre></details></div>\`).join('');
|
||||
const s=await fetch('/debug/subdomains');
|
||||
const sd=await s.json();
|
||||
document.getElementById('subs').innerHTML=Object.entries(sd).map(([k,v])=>'<span class="sub">'+k+'</span>: '+v.count).join(' | ');
|
||||
}
|
||||
async function clearReqs(){await fetch('/debug/requests',{method:'DELETE'});refresh();}
|
||||
setInterval(refresh,5000);refresh();
|
||||
</script></body></html>`);
|
||||
});
|
||||
|
||||
app.get('/debug/requests', (req, res) => {
|
||||
let filtered = capturedRequests;
|
||||
if (req.query.subdomain) filtered = filtered.filter(r => r.subdomain === req.query.subdomain);
|
||||
res.json({ total: capturedRequests.length, filtered: filtered.length, requests: filtered.slice(0, 100) });
|
||||
});
|
||||
|
||||
app.get('/debug/subdomains', (req, res) => {
|
||||
const summary = {};
|
||||
for (const r of capturedRequests) {
|
||||
const sub = r.subdomain || 'main';
|
||||
if (!summary[sub]) summary[sub] = { count: 0 };
|
||||
summary[sub].count++;
|
||||
}
|
||||
res.json(summary);
|
||||
});
|
||||
|
||||
app.delete('/debug/requests', (req, res) => {
|
||||
capturedRequests.length = 0;
|
||||
res.json({ cleared: true });
|
||||
});
|
||||
|
||||
// Catch-all for unknown endpoints (must be registered last)
|
||||
app.all('*', (req, res, next) => {
|
||||
// Only catch truly unknown paths
|
||||
if (res.headersSent) return;
|
||||
|
||||
const subdomain = extractSubdomain(req.headers.host);
|
||||
console.log(`\x1b[31m[UNKNOWN]\x1b[0m ${req.method} ${req.path} (subdomain: ${subdomain})`);
|
||||
|
||||
res.status(200).json({
|
||||
debug: true,
|
||||
message: 'Unknown endpoint captured',
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
subdomain,
|
||||
headers: req.headers,
|
||||
body: req.body
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { debugMiddleware, setupDebugRoutes, capturedRequests };
|
||||
Reference in New Issue
Block a user