Spaces:
Sleeping
Sleeping
| /** | |
| * Express server with WebSocket proxy for TASTE Voice Bot frontend | |
| * | |
| * Architecture: | |
| * - Browser connects to this server on port 3000 (HTTP + WebSocket) | |
| * - Server serves static files from current directory | |
| * - WebSocket requests to /ws/* are proxied internally to backend | |
| * - Browser never directly connects to backend port | |
| */ | |
| const express = require('express'); | |
| const { createProxyMiddleware } = require('http-proxy-middleware'); | |
| const path = require('path'); | |
| // Read backend configuration from environment variables | |
| const BACKEND_HOST = process.env.BACKEND_HOST || 'localhost'; | |
| const BACKEND_PORT = process.env.BACKEND_PORT || '8000'; | |
| const BACKEND_WS_PATH = process.env.BACKEND_WS_PATH || '/ws/orchestrator'; | |
| const FRONTEND_PORT = process.env.FRONTEND_PORT || '3000'; | |
| // Parse BACKEND_HOST to extract protocol and hostname | |
| let backendUrl; | |
| try { | |
| // If BACKEND_HOST includes protocol (http:// or https://), parse it | |
| if (BACKEND_HOST.includes('://')) { | |
| const url = new URL(BACKEND_HOST); | |
| const protocol = url.protocol === 'https:' ? 'https' : 'http'; | |
| const wsProtocol = url.protocol === 'https:' ? 'wss' : 'ws'; | |
| const host = url.hostname; | |
| const port = url.port || BACKEND_PORT; | |
| backendUrl = { | |
| http: `${protocol}://${host}:${port}`, | |
| ws: `${wsProtocol}://${host}:${port}`, | |
| display: `${protocol}://${host}:${port}` | |
| }; | |
| } else { | |
| // BACKEND_HOST is just hostname | |
| backendUrl = { | |
| http: `http://${BACKEND_HOST}:${BACKEND_PORT}`, | |
| ws: `ws://${BACKEND_HOST}:${BACKEND_PORT}`, | |
| display: `http://${BACKEND_HOST}:${BACKEND_PORT}` | |
| }; | |
| } | |
| } catch (e) { | |
| // Fallback to simple hostname | |
| backendUrl = { | |
| http: `http://${BACKEND_HOST}:${BACKEND_PORT}`, | |
| ws: `ws://${BACKEND_HOST}:${BACKEND_PORT}`, | |
| display: `http://${BACKEND_HOST}:${BACKEND_PORT}` | |
| }; | |
| } | |
| const app = express(); | |
| // Serve static files from current directory | |
| app.use(express.static(__dirname)); | |
| // WebSocket proxy middleware | |
| const wsProxy = createProxyMiddleware({ | |
| target: backendUrl.http, | |
| changeOrigin: true, | |
| ws: true, // Enable WebSocket proxying | |
| logLevel: 'info', | |
| onError: (err, req, res) => { | |
| console.error('Proxy error:', err); | |
| if (res.writeHead) { | |
| res.writeHead(502, { 'Content-Type': 'text/plain' }); | |
| res.end('Bad Gateway: Could not connect to backend'); | |
| } | |
| }, | |
| onProxyReqWs: (proxyReq, req, socket, options, head) => { | |
| console.log(`[WS Proxy] ${req.url} -> ${backendUrl.ws}${req.url}`); | |
| } | |
| }); | |
| // Apply WebSocket proxy to /ws/* paths | |
| app.use('/ws', wsProxy); | |
| // Start server | |
| const server = app.listen(FRONTEND_PORT, () => { | |
| console.log('================================='); | |
| console.log('TASTE Voice Bot Frontend Server'); | |
| console.log('================================='); | |
| console.log(`Frontend: http://localhost:${FRONTEND_PORT}`); | |
| console.log(`Backend: ${backendUrl.display}`); | |
| console.log(`WS Proxy: /ws/* -> ${backendUrl.ws}/ws/*`); | |
| console.log('================================='); | |
| }); | |
| // Enable WebSocket upgrade handling | |
| server.on('upgrade', (req, socket, head) => { | |
| if (req.url.startsWith('/ws')) { | |
| wsProxy.upgrade(req, socket, head); | |
| } else { | |
| socket.destroy(); | |
| } | |
| }); | |
| // Graceful shutdown | |
| process.on('SIGTERM', () => { | |
| console.log('SIGTERM received, shutting down gracefully...'); | |
| server.close(() => { | |
| console.log('Server closed'); | |
| process.exit(0); | |
| }); | |
| }); | |
| process.on('SIGINT', () => { | |
| console.log('SIGINT received, shutting down gracefully...'); | |
| server.close(() => { | |
| console.log('Server closed'); | |
| process.exit(0); | |
| }); | |
| }); | |