import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' function healthCheckPlugin() { return { name: 'health-check-proxy', configureServer(server) { server.middlewares.use('/api/health', async (req, res) => { const params = new URL(req.url, 'http://localhost').searchParams; const url = params.get('url'); const mode = params.get('mode'); // 'tcp' for raw TCP port check if (!url) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing url param' })); return; } const start = Date.now(); const parsedUrl = new URL(url); try { if (mode === 'tcp') { // TCP socket connect check for non-HTTP services (e.g. Wyoming) const { default: net } = await import('net'); await new Promise((resolve, reject) => { const socket = net.createConnection( { host: parsedUrl.hostname, port: parseInt(parsedUrl.port), timeout: 5000 }, () => { socket.destroy(); resolve(); } ); socket.on('error', reject); socket.on('timeout', () => { socket.destroy(); reject(new Error('timeout')); }); }); } else { // HTTP/HTTPS health check const { default: https } = await import('https'); const { default: http } = await import('http'); const client = parsedUrl.protocol === 'https:' ? https : http; await new Promise((resolve, reject) => { const reqObj = client.get(url, { rejectUnauthorized: false, timeout: 5000 }, (resp) => { resp.resume(); resolve(); }); reqObj.on('error', reject); reqObj.on('timeout', () => { reqObj.destroy(); reject(new Error('timeout')); }); }); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'online', responseTime: Date.now() - start })); } catch { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'offline', responseTime: null })); } }); // TTS preview proxy — forwards POST to OpenClaw bridge, returns audio server.middlewares.use('/api/tts', async (req, res) => { if (req.method !== 'POST') { res.writeHead(405); res.end(); return; } try { const { default: http } = await import('http'); const chunks = []; for await (const chunk of req) chunks.push(chunk); const body = Buffer.concat(chunks); await new Promise((resolve, reject) => { const proxyReq = http.request( 'http://localhost:8081/api/tts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': body.length }, timeout: 30000 }, (proxyRes) => { res.writeHead(proxyRes.statusCode, { 'Content-Type': proxyRes.headers['content-type'] || 'audio/wav', }); proxyRes.pipe(res); proxyRes.on('end', resolve); } ); proxyReq.on('error', reject); proxyReq.on('timeout', () => { proxyReq.destroy(); reject(new Error('timeout')); }); proxyReq.write(body); proxyReq.end(); }); } catch { res.writeHead(502, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'TTS bridge unreachable' })); } }); }, }; } // https://vite.dev/config/ export default defineConfig({ plugins: [ healthCheckPlugin(), tailwindcss(), react(), ], })