This commit is contained in:
Chris Wanstrath 2026-02-16 13:29:58 -08:00
parent c19dc02090
commit b967bb9e9a

View File

@ -25,9 +25,11 @@ type Success = {
export const GIT_SHA = process.env.RENDER_GIT_COMMIT ?? (await Bun.$`git rev-parse HEAD`.text()).trim() export const GIT_SHA = process.env.RENDER_GIT_COMMIT ?? (await Bun.$`git rev-parse HEAD`.text()).trim()
const REQUEST_TIMEOUT = 30_000
type Connection = { app: string, ws: any } type Connection = { app: string, ws: any }
let connections: Record<string, Connection> = {} let connections: Record<string, Connection> = {}
const pending = new Map<string, (res: any) => void> const pending = new Map<string, { resolve: (res: Response) => void, subdomain: string }>
const app = new Hono const app = new Hono
@ -65,15 +67,21 @@ app.get("/tunnel", c => {
console.log(`connection opened: ${name} -> ${app}`) console.log(`connection opened: ${name} -> ${app}`)
send(ws, { subdomain: name }) send(ws, { subdomain: name })
}, },
onClose: (_event, ws) => { onClose: (_event, _ws) => {
console.log("connection closed:", name) console.log("connection closed:", name)
delete connections[name] delete connections[name]
for (const [id, entry] of pending) {
if (entry.subdomain === name) {
entry.resolve({ id, status: 502, headers: {}, body: "Tunnel disconnected" })
pending.delete(id)
}
}
}, },
async onMessage(event, _ws) { async onMessage(event, _ws) {
const msg = JSON.parse(event.data.toString()) const msg = JSON.parse(event.data.toString())
const resolve = pending.get(msg.id) const entry = pending.get(msg.id)
if (resolve) { if (entry) {
resolve(msg) entry.resolve(msg)
pending.delete(msg.id) pending.delete(msg.id)
} }
}, },
@ -95,11 +103,24 @@ app.all("*", async c => {
const id = randomID() const id = randomID()
const headers = Object.fromEntries(c.req.raw.headers) const headers = Object.fromEntries(c.req.raw.headers)
delete headers['host']
delete headers['connection']
delete headers['keep-alive']
delete headers['transfer-encoding']
delete headers['content-length']
const body = await c.req.text() const body = await c.req.text()
const app = connection.app const app = connection.app
const result = await new Promise<Response>(resolve => { const result = await new Promise<Response>((resolve, reject) => {
pending.set(id, resolve) const timer = setTimeout(() => {
pending.delete(id)
reject(new Error("Tunnel request timed out"))
}, REQUEST_TIMEOUT)
pending.set(id, {
resolve: (res) => { clearTimeout(timer); resolve(res) },
subdomain,
})
send(connection.ws, { send(connection.ws, {
id, id,
app, app,
@ -108,7 +129,7 @@ app.all("*", async c => {
headers, headers,
body body
}) })
}) }).catch((): Response => ({ id, status: 504, headers: {}, body: "Gateway Timeout" }))
if (result.isBinary) { if (result.isBinary) {
const buffer = Buffer.from(result.body, 'base64') const buffer = Buffer.from(result.body, 'base64')
@ -125,7 +146,7 @@ app.all("*", async c => {
}) })
function send(connection: any, msg: Request | Success) { function send(connection: any, msg: Request | Success) {
console.log("sending", msg) console.log("sending", 'id' in msg ? `${msg.id} ${msg.method} ${msg.path}` : `connected: ${msg.subdomain}`)
connection.send(JSON.stringify(msg)) connection.send(JSON.stringify(msg))
} }