openpetswithchatandmcp/website/scripts/live-auth-smoke-host.mjs

116 lines
3.6 KiB
JavaScript

import fs from 'node:fs'
import path from 'node:path'
import http from 'node:http'
import { pipeline } from 'node:stream/promises'
import { Readable } from 'node:stream'
const port = Math.max(1, Number(process.env.UI_SMOKE_HOST_PORT || 4173))
const proxyTarget = new URL(
process.env.FAMILIAROS_UI_PROXY_TARGET
|| process.env.SCRIPTORIUM_UI_PROXY_TARGET
|| 'http://127.0.0.1:3014'
)
const distDir = path.resolve(process.cwd(), 'dist')
const MIME_TYPES = {
'.css': 'text/css; charset=utf-8',
'.html': 'text/html; charset=utf-8',
'.ico': 'image/x-icon',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.map': 'application/json; charset=utf-8',
'.png': 'image/png',
'.svg': 'image/svg+xml; charset=utf-8',
'.txt': 'text/plain; charset=utf-8',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
}
function isProxyPath(pathname) {
return pathname === '/api'
|| pathname.startsWith('/api/')
|| pathname === '/auth'
|| pathname.startsWith('/auth/')
}
function resolveStaticPath(pathname) {
const normalized = pathname === '/' ? '/index.html' : pathname
const absolute = path.resolve(distDir, `.${normalized}`)
if (!absolute.startsWith(distDir)) return null
return absolute
}
async function proxyRequest(req, res, requestUrl) {
const upstream = new URL(`${requestUrl.pathname}${requestUrl.search}`, proxyTarget)
const headers = new Headers()
for (const [key, value] of Object.entries(req.headers)) {
if (value === undefined) continue
if (Array.isArray(value)) {
for (const part of value) headers.append(key, part)
} else {
headers.set(key, value)
}
}
headers.set('host', proxyTarget.host)
const response = await fetch(upstream, {
method: req.method || 'GET',
headers,
body: req.method === 'GET' || req.method === 'HEAD' ? undefined : req,
duplex: req.method === 'GET' || req.method === 'HEAD' ? undefined : 'half',
redirect: 'manual',
})
const responseHeaders = Object.fromEntries(response.headers.entries())
res.writeHead(response.status, responseHeaders)
if (!response.body || req.method === 'HEAD') {
res.end()
return
}
await pipeline(Readable.fromWeb(response.body), res)
}
async function serveStaticFile(res, filePath) {
const extension = path.extname(filePath).toLowerCase()
const contentType = MIME_TYPES[extension] || 'application/octet-stream'
const stream = fs.createReadStream(filePath)
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': extension === '.html' ? 'no-cache' : 'public, max-age=31536000, immutable',
})
await pipeline(stream, res)
}
const server = http.createServer(async (req, res) => {
const requestUrl = new URL(req.url || '/', `http://${req.headers.host || `127.0.0.1:${port}`}`)
try {
if (isProxyPath(requestUrl.pathname)) {
await proxyRequest(req, res, requestUrl)
return
}
const candidate = resolveStaticPath(requestUrl.pathname)
if (candidate && fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
await serveStaticFile(res, candidate)
return
}
await serveStaticFile(res, path.join(distDir, 'index.html'))
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' })
res.end(error instanceof Error ? error.message : String(error))
}
})
server.listen(port, '127.0.0.1', () => {
process.stdout.write(`[live-auth-smoke-host] listening on http://127.0.0.1:${port} -> ${proxyTarget.origin}\n`)
})
function shutdown() {
server.close(() => process.exit(0))
}
process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)