@colyseus/core 0.17.44 → 0.18.1
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 +22 -13
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +10 -0
- package/build/MatchMaker.mjs +22 -14
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.cjs +102 -37
- package/build/Protocol.cjs.map +2 -2
- package/build/Protocol.d.ts +33 -2
- package/build/Protocol.mjs +102 -37
- package/build/Protocol.mjs.map +2 -2
- package/build/Rewind.cjs +385 -0
- package/build/Rewind.cjs.map +7 -0
- package/build/Rewind.d.ts +291 -0
- package/build/Rewind.mjs +359 -0
- package/build/Rewind.mjs.map +7 -0
- package/build/Room.cjs +425 -149
- package/build/Room.cjs.map +3 -3
- package/build/Room.d.ts +314 -27
- package/build/Room.mjs +434 -152
- package/build/Room.mjs.map +3 -3
- package/build/RoomMessages.cjs +265 -0
- package/build/RoomMessages.cjs.map +7 -0
- package/build/RoomMessages.d.ts +49 -0
- package/build/RoomMessages.mjs +240 -0
- package/build/RoomMessages.mjs.map +7 -0
- package/build/RoomPlugin.cjs +252 -0
- package/build/RoomPlugin.cjs.map +7 -0
- package/build/RoomPlugin.d.ts +271 -0
- package/build/RoomPlugin.mjs +220 -0
- package/build/RoomPlugin.mjs.map +7 -0
- package/build/Server.cjs +49 -11
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +26 -0
- package/build/Server.mjs +50 -12
- package/build/Server.mjs.map +2 -2
- package/build/Transport.cjs +64 -2
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +108 -5
- package/build/Transport.mjs +63 -2
- package/build/Transport.mjs.map +2 -2
- package/build/errors/RoomExceptions.cjs +7 -4
- package/build/errors/RoomExceptions.cjs.map +2 -2
- package/build/errors/RoomExceptions.d.ts +15 -3
- package/build/errors/RoomExceptions.mjs +5 -3
- package/build/errors/RoomExceptions.mjs.map +2 -2
- package/build/index.cjs +30 -4
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +9 -5
- package/build/index.mjs +29 -6
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +536 -0
- package/build/input/InputBuffer.cjs.map +7 -0
- package/build/input/InputBuffer.d.ts +226 -0
- package/build/input/InputBuffer.mjs +507 -0
- package/build/input/InputBuffer.mjs.map +7 -0
- package/build/input/RoomInput.cjs +306 -0
- package/build/input/RoomInput.cjs.map +7 -0
- package/build/input/RoomInput.d.ts +91 -0
- package/build/input/RoomInput.mjs +287 -0
- package/build/input/RoomInput.mjs.map +7 -0
- package/build/input/types.cjs +18 -0
- package/build/input/types.cjs.map +7 -0
- package/build/input/types.d.ts +476 -0
- package/build/input/types.mjs +0 -0
- package/build/input/types.mjs.map +7 -0
- package/build/internal.cjs +61 -0
- package/build/internal.cjs.map +7 -0
- package/build/internal.d.ts +9 -0
- package/build/internal.mjs +29 -0
- package/build/internal.mjs.map +7 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs +13 -0
- package/build/matchmaker/LocalDriver/LocalDriver.cjs.map +2 -2
- package/build/matchmaker/LocalDriver/LocalDriver.d.ts +1 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs +13 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +2 -2
- package/build/matchmaker/driver.cjs.map +1 -1
- package/build/matchmaker/driver.d.ts +12 -0
- package/build/matchmaker/driver.mjs.map +1 -1
- package/build/presence/LocalPresence.d.ts +1 -1
- package/build/rooms/LobbyRoom.cjs +8 -10
- package/build/rooms/LobbyRoom.cjs.map +2 -2
- package/build/rooms/LobbyRoom.d.ts +4 -3
- package/build/rooms/LobbyRoom.mjs +8 -10
- package/build/rooms/LobbyRoom.mjs.map +2 -2
- package/build/rooms/QueueRoom.cjs +1 -1
- package/build/rooms/QueueRoom.cjs.map +2 -2
- package/build/rooms/QueueRoom.mjs +1 -1
- package/build/rooms/QueueRoom.mjs.map +2 -2
- package/build/rooms/RelayRoom.cjs +12 -16
- package/build/rooms/RelayRoom.cjs.map +2 -2
- package/build/rooms/RelayRoom.d.ts +32 -11
- package/build/rooms/RelayRoom.mjs +10 -16
- package/build/rooms/RelayRoom.mjs.map +2 -2
- package/build/router/default_routes.cjs +4 -1
- package/build/router/default_routes.cjs.map +2 -2
- package/build/router/default_routes.mjs +4 -1
- package/build/router/default_routes.mjs.map +2 -2
- package/build/router/index.cjs +71 -5
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.d.ts +30 -6
- package/build/router/index.mjs +72 -7
- package/build/router/index.mjs.map +3 -3
- package/build/serializer/NoneSerializer.cjs +2 -2
- package/build/serializer/NoneSerializer.cjs.map +2 -2
- package/build/serializer/NoneSerializer.d.ts +3 -3
- package/build/serializer/NoneSerializer.mjs +2 -2
- package/build/serializer/NoneSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializer.cjs +44 -14
- package/build/serializer/SchemaSerializer.cjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +3 -3
- package/build/serializer/SchemaSerializer.mjs +46 -16
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/Serializer.cjs.map +1 -1
- package/build/serializer/Serializer.d.ts +12 -2
- package/build/utils/DevMode.cjs +14 -14
- package/build/utils/DevMode.cjs.map +2 -2
- package/build/utils/DevMode.d.ts +0 -2
- package/build/utils/DevMode.mjs +14 -12
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/UserSessionIndex.cjs +162 -0
- package/build/utils/UserSessionIndex.cjs.map +7 -0
- package/build/utils/UserSessionIndex.d.ts +166 -0
- package/build/utils/UserSessionIndex.mjs +130 -0
- package/build/utils/UserSessionIndex.mjs.map +7 -0
- package/build/utils/Utils.cjs +3 -0
- package/build/utils/Utils.cjs.map +2 -2
- package/build/utils/Utils.d.ts +8 -0
- package/build/utils/Utils.mjs +2 -0
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +19 -14
- package/src/MatchMaker.ts +47 -15
- package/src/Protocol.ts +130 -59
- package/src/Rewind.ts +572 -0
- package/src/Room.ts +649 -235
- package/src/RoomMessages.ts +342 -0
- package/src/RoomPlugin.ts +563 -0
- package/src/Server.ts +82 -15
- package/src/Transport.ts +172 -9
- package/src/errors/RoomExceptions.ts +18 -4
- package/src/index.ts +23 -3
- package/src/input/InputBuffer.ts +549 -0
- package/src/input/RoomInput.ts +337 -0
- package/src/input/types.ts +503 -0
- package/src/internal.ts +46 -0
- package/src/matchmaker/LocalDriver/LocalDriver.ts +10 -0
- package/src/matchmaker/driver.ts +13 -0
- package/src/rooms/LobbyRoom.ts +12 -8
- package/src/rooms/QueueRoom.ts +1 -1
- package/src/rooms/RelayRoom.ts +9 -15
- package/src/router/default_routes.ts +6 -1
- package/src/router/index.ts +117 -11
- package/src/serializer/NoneSerializer.ts +3 -3
- package/src/serializer/SchemaSerializer.ts +110 -19
- package/src/serializer/Serializer.ts +13 -2
- package/src/utils/DevMode.ts +18 -13
- package/src/utils/UserSessionIndex.ts +311 -0
- package/src/utils/Utils.ts +9 -0
package/src/Server.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { Presence } from "./presence/Presence.ts";
|
|
|
14
14
|
import { setTransport, Transport } from './Transport.ts';
|
|
15
15
|
import { logger, setLogger } from './Logger.ts';
|
|
16
16
|
import { setDevMode, isDevMode } from './utils/DevMode.ts';
|
|
17
|
-
import { type Router, bindRouterToTransport } from './router/index.ts';
|
|
17
|
+
import { type Router, bindRouterToTransport, createRouter } from './router/index.ts';
|
|
18
18
|
import { prereadRequestBodies } from './router/node.ts';
|
|
19
19
|
import { type SDKTypes as SharedSDKTypes } from '@colyseus/shared-types';
|
|
20
20
|
import { getDefaultRouter } from './router/default_routes.ts';
|
|
@@ -33,6 +33,29 @@ export type ServerOptions = {
|
|
|
33
33
|
*/
|
|
34
34
|
beforeListen?: () => Promise<void> | void,
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Booted before `matchMaker.accept()`. Structural so `@colyseus/core` keeps
|
|
38
|
+
* no runtime dep on `@colyseus/database` — any `{ boot(): Promise<void> }`
|
|
39
|
+
* works. The optional `applyRouterDefaults` is invoked after boot with the
|
|
40
|
+
* user's router so the database can contribute endpoints (e.g. auth routes).
|
|
41
|
+
*/
|
|
42
|
+
database?: {
|
|
43
|
+
boot(): Promise<void>;
|
|
44
|
+
applyRouterDefaults?(router: Router): Router | Promise<Router>;
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Mount `@colyseus/auth` routes into the router. Pass an options object
|
|
49
|
+
* (forwarded to `auth.endpoints(...)`) to wire it explicitly — useful when
|
|
50
|
+
* `database` is not in use. `false` disables auto-mounting even if a
|
|
51
|
+
* `database` is present and would have provided defaults.
|
|
52
|
+
*/
|
|
53
|
+
auth?: false | {
|
|
54
|
+
settings?: Record<string, any>;
|
|
55
|
+
oauth?: boolean | { cookieSecret?: string };
|
|
56
|
+
prefix?: string;
|
|
57
|
+
},
|
|
58
|
+
|
|
36
59
|
/**
|
|
37
60
|
* Optional callback to configure Express routes.
|
|
38
61
|
* When provided, the transport layer will initialize an Express-compatible app
|
|
@@ -104,6 +127,12 @@ export class Server<
|
|
|
104
127
|
|
|
105
128
|
private _originalRoomOnMessage: typeof Room.prototype['_onMessage'] | null = null;
|
|
106
129
|
|
|
130
|
+
// Implicit default for callers that omit explicit Server reference — e.g.
|
|
131
|
+
// `playground()` reads `Server.current.router.endpoints` at request time.
|
|
132
|
+
// Last-construction wins; multi-server setups should reference instances
|
|
133
|
+
// explicitly.
|
|
134
|
+
static current: Server<any, any> | undefined;
|
|
135
|
+
|
|
107
136
|
constructor(options: ServerOptions = {}) {
|
|
108
137
|
const {
|
|
109
138
|
gracefullyShutdown = true,
|
|
@@ -115,6 +144,8 @@ export class Server<
|
|
|
115
144
|
this.options = options;
|
|
116
145
|
this.greet = greet;
|
|
117
146
|
|
|
147
|
+
(Server as { current: Server<any, any> | undefined }).current = this as Server<any, any>;
|
|
148
|
+
|
|
118
149
|
this.attach(options);
|
|
119
150
|
|
|
120
151
|
// Pass options.presence/driver through as-is (possibly undefined).
|
|
@@ -141,14 +172,8 @@ export class Server<
|
|
|
141
172
|
|
|
142
173
|
public async attach(options: ServerOptions) {
|
|
143
174
|
this.transport = options.transport || await this.getDefaultTransport(options);
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
if (options.express && this.transport.getExpressApp) {
|
|
147
|
-
const expressApp = await this.transport.getExpressApp();
|
|
148
|
-
await options.express(expressApp);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Resolve the promise when the transport is ready
|
|
175
|
+
// `options.express` runs in `listen()` after `database.boot()` so user
|
|
176
|
+
// code reading `database.auth.settings` etc. finds services instantiated.
|
|
152
177
|
this._onTransportReady.resolve(this.transport);
|
|
153
178
|
}
|
|
154
179
|
|
|
@@ -161,9 +186,7 @@ export class Server<
|
|
|
161
186
|
* @param listeningListener
|
|
162
187
|
*/
|
|
163
188
|
public async listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: Function) {
|
|
164
|
-
|
|
165
|
-
await this.options.beforeListen();
|
|
166
|
-
}
|
|
189
|
+
await this._bootServices();
|
|
167
190
|
|
|
168
191
|
//
|
|
169
192
|
// if Colyseus Cloud is detected, use @colyseus/tools to listen
|
|
@@ -254,9 +277,7 @@ export class Server<
|
|
|
254
277
|
* ```
|
|
255
278
|
*/
|
|
256
279
|
public async serverless(): Promise<HttpServer> {
|
|
257
|
-
|
|
258
|
-
await this.options.beforeListen();
|
|
259
|
-
}
|
|
280
|
+
await this._bootServices();
|
|
260
281
|
|
|
261
282
|
// transport (and its HTTP server) must be ready before binding routes
|
|
262
283
|
await this._onTransportReady;
|
|
@@ -419,6 +440,52 @@ export class Server<
|
|
|
419
440
|
this.onBeforeShutdownCallback = callback;
|
|
420
441
|
}
|
|
421
442
|
|
|
443
|
+
// Boot sequence shared by listen() and serverless(): user `beforeListen`
|
|
444
|
+
// hook, database boot, framework router defaults (auth/db endpoints), then
|
|
445
|
+
// the express app. serverless() needs this too — its exported server still
|
|
446
|
+
// serves the express callback and auth/database routes.
|
|
447
|
+
private async _bootServices(): Promise<void> {
|
|
448
|
+
const { beforeListen, database, express } = this.options;
|
|
449
|
+
|
|
450
|
+
if (beforeListen) { await beforeListen(); }
|
|
451
|
+
if (database) { await database.boot(); }
|
|
452
|
+
|
|
453
|
+
await this._applyRouterDefaults();
|
|
454
|
+
|
|
455
|
+
if (express) {
|
|
456
|
+
await this._onTransportReady;
|
|
457
|
+
if (this.transport.getExpressApp) {
|
|
458
|
+
const expressApp = await this.transport.getExpressApp();
|
|
459
|
+
await express(expressApp);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Extend the user's router with framework-contributed endpoints. An explicit
|
|
465
|
+
// `auth` option wins over the database's `applyRouterDefaults` so a non-DB
|
|
466
|
+
// setup can still auto-mount @colyseus/auth, and a DB user can opt out.
|
|
467
|
+
private async _applyRouterDefaults(): Promise<void> {
|
|
468
|
+
const { auth, database } = this.options;
|
|
469
|
+
const wantsAuth = auth !== undefined && auth !== false;
|
|
470
|
+
const wantsDatabase = auth === undefined && !!database?.applyRouterDefaults;
|
|
471
|
+
if (!wantsAuth && !wantsDatabase) { return; }
|
|
472
|
+
|
|
473
|
+
// Boot an empty router if the user didn't pass `routes` — the framework's
|
|
474
|
+
// default routes get layered on later in the `transport.listen()` callback.
|
|
475
|
+
this.router ??= createRouter({}) as unknown as Routes;
|
|
476
|
+
|
|
477
|
+
if (wantsAuth) {
|
|
478
|
+
const authMod: any = await dynamicImport('@colyseus/auth').catch(() => undefined);
|
|
479
|
+
const endpointsFn = authMod?.auth?.endpoints ?? authMod?.default?.auth?.endpoints;
|
|
480
|
+
if (typeof endpointsFn === 'function') {
|
|
481
|
+
this.router = (this.router as Router).extend(endpointsFn(auth)) as Routes;
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
const updated = await database!.applyRouterDefaults!(this.router as Router);
|
|
485
|
+
if (updated) { this.router = updated as Routes; }
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
422
489
|
protected async getDefaultTransport(options: any): Promise<Transport> {
|
|
423
490
|
try {
|
|
424
491
|
const module = await dynamicImport('@colyseus/ws-transport');
|
package/src/Transport.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { Router } from '@colyseus/better-call';
|
|
|
5
5
|
|
|
6
6
|
import { ErrorCode } from '@colyseus/shared-types';
|
|
7
7
|
import { StateView } from '@colyseus/schema';
|
|
8
|
+
import type { InputDecoder } from '@colyseus/schema/input';
|
|
8
9
|
|
|
9
10
|
import { EventEmitter } from 'events';
|
|
10
11
|
import { spliceOne } from './utils/Utils.ts';
|
|
@@ -18,6 +19,11 @@ export function getTransport() { return _transport; }
|
|
|
18
19
|
|
|
19
20
|
export abstract class Transport {
|
|
20
21
|
public protocol?: string;
|
|
22
|
+
/** Self-signed cert SHA-256 hash (byte array), surfaced to clients in the
|
|
23
|
+
* matchmake response so a WebTransport client can pin it via
|
|
24
|
+
* `serverCertificateHashes`. Set by transports that generate their own cert
|
|
25
|
+
* (h3). Undefined for transports using a CA-trusted cert. */
|
|
26
|
+
public fingerprint?: number[];
|
|
21
27
|
public server?: http.Server | https.Server;
|
|
22
28
|
|
|
23
29
|
public abstract listen(port?: number | string, hostname?: string, backlog?: number, listeningListener?: Function): this;
|
|
@@ -90,15 +96,10 @@ export interface Client<T extends { userData?: any, auth?: any, messages?: Recor
|
|
|
90
96
|
|
|
91
97
|
ref: EventEmitter;
|
|
92
98
|
|
|
93
|
-
/**
|
|
94
|
-
* @deprecated use `sessionId` instead.
|
|
95
|
-
*/
|
|
96
|
-
id: string;
|
|
97
|
-
|
|
98
99
|
/**
|
|
99
100
|
* Unique id per session.
|
|
100
101
|
*/
|
|
101
|
-
sessionId: string;
|
|
102
|
+
sessionId: string;
|
|
102
103
|
|
|
103
104
|
/**
|
|
104
105
|
* Connection state
|
|
@@ -190,7 +191,6 @@ export interface Client<T extends { userData?: any, auth?: any, messages?: Recor
|
|
|
190
191
|
export interface ClientPrivate {
|
|
191
192
|
readyState: number; // TODO: remove readyState on version 1.0.0. Use only "state" instead.
|
|
192
193
|
_enqueuedMessages?: any[];
|
|
193
|
-
_afterNextPatchQueue: Array<[string | number | Client, ArrayLike<any>]>;
|
|
194
194
|
_joinedAt: number; // "elapsedTime" when the client joined the room.
|
|
195
195
|
|
|
196
196
|
/**
|
|
@@ -198,15 +198,178 @@ export interface ClientPrivate {
|
|
|
198
198
|
*/
|
|
199
199
|
_numMessagesLastSecond?: number;
|
|
200
200
|
_lastMessageTime?: number;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Per-client input Schema instance, allocated on join when the Room
|
|
204
|
+
* declares `input`. Mutated in-place by {@link _inputDecoder} on each
|
|
205
|
+
* incoming ROOM_INPUT_* packet.
|
|
206
|
+
*
|
|
207
|
+
* Typed loosely (`any`) so duplicate `@colyseus/schema` installs don't
|
|
208
|
+
* trigger type-identity errors against user-defined input classes.
|
|
209
|
+
*/
|
|
210
|
+
_input?: any;
|
|
211
|
+
_inputDecoder?: InputDecoder;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Per-client buffer of cloned input snapshots, allocated on join when
|
|
215
|
+
* `Room.inputOptions.bufferMaxSize > 0`. Populated on each decoded frame.
|
|
216
|
+
*/
|
|
217
|
+
_inputBuffer?: import('./input/InputBuffer.ts').InputBufferImpl;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Cached per-client accessor returned by `room.input(sessionId)`. Built
|
|
221
|
+
* once at join (when the Room called `defineInput()`), so the public API
|
|
222
|
+
* call is a Map lookup + property read with no per-call allocation.
|
|
223
|
+
*/
|
|
224
|
+
_inputAccessor?: import('./input/types.ts').InputAccessor;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Used for rate limiting ROOM_INPUT_* packets via maxInputsPerSecond,
|
|
228
|
+
* independent of maxMessagesPerSecond.
|
|
229
|
+
*/
|
|
230
|
+
_numInputsLastSecond?: number;
|
|
231
|
+
_lastInputTime?: number;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* `performance.now()` recorded when the most recent ROOM_INPUT_* packet
|
|
235
|
+
* from this client was received. Drives the per-recipient `lastTReceived`
|
|
236
|
+
* field of the {@link ProtocolModifier.TIMED} state prefix.
|
|
237
|
+
*
|
|
238
|
+
* `0` until the client has sent its first input.
|
|
239
|
+
*/
|
|
240
|
+
_lastInputReceivedAt?: number;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Monotonic count of *reliable* inputs successfully received from this
|
|
244
|
+
* client. Echoed back in the TIMED prefix as `lastInputSeq` so the
|
|
245
|
+
* client can correlate to its own send-time table and compute RTT.
|
|
246
|
+
* Stays at the default `0` until the client sends its first reliable
|
|
247
|
+
* input.
|
|
248
|
+
*
|
|
249
|
+
* Only ROOM_INPUT_RELIABLE bumps this — unreliable's redundant-ring
|
|
250
|
+
* pattern would double-count.
|
|
251
|
+
*/
|
|
252
|
+
_receivedInputCount?: number;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Running baseline for the DELTA-CODED lag-comp stamp on ROOM_INPUT_RELIABLE
|
|
256
|
+
* frames (the {@link ProtocolModifier.TIMED} prefix). Each frame carries only
|
|
257
|
+
* the signed change from the previous stamp; this accumulates them back into
|
|
258
|
+
* the absolute timeline value. Zeroed on (re)connect alongside the SDK's own
|
|
259
|
+
* baseline so the first delta after a reset is absolute. `0` until allocated.
|
|
260
|
+
*/
|
|
261
|
+
_reckonBaseline?: number;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @internal Per-client raw frames staged to ride out right AFTER this client's
|
|
265
|
+
* next state patch — per-client `afterNextPatch` messages. Lazily allocated.
|
|
266
|
+
* Pushed by {@link enqueueClientRaw} (the `afterNextPatch` path), flushed as
|
|
267
|
+
* standalone frames after the patch by {@link Room._flushPendingClientFrames}.
|
|
268
|
+
* Room-level `broadcast` `afterNextPatch` uses the Room's own queue instead, not
|
|
269
|
+
* this buffer.
|
|
270
|
+
*/
|
|
271
|
+
_pendingFrames?: Uint8Array[];
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @internal Back-reference to the Room's "clients with staged frames" list,
|
|
275
|
+
* shared by reference at join. {@link enqueueClientRaw} pushes the client here
|
|
276
|
+
* on its first staged frame of a cycle, so the after-patch flush iterates just
|
|
277
|
+
* those clients rather than scanning every client.
|
|
278
|
+
*/
|
|
279
|
+
_pendingFrameClients?: Array<Client & ClientPrivate>;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The framework-level send path shared by every transport's `enqueueRaw` — the
|
|
284
|
+
* single source of truth so each transport implements only the wire-level `raw`.
|
|
285
|
+
* Routes a raw frame by where it should go:
|
|
286
|
+
*
|
|
287
|
+
* - `afterNextPatch` → stage onto the per-client {@link ClientPrivate._pendingFrames}
|
|
288
|
+
* buffer, sent as a standalone frame right AFTER the next state patch; a
|
|
289
|
+
* no-allocation push into a reused array. The client announces itself to the
|
|
290
|
+
* Room's {@link ClientPrivate._pendingFrameClients} list on its first staged
|
|
291
|
+
* frame of a cycle.
|
|
292
|
+
* - before JOIN → buffer in `_enqueuedMessages` until the JOIN_ROOM handshake flushes.
|
|
293
|
+
* - otherwise → send now via the transport's `raw`.
|
|
294
|
+
*
|
|
295
|
+
* @internal
|
|
296
|
+
*/
|
|
297
|
+
export function enqueueClientRaw(
|
|
298
|
+
client: Client & ClientPrivate,
|
|
299
|
+
data: Uint8Array | Buffer,
|
|
300
|
+
options?: ISendOptions,
|
|
301
|
+
): void {
|
|
302
|
+
if (options?.afterNextPatch) {
|
|
303
|
+
let frames = client._pendingFrames;
|
|
304
|
+
if (frames === undefined) { frames = client._pendingFrames = []; }
|
|
305
|
+
if (frames.length === 0) { client._pendingFrameClients?.push(client); } // first frame this cycle
|
|
306
|
+
frames.push(data);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (client.state !== ClientState.JOINED) {
|
|
310
|
+
// During `onJoin` / `onReconnect` the client can't register onMessage
|
|
311
|
+
// handlers yet — buffer until JOIN_ROOM has been sent.
|
|
312
|
+
client._enqueuedMessages?.push(data);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
client.raw(data, options);
|
|
201
316
|
}
|
|
202
317
|
|
|
203
318
|
export class ClientArray<C extends Client = Client> extends Array<C> {
|
|
319
|
+
/**
|
|
320
|
+
* Secondary index for O(1) lookup by sessionId. Kept in sync by the
|
|
321
|
+
* mutating methods overridden below. Direct index assignment
|
|
322
|
+
* (`arr[i] = client`) and `arr.length = 0` bypass this index — use
|
|
323
|
+
* `push` / `splice` / `delete` / `pop` / `shift` / `unshift` instead.
|
|
324
|
+
*/
|
|
325
|
+
private _byId: Map<string, C> = new Map();
|
|
326
|
+
|
|
327
|
+
/** The client for `sessionId`, or `undefined` — O(1). The canonical per-session
|
|
328
|
+
* lookup (mirrors `room.inputs.get(sessionId)`). */
|
|
329
|
+
public get(sessionId: string): C | undefined {
|
|
330
|
+
return this._byId.get(sessionId);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** @deprecated Use {@link get}. */
|
|
204
334
|
public getById(sessionId: string): C | undefined {
|
|
205
|
-
return this.
|
|
335
|
+
return this._byId.get(sessionId);
|
|
206
336
|
}
|
|
207
337
|
|
|
208
338
|
public delete(client: C): boolean {
|
|
209
|
-
|
|
339
|
+
const removed = spliceOne(this, this.indexOf(client));
|
|
340
|
+
if (removed) this._byId.delete(client.sessionId);
|
|
341
|
+
return removed;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public push(...items: C[]): number {
|
|
345
|
+
for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
|
|
346
|
+
return super.push(...items);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public pop(): C | undefined {
|
|
350
|
+
const removed = super.pop();
|
|
351
|
+
if (removed !== undefined) this._byId.delete(removed.sessionId);
|
|
352
|
+
return removed;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
public shift(): C | undefined {
|
|
356
|
+
const removed = super.shift();
|
|
357
|
+
if (removed !== undefined) this._byId.delete(removed.sessionId);
|
|
358
|
+
return removed;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public unshift(...items: C[]): number {
|
|
362
|
+
for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
|
|
363
|
+
return super.unshift(...items);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
public splice(start: number, deleteCount?: number, ...items: C[]): C[] {
|
|
367
|
+
const removed = (deleteCount === undefined)
|
|
368
|
+
? super.splice(start)
|
|
369
|
+
: super.splice(start, deleteCount, ...items);
|
|
370
|
+
for (let i = 0; i < removed.length; i++) this._byId.delete(removed[i].sessionId);
|
|
371
|
+
for (let i = 0; i < items.length; i++) this._byId.set(items[i].sessionId, items[i]);
|
|
372
|
+
return removed;
|
|
210
373
|
}
|
|
211
374
|
}
|
|
212
375
|
|
|
@@ -9,7 +9,8 @@ export type RoomMethodName = 'onCreate'
|
|
|
9
9
|
| 'onReconnect'
|
|
10
10
|
| 'onDispose'
|
|
11
11
|
| 'onMessage'
|
|
12
|
-
| '
|
|
12
|
+
| 'setTimestep'
|
|
13
|
+
| 'setFixedTimestep'
|
|
13
14
|
| 'setInterval'
|
|
14
15
|
| 'setTimeout';
|
|
15
16
|
|
|
@@ -22,7 +23,7 @@ export type RoomException<R extends Room = Room> =
|
|
|
22
23
|
OnReconnectException<R> |
|
|
23
24
|
OnDisposeException |
|
|
24
25
|
OnMessageException<R> |
|
|
25
|
-
|
|
26
|
+
TimestepException |
|
|
26
27
|
TimedEventException;
|
|
27
28
|
|
|
28
29
|
export class OnCreateException<R extends Room = Room> extends Error {
|
|
@@ -151,16 +152,29 @@ export class OnMessageException<R extends Room, MessageType extends keyof R['mes
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
|
|
155
|
+
/** Thrown when a {@link Room.setTimestep} or {@link Room.setFixedTimestep} tick
|
|
156
|
+
* callback throws (or rejects). Reported to `onUncaughtException` with the
|
|
157
|
+
* originating method as `methodName` (`'setTimestep'` / `'setFixedTimestep'`). */
|
|
158
|
+
export class TimestepException extends Error {
|
|
155
159
|
constructor(
|
|
156
160
|
cause: Error,
|
|
157
161
|
message: string,
|
|
158
162
|
) {
|
|
159
163
|
super(message, { cause });
|
|
160
|
-
this.name = '
|
|
164
|
+
this.name = 'TimestepException';
|
|
161
165
|
}
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
/**
|
|
169
|
+
* @deprecated Renamed to {@link TimestepException} (it now covers both
|
|
170
|
+
* `setTimestep` and `setFixedTimestep`). Kept as an alias for backwards
|
|
171
|
+
* compatibility — `instanceof` checks against either name still match.
|
|
172
|
+
*
|
|
173
|
+
* TODO: remove this on 1.0
|
|
174
|
+
*/
|
|
175
|
+
export const SimulationIntervalException = TimestepException;
|
|
176
|
+
export type SimulationIntervalException = TimestepException;
|
|
177
|
+
|
|
164
178
|
export class TimedEventException extends Error {
|
|
165
179
|
public args: any[];
|
|
166
180
|
constructor(
|
package/src/index.ts
CHANGED
|
@@ -3,16 +3,30 @@ import { ClockTimer as Clock, Delayed } from '@colyseus/timer';
|
|
|
3
3
|
// Shared types - re-export from @colyseus/shared-types for convenience
|
|
4
4
|
export {
|
|
5
5
|
Protocol,
|
|
6
|
+
ProtocolModifier,
|
|
6
7
|
ErrorCode,
|
|
7
8
|
CloseCode,
|
|
9
|
+
ResponseStatus,
|
|
8
10
|
type InferState,
|
|
9
11
|
type ExtractRoomMessages,
|
|
10
12
|
type ExtractRoomClientMessages,
|
|
13
|
+
type MessageContext,
|
|
14
|
+
type Rejection,
|
|
15
|
+
type Resolution,
|
|
16
|
+
type ExtractRejectReason,
|
|
17
|
+
type ExtractResponseType,
|
|
11
18
|
} from '@colyseus/shared-types';
|
|
12
19
|
|
|
13
20
|
// Core classes
|
|
14
21
|
export { Server, defineRoom, defineServer, registerRoomDefinitions, unregisterRoomDefinitions, type RoomDefinitions, type ServerOptions, type SDKTypes } from './Server.ts';
|
|
15
|
-
export { Room,
|
|
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
|
+
export { InputBufferImpl, compileSanitizer } from './input/InputBuffer.ts';
|
|
24
|
+
export { type InputAccessor, type InputAPI, type NormalizedInputOptions, type ConsumeOptions, type IdleInput, type IdleContext, type SanitizeInput, type NumericFieldsOf } from './input/types.ts';
|
|
25
|
+
export { Rewind, RewindView, type RewindOptions, type RewindMode } from './Rewind.ts';
|
|
26
|
+
export {
|
|
27
|
+
RoomPlugin, definePlugins, attachToTestRoom,
|
|
28
|
+
type RoomPluginOrder, type PluginDependencies, type RoomPluginClass,
|
|
29
|
+
} from './RoomPlugin.ts';
|
|
16
30
|
export { getMessageBytes } from './Protocol.ts';
|
|
17
31
|
export { RegisteredHandler } from './matchmaker/RegisteredHandler.ts';
|
|
18
32
|
export { ServerError } from './errors/ServerError.ts';
|
|
@@ -26,6 +40,7 @@ export {
|
|
|
26
40
|
OnLeaveException,
|
|
27
41
|
OnDisposeException,
|
|
28
42
|
OnMessageException,
|
|
43
|
+
TimestepException,
|
|
29
44
|
SimulationIntervalException,
|
|
30
45
|
TimedEventException,
|
|
31
46
|
} from './errors/RoomExceptions.ts';
|
|
@@ -41,7 +56,7 @@ export * from './matchmaker/LocalDriver/LocalDriver.ts';
|
|
|
41
56
|
export { initializeRoomCache } from './matchmaker/driver.ts';
|
|
42
57
|
|
|
43
58
|
// Transport
|
|
44
|
-
export { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, connectClientToRoom } from './Transport.ts';
|
|
59
|
+
export { type Client, type ClientPrivate, type AuthContext, ClientState, ClientArray, Transport, type ISendOptions, connectClientToRoom, enqueueClientRaw } from './Transport.ts';
|
|
45
60
|
|
|
46
61
|
// Presence
|
|
47
62
|
export { type Presence } from './presence/Presence.ts';
|
|
@@ -82,8 +97,10 @@ export {
|
|
|
82
97
|
createInternalContext,
|
|
83
98
|
createMiddleware,
|
|
84
99
|
createRouter,
|
|
100
|
+
basicAuth,
|
|
101
|
+
type BasicAuthOptions,
|
|
85
102
|
toNodeHandler,
|
|
86
|
-
|
|
103
|
+
dualModeEndpoints,
|
|
87
104
|
type Router,
|
|
88
105
|
type RouterConfig,
|
|
89
106
|
type Endpoint,
|
|
@@ -91,6 +108,9 @@ export {
|
|
|
91
108
|
type EndpointOptions,
|
|
92
109
|
type EndpointContext,
|
|
93
110
|
type StrictEndpoint,
|
|
111
|
+
type ExpressMiddleware,
|
|
112
|
+
type NodeHandler,
|
|
113
|
+
type DualModeHelpers,
|
|
94
114
|
} from './router/index.ts';
|
|
95
115
|
|
|
96
116
|
// Abstract logging support
|