@colyseus/core 0.18.5 → 0.18.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Server.cjs +52 -18
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +19 -1
- package/build/Server.mjs +51 -19
- package/build/Server.mjs.map +2 -2
- package/build/index.cjs +4 -0
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +1 -1
- package/build/index.mjs +3 -1
- package/build/index.mjs.map +2 -2
- package/build/router/index.cjs +47 -1
- package/build/router/index.cjs.map +3 -3
- package/build/router/index.d.ts +6 -2
- package/build/router/index.mjs +47 -1
- package/build/router/index.mjs.map +2 -2
- package/package.json +5 -5
- package/src/Server.ts +68 -24
- package/src/index.ts +1 -1
- package/src/router/index.ts +72 -3
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export {
|
|
|
18
18
|
} from '@colyseus/shared-types';
|
|
19
19
|
|
|
20
20
|
// Core classes
|
|
21
|
-
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
21
|
+
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, applySimulatedLatency, parseLatencyEnv, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
22
22
|
export { Room, RoomInternalState, validate, type RoomOptions, type DefineInputOptions, type SimulationCallback, type FixedTimestepCallback, type StepContext, type MessageHandlerWithFormat, type Messages, type ExtractRoomState, type ExtractRoomMetadata, type ExtractRoomClient } from './Room.ts';
|
|
23
23
|
export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
|
|
24
24
|
export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';
|
package/src/router/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type express from "express";
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "http";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
4
6
|
import { type Endpoint, type Router, type RouterConfig, createRouter as createBetterCallRouter, createEndpoint, createMiddleware, APIError } from "@colyseus/better-call";
|
|
5
7
|
import { toNodeHandler, getRequest, setResponse } from "@colyseus/better-call/node";
|
|
@@ -250,8 +252,12 @@ export function dualModeEndpoints<E extends Record<string, Endpoint>>(
|
|
|
250
252
|
opts: {
|
|
251
253
|
/** Key in `endpoints` whose path is a catch-all. Excluded from `specificRouter` so it doesn't eat fall-through decisions. */
|
|
252
254
|
catchAllKey?: keyof E;
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
+
/** Endpoint-path prefix baked into `endpoints` (`''`, `'/monitor'`). Used by the default middleware to rebase path mounts. */
|
|
256
|
+
prefix?: string;
|
|
257
|
+
/** SPA dist dir — the default middleware dispatches catch-all requests only for files that exist here. */
|
|
258
|
+
staticDir?: string;
|
|
259
|
+
/** Build a custom express middleware given the pre-built routers + node handlers. Omit for the default SPA-panel middleware. */
|
|
260
|
+
buildMiddleware?: (helpers: DualModeHelpers) => ExpressMiddleware;
|
|
255
261
|
},
|
|
256
262
|
): ExpressMiddleware & E {
|
|
257
263
|
const fullRouter = createRouter(endpoints);
|
|
@@ -265,8 +271,71 @@ export function dualModeEndpoints<E extends Record<string, Endpoint>>(
|
|
|
265
271
|
const specificRouter = createRouter(specificEndpoints as E);
|
|
266
272
|
const specificHandler = toNodeHandler(specificRouter.handler) as NodeHandler;
|
|
267
273
|
|
|
268
|
-
const
|
|
274
|
+
const buildMiddleware = opts.buildMiddleware ?? panelMiddleware(opts.prefix ?? '', opts.staticDir);
|
|
275
|
+
const middleware = buildMiddleware({
|
|
269
276
|
specificRouter, specificHandler, fullRouter, fullHandler,
|
|
270
277
|
});
|
|
271
278
|
return Object.assign(middleware, endpoints) as ExpressMiddleware & E;
|
|
272
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Default express middleware for a single-prefix SPA panel (monitor,
|
|
283
|
+
* playground). Express and the endpoint map use different coordinates: a path
|
|
284
|
+
* mount strips its prefix into `req.baseUrl`, while the endpoints keep the
|
|
285
|
+
* configured `prefix` baked into their paths. This rebases every request onto
|
|
286
|
+
* the endpoint namespace, so the panel works at any mount path — `prefix` only
|
|
287
|
+
* matters in router mode and at root mounts.
|
|
288
|
+
*/
|
|
289
|
+
function panelMiddleware(prefix: string, staticDir?: string) {
|
|
290
|
+
const staticRoot = staticDir && path.resolve(staticDir);
|
|
291
|
+
return ({ specificRouter, specificHandler, fullHandler }: DualModeHelpers): ExpressMiddleware =>
|
|
292
|
+
(req, res, next) => {
|
|
293
|
+
const r = req as IncomingMessage & { baseUrl?: string; originalUrl?: string };
|
|
294
|
+
const raw = r.url ?? '';
|
|
295
|
+
const q = raw.indexOf('?');
|
|
296
|
+
const url = q < 0 ? raw : raw.slice(0, q);
|
|
297
|
+
const query = q < 0 ? '' : raw.slice(q);
|
|
298
|
+
|
|
299
|
+
// Path mounts arrive stripped (`req.url` is mount-relative); root and
|
|
300
|
+
// pathless mounts arrive unstripped and already match the endpoints.
|
|
301
|
+
const dispatchPath = r.baseUrl ? prefix + url : url;
|
|
302
|
+
|
|
303
|
+
// Canonicalize the index: the SPA references its assets relatively, so
|
|
304
|
+
// they only resolve from the trailing-slash URL. The browser's address
|
|
305
|
+
// is originalUrl — 302 (not 301: browsers cache 301s across remounts).
|
|
306
|
+
if (dispatchPath === prefix || dispatchPath === `${prefix}/`) {
|
|
307
|
+
const original = (r.originalUrl ?? raw).split('?')[0]!;
|
|
308
|
+
if (r.method === 'GET' && !original.endsWith('/')) {
|
|
309
|
+
res.writeHead(302, { location: `${original}/${query}` });
|
|
310
|
+
res.end();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const dispatch = (handler: NodeHandler) => {
|
|
316
|
+
// better-call's getRequest resolves the path as baseUrl + url
|
|
317
|
+
const wrapped = Object.create(req, {
|
|
318
|
+
url: { value: dispatchPath + query, enumerable: true, configurable: true },
|
|
319
|
+
baseUrl: { value: '', enumerable: true, configurable: true },
|
|
320
|
+
originalUrl: { value: dispatchPath + query, enumerable: true, configurable: true },
|
|
321
|
+
});
|
|
322
|
+
handler(wrapped as any, res).catch(next);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const route = specificRouter.findRoute(r.method ?? 'GET', dispatchPath);
|
|
326
|
+
// rou3 normalizes trailing slashes in findRoute but processRequest
|
|
327
|
+
// exact-matches — only trust the hit when the slashes agree.
|
|
328
|
+
if (route && (!route.data?.path?.endsWith('/') || dispatchPath.endsWith('/'))) {
|
|
329
|
+
return dispatch(specificHandler);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Asset request — only delegate when the file exists on disk, so the
|
|
333
|
+
// catch-all's SPA fallback can't mask sibling express routes.
|
|
334
|
+
if (r.method !== 'GET' || !staticRoot || !dispatchPath.startsWith(prefix)) { return next(); }
|
|
335
|
+
const rel = dispatchPath.slice(prefix.length).replace(/^\/+/, '');
|
|
336
|
+
if (!rel || rel.includes('..')) { return next(); }
|
|
337
|
+
const filePath = path.resolve(staticRoot, rel);
|
|
338
|
+
if (!filePath.startsWith(staticRoot + path.sep)) { return next(); }
|
|
339
|
+
fs.stat(filePath).then((stat) => stat.isFile() ? dispatch(fullHandler) : next()).catch(() => next());
|
|
340
|
+
};
|
|
341
|
+
}
|