const fs = require('fs'); const path = require('path'); const http = require('http'); const { pipeline } = require('stream'); const { URL } = require('url'); const ROOT_DIR = path.resolve(__dirname, '..', 'dist'); const UI_PORT = Number(process.env.CRANK_E2E_UI_PORT || 3300); const ADMIN_PORT = Number(process.env.CRANK_E2E_ADMIN_PORT || 3301); const ADMIN_BASE = new URL(`http://127.0.0.1:${ADMIN_PORT}`); const MIME_TYPES = { '.css': 'text/css; charset=utf-8', '.html': 'text/html; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.woff2': 'font/woff2', '.txt': 'text/plain; charset=utf-8', }; function sendNotFound(response) { response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); response.end('Not Found'); } function redirect(response, location) { response.writeHead(302, { Location: location }); response.end(); } function mapRoute(urlPath) { if (urlPath === '/' || urlPath === '/index.html') { return path.join(ROOT_DIR, 'index.html'); } if (urlPath === '/login' || urlPath === '/login.html') { return path.join(ROOT_DIR, 'html', 'login.html'); } if (urlPath === '/agents') { return path.join(ROOT_DIR, 'html', 'agents.html'); } if (urlPath === '/api-keys') { return path.join(ROOT_DIR, 'html', 'api-keys.html'); } if (urlPath === '/secrets') { return path.join(ROOT_DIR, 'html', 'secrets.html'); } if (urlPath === '/logs') { return path.join(ROOT_DIR, 'html', 'logs.html'); } if (urlPath === '/usage') { return path.join(ROOT_DIR, 'html', 'usage.html'); } if (urlPath === '/settings') { return path.join(ROOT_DIR, 'html', 'settings.html'); } if (urlPath === '/workspace-setup') { return path.join(ROOT_DIR, 'html', 'workspace-setup.html'); } if (urlPath === '/wizard/') { return path.join(ROOT_DIR, 'html', 'wizard', 'index.html'); } if (urlPath.startsWith('/wizard/')) { return path.join(ROOT_DIR, 'html', 'wizard', urlPath.slice('/wizard/'.length)); } if (urlPath.startsWith('/css/')) { return path.join(ROOT_DIR, urlPath.slice(1)); } if (urlPath.startsWith('/js/')) { return path.join(ROOT_DIR, urlPath.slice(1)); } if (urlPath.startsWith('/icons/')) { return path.join(ROOT_DIR, urlPath.slice(1)); } if (urlPath.startsWith('/data/')) { return path.join(ROOT_DIR, urlPath.slice(1)); } if (urlPath === '/Crank.png') { return path.resolve(ROOT_DIR, '..', '..', 'crank-community.png'); } return null; } function serveFile(filePath, response) { const normalized = path.normalize(filePath); if (!normalized.startsWith(ROOT_DIR) && !normalized.endsWith(path.sep + 'crank-community.png')) { sendNotFound(response); return; } fs.stat(normalized, (error, stats) => { if (error || !stats.isFile()) { sendNotFound(response); return; } const extension = path.extname(normalized).toLowerCase(); response.writeHead(200, { 'Content-Length': stats.size, 'Content-Type': MIME_TYPES[extension] || 'application/octet-stream', 'Cache-Control': 'no-store', }); pipeline(fs.createReadStream(normalized), response, () => {}); }); } function proxyRequest(request, response) { const target = new URL(request.url, ADMIN_BASE); const proxy = http.request( target, { method: request.method, headers: { ...request.headers, host: `127.0.0.1:${ADMIN_PORT}`, }, }, (proxyResponse) => { if (response.destroyed || response.writableEnded) { proxyResponse.resume(); return; } response.writeHead(proxyResponse.statusCode || 502, proxyResponse.headers); proxyResponse.on('error', function() { if (!response.destroyed) response.destroy(); }); response.on('close', function() { if (!proxyResponse.destroyed) proxyResponse.destroy(); }); proxyResponse.pipe(response); }, ); proxy.on('error', () => { response.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' }); response.end('Bad Gateway'); }); pipeline(request, proxy, () => {}); } const server = http.createServer((request, response) => { const requestUrl = new URL(request.url, `http://127.0.0.1:${UI_PORT}`); const urlPath = requestUrl.pathname; if (urlPath === '/wizard') { redirect(response, '/wizard/'); return; } if (urlPath === '/html/login.html') { redirect(response, '/login'); return; } if (urlPath === '/html/agents.html') { redirect(response, '/agents'); return; } if (urlPath === '/html/api-keys.html') { redirect(response, '/api-keys'); return; } if (urlPath === '/html/secrets.html') { redirect(response, '/secrets'); return; } if (urlPath === '/html/logs.html') { redirect(response, '/logs'); return; } if (urlPath === '/html/usage.html') { redirect(response, '/usage'); return; } if (urlPath === '/html/settings.html') { redirect(response, '/settings'); return; } if (urlPath === '/html/workspace-setup.html') { redirect(response, '/workspace-setup'); return; } if (urlPath === '/html/wizard' || urlPath === '/html/wizard/') { redirect(response, '/wizard/'); return; } if (urlPath.startsWith('/api/auth/') || urlPath.startsWith('/api/admin/')) { proxyRequest(request, response); return; } const filePath = mapRoute(urlPath); if (!filePath) { sendNotFound(response); return; } serveFile(filePath, response); }); server.listen(UI_PORT, '127.0.0.1', () => { console.log(`Playwright UI server listening on http://127.0.0.1:${UI_PORT}`); });