Files
homarr/apps/nextjs/server.ts
T

100 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Must be imported first to set up globalThis.AsyncLocalStorage for Next.js
import "next/dist/server/node-environment-baseline";
import { createServer } from "http";
import path from "path";
import next from "next";
import { parse } from "url";
import { applyWSSHandler } from "@trpc/server/adapters/ws";
import { WebSocketServer } from "ws";
import { appRouter, createTRPCContext } from "@homarr/api/websocket";
import { getSessionFromToken, sessionTokenCookieName } from "@homarr/auth";
import { parseCookies } from "@homarr/common";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { db } from "@homarr/db";
const logger = createLogger({ module: "customServer" });
const dev = process.env.NODE_ENV !== "production";
const hostname = process.env.HOSTNAME || "localhost";
const port = parseInt(process.env.PORT || "3000", 10);
// Resolve the directory of this file so Next.js can find the app/pages dirs
const dir = path.resolve(__dirname);
const app = next({ dev, hostname, port, dir });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = createServer((req, res) => {
const parsedUrl = parse(req.url!, true);
handle(req, res, parsedUrl);
});
// Create WebSocket server in noServer mode so we handle upgrade manually
const wss = new WebSocketServer({ noServer: true });
const wssHandler = applyWSSHandler({
wss,
router: appRouter,
// eslint-disable-next-line no-restricted-syntax
createContext: async ({ req }) => {
try {
const headers = Object.entries(req.headers).map(
([key, value]) => [key, typeof value === "string" ? value : value?.[0]] as [string, string],
);
const nextHeaders = new Headers(headers);
const store = parseCookies(nextHeaders.get("cookie") ?? "");
const sessionToken = store[sessionTokenCookieName];
const session = await getSessionFromToken(db, sessionToken);
return createTRPCContext({
headers: nextHeaders,
session,
});
} catch (error) {
logger.error(error);
return createTRPCContext({
headers: new Headers(),
session: null,
});
}
},
keepAlive: {
enabled: true,
pingMs: 30000,
pongWaitMs: 5000,
},
});
// Handle WebSocket upgrade requests on /websockets path
server.on("upgrade", (req, socket, head) => {
if (req.url?.startsWith("/websockets")) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
}
});
wss.on("connection", (websocket, incomingMessage) => {
logger.info(`➕ Connection (${wss.clients.size}) ${incomingMessage.method} ${incomingMessage.url}`);
websocket.once("close", (code, reason) => {
logger.info(`➖ Connection (${wss.clients.size}) ${code} ${reason.toString()}`);
});
});
server.listen(port, () => {
logger.info(`✅ Custom server ready on http://${hostname}:${port}`);
logger.info(`✅ WebSocket Server integrated on ws://${hostname}:${port}/websockets`);
});
process.on("SIGTERM", () => {
logger.info("SIGTERM");
wssHandler.broadcastReconnectNotification();
wss.close();
server.close();
});
});