const express = require('express'); const path = require('path'); const { GoogleAuth } = require('google-auth-library'); const app = express(); const port = process.env.PORT || 8080; const BACKEND_URL = process.env.BACKEND_URL; // Validate required environment variable at startup if (!BACKEND_URL) { console.error('ERROR: BACKEND_URL environment variable is required'); process.exit(1); } // Initialize Google Auth client (auto-detects credentials on Cloud Run) const auth = new GoogleAuth(); app.use(express.json()); // Serve static React build app.use(express.static(path.join(__dirname, 'dist'))); // Proxy endpoint - generates ID token and forwards to backend app.post('/api/chat/stream', async (req, res) => { console.log('Proxy request received for /api/chat/stream'); try { // Get ID token client with backend URL as audience const client = await auth.getIdTokenClient(BACKEND_URL); const headers = await client.getRequestHeaders(); console.log('Generated ID token, forwarding to backend...'); // Forward request to backend with auth const backendResponse = await fetch(`${BACKEND_URL}/chat/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, }, body: JSON.stringify(req.body), }); // Check if backend returned an error if (!backendResponse.ok) { const errorText = await backendResponse.text(); console.error(`Backend error: ${backendResponse.status} - ${errorText}`); res.status(backendResponse.status).json({ error: 'Backend error', status: backendResponse.status, message: errorText, }); return; } // Set SSE headers and flush immediately res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders?.(); // Pipe raw SSE stream from backend to browser (no parsing) const reader = backendResponse.body.getReader(); // Handle client disconnect req.on('close', () => { console.log('Client disconnected'); reader.cancel(); }); // Stream loop while (true) { const { done, value } = await reader.read(); if (done) { console.log('Stream complete'); res.end(); return; } res.write(value); } } catch (error) { console.error('Proxy error:', error); if (!res.headersSent) { res.status(500).json({ error: 'Proxy error', message: error.message }); } else { res.end(); } } }); // Health check endpoint for Cloud Run app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy' }); }); // SPA fallback - all other routes serve index.html app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); // Listen on 0.0.0.0 for Cloud Run app.listen(port, '0.0.0.0', () => { console.log(`Server listening on ${port}`); console.log(`Backend URL: ${BACKEND_URL}`); });