import "dotenv/config"; import express from "express"; import { createServer } from "http"; import net from "net"; import { createExpressMiddleware } from "@trpc/server/adapters/express"; import { registerOAuthRoutes } from "./oauth"; import { appRouter } from "../routers"; import { createContext } from "./context"; import { registerVoiceWebSocket } from "../voice-ws"; function isPortAvailable(port: number): Promise { return new Promise((resolve) => { const server = net.createServer(); server.listen(port, "0.0.0.0", () => { server.close(() => resolve(true)); }); server.on("error", () => resolve(false)); }); } async function findAvailablePort(startPort: number = 3000): Promise { for (let port = startPort; port < startPort + 20; port++) { if (await isPortAvailable(port)) { return port; } } throw new Error(`No available port found starting from ${startPort}`); } async function startServer() { const app = express(); const server = createServer(app); // Enable CORS for all routes // In production, restrict to known origins; in dev, reflect any origin. const ALLOWED_ORIGINS = process.env.CORS_ORIGINS ? process.env.CORS_ORIGINS.split(",").map((o) => o.trim()) : null; // null = allow all (dev mode) app.use((req, res, next) => { const origin = req.headers.origin; if (origin) { const isAllowed = !ALLOWED_ORIGINS || ALLOWED_ORIGINS.includes(origin); if (isAllowed) { res.header("Access-Control-Allow-Origin", origin); } } res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); res.header( "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization", ); res.header("Access-Control-Allow-Credentials", "true"); // Handle preflight requests if (req.method === "OPTIONS") { res.sendStatus(200); return; } next(); }); app.use(express.json({ limit: "50mb" })); app.use(express.urlencoded({ limit: "50mb", extended: true })); registerOAuthRoutes(app); app.get("/api/health", (_req, res) => { res.json({ ok: true, timestamp: Date.now() }); }); app.use( "/api/trpc", createExpressMiddleware({ router: appRouter, createContext, }), ); const preferredPort = parseInt(process.env.PORT || "3000"); const port = await findAvailablePort(preferredPort); if (port !== preferredPort) { console.warn(`⚠️ Port ${preferredPort} is busy, using port ${port} instead`); console.warn(`⚠️ Update EXPO_PUBLIC_API_BASE_URL in .env to use port ${port}`); } // تسجيل WebSocket للتعرف الفوري على الصوت registerVoiceWebSocket(server); server.listen(port, "0.0.0.0", () => { console.log(`[api] server listening on http://0.0.0.0:${port}`); console.log(`[api] LAN clients should connect to http://:${port}`); }); } startServer().catch(console.error);