/** * 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(` Debug

Debug Dashboard

Subdomains

Requests

`); }); 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 };