2025-05-29 09:33:05 -07:00
|
|
|
import { fileURLToPath } from "url";
|
2025-08-26 20:40:11 -07:00
|
|
|
import * as path from "path";
|
2025-05-29 09:33:05 -07:00
|
|
|
|
|
|
|
|
import cors from "cors";
|
|
|
|
|
import express, { Request, Response } from "express";
|
|
|
|
|
|
|
|
|
|
import adminRouter from "./routes/admin.js";
|
2025-08-26 20:40:11 -07:00
|
|
|
import wakuRouter from "./routes/waku.js";
|
|
|
|
|
import { initBrowser, getPage, closeBrowser } from "./browser/index.js";
|
2025-05-29 09:33:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
const app = express();
|
|
|
|
|
|
|
|
|
|
app.use(cors());
|
|
|
|
|
app.use(express.json());
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
import * as fs from "fs";
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
|
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
|
const distRoot = path.resolve(__dirname, ".."); // server.js is in dist/src/, so go up to dist/
|
|
|
|
|
const webDir = path.resolve(distRoot, "web");
|
|
|
|
|
console.log("Setting up static file serving:");
|
|
|
|
|
console.log("__dirname:", __dirname);
|
|
|
|
|
console.log("webDir:", webDir);
|
|
|
|
|
console.log("Files in webDir:", fs.readdirSync(webDir));
|
|
|
|
|
|
|
|
|
|
// Serve dynamic index.html with network configuration BEFORE static files
|
|
|
|
|
app.get("/app/index.html", (_req: Request, res: Response) => {
|
2025-05-29 09:33:05 -07:00
|
|
|
try {
|
2025-08-26 20:40:11 -07:00
|
|
|
const htmlPath = path.join(webDir, "index.html");
|
|
|
|
|
let htmlContent = fs.readFileSync(htmlPath, "utf8");
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Build network configuration from environment variables
|
|
|
|
|
const networkConfig: any = {};
|
|
|
|
|
if (process.env.WAKU_CLUSTER_ID) {
|
|
|
|
|
networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10);
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
2025-08-26 20:40:11 -07:00
|
|
|
if (process.env.WAKU_SHARD) {
|
|
|
|
|
networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)];
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Inject network configuration as a global variable
|
|
|
|
|
const configScript = ` <script>window.__WAKU_NETWORK_CONFIG = ${JSON.stringify(networkConfig)};</script>`;
|
|
|
|
|
const originalPattern = ' <script type="module" src="./index.js"></script>';
|
|
|
|
|
const replacement = `${configScript}\n <script type="module" src="./index.js"></script>`;
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
htmlContent = htmlContent.replace(originalPattern, replacement);
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
res.setHeader("Content-Type", "text/html");
|
|
|
|
|
res.send(htmlContent);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error serving dynamic index.html:", error);
|
|
|
|
|
res.status(500).send("Error loading page");
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
2025-08-26 20:40:11 -07:00
|
|
|
});
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Serve static files (excluding index.html which is handled above)
|
|
|
|
|
app.use("/app", express.static(webDir, { index: false }));
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
app.use(adminRouter);
|
|
|
|
|
app.use(wakuRouter);
|
2025-05-29 09:33:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
async function startAPI(requestedPort: number): Promise<number> {
|
2025-05-29 09:33:05 -07:00
|
|
|
try {
|
|
|
|
|
app.get("/", (_req: Request, res: Response) => {
|
|
|
|
|
res.json({ status: "Waku simulation server is running" });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
app
|
|
|
|
|
.listen(requestedPort, () => {
|
|
|
|
|
console.log(`API server running on http://localhost:${requestedPort}`);
|
|
|
|
|
})
|
|
|
|
|
.on("error", (error: any) => {
|
|
|
|
|
if (error.code === "EADDRINUSE") {
|
|
|
|
|
console.error(
|
|
|
|
|
`Port ${requestedPort} is already in use. Please close the application using this port and try again.`,
|
|
|
|
|
);
|
2025-05-29 09:33:05 -07:00
|
|
|
} else {
|
2025-08-26 20:40:11 -07:00
|
|
|
console.error("Error starting server:", error);
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
2025-08-26 20:40:11 -07:00
|
|
|
throw error;
|
|
|
|
|
});
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
return requestedPort;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error("Error starting server:", error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
async function startServer(port: number = 3000): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const actualPort = await startAPI(port);
|
|
|
|
|
await initBrowser(actualPort);
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Optional auto-create/start with consistent bootstrap approach
|
|
|
|
|
const autoStart =
|
|
|
|
|
process.env.AUTO_START === "1" || process.env.HEADLESS_AUTO_START === "1";
|
|
|
|
|
if (autoStart) {
|
2025-05-29 09:33:05 -07:00
|
|
|
try {
|
2025-08-26 20:40:11 -07:00
|
|
|
console.log("Auto-starting node with CLI configuration...");
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Build network config from environment variables for auto-start
|
|
|
|
|
const networkConfig: any = { defaultBootstrap: true };
|
|
|
|
|
if (process.env.WAKU_CLUSTER_ID) {
|
|
|
|
|
networkConfig.networkConfig = networkConfig.networkConfig || {};
|
|
|
|
|
networkConfig.networkConfig.clusterId = parseInt(process.env.WAKU_CLUSTER_ID, 10);
|
|
|
|
|
}
|
|
|
|
|
if (process.env.WAKU_SHARD) {
|
|
|
|
|
networkConfig.networkConfig = networkConfig.networkConfig || {};
|
|
|
|
|
networkConfig.networkConfig.shards = [parseInt(process.env.WAKU_SHARD, 10)];
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
await getPage()?.evaluate((config) => {
|
|
|
|
|
return window.wakuApi.createWakuNode(config);
|
|
|
|
|
}, networkConfig);
|
|
|
|
|
await getPage()?.evaluate(() => window.wakuApi.startNode());
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Wait for bootstrap peers to connect
|
|
|
|
|
await getPage()?.evaluate(() =>
|
|
|
|
|
window.wakuApi.waitForPeers?.(5000, ["lightpush"] as any),
|
2025-05-29 09:33:05 -07:00
|
|
|
);
|
2025-08-26 20:40:11 -07:00
|
|
|
console.log("Auto-start completed with bootstrap peers");
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("Auto-start failed:", e);
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error("Error starting server:", error);
|
2025-08-26 20:40:11 -07:00
|
|
|
// Don't exit the process, just log the error
|
|
|
|
|
// The server might still be partially functional
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
// Process error handlers to prevent container from crashing
|
|
|
|
|
process.on("uncaughtException", (error) => {
|
|
|
|
|
console.error("Uncaught Exception:", error);
|
|
|
|
|
// Don't exit in production/container environment
|
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
process.on("unhandledRejection", (reason, promise) => {
|
|
|
|
|
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
|
|
|
|
// Don't exit in production/container environment
|
|
|
|
|
if (process.env.NODE_ENV !== "production") {
|
|
|
|
|
process.exit(1);
|
2025-05-29 09:33:05 -07:00
|
|
|
}
|
2025-08-26 20:40:11 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.on("SIGINT", (async () => {
|
|
|
|
|
console.log("Received SIGINT, gracefully shutting down...");
|
|
|
|
|
try {
|
|
|
|
|
await closeBrowser();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("Error closing browser:", e);
|
|
|
|
|
}
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}) as any);
|
2025-05-29 09:33:05 -07:00
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
process.on("SIGTERM", (async () => {
|
|
|
|
|
console.log("Received SIGTERM, gracefully shutting down...");
|
|
|
|
|
try {
|
|
|
|
|
await closeBrowser();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.warn("Error closing browser:", e);
|
|
|
|
|
}
|
2025-05-29 09:33:05 -07:00
|
|
|
process.exit(0);
|
|
|
|
|
}) as any);
|
|
|
|
|
|
2025-08-26 20:40:11 -07:00
|
|
|
/**
|
|
|
|
|
* Parse CLI arguments for cluster and shard configuration
|
|
|
|
|
*/
|
|
|
|
|
function parseCliArgs() {
|
|
|
|
|
const args = process.argv.slice(2);
|
|
|
|
|
let clusterId: number | undefined;
|
|
|
|
|
let shard: number | undefined;
|
|
|
|
|
|
|
|
|
|
for (const arg of args) {
|
|
|
|
|
if (arg.startsWith('--cluster-id=')) {
|
|
|
|
|
clusterId = parseInt(arg.split('=')[1], 10);
|
|
|
|
|
if (isNaN(clusterId)) {
|
|
|
|
|
console.error('Invalid cluster-id value. Must be a number.');
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
} else if (arg.startsWith('--shard=')) {
|
|
|
|
|
shard = parseInt(arg.split('=')[1], 10);
|
|
|
|
|
if (isNaN(shard)) {
|
|
|
|
|
console.error('Invalid shard value. Must be a number.');
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { clusterId, shard };
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 09:33:05 -07:00
|
|
|
const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
|
|
|
|
|
|
|
|
|
|
if (isMainModule) {
|
|
|
|
|
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
2025-08-26 20:40:11 -07:00
|
|
|
const cliArgs = parseCliArgs();
|
|
|
|
|
|
|
|
|
|
// Set global configuration for CLI arguments
|
|
|
|
|
if (cliArgs.clusterId !== undefined) {
|
|
|
|
|
process.env.WAKU_CLUSTER_ID = cliArgs.clusterId.toString();
|
|
|
|
|
console.log(`Using CLI cluster ID: ${cliArgs.clusterId}`);
|
|
|
|
|
}
|
|
|
|
|
if (cliArgs.shard !== undefined) {
|
|
|
|
|
process.env.WAKU_SHARD = cliArgs.shard.toString();
|
|
|
|
|
console.log(`Using CLI shard: ${cliArgs.shard}`);
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-29 09:33:05 -07:00
|
|
|
void startServer(port);
|
|
|
|
|
}
|