64 lines
1.8 KiB
JavaScript
64 lines
1.8 KiB
JavaScript
const express = require('express');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 80;
|
|
const BACKEND_URL = process.env.BACKEND_URL;
|
|
|
|
app.use(express.json());
|
|
|
|
// Serve static React build
|
|
app.use(express.static(path.join(__dirname, 'dist')));
|
|
|
|
// Proxy endpoint - fetches identity token and forwards to backend
|
|
app.post('/api/chat/stream', async (req, res) => {
|
|
try {
|
|
// Fetch identity token from GCP metadata server
|
|
const tokenResponse = await fetch(
|
|
`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${BACKEND_URL}`,
|
|
{ headers: { 'Metadata-Flavor': 'Google' } }
|
|
);
|
|
const identityToken = await tokenResponse.text();
|
|
|
|
// Forward request to backend with auth
|
|
const backendResponse = await fetch(`${BACKEND_URL}/chat/stream`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${identityToken}`,
|
|
},
|
|
body: JSON.stringify(req.body),
|
|
});
|
|
|
|
// Stream response back to client
|
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
res.setHeader('Cache-Control', 'no-cache');
|
|
res.setHeader('Connection', 'keep-alive');
|
|
|
|
const reader = backendResponse.body.getReader();
|
|
const pump = async () => {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
res.end();
|
|
return;
|
|
}
|
|
res.write(value);
|
|
pump();
|
|
};
|
|
pump();
|
|
|
|
} catch (error) {
|
|
console.error('Proxy error:', error);
|
|
res.status(500).json({ error: 'Proxy error' });
|
|
}
|
|
});
|
|
|
|
// SPA fallback - all other routes serve index.html
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server running on port ${PORT}`);
|
|
});
|