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.
|
* API server factory module.
|
||||||
* Exports a function to create and start the web server.
|
* Exports a function to create and start the API server.
|
||||||
* This allows the server to be started in-process from the main application.
|
* 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 { join, resolve, dirname } from "path";
|
||||||
import { logger } from "@shared/lib/logger";
|
import { logger } from "@shared/lib/logger";
|
||||||
|
|
||||||
@@ -20,37 +20,13 @@ export interface WebServerInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and starts the web server.
|
* Creates and starts the API server.
|
||||||
*
|
|
||||||
* Automatically handles building the frontend:
|
|
||||||
* - In development: Spawns 'bun run build.ts --watch'
|
|
||||||
* - In production: Assumes 'dist' is already built (or builds once)
|
|
||||||
*/
|
*/
|
||||||
export async function createWebServer(config: WebServerConfig = {}): Promise<WebServerInstance> {
|
export async function createWebServer(config: WebServerConfig = {}): Promise<WebServerInstance> {
|
||||||
const { port = 3000, hostname = "localhost" } = config;
|
const { port = 3000, hostname = "localhost" } = config;
|
||||||
|
|
||||||
// Resolve directories
|
// Resolve directories for asset serving
|
||||||
// server.ts is in web/src/, so we go up one level to get web/
|
|
||||||
const currentDir = dirname(new URL(import.meta.url).pathname);
|
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
|
// Configuration constants
|
||||||
const MAX_CONNECTIONS = 10;
|
const MAX_CONNECTIONS = 10;
|
||||||
@@ -737,52 +713,8 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
return new Response("Not found", { status: 404 });
|
return new Response("Not found", { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static File Serving
|
// No frontend - return 404 for unknown routes
|
||||||
let pathName = url.pathname;
|
return new Response("Not Found", { status: 404 });
|
||||||
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" } });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
websocket: {
|
websocket: {
|
||||||
@@ -847,7 +779,6 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
idleTimeout: IDLE_TIMEOUT_SECONDS,
|
idleTimeout: IDLE_TIMEOUT_SECONDS,
|
||||||
},
|
},
|
||||||
|
|
||||||
development: isDev,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -947,9 +878,6 @@ export async function createWebServer(config: WebServerConfig = {}): Promise<Web
|
|||||||
server,
|
server,
|
||||||
url,
|
url,
|
||||||
stop: async () => {
|
stop: async () => {
|
||||||
if (buildProcess) {
|
|
||||||
buildProcess.kill();
|
|
||||||
}
|
|
||||||
if (statsBroadcastInterval) {
|
if (statsBroadcastInterval) {
|
||||||
clearInterval(statsBroadcastInterval);
|
clearInterval(statsBroadcastInterval);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user