@colyseus/core 0.17.43 → 0.18.0
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 +19 -6
- package/build/MatchMaker.cjs.map +2 -2
- package/build/MatchMaker.d.ts +10 -0
- package/build/MatchMaker.mjs +18 -6
- 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/Room.cjs +296 -19
- package/build/Room.cjs.map +3 -3
- package/build/Room.d.ts +186 -3
- package/build/Room.mjs +303 -21
- package/build/Room.mjs.map +3 -3
- 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 -15
- package/build/Server.cjs.map +2 -2
- package/build/Server.d.ts +25 -0
- package/build/Server.mjs +50 -16
- package/build/Server.mjs.map +2 -2
- package/build/Transport.cjs +38 -2
- package/build/Transport.cjs.map +2 -2
- package/build/Transport.d.ts +40 -4
- package/build/Transport.mjs +38 -2
- package/build/Transport.mjs.map +2 -2
- package/build/index.cjs +11 -2
- package/build/index.cjs.map +2 -2
- package/build/index.d.ts +2 -1
- package/build/index.mjs +12 -2
- package/build/index.mjs.map +2 -2
- package/build/input/InputBuffer.cjs +113 -0
- package/build/input/InputBuffer.cjs.map +7 -0
- package/build/input/InputBuffer.d.ts +136 -0
- package/build/input/InputBuffer.mjs +86 -0
- package/build/input/InputBuffer.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/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/index.cjs +65 -4
- package/build/router/index.cjs.map +2 -2
- package/build/router/index.d.ts +30 -6
- package/build/router/index.mjs +66 -6
- package/build/router/index.mjs.map +3 -3
- package/build/utils/Env.cjs +4 -8
- package/build/utils/Env.cjs.map +3 -3
- package/build/utils/Env.mjs +4 -8
- package/build/utils/Env.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/package.json +20 -15
- package/src/MatchMaker.ts +40 -6
- package/src/Protocol.ts +130 -59
- package/src/Room.ts +475 -22
- package/src/RoomPlugin.ts +563 -0
- package/src/Server.ts +81 -22
- package/src/Transport.ts +76 -8
- package/src/index.ts +10 -1
- package/src/input/InputBuffer.ts +192 -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/RelayRoom.ts +9 -15
- package/src/router/index.ts +112 -11
- package/src/utils/Env.ts +4 -12
- package/src/utils/UserSessionIndex.ts +311 -0
package/build/Room.cjs
CHANGED
|
@@ -33,12 +33,18 @@ __export(Room_exports, {
|
|
|
33
33
|
DEFAULT_SEAT_RESERVATION_TIME: () => DEFAULT_SEAT_RESERVATION_TIME,
|
|
34
34
|
Room: () => Room,
|
|
35
35
|
RoomInternalState: () => RoomInternalState,
|
|
36
|
+
RoomPlugin: () => import_RoomPlugin2.RoomPlugin,
|
|
37
|
+
attachToTestRoom: () => import_RoomPlugin2.attachToTestRoom,
|
|
38
|
+
definePlugins: () => import_RoomPlugin2.definePlugins,
|
|
36
39
|
room: () => room,
|
|
37
40
|
validate: () => validate
|
|
38
41
|
});
|
|
39
42
|
module.exports = __toCommonJS(Room_exports);
|
|
40
|
-
var import_msgpackr = require("
|
|
43
|
+
var import_msgpackr = require("msgpackr");
|
|
41
44
|
var import_schema = require("@colyseus/schema");
|
|
45
|
+
var import_input = require("@colyseus/schema/input");
|
|
46
|
+
var import_InputBuffer = require("./input/InputBuffer.cjs");
|
|
47
|
+
var import_InputBuffer2 = require("./input/InputBuffer.cjs");
|
|
42
48
|
var import_timer = require("@colyseus/timer");
|
|
43
49
|
var import_events = require("events");
|
|
44
50
|
var import_Logger = require("./Logger.cjs");
|
|
@@ -55,9 +61,19 @@ var import_RoomExceptions = require("./errors/RoomExceptions.cjs");
|
|
|
55
61
|
var import_StandardSchema = require("./utils/StandardSchema.cjs");
|
|
56
62
|
var matchMaker = __toESM(require("./MatchMaker.cjs"), 1);
|
|
57
63
|
var import_shared_types = require("@colyseus/shared-types");
|
|
64
|
+
var import_RoomPlugin = require("./RoomPlugin.cjs");
|
|
65
|
+
var import_RoomPlugin2 = require("./RoomPlugin.cjs");
|
|
66
|
+
var _inputReflectionCache = /* @__PURE__ */ new WeakMap();
|
|
58
67
|
var DEFAULT_PATCH_RATE = 1e3 / 20;
|
|
59
68
|
var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
|
|
60
69
|
var noneSerializer = new import_NoneSerializer.NoneSerializer();
|
|
70
|
+
function toResponseError(e) {
|
|
71
|
+
if (e instanceof Error) {
|
|
72
|
+
const code = e.code;
|
|
73
|
+
return code !== void 0 ? { name: e.name, message: e.message, code } : { name: e.name, message: e.message };
|
|
74
|
+
}
|
|
75
|
+
return { name: "Error", message: String(e) };
|
|
76
|
+
}
|
|
61
77
|
var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
62
78
|
function validate(format, handler) {
|
|
63
79
|
return { format, handler };
|
|
@@ -140,6 +156,7 @@ var Room = class _Room {
|
|
|
140
156
|
this._internalState = RoomInternalState.CREATING;
|
|
141
157
|
this._lockedExplicitly = false;
|
|
142
158
|
this.#_locked = false;
|
|
159
|
+
this.#_tick = 0;
|
|
143
160
|
this._events.once("dispose", () => {
|
|
144
161
|
this.#_dispose().catch((e) => (0, import_Debug.debugAndPrintError)(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
|
|
145
162
|
});
|
|
@@ -256,6 +273,9 @@ var Room = class _Room {
|
|
|
256
273
|
if (this.#_state) {
|
|
257
274
|
this.state = this.#_state;
|
|
258
275
|
}
|
|
276
|
+
if (this.plugins !== void 0) {
|
|
277
|
+
(0, import_RoomPlugin.setupRoomPlugins)(this);
|
|
278
|
+
}
|
|
259
279
|
if (this.messages !== void 0) {
|
|
260
280
|
if (this.messages["_"]) {
|
|
261
281
|
this.onMessage("*", this.messages["_"].bind(this));
|
|
@@ -312,6 +332,44 @@ var Room = class _Room {
|
|
|
312
332
|
}
|
|
313
333
|
this.#_roomId = roomId;
|
|
314
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Declare the input schema and configuration in a single line. Returns the
|
|
337
|
+
* callable accessor that gets assigned to `this.input` — call
|
|
338
|
+
* `this.input(sessionId)` per tick to consume.
|
|
339
|
+
*
|
|
340
|
+
* ```typescript
|
|
341
|
+
* class FpsRoom extends Room<{ input: MoveInput }> {
|
|
342
|
+
* input = this.defineInput(MoveInput, {
|
|
343
|
+
* seqField: "tick", // typed: only numeric fields of MoveInput
|
|
344
|
+
* bufferMaxSize: 64,
|
|
345
|
+
* });
|
|
346
|
+
*
|
|
347
|
+
* // …or without options — defaults to seqField: "seq", bufferMaxSize: 32:
|
|
348
|
+
* // input = this.defineInput(MoveInput);
|
|
349
|
+
* }
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* **Defaults** when `opts` (or individual fields) are omitted:
|
|
353
|
+
* - `seqField`: `"seq"` — framework dedupes by `input.seq` if the schema has
|
|
354
|
+
* such a field. Schemas without it gracefully skip dedupe.
|
|
355
|
+
* - `bufferMaxSize`: `32` — enables per-client snapshot buffering for
|
|
356
|
+
* `room.input(sessionId).drain() / .peek() / .at()`. Set to `0` to disable
|
|
357
|
+
* buffering (the `.latest` read still works).
|
|
358
|
+
*/
|
|
359
|
+
defineInput(type, opts) {
|
|
360
|
+
this.inputOptions = {
|
|
361
|
+
ctor: type,
|
|
362
|
+
seqField: opts?.seqField ?? "seq",
|
|
363
|
+
bufferMaxSize: opts?.bufferMaxSize ?? 32
|
|
364
|
+
};
|
|
365
|
+
if (!_inputReflectionCache.has(type)) {
|
|
366
|
+
_inputReflectionCache.set(type, import_schema.Reflection.encode(new import_schema.Encoder(new type())));
|
|
367
|
+
}
|
|
368
|
+
return ((sessionId) => {
|
|
369
|
+
const c = this.clients.getById(sessionId);
|
|
370
|
+
return c?._inputAccessor ?? import_InputBuffer.NO_OP_INPUT_ACCESSOR;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
315
373
|
/**
|
|
316
374
|
* This method is called before onJoin() - this is where you should authenticate the client
|
|
317
375
|
* @param client - The client that is authenticating.
|
|
@@ -412,6 +470,47 @@ var Room = class _Room {
|
|
|
412
470
|
}, delay);
|
|
413
471
|
}
|
|
414
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Run a fixed-rate simulation tagged with a monotonic server tick number.
|
|
475
|
+
* Combine with `room.input(sessionId).at(tick)` to retrieve each client's
|
|
476
|
+
* input *for a specific tick* — the building block for lockstep / rollback
|
|
477
|
+
* netcode.
|
|
478
|
+
*
|
|
479
|
+
* Replaces any previous {@link setSimulationInterval}. The current tick is
|
|
480
|
+
* exposed via {@link tick}.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```typescript
|
|
484
|
+
* class LockstepRoom extends Room<{ input: MoveInput }> {
|
|
485
|
+
* input = this.defineInput(MoveInput, { seqField: "tick", bufferMaxSize: 64 });
|
|
486
|
+
*
|
|
487
|
+
* onCreate() {
|
|
488
|
+
* this.setTickedSimulation((tick, dt) => {
|
|
489
|
+
* for (const c of this.clients) {
|
|
490
|
+
* const snapshot = this.input(c.sessionId).at(tick);
|
|
491
|
+
* if (snapshot) this.apply(c, snapshot);
|
|
492
|
+
* // else: predict, freeze, etc. — game-level decision
|
|
493
|
+
* }
|
|
494
|
+
* }, 1000 / 60);
|
|
495
|
+
* }
|
|
496
|
+
* }
|
|
497
|
+
* ```
|
|
498
|
+
*/
|
|
499
|
+
setTickedSimulation(onTickCallback, delay = DEFAULT_SIMULATION_INTERVAL, startTick = 0) {
|
|
500
|
+
this.#_tick = startTick;
|
|
501
|
+
this.setSimulationInterval((dt) => {
|
|
502
|
+
onTickCallback(this.#_tick, dt);
|
|
503
|
+
this.#_tick++;
|
|
504
|
+
}, delay);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Current server tick. Incremented by {@link setTickedSimulation} after each
|
|
508
|
+
* tick callback returns. Returns 0 when no ticked simulation is running.
|
|
509
|
+
*/
|
|
510
|
+
get tick() {
|
|
511
|
+
return this.#_tick;
|
|
512
|
+
}
|
|
513
|
+
#_tick;
|
|
415
514
|
/**
|
|
416
515
|
* @deprecated Use `.patchRate=` instead.
|
|
417
516
|
*/
|
|
@@ -428,19 +527,7 @@ var Room = class _Room {
|
|
|
428
527
|
this._serializer = serializer;
|
|
429
528
|
}
|
|
430
529
|
async setMetadata(meta, persist = true) {
|
|
431
|
-
|
|
432
|
-
this._listing.metadata = meta;
|
|
433
|
-
} else {
|
|
434
|
-
for (const field in meta) {
|
|
435
|
-
if (!meta.hasOwnProperty(field)) {
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
this._listing.metadata[field] = meta[field];
|
|
439
|
-
}
|
|
440
|
-
if ("markModified" in this._listing) {
|
|
441
|
-
this._listing.markModified("metadata");
|
|
442
|
-
}
|
|
443
|
-
}
|
|
530
|
+
this._listing.metadata = meta;
|
|
444
531
|
if (persist && this._internalState === RoomInternalState.CREATED) {
|
|
445
532
|
await matchMaker.driver.persist(this._listing);
|
|
446
533
|
this._events.emit("metadata-change");
|
|
@@ -481,7 +568,8 @@ var Room = class _Room {
|
|
|
481
568
|
*
|
|
482
569
|
* @example
|
|
483
570
|
* ```typescript
|
|
484
|
-
* //
|
|
571
|
+
* // Merging with existing metadata: spread `this.metadata` yourself.
|
|
572
|
+
* // `metadata` is always REPLACED (not merged) by setMatchmaking()/setMetadata().
|
|
485
573
|
* await this.setMatchmaking({
|
|
486
574
|
* metadata: { ...this.metadata, round: this.metadata.round + 1 }
|
|
487
575
|
* });
|
|
@@ -655,6 +743,100 @@ var Room = class _Room {
|
|
|
655
743
|
return this.onMessage(messageType, callback);
|
|
656
744
|
}
|
|
657
745
|
}
|
|
746
|
+
// ---------------------------------------------------------------------------
|
|
747
|
+
// Operator API — used by @colyseus/admin (and monitor in due course)
|
|
748
|
+
// through `remoteRoomCall(roomId, methodName)`. Marked `@internal` because
|
|
749
|
+
// they're framework-tooling primitives, not part of the game-code surface.
|
|
750
|
+
// ---------------------------------------------------------------------------
|
|
751
|
+
/**
|
|
752
|
+
* Snapshot the room's live state for an inspector / admin UI. Includes:
|
|
753
|
+
*
|
|
754
|
+
* roomId, name, maxClients, locked, elapsedTime (ms),
|
|
755
|
+
* metadata, clients (sessionId + per-client elapsed + userId when set),
|
|
756
|
+
* state (the schema/json the SDK would see),
|
|
757
|
+
* stateSize (bytes of the encoded full state, or 0 when no serializer).
|
|
758
|
+
*
|
|
759
|
+
* The payload is intentionally plain JSON — `remoteRoomCall` serializes
|
|
760
|
+
* the return value across process boundaries.
|
|
761
|
+
*
|
|
762
|
+
* @internal Operator-only. Game code should not call this.
|
|
763
|
+
*/
|
|
764
|
+
getInspectorView() {
|
|
765
|
+
const elapsed = this.clock.elapsedTime;
|
|
766
|
+
return {
|
|
767
|
+
roomId: this.roomId,
|
|
768
|
+
name: this.roomName,
|
|
769
|
+
clients: this.clients.length,
|
|
770
|
+
maxClients: this.maxClients,
|
|
771
|
+
locked: this.#_locked,
|
|
772
|
+
elapsedTime: elapsed,
|
|
773
|
+
metadata: this.metadata ?? null,
|
|
774
|
+
// Cast through `unknown`: `ExtractRoomClient<T>` vs `Client & ClientPrivate`
|
|
775
|
+
// don't structurally overlap (the user's `client` generic may carry a
|
|
776
|
+
// narrower userData/auth shape), but the runtime objects we walk here
|
|
777
|
+
// always have the private join-time field. The narrow per-property
|
|
778
|
+
// `(c as any)` reads below keep the cast scoped.
|
|
779
|
+
//
|
|
780
|
+
// userId / userEmail read straight off `client.auth` — the JWT
|
|
781
|
+
// payload @colyseus/auth's default onAuth decodes carries both
|
|
782
|
+
// when the user has them on file. Saves the admin a per-client
|
|
783
|
+
// database round-trip; falls back to null when the client signed
|
|
784
|
+
// in anonymously (or with a custom onAuth that returns a shape
|
|
785
|
+
// without those fields).
|
|
786
|
+
clientList: this.clients.map((c) => {
|
|
787
|
+
const auth = c.auth;
|
|
788
|
+
return {
|
|
789
|
+
sessionId: c.sessionId,
|
|
790
|
+
userId: c.userId ?? auth?.id ?? null,
|
|
791
|
+
userEmail: auth?.email ?? null,
|
|
792
|
+
elapsedTime: elapsed - (c._joinedAt ?? elapsed)
|
|
793
|
+
};
|
|
794
|
+
}),
|
|
795
|
+
state: this.state ?? null,
|
|
796
|
+
stateSize: this.#_inspectorStateSize()
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Force-disconnect a single client by sessionId. No-op when the client
|
|
801
|
+
* isn't connected (idempotent — the caller doesn't need to race-check).
|
|
802
|
+
*
|
|
803
|
+
* @internal Operator-only. Game code disconnects clients by calling
|
|
804
|
+
* `.leave()` on the Client object directly.
|
|
805
|
+
*/
|
|
806
|
+
kickClient(sessionId, closeCode = import_shared_types.CloseCode.CONSENTED, reason) {
|
|
807
|
+
for (const client of this.clients) {
|
|
808
|
+
if (client.sessionId === sessionId) {
|
|
809
|
+
this.#_forciblyCloseClient(client, closeCode, reason);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Best-effort byte size of the current full state. Falls back to `0`
|
|
816
|
+
* when the room has no serializer or the serializer can't produce a
|
|
817
|
+
* payload (raw rooms, very-early-onCreate, etc.). The serializer
|
|
818
|
+
* detection mirrors `@colyseus/monitor`'s — we read whichever buffer
|
|
819
|
+
* is available across schema v2 and v3.
|
|
820
|
+
*/
|
|
821
|
+
#_inspectorStateSize() {
|
|
822
|
+
const ser = this._serializer;
|
|
823
|
+
if (!ser) {
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
826
|
+
const hasState = ser.encoder || ser.state;
|
|
827
|
+
if (!hasState) {
|
|
828
|
+
return 0;
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
const full = ser.getFullState?.();
|
|
832
|
+
if (!full) {
|
|
833
|
+
return 0;
|
|
834
|
+
}
|
|
835
|
+
return full.byteLength ?? full.length ?? 0;
|
|
836
|
+
} catch {
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
658
840
|
/**
|
|
659
841
|
* Disconnect all connected clients, and then dispose the room.
|
|
660
842
|
*
|
|
@@ -692,6 +874,15 @@ var Room = class _Room {
|
|
|
692
874
|
async _onJoin(client, authContext, connectionOptions) {
|
|
693
875
|
const sessionId = client.sessionId;
|
|
694
876
|
client.reconnectionToken = (0, import_Utils.generateId)();
|
|
877
|
+
if (this.inputOptions !== void 0) {
|
|
878
|
+
client._input = new this.inputOptions.ctor();
|
|
879
|
+
client._inputDecoder = new import_input.InputDecoder(client._input);
|
|
880
|
+
const maxSize = this.inputOptions.bufferMaxSize;
|
|
881
|
+
if (maxSize > 0) {
|
|
882
|
+
client._inputBuffer = new import_InputBuffer.InputBufferImpl(maxSize, this.inputOptions.seqField);
|
|
883
|
+
}
|
|
884
|
+
client._inputAccessor = new import_InputBuffer.InputAccessorImpl(client);
|
|
885
|
+
}
|
|
695
886
|
if (this._reservedSeatTimeouts[sessionId]) {
|
|
696
887
|
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
697
888
|
delete this._reservedSeatTimeouts[sessionId];
|
|
@@ -794,6 +985,13 @@ var Room = class _Room {
|
|
|
794
985
|
client.ref["onleave"] = this._onLeave.bind(this, client);
|
|
795
986
|
client.ref.once("close", client.ref["onleave"]);
|
|
796
987
|
client.ref.on("message", this._onMessage.bind(this, client));
|
|
988
|
+
let extraSections;
|
|
989
|
+
if (!connectionOptions?.skipHandshake && this.inputOptions !== void 0) {
|
|
990
|
+
const inputBytes = _inputReflectionCache.get(this.inputOptions.ctor);
|
|
991
|
+
if (inputBytes !== void 0) {
|
|
992
|
+
extraSections = [{ tag: import_shared_types.HandshakeSection.INPUT_REFLECTION, bytes: inputBytes }];
|
|
993
|
+
}
|
|
994
|
+
}
|
|
797
995
|
client.raw(import_Protocol.getMessageBytes[import_shared_types.Protocol.JOIN_ROOM](
|
|
798
996
|
client.reconnectionToken,
|
|
799
997
|
this._serializer.id,
|
|
@@ -801,7 +999,8 @@ var Room = class _Room {
|
|
|
801
999
|
* if skipHandshake is true, we don't need to send the handshake
|
|
802
1000
|
* (in case client already has handshake data)
|
|
803
1001
|
*/
|
|
804
|
-
connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
|
|
1002
|
+
connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake(),
|
|
1003
|
+
extraSections
|
|
805
1004
|
));
|
|
806
1005
|
}
|
|
807
1006
|
}
|
|
@@ -868,7 +1067,7 @@ var Room = class _Room {
|
|
|
868
1067
|
}).finally(() => {
|
|
869
1068
|
cleanup();
|
|
870
1069
|
});
|
|
871
|
-
if (this._reconnectionAttempts[reconnectionToken]) {
|
|
1070
|
+
if (this._reconnectionAttempts[reconnectionToken] !== void 0) {
|
|
872
1071
|
(0, import_Debug.debugMatchMaking)("resolving reconnection attempt for client - sessionId: '%s', roomId: '%s'", sessionId, this.roomId);
|
|
873
1072
|
this._reconnectionAttempts[reconnectionToken].resolve(true);
|
|
874
1073
|
}
|
|
@@ -896,6 +1095,12 @@ var Room = class _Room {
|
|
|
896
1095
|
}
|
|
897
1096
|
}
|
|
898
1097
|
}
|
|
1098
|
+
// Encode and enqueue a ROOM_RESPONSE for a client request. If the client has
|
|
1099
|
+
// already left, `enqueueRaw` is a no-op — no response is needed.
|
|
1100
|
+
#replyToRequest(client, requestId, status, payload) {
|
|
1101
|
+
(0, import_Debug.debugMessage)("response #%d: status=%d -> %j (roomId: %s)", requestId, status, payload, this.roomId);
|
|
1102
|
+
client.enqueueRaw(import_Protocol.getMessageBytes[import_shared_types.Protocol.ROOM_RESPONSE](requestId, status, payload));
|
|
1103
|
+
}
|
|
899
1104
|
sendFullState(client) {
|
|
900
1105
|
client.raw(this._serializer.getFullState(client));
|
|
901
1106
|
}
|
|
@@ -993,6 +1198,26 @@ var Room = class _Room {
|
|
|
993
1198
|
this.clock.stop();
|
|
994
1199
|
return await (userReturnData || Promise.resolve());
|
|
995
1200
|
}
|
|
1201
|
+
/**
|
|
1202
|
+
* After the decoder has mutated `client._input`, push a clone into the
|
|
1203
|
+
* per-client buffer (when buffering is enabled). Honors
|
|
1204
|
+
* `inputOptions.seqField` for dedupe of redundant frames.
|
|
1205
|
+
*/
|
|
1206
|
+
#captureInput(client) {
|
|
1207
|
+
const buf = client._inputBuffer;
|
|
1208
|
+
if (!buf) {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const inst = client._input;
|
|
1212
|
+
const seqField = this.inputOptions?.seqField;
|
|
1213
|
+
if (seqField !== void 0) {
|
|
1214
|
+
const value = inst[seqField];
|
|
1215
|
+
if (typeof value === "number" && !buf.accept(value)) {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
buf.push(inst.clone());
|
|
1220
|
+
}
|
|
996
1221
|
_onMessage(client, buffer) {
|
|
997
1222
|
if (client.state === import_Transport.ClientState.LEAVING) {
|
|
998
1223
|
return;
|
|
@@ -1031,6 +1256,36 @@ var Room = class _Room {
|
|
|
1031
1256
|
} else {
|
|
1032
1257
|
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
1033
1258
|
}
|
|
1259
|
+
} else if (code === import_shared_types.Protocol.ROOM_REQUEST) {
|
|
1260
|
+
const requestId = import_schema.decode.number(buffer, it);
|
|
1261
|
+
const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
|
|
1262
|
+
let message;
|
|
1263
|
+
try {
|
|
1264
|
+
message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
|
|
1265
|
+
(0, import_Debug.debugMessage)("request #%d: '%s' -> %j (roomId: %s)", requestId, messageType, message, this.roomId);
|
|
1266
|
+
if (this.onMessageValidators[messageType] !== void 0) {
|
|
1267
|
+
message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[messageType], message);
|
|
1268
|
+
}
|
|
1269
|
+
} catch (e) {
|
|
1270
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
1271
|
+
this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const handler = this.onMessageEvents.events[messageType]?.[0];
|
|
1275
|
+
if (handler === void 0) {
|
|
1276
|
+
this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, {
|
|
1277
|
+
name: "no_handler",
|
|
1278
|
+
message: `room "${this.roomName}" has no onMessage("${messageType}") handler to answer this request.`
|
|
1279
|
+
});
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
Promise.resolve().then(() => handler(client, message)).then(
|
|
1283
|
+
(response) => this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.OK, response),
|
|
1284
|
+
(e) => {
|
|
1285
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
1286
|
+
this.#replyToRequest(client, requestId, import_shared_types.ResponseStatus.ERROR, toResponseError(e));
|
|
1287
|
+
}
|
|
1288
|
+
);
|
|
1034
1289
|
} else if (code === import_shared_types.Protocol.ROOM_DATA_BYTES) {
|
|
1035
1290
|
const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
|
|
1036
1291
|
let message = buffer.subarray(it.offset, buffer.byteLength);
|
|
@@ -1052,6 +1307,25 @@ var Room = class _Room {
|
|
|
1052
1307
|
} else {
|
|
1053
1308
|
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
1054
1309
|
}
|
|
1310
|
+
} else if (code === import_shared_types.Protocol.ROOM_INPUT_RELIABLE) {
|
|
1311
|
+
if (client._inputDecoder) {
|
|
1312
|
+
try {
|
|
1313
|
+
client._inputDecoder.decode(buffer.subarray(1));
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
this.#captureInput(client);
|
|
1319
|
+
}
|
|
1320
|
+
} else if (code === import_shared_types.Protocol.ROOM_INPUT_UNRELIABLE) {
|
|
1321
|
+
if (client._inputDecoder) {
|
|
1322
|
+
try {
|
|
1323
|
+
client._inputDecoder.decodeAll(buffer.subarray(1), () => this.#captureInput(client));
|
|
1324
|
+
} catch (e) {
|
|
1325
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1055
1329
|
} else if (code === import_shared_types.Protocol.JOIN_ROOM && client.state === import_Transport.ClientState.JOINING) {
|
|
1056
1330
|
client.state = import_Transport.ClientState.JOINED;
|
|
1057
1331
|
client._joinedAt = this.clock.elapsedTime;
|
|
@@ -1068,10 +1342,10 @@ var Room = class _Room {
|
|
|
1068
1342
|
this.#_forciblyCloseClient(client, import_shared_types.CloseCode.CONSENTED);
|
|
1069
1343
|
}
|
|
1070
1344
|
}
|
|
1071
|
-
#_forciblyCloseClient(client, closeCode) {
|
|
1345
|
+
#_forciblyCloseClient(client, closeCode, reason) {
|
|
1072
1346
|
client.ref.removeAllListeners("message");
|
|
1073
1347
|
client.ref.removeListener("close", client.ref["onleave"]);
|
|
1074
|
-
this._onLeave(client, closeCode).then(() => client.leave(closeCode));
|
|
1348
|
+
this._onLeave(client, closeCode).then(() => client.leave(closeCode, reason));
|
|
1075
1349
|
}
|
|
1076
1350
|
async _onLeave(client, code) {
|
|
1077
1351
|
const method = code === import_shared_types.CloseCode.CONSENTED || client.state === import_Transport.ClientState.RECONNECTING ? this.onLeave : this.onDrop || this.onLeave;
|
|
@@ -1190,6 +1464,9 @@ function room(options) {
|
|
|
1190
1464
|
DEFAULT_SEAT_RESERVATION_TIME,
|
|
1191
1465
|
Room,
|
|
1192
1466
|
RoomInternalState,
|
|
1467
|
+
RoomPlugin,
|
|
1468
|
+
attachToTestRoom,
|
|
1469
|
+
definePlugins,
|
|
1193
1470
|
room,
|
|
1194
1471
|
validate
|
|
1195
1472
|
});
|