@colyseus/core 0.17.40 → 0.17.41
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/MatchMaker.cjs +40 -0
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +13 -0
- package/build/MatchMaker.mjs +39 -0
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Room.cjs +44 -15
- package/build/Room.cjs.map +2 -2
- package/build/Room.d.ts +1 -0
- package/build/Room.mjs +24 -5
- package/build/Room.mjs.map +2 -2
- package/build/Server.cjs +35 -6
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +3 -0
- package/build/Server.mjs +32 -5
- package/build/Server.mjs.map +2 -2
- package/build/index.cjs +9 -0
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +3 -2
- package/build/index.mjs +7 -2
- package/build/index.mjs.map +2 -2
- package/build/router/node.cjs +98 -0
- package/build/router/node.cjs.map +7 -0
- package/build/router/node.d.ts +11 -0
- package/build/router/node.mjs +63 -0
- package/build/router/node.mjs.map +7 -0
- package/build/utils/DevMode.cjs +14 -2
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.mjs +15 -3
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/Utils.cjs +4 -1
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.mjs +4 -1
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +5 -5
- package/src/MatchMaker.ts +67 -0
- package/src/Room.ts +29 -9
- package/src/Server.ts +47 -6
- package/src/index.ts +3 -2
- package/src/router/node.ts +88 -0
- package/src/utils/DevMode.ts +29 -6
- package/src/utils/Utils.ts +2 -2
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw Node.js adapter for Colyseus matchmaking routes used by `colyseus/vite`.
|
|
3
|
+
*
|
|
4
|
+
* This file exists specifically so the Vite plugin can share Vite's dev HTTP
|
|
5
|
+
* server while still exposing the Colyseus `/matchmake/*` endpoints.
|
|
6
|
+
*
|
|
7
|
+
* Keep the matchmaking behavior itself in `router/default_routes.ts` and use
|
|
8
|
+
* this file only as the thin raw Node/Express adapter around it.
|
|
9
|
+
*/
|
|
10
|
+
import type http from 'http';
|
|
11
|
+
import { URL } from 'url';
|
|
12
|
+
import * as matchMaker from '../MatchMaker.ts';
|
|
13
|
+
import { setResponse } from '@colyseus/better-call/node';
|
|
14
|
+
import { postMatchmakeMethod } from './default_routes.ts';
|
|
15
|
+
|
|
16
|
+
function readBody(req: http.IncomingMessage): Promise<any> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let data = '';
|
|
19
|
+
|
|
20
|
+
req.on('data', (chunk: Buffer | string) => {
|
|
21
|
+
data += chunk.toString();
|
|
22
|
+
});
|
|
23
|
+
req.on('end', () => resolve(data ? JSON.parse(data) : {}));
|
|
24
|
+
req.on('error', reject);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getCorsHeaders(req: http.IncomingMessage, headers?: Headers): Record<string, string> {
|
|
29
|
+
return {
|
|
30
|
+
...matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
31
|
+
...matchMaker.controller.getCorsHeaders(headers),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createNodeMatchmakingMiddleware() {
|
|
36
|
+
return async (
|
|
37
|
+
req: http.IncomingMessage,
|
|
38
|
+
res: http.ServerResponse,
|
|
39
|
+
next: () => void,
|
|
40
|
+
) => {
|
|
41
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
42
|
+
const isMatchmakeRoute = url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}/`);
|
|
43
|
+
|
|
44
|
+
if (!isMatchmakeRoute) {
|
|
45
|
+
next();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const headers = new Headers(req.headers as Record<string, string>);
|
|
50
|
+
const corsHeaders = getCorsHeaders(req, headers);
|
|
51
|
+
|
|
52
|
+
if (req.method === 'OPTIONS') {
|
|
53
|
+
res.writeHead(204, corsHeaders);
|
|
54
|
+
res.end();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (req.method !== 'POST') {
|
|
59
|
+
next();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const match = url.pathname.match(/^\/matchmake\/(\w+)\/(.+)/);
|
|
64
|
+
if (!match) {
|
|
65
|
+
next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const [, method, roomName] = match;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await postMatchmakeMethod({
|
|
73
|
+
params: { method, roomName },
|
|
74
|
+
body: await readBody(req),
|
|
75
|
+
headers: req.headers as Record<string, string>,
|
|
76
|
+
request: { headers } as any,
|
|
77
|
+
asResponse: true,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await setResponse(res, response);
|
|
81
|
+
|
|
82
|
+
} catch {
|
|
83
|
+
// Endpoint-level failures are returned as Response when `asResponse` is true.
|
|
84
|
+
// Any thrown error here is unexpected, so let the next middleware decide.
|
|
85
|
+
next();
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
package/src/utils/DevMode.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { type Schema, MapSchema, ArraySchema, SetSchema, CollectionSchema, $childType } from '@colyseus/schema';
|
|
3
|
+
import { type Schema, MapSchema, ArraySchema, SetSchema, CollectionSchema, $childType, $changes } from '@colyseus/schema';
|
|
4
4
|
import { logger } from '../Logger.ts';
|
|
5
5
|
import { debugAndPrintError, debugDevMode } from '../Debug.ts';
|
|
6
6
|
import { getLocalRoomById, handleCreateRoom, presence, remoteRoomCall } from '../MatchMaker.ts';
|
|
@@ -44,19 +44,33 @@ export async function reloadFromCache() {
|
|
|
44
44
|
logger.debug(`📋 room '${roomId}' state =>`, rawState);
|
|
45
45
|
|
|
46
46
|
(recreatedRoom.state as Schema).restore(rawState);
|
|
47
|
+
|
|
48
|
+
// Restore the encoder's nextUniqueId so refIds increase
|
|
49
|
+
// monotonically across HMR cycles. Without this, restore()
|
|
50
|
+
// always produces the same refIds (0,1,2,3...) and onJoin()
|
|
51
|
+
// always assigns the same next refIds (4,5...), causing the
|
|
52
|
+
// client decoder to reuse stale instances on the 2nd+ cycle.
|
|
53
|
+
if (roomHistory.nextRefId !== undefined) {
|
|
54
|
+
const encoderRoot = recreatedRoom.state[$changes]?.root;
|
|
55
|
+
if (encoderRoot && roomHistory.nextRefId > encoderRoot['nextUniqueId']) {
|
|
56
|
+
encoderRoot['nextUniqueId'] = roomHistory.nextRefId;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
} catch (e: any) {
|
|
48
60
|
debugAndPrintError(`❌ couldn't restore room '${roomId}' state:\n${e.stack}`);
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
// Reserve seats for clients from cached history
|
|
64
|
+
// Reserve seats for clients from cached history.
|
|
65
|
+
// Skip entries without a reconnectionToken — these are stale
|
|
66
|
+
// seats from allowReconnection() where the client already left
|
|
67
|
+
// (e.g. page refresh). Restoring them would block room disposal.
|
|
53
68
|
if (roomHistory.clients) {
|
|
54
69
|
for (const clientData of roomHistory.clients) {
|
|
55
|
-
// TODO: need to restore each client's StateView as well
|
|
56
|
-
// reserve seat for 20 seconds
|
|
57
70
|
const { sessionId, reconnectionToken } = clientData;
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
if (!reconnectionToken) { continue; }
|
|
72
|
+
// TODO: need to restore each client's StateView as well
|
|
73
|
+
await remoteRoomCall(recreatedRoomListing.roomId, '_reserveSeat', [sessionId, {}, {}, recreatedRoom.seatReservationTimeout, false, reconnectionToken]);
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -86,6 +100,15 @@ export async function cacheRoomHistory(rooms: { [roomId: string]: Room }) {
|
|
|
86
100
|
|
|
87
101
|
if (room.state) {
|
|
88
102
|
roomHistory["state"] = JSON.stringify(room.state);
|
|
103
|
+
|
|
104
|
+
// Cache the encoder's nextUniqueId so it can be restored.
|
|
105
|
+
// This ensures refIds increase monotonically across HMR cycles,
|
|
106
|
+
// preventing the client decoder from reusing stale refs that
|
|
107
|
+
// happen to have the same refId as newly created instances.
|
|
108
|
+
const encoderRoot = room.state[$changes]?.root;
|
|
109
|
+
if (encoderRoot) {
|
|
110
|
+
roomHistory["nextRefId"] = encoderRoot['nextUniqueId'];
|
|
111
|
+
}
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
// cache active clients with their reconnection tokens
|
package/src/utils/Utils.ts
CHANGED
|
@@ -190,8 +190,8 @@ export function dynamicImport<T = any>(moduleName: string): Promise<T> {
|
|
|
190
190
|
}
|
|
191
191
|
} else {
|
|
192
192
|
// ESM context - use import()
|
|
193
|
-
const promise = import(moduleName);
|
|
193
|
+
const promise = import(/* @vite-ignore */ moduleName);
|
|
194
194
|
promise.catch(() => {}); // prevent unhandled rejection warnings
|
|
195
195
|
return promise;
|
|
196
196
|
}
|
|
197
|
-
}
|
|
197
|
+
}
|