From 00edc6006e5d1a667e41a4d460fe14e660ddebbe Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Sun, 22 Mar 2026 22:09:48 -0400 Subject: [PATCH] fix: exclude /api paths from tower SPA fallback route The /tower/*splat catch-all was serving index.html for all sub-paths including /tower/api/ws, which prevented the WebSocket upgrade from reaching the ws server. Now requests matching /tower/api/* are passed through via next() so they can be handled by the API router or the WebSocket server. Fixes #36 Co-Authored-By: Claude Opus 4.6 (1M context) --- artifacts/api-server/src/app.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artifacts/api-server/src/app.ts b/artifacts/api-server/src/app.ts index d415685..82f2a4b 100644 --- a/artifacts/api-server/src/app.ts +++ b/artifacts/api-server/src/app.ts @@ -79,7 +79,15 @@ const towerDist = (() => { return path.join(process.cwd(), "the-matrix", "dist"); })(); app.use("/tower", express.static(towerDist)); -app.get("/tower/*splat", (_req, res) => res.sendFile(path.join(towerDist, "index.html"))); +app.get("/tower/*splat", (req, res, next) => { + // Never serve the SPA shell for requests that should hit the API or WS endpoint. + // The *splat wildcard would otherwise swallow paths like /tower/api/ws and return + // index.html, preventing the WebSocket upgrade from reaching the ws server. + const splatArr = (req.params as Record)["splat"] ?? []; + const sub = splatArr.join("/"); + if (sub === "api" || sub.startsWith("api/")) return next(); + res.sendFile(path.join(towerDist, "index.html")); +}); // Vite builds asset references as absolute /assets/... paths. // Mirror them at the root so the browser can load them from /tower. -- 2.43.0