diff --git a/app-prefixable/dev.ts b/app-prefixable/dev.ts index e453ef55..f05a4b75 100644 --- a/app-prefixable/dev.ts +++ b/app-prefixable/dev.ts @@ -48,6 +48,42 @@ function withNoStoreHeaders(response: Response) { }) } +function stripHopByHopHeaders(headers: Headers) { + const connection = headers.get("connection") + if (connection) { + for (const name of connection.split(",")) { + const trimmed = name.trim() + if (trimmed) headers.delete(trimmed) + } + } + + for (const name of [ + "connection", + "content-length", + "host", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailer", + "transfer-encoding", + "upgrade", + ]) { + headers.delete(name) + } +} + +function normalizeDevProxiedResponse(response: Response) { + const normalized = normalizeProxiedResponse(response) + const headers = new Headers(normalized.headers) + stripHopByHopHeaders(headers) + return new Response(normalized.body, { + status: normalized.status, + statusText: normalized.statusText, + headers, + }) +} + const server = Bun.serve<{ target: string }>({ port: PORT, idleTimeout: 0, // Disable timeout for SSE connections @@ -94,6 +130,7 @@ const server = Bun.serve<{ target: string }>({ if (isApiPath(strippedPath)) { const target = new URL(strippedPath + url.search, API_URL) const headers = new Headers(req.headers) + stripHopByHopHeaders(headers) // SSE requests - just pass through the response body directly if (strippedPath.startsWith("/event")) { @@ -102,6 +139,7 @@ const server = Bun.serve<{ target: string }>({ const response = await fetch(target.toString(), { method: req.method, headers, + signal: req.signal, }) if (!response.ok) { @@ -140,8 +178,9 @@ const server = Bun.serve<{ target: string }>({ method: req.method, headers, body, + signal: req.signal, }) - return withNoStoreHeaders(normalizeProxiedResponse(response)) + return withNoStoreHeaders(normalizeDevProxiedResponse(response)) } catch (e) { console.error("[Proxy] API error:", e) return withNoStoreHeaders(new Response("API proxy error", { status: 502 })) diff --git a/app-prefixable/src/context/global-events.tsx b/app-prefixable/src/context/global-events.tsx index 686dd4d9..c4823812 100644 --- a/app-prefixable/src/context/global-events.tsx +++ b/app-prefixable/src/context/global-events.tsx @@ -10,6 +10,8 @@ import { createStore, produce } from "solid-js/store" import { useServer } from "./server" import { createSSEParser } from "../utils/sse" +const MAX_GLOBAL_EVENT_CONNECTIONS = 16 + /** * Alert priority: permission (highest) > question > busy */ @@ -157,6 +159,12 @@ export function GlobalEventsProvider(props: ParentProps & { }, 5000)) } + function wantedDirectories(dirs: string[], active: string | undefined) { + const wanted = new Set(dirs.slice(0, MAX_GLOBAL_EVENT_CONNECTIONS + 1)) + if (active) wanted.delete(active) + return new Set([...wanted].slice(0, MAX_GLOBAL_EVENT_CONNECTIONS)) + } + function connectToDirectory(dir: string) { if (connections.has(dir)) return // Don't connect when a remote server is active @@ -349,9 +357,8 @@ export function GlobalEventsProvider(props: ParentProps & { if (disposed) return // Don't reconnect when a remote server is active — local projects don't exist there if (!activeServer().isDefault) return - const active = props.activeDirectory() - const wanted = props.projects().some((p) => p.worktree === dir) - if (wanted && dir !== active) { + const wanted = wantedDirectories(props.projects().map((p) => p.worktree), props.activeDirectory()) + if (wanted.has(dir)) { connectToDirectory(dir) return } @@ -567,18 +574,22 @@ export function GlobalEventsProvider(props: ParentProps & { return } - const wanted = new Set(current.dirs) - if (current.active) wanted.delete(current.active) + const limitedWanted = wantedDirectories(current.dirs, current.active) + + for (const [dir] of [...reconnectTimers]) { + if (limitedWanted.has(dir)) continue + disconnectDirectory(dir) + } // Disconnect directories we no longer need (including newly-active project) for (const dir of [...connections.keys()]) { - if (!wanted.has(dir)) { + if (!limitedWanted.has(dir)) { disconnectDirectory(dir) } } // Connect to new inactive directories - for (const dir of wanted) { + for (const dir of limitedWanted) { if (!connections.has(dir)) { connectToDirectory(dir) }