forked from syntaxbullet/aurorabot
refactor(web): convert server to API-only mode
- Remove build process spawning for frontend bundler - Remove SPA fallback and static file serving - Return 404 for unknown routes instead of serving index.html - Keep all REST API endpoints and WebSocket functionality
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Web server factory module.
|
||||
* Exports a function to create and start the web server.
|
||||
* API server factory module.
|
||||
* Exports a function to create and start the API server.
|
||||
* This allows the server to be started in-process from the main application.
|
||||
*/
|
||||
|
||||
import { serve, spawn, type Subprocess } from "bun";
|
||||
import { serve } from "bun";
|
||||
import { join, resolve, dirname } from "path";
|
||||
import { logger } from "@shared/lib/logger";
|
||||
|
||||
@@ -20,37 +20,13 @@ export interface WebServerInstance {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and starts the web server.
|
||||
*
|
||||
* Automatically handles building the frontend:
|
||||
* - In development: Spawns 'bun run build.ts --watch'
|
||||
* - In production: Assumes 'dist' is already built (or builds once)
|
||||
* Creates and starts the API server.
|
||||
*/
|
||||
export async function createWebServer(config: WebServerConfig = {}): Promise<WebServerInstance> {
|
||||
const { port = 3000, hostname = "localhost" } = config;
|
||||
|
||||
// Resolve directories
|
||||
// server.ts is in web/src/, so we go up one level to get web/
|
||||
// Resolve directories for asset serving
|
||||
const currentDir = dirname(new URL(import.meta.url).pathname);
|
||||
const webRoot = resolve(currentDir, "..");
|
||||
const distDir = join(webRoot, "dist");
|
||||
|
||||
// Manage build process in development
|
||||
let buildProcess: Subprocess | undefined;
|
||||
const isDev = process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test";
|
||||
|
||||
if (isDev) {
|
||||
logger.info("web", "Starting Web Bundler in Watch Mode...");
|
||||
try {
|
||||
buildProcess = spawn(["bun", "run", "build.ts", "--watch"], {
|
||||
cwd: webRoot,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("web", "Failed to start build process", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration constants
|
||||
const MAX_CONNECTIONS = 10;
|
||||
@@ -737,52 +713,8 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
// Static File Serving
|
||||
let pathName = url.pathname;
|
||||
if (pathName === "/") pathName = "/index.html";
|
||||
|
||||
// Construct safe file path
|
||||
// Remove leading slash to join correctly
|
||||
const safePath = join(distDir, pathName.replace(/^\//, ""));
|
||||
|
||||
// Security check: ensure path is within distDir
|
||||
if (!safePath.startsWith(distDir)) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
const fileRef = Bun.file(safePath);
|
||||
if (await fileRef.exists()) {
|
||||
// If serving index.html, inject env vars for frontend
|
||||
if (pathName === "/index.html") {
|
||||
const html = await fileRef.text();
|
||||
return new Response(html, { headers: { "Content-Type": "text/html" } });
|
||||
}
|
||||
return new Response(fileRef);
|
||||
}
|
||||
|
||||
// SPA Fallback: Serve index.html for unknown non-file routes
|
||||
const parts = pathName.split("/");
|
||||
const lastPart = parts[parts.length - 1];
|
||||
|
||||
// If it's a direct request for a missing file (has dot), return 404
|
||||
// EXCEPT for index.html which is our fallback entry point
|
||||
if (lastPart?.includes(".") && lastPart !== "index.html") {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
const indexFile = Bun.file(join(distDir, "index.html"));
|
||||
if (!(await indexFile.exists())) {
|
||||
if (isDev) {
|
||||
return new Response("<html><body><h1>🛠️ Dashboard is building...</h1><p>Please refresh in a few seconds. The bundler is currently generating the static assets.</p><script>setTimeout(() => location.reload(), 2000);</script></body></html>", {
|
||||
status: 503,
|
||||
headers: { "Content-Type": "text/html" }
|
||||
});
|
||||
}
|
||||
return new Response("Dashboard Not Found", { status: 404 });
|
||||
}
|
||||
|
||||
const indexHtml = await indexFile.text();
|
||||
return new Response(indexHtml, { headers: { "Content-Type": "text/html" } });
|
||||
// No frontend - return 404 for unknown routes
|
||||
return new Response("Not Found", { status: 404 });
|
||||
},
|
||||
|
||||
websocket: {
|
||||
@@ -847,7 +779,6 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
idleTimeout: IDLE_TIMEOUT_SECONDS,
|
||||
},
|
||||
|
||||
development: isDev,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -947,9 +878,6 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
||||
server,
|
||||
url,
|
||||
stop: async () => {
|
||||
if (buildProcess) {
|
||||
buildProcess.kill();
|
||||
}
|
||||
if (statsBroadcastInterval) {
|
||||
clearInterval(statsBroadcastInterval);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user