tronbyt/src/proxy.ts
Corey Johnson 869fd18d42 Use redirect: manual to let browser handle redirects
Bun's fetch was following Go's 303 redirects internally, which
caused ECONNRESET errors during the auto-login redirect chain.
Let the browser handle redirects instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 19:36:13 -07:00

69 lines
2.4 KiB
TypeScript

import type { ServerWebSocket } from 'bun'
export interface WsData {
path: string
protocols: string[]
}
const GO_PORT = 8000
const GO_BASE = `http://127.0.0.1:${GO_PORT}`
const upstreams = new Map<ServerWebSocket<WsData>, WebSocket>()
export function createProxy(isHealthy: () => boolean, isRunning: () => boolean) {
async function proxyFetch(req: Request): Promise<Response> {
const url = new URL(req.url)
if (url.pathname === '/ok') {
if (!isHealthy()) return new Response('starting', { status: isRunning() ? 200 : 503 })
return fetch(`${GO_BASE}/health`)
.then((r) => (r.ok ? new Response('ok') : new Response('unhealthy', { status: 503 })))
.catch(() => new Response('unhealthy', { status: 503 }))
}
const hasBody = req.method !== 'GET' && req.method !== 'HEAD'
const body = hasBody ? await req.arrayBuffer() : undefined
return fetch(`${GO_BASE}${url.pathname}${url.search}`, {
method: req.method,
headers: req.headers,
body,
redirect: 'manual',
}).then((r) => {
// Bun auto-decompresses gzip but leaves content-encoding header.
// Strip it so the next proxy layer doesn't try to decompress again.
const headers = new Headers(r.headers)
headers.delete('content-encoding')
headers.delete('content-length')
return new Response(r.body, { status: r.status, headers })
}).catch((e) => {
console.error('Proxy error:', e)
return new Response('Tronbyt server is not responding', { status: 502 })
})
}
const websocket = {
open(ws: ServerWebSocket<WsData>) {
const upstream = new WebSocket(`ws://127.0.0.1:${GO_PORT}${ws.data.path}`, ws.data.protocols)
upstream.binaryType = 'arraybuffer'
upstreams.set(ws, upstream)
upstream.addEventListener('message', (e) => ws.send(e.data as string | ArrayBuffer))
upstream.addEventListener('close', () => { upstreams.delete(ws); ws.close() })
upstream.addEventListener('error', () => { upstreams.delete(ws); ws.close() })
},
message(ws: ServerWebSocket<WsData>, msg: string | ArrayBuffer | Uint8Array) {
const upstream = upstreams.get(ws)
if (upstream?.readyState === WebSocket.OPEN) upstream.send(msg)
},
close(ws: ServerWebSocket<WsData>) {
upstreams.get(ws)?.close()
upstreams.delete(ws)
},
}
return { proxyFetch, websocket }
}