@colyseus/core
Version:
Multiplayer Framework for Node.js.
139 lines (113 loc) • 4.56 kB
text/typescript
import type express from "express";
import type { IncomingMessage, ServerResponse } from "http";
import { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint } from "@colyseus/better-call";
import { toNodeHandler, getRequest, setResponse } from "@colyseus/better-call/node";
import { Transport } from "../Transport.ts";
import { controller } from "../matchmaker/controller.ts";
import pkg from "../../package.json" with { type: "json" };
export {
createEndpoint,
createMiddleware,
createInternalContext,
// Re-export types needed for declaration emit
type Router,
type RouterConfig,
type Endpoint,
type EndpointHandler,
type EndpointOptions,
type EndpointContext,
type StrictEndpoint,
} from "@colyseus/better-call";
export { toNodeHandler };
export function bindRouterToTransport(transport: Transport, router: Router, useExpress: boolean) {
// add default "/__healthcheck" endpoint
router.addEndpoint(createEndpoint("/__healthcheck", { method: "GET" }, async (ctx) => {
return new Response("OK", { status: 200 });
}));
const server = transport.server;
// check if the server is bound to an express app
const expressApp: express.Application = (useExpress)
? transport.getExpressApp() as express.Application
// fallback searching for express app in server listeners
: server?.listeners('request').find((listener: Function) => listener.name === "app" && listener['mountpath'] === '/') as express.Application;
// add default "/" route, if not provided.
const hasRootRoute = (
// check if express app has a root route
(expressApp && expressRootRoute(expressApp) !== undefined) ||
// check if router has a root route
Object.values(router.endpoints).some(endpoint => endpoint.path === "/")
);
if (!hasRootRoute) {
router.addEndpoint(createEndpoint("/", { method: "GET" }, async (ctx) => {
return new Response(`Colyseus ${pkg.version}`, { status: 200 });
}));
}
// use custom bindRouter method if provided
if (!server && transport.bindRouter) {
transport.bindRouter(router);
return;
}
// which route handler to use
// (router + fallback to express, or just router)
let next: any;
if (expressApp) {
server.removeListener('request', expressApp);
next = async (req: IncomingMessage, res: ServerResponse) => {
// check if the route is defined in the router
// if so, use the router handler, otherwise fallback to express
if (router.findRoute(req.method, req.url.split('?')[0]) !== undefined) {
const protocol = req.headers["x-forwarded-proto"] || ((req.socket as any).encrypted ? "https" : "http");
const base = `${protocol}://${req.headers[":authority"] || req.headers.host}`;
const response = await router.handler(getRequest({ base, request: req }));
return setResponse(res, response);
} else {
return expressApp['handle'](req, res);
}
};
} else {
next = toNodeHandler(router.handler);
}
// handle cors headers for all requests by default
server.prependListener('request', (req: IncomingMessage, res: ServerResponse) => {
const corsHeaders = {
...controller.DEFAULT_CORS_HEADERS,
...controller.getCorsHeaders(new Headers(req.headers as any)),
};
if (req.method === "OPTIONS") {
res.writeHead(204, corsHeaders);
res.end();
return;
}
Object.entries(corsHeaders).forEach(([key, value]) => {
res.setHeader(key, value);
});
next(req, res);
});
}
function expressRootRoute(expressApp: express.Application) {
//
// express v5 uses `app.router`, express v4 uses `app._router`
// check for `app._router` first, then `app.router`
//
// (express v4 will show a warning if `app.router` is used)
//
const stack = (expressApp as any)?._router?.stack ?? (expressApp as any)?.router?.stack;
if (!stack) {
return false;
}
return stack.find((layer: any) => layer.match('/') && !['query', 'expressInit'].includes(layer.name));
}
/**
* Do not use this directly. This is used internally by `@colyseus/playground`.
* TODO: refactor. Avoid using globals.
* @internal
*/
export let __globalEndpoints: Record<string, Endpoint> = {};
export function createRouter<
E extends Record<string, Endpoint>,
Config extends RouterConfig
>(endpoints: E, config: Config = {} as Config) {
// TODO: refactor. Avoid using globals.
__globalEndpoints = endpoints;
return createBetterCallRouter({ ...endpoints }, config);
}