@colyseus/core 0.16.23 → 0.17.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/Debug.js +16 -4
- package/build/Debug.js.map +2 -2
- package/build/Debug.mjs +11 -1
- package/build/Debug.mjs.map +2 -2
- package/build/IPC.js +5 -3
- package/build/IPC.js.map +2 -2
- package/build/IPC.mjs +1 -0
- package/build/IPC.mjs.map +2 -2
- package/build/Logger.js +1 -0
- package/build/Logger.js.map +1 -1
- package/build/MatchMaker.js +148 -84
- package/build/MatchMaker.js.map +3 -3
- package/build/MatchMaker.mjs +130 -70
- package/build/MatchMaker.mjs.map +3 -3
- package/build/Protocol.js +54 -41
- package/build/Protocol.js.map +3 -3
- package/build/Protocol.mjs +52 -41
- package/build/Protocol.mjs.map +3 -3
- package/build/Room.js +430 -195
- package/build/Room.js.map +3 -3
- package/build/Room.mjs +417 -175
- package/build/Room.mjs.map +3 -3
- package/build/Server.js +48 -108
- package/build/Server.js.map +3 -3
- package/build/Server.mjs +39 -102
- package/build/Server.mjs.map +3 -3
- package/build/Stats.js +2 -1
- package/build/Stats.js.map +2 -2
- package/build/Stats.mjs.map +1 -1
- package/build/Transport.js +29 -11
- package/build/Transport.js.map +3 -3
- package/build/Transport.mjs +15 -9
- package/build/Transport.mjs.map +3 -3
- package/build/errors/RoomExceptions.js +9 -5
- package/build/errors/RoomExceptions.js.map +2 -2
- package/build/errors/RoomExceptions.mjs +8 -5
- package/build/errors/RoomExceptions.mjs.map +2 -2
- package/build/errors/SeatReservationError.js +1 -0
- package/build/errors/SeatReservationError.js.map +1 -1
- package/build/errors/ServerError.js +2 -1
- package/build/errors/ServerError.js.map +2 -2
- package/build/errors/ServerError.mjs.map +1 -1
- package/build/index.js +60 -26
- package/build/index.js.map +2 -2
- package/build/index.mjs +33 -7
- package/build/index.mjs.map +2 -2
- package/build/matchmaker/Lobby.js +13 -3
- package/build/matchmaker/Lobby.js.map +2 -2
- package/build/matchmaker/Lobby.mjs +11 -2
- package/build/matchmaker/Lobby.mjs.map +2 -2
- package/build/matchmaker/LocalDriver/LocalDriver.js +94 -0
- package/build/matchmaker/LocalDriver/LocalDriver.js.map +7 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs +71 -0
- package/build/matchmaker/LocalDriver/LocalDriver.mjs.map +7 -0
- package/build/matchmaker/LocalDriver/Query.js +111 -0
- package/build/matchmaker/LocalDriver/Query.js.map +7 -0
- package/build/matchmaker/LocalDriver/Query.mjs +88 -0
- package/build/matchmaker/LocalDriver/Query.mjs.map +7 -0
- package/build/matchmaker/RegisteredHandler.js +57 -7
- package/build/matchmaker/RegisteredHandler.js.map +2 -2
- package/build/matchmaker/RegisteredHandler.mjs +54 -5
- package/build/matchmaker/RegisteredHandler.mjs.map +2 -2
- package/build/matchmaker/controller.js +8 -8
- package/build/matchmaker/controller.js.map +2 -2
- package/build/matchmaker/controller.mjs +4 -5
- package/build/matchmaker/controller.mjs.map +2 -2
- package/build/matchmaker/driver/api.js +21 -2
- package/build/matchmaker/driver/api.js.map +2 -2
- package/build/matchmaker/driver/api.mjs +18 -1
- package/build/matchmaker/driver/api.mjs.map +2 -2
- package/build/matchmaker/driver/local/LocalDriver.js +36 -7
- package/build/matchmaker/driver/local/LocalDriver.js.map +2 -2
- package/build/matchmaker/driver/local/LocalDriver.mjs +33 -5
- package/build/matchmaker/driver/local/LocalDriver.mjs.map +2 -2
- package/build/matchmaker/driver/local/Query.js +51 -18
- package/build/matchmaker/driver/local/Query.js.map +2 -2
- package/build/matchmaker/driver/local/Query.mjs +50 -18
- package/build/matchmaker/driver/local/Query.mjs.map +2 -2
- package/build/matchmaker/driver.js +44 -0
- package/build/matchmaker/driver.js.map +7 -0
- package/build/matchmaker/driver.mjs +20 -0
- package/build/matchmaker/driver.mjs.map +7 -0
- package/build/matchmaker/routes.js +79 -0
- package/build/matchmaker/routes.js.map +7 -0
- package/build/matchmaker/routes.mjs +45 -0
- package/build/matchmaker/routes.mjs.map +7 -0
- package/build/presence/LocalPresence.js +13 -27
- package/build/presence/LocalPresence.js.map +3 -3
- package/build/presence/LocalPresence.mjs +11 -16
- package/build/presence/LocalPresence.mjs.map +2 -2
- package/build/presence/Presence.js +37 -0
- package/build/presence/Presence.js.map +2 -2
- package/build/presence/Presence.mjs +29 -0
- package/build/presence/Presence.mjs.map +3 -3
- package/build/rooms/LobbyRoom.js +5 -5
- package/build/rooms/LobbyRoom.js.map +2 -2
- package/build/rooms/LobbyRoom.mjs +1 -2
- package/build/rooms/LobbyRoom.mjs.map +2 -2
- package/build/rooms/RankedQueueRoom.js +224 -0
- package/build/rooms/RankedQueueRoom.js.map +7 -0
- package/build/rooms/RankedQueueRoom.mjs +201 -0
- package/build/rooms/RankedQueueRoom.mjs.map +7 -0
- package/build/rooms/RelayRoom.js +6 -6
- package/build/rooms/RelayRoom.js.map +2 -2
- package/build/rooms/RelayRoom.mjs +4 -5
- package/build/rooms/RelayRoom.mjs.map +2 -2
- package/build/rooms/createRoom.js +51 -0
- package/build/rooms/createRoom.js.map +7 -0
- package/build/rooms/createRoom.mjs +28 -0
- package/build/rooms/createRoom.mjs.map +7 -0
- package/build/router/default_routes.js +79 -0
- package/build/router/default_routes.js.map +7 -0
- package/build/router/default_routes.mjs +45 -0
- package/build/router/default_routes.mjs.map +7 -0
- package/build/router/index.js +55 -0
- package/build/router/index.js.map +7 -0
- package/build/router/index.mjs +30 -0
- package/build/router/index.mjs.map +7 -0
- package/build/serializer/NoneSerializer.js +1 -0
- package/build/serializer/NoneSerializer.js.map +2 -2
- package/build/serializer/NoneSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializer.js +6 -7
- package/build/serializer/SchemaSerializer.js.map +2 -2
- package/build/serializer/SchemaSerializer.mjs +3 -5
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializerDebug.js +29 -0
- package/build/serializer/SchemaSerializerDebug.js.map +3 -3
- package/build/serializer/SchemaSerializerDebug.mjs +7 -0
- package/build/serializer/SchemaSerializerDebug.mjs.map +3 -3
- package/build/serializer/Serializer.js +1 -0
- package/build/serializer/Serializer.js.map +2 -2
- package/build/{Debug.d.ts → src/Debug.d.ts} +2 -1
- package/build/{IPC.d.ts → src/IPC.d.ts} +2 -2
- package/build/{MatchMaker.d.ts → src/MatchMaker.d.ts} +58 -33
- package/build/src/Protocol.d.ts +53 -0
- package/build/src/Room.d.ts +497 -0
- package/build/{Server.d.ts → src/Server.d.ts} +23 -25
- package/build/{Transport.d.ts → src/Transport.d.ts} +65 -22
- package/build/{errors → src/errors}/RoomExceptions.d.ts +8 -7
- package/build/src/index.d.ts +27 -0
- package/build/src/matchmaker/Lobby.d.ts +4 -0
- package/build/src/matchmaker/LocalDriver/LocalDriver.d.ts +17 -0
- package/build/src/matchmaker/LocalDriver/Query.d.ts +12 -0
- package/build/src/matchmaker/RegisteredHandler.d.ts +81 -0
- package/build/{matchmaker → src/matchmaker}/controller.d.ts +5 -6
- package/build/src/matchmaker/driver/api.d.ts +145 -0
- package/build/src/matchmaker/driver/local/LocalDriver.d.ts +17 -0
- package/build/src/matchmaker/driver/local/Query.d.ts +12 -0
- package/build/src/matchmaker/driver.d.ts +145 -0
- package/build/src/matchmaker/routes.d.ts +92 -0
- package/build/{presence → src/presence}/LocalPresence.d.ts +1 -1
- package/build/{presence → src/presence}/Presence.d.ts +2 -0
- package/build/{rooms → src/rooms}/LobbyRoom.d.ts +4 -4
- package/build/src/rooms/RankedQueueRoom.d.ts +125 -0
- package/build/{rooms → src/rooms}/RelayRoom.d.ts +5 -4
- package/build/src/rooms/createRoom.d.ts +65 -0
- package/build/src/router/default_routes.d.ts +103 -0
- package/build/src/router/index.d.ts +68 -0
- package/build/{serializer → src/serializer}/NoneSerializer.d.ts +2 -2
- package/build/{serializer → src/serializer}/SchemaSerializer.d.ts +9 -9
- package/build/{serializer → src/serializer}/Serializer.d.ts +3 -3
- package/build/{utils → src/utils}/DevMode.d.ts +5 -4
- package/build/{utils → src/utils}/StandardSchema.d.ts +1 -1
- package/build/{utils → src/utils}/Utils.d.ts +15 -4
- package/build/utils/DevMode.js +54 -26
- package/build/utils/DevMode.js.map +3 -3
- package/build/utils/DevMode.mjs +44 -19
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/StandardSchema.js.map +1 -1
- package/build/utils/StandardSchema.mjs.map +1 -1
- package/build/utils/Utils.js +8 -15
- package/build/utils/Utils.js.map +3 -3
- package/build/utils/Utils.mjs +6 -4
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +20 -14
- package/build/Protocol.d.ts +0 -37
- package/build/Room.d.ts +0 -265
- package/build/discovery/index.d.ts +0 -8
- package/build/discovery/index.js +0 -50
- package/build/discovery/index.js.map +0 -7
- package/build/discovery/index.mjs +0 -26
- package/build/discovery/index.mjs.map +0 -7
- package/build/index.d.ts +0 -24
- package/build/matchmaker/Lobby.d.ts +0 -4
- package/build/matchmaker/RegisteredHandler.d.ts +0 -19
- package/build/matchmaker/driver/Query.d.ts +0 -8
- package/build/matchmaker/driver/Query.js +0 -68
- package/build/matchmaker/driver/Query.js.map +0 -7
- package/build/matchmaker/driver/Query.mjs +0 -45
- package/build/matchmaker/driver/Query.mjs.map +0 -7
- package/build/matchmaker/driver/RoomData.d.ts +0 -19
- package/build/matchmaker/driver/RoomData.js +0 -79
- package/build/matchmaker/driver/RoomData.js.map +0 -7
- package/build/matchmaker/driver/RoomData.mjs +0 -56
- package/build/matchmaker/driver/RoomData.mjs.map +0 -7
- package/build/matchmaker/driver/api.d.ts +0 -104
- package/build/matchmaker/driver/index.d.ts +0 -13
- package/build/matchmaker/driver/index.js +0 -64
- package/build/matchmaker/driver/index.js.map +0 -7
- package/build/matchmaker/driver/index.mjs +0 -42
- package/build/matchmaker/driver/index.mjs.map +0 -7
- package/build/matchmaker/driver/interfaces.d.ts +0 -73
- package/build/matchmaker/driver/interfaces.js +0 -15
- package/build/matchmaker/driver/interfaces.js.map +0 -7
- package/build/matchmaker/driver/interfaces.mjs +0 -0
- package/build/matchmaker/driver/interfaces.mjs.map +0 -7
- package/build/matchmaker/driver/local/LocalDriver.d.ts +0 -13
- package/build/matchmaker/driver/local/Query.d.ts +0 -9
- package/build/matchmaker/driver/local/RoomData.d.ts +0 -19
- package/build/matchmaker/driver/local/RoomData.js +0 -79
- package/build/matchmaker/driver/local/RoomData.js.map +0 -7
- package/build/matchmaker/driver/local/RoomData.mjs +0 -57
- package/build/matchmaker/driver/local/RoomData.mjs.map +0 -7
- package/build/utils/types.d.ts +0 -1
- package/build/utils/types.js +0 -15
- package/build/utils/types.js.map +0 -7
- package/build/utils/types.mjs +0 -0
- package/build/utils/types.mjs.map +0 -7
- /package/build/{Logger.d.ts → src/Logger.d.ts} +0 -0
- /package/build/{Stats.d.ts → src/Stats.d.ts} +0 -0
- /package/build/{errors → src/errors}/SeatReservationError.d.ts +0 -0
- /package/build/{errors → src/errors}/ServerError.d.ts +0 -0
- /package/build/{serializer → src/serializer}/SchemaSerializerDebug.d.ts +0 -0
- /package/build/{utils → src/utils}/nanoevents.d.ts +0 -0
package/build/Room.mjs
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
// packages/core/src/Room.ts
|
|
2
2
|
import { unpack } from "@colyseus/msgpackr";
|
|
3
3
|
import { decode, $changes } from "@colyseus/schema";
|
|
4
|
-
import Clock from "@colyseus/timer";
|
|
4
|
+
import { ClockTimer as Clock } from "@colyseus/timer";
|
|
5
5
|
import { EventEmitter } from "events";
|
|
6
6
|
import { logger } from "./Logger.mjs";
|
|
7
7
|
import { NoneSerializer } from "./serializer/NoneSerializer.mjs";
|
|
8
8
|
import { SchemaSerializer } from "./serializer/SchemaSerializer.mjs";
|
|
9
|
-
import { ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
|
|
9
|
+
import { CloseCode, ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
|
|
10
10
|
import { Deferred, generateId, wrapTryCatch } from "./utils/Utils.mjs";
|
|
11
|
+
import { createNanoEvents } from "./utils/nanoevents.mjs";
|
|
11
12
|
import { isDevMode } from "./utils/DevMode.mjs";
|
|
12
13
|
import { debugAndPrintError, debugMatchMaking, debugMessage } from "./Debug.mjs";
|
|
13
14
|
import { ServerError } from "./errors/ServerError.mjs";
|
|
14
|
-
import {
|
|
15
|
+
import { ClientState, ClientArray } from "./Transport.mjs";
|
|
15
16
|
import { OnAuthException, OnCreateException, OnDisposeException, OnJoinException, OnLeaveException, OnMessageException, SimulationIntervalException, TimedEventException } from "./errors/RoomExceptions.mjs";
|
|
17
|
+
import { standardValidate } from "./utils/StandardSchema.mjs";
|
|
18
|
+
import { matchMaker } from "@colyseus/core";
|
|
16
19
|
var DEFAULT_PATCH_RATE = 1e3 / 20;
|
|
17
20
|
var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
|
|
18
21
|
var noneSerializer = new NoneSerializer();
|
|
19
22
|
var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
function validate(format, handler) {
|
|
24
|
+
return { format, handler };
|
|
25
|
+
}
|
|
26
|
+
var RoomInternalState = {
|
|
27
|
+
CREATING: 0,
|
|
28
|
+
CREATED: 1,
|
|
29
|
+
DISPOSING: 2
|
|
30
|
+
};
|
|
26
31
|
var Room = class _Room {
|
|
27
32
|
constructor() {
|
|
28
33
|
/**
|
|
@@ -51,40 +56,52 @@ var Room = class _Room {
|
|
|
51
56
|
* @default 50ms (20fps)
|
|
52
57
|
*/
|
|
53
58
|
this.patchRate = DEFAULT_PATCH_RATE;
|
|
59
|
+
/**
|
|
60
|
+
* Maximum number of messages a client can send to the server per second.
|
|
61
|
+
* If a client sends more messages than this, it will be disconnected.
|
|
62
|
+
*
|
|
63
|
+
* @default 60
|
|
64
|
+
*/
|
|
65
|
+
this.maxMessagesPerSecond = 60;
|
|
54
66
|
/**
|
|
55
67
|
* The array of connected clients.
|
|
56
68
|
*
|
|
57
|
-
* @see
|
|
69
|
+
* @see [Client instance](https://docs.colyseus.io/room#client)
|
|
58
70
|
*/
|
|
59
71
|
this.clients = new ClientArray();
|
|
60
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Set the number of seconds a room can wait for a client to effectively join the room.
|
|
74
|
+
* You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
|
|
75
|
+
* The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
|
|
76
|
+
* environment variable if you'd like to change the seat reservation time globally.
|
|
77
|
+
*
|
|
78
|
+
* @default 15 seconds
|
|
79
|
+
*/
|
|
80
|
+
this.seatReservationTimeout = DEFAULT_SEAT_RESERVATION_TIME;
|
|
61
81
|
this._events = new EventEmitter();
|
|
62
|
-
|
|
63
|
-
this.
|
|
64
|
-
this.reservedSeats = {};
|
|
65
|
-
this.reservedSeatTimeouts = {};
|
|
82
|
+
this._reservedSeats = {};
|
|
83
|
+
this._reservedSeatTimeouts = {};
|
|
66
84
|
this._reconnections = {};
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
85
|
+
this.onMessageEvents = createNanoEvents();
|
|
86
|
+
this.onMessageValidators = {};
|
|
87
|
+
this.onMessageFallbacks = {
|
|
88
|
+
"__no_message_handler": (client, messageType, _) => {
|
|
89
|
+
const errorMessage = `room onMessage for "${messageType}" not registered.`;
|
|
90
|
+
debugMessage(`${errorMessage} (roomId: ${this.roomId})`);
|
|
91
|
+
if (isDevMode) {
|
|
92
|
+
client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
93
|
+
} else {
|
|
94
|
+
client.leave(CloseCode.WITH_ERROR, errorMessage);
|
|
78
95
|
}
|
|
79
96
|
}
|
|
80
97
|
};
|
|
81
98
|
this._serializer = noneSerializer;
|
|
82
99
|
this._afterNextPatchQueue = [];
|
|
83
|
-
this._internalState =
|
|
100
|
+
this._internalState = RoomInternalState.CREATING;
|
|
84
101
|
this._lockedExplicitly = false;
|
|
85
102
|
this.#_locked = false;
|
|
86
103
|
this._events.once("dispose", () => {
|
|
87
|
-
this
|
|
104
|
+
this.#_dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
|
|
88
105
|
});
|
|
89
106
|
if (this.onUncaughtException !== void 0) {
|
|
90
107
|
this.#registerUncaughtExceptionHandlers();
|
|
@@ -100,8 +117,31 @@ var Room = class _Room {
|
|
|
100
117
|
get locked() {
|
|
101
118
|
return this.#_locked;
|
|
102
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Get the room's matchmaking metadata.
|
|
122
|
+
*/
|
|
103
123
|
get metadata() {
|
|
104
|
-
return this.
|
|
124
|
+
return this._listing.metadata;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Set the room's matchmaking metadata.
|
|
128
|
+
*
|
|
129
|
+
* **Note**: This setter does NOT automatically persist. Use `setMatchmaking()` for automatic persistence.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* class MyRoom extends Room<{ metadata: { difficulty: string; rating: number } }> {
|
|
134
|
+
* async onCreate() {
|
|
135
|
+
* this.metadata = { difficulty: "hard", rating: 1500 };
|
|
136
|
+
* }
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
set metadata(meta) {
|
|
141
|
+
if (this._internalState !== RoomInternalState.CREATING) {
|
|
142
|
+
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'metadata' can only be manually set during onCreate(). Use setMatchmaking() instead.");
|
|
143
|
+
}
|
|
144
|
+
this._listing.metadata = meta;
|
|
105
145
|
}
|
|
106
146
|
#_roomId;
|
|
107
147
|
#_roomName;
|
|
@@ -142,29 +182,14 @@ var Room = class _Room {
|
|
|
142
182
|
enumerable: true,
|
|
143
183
|
get: () => this.#_maxClients,
|
|
144
184
|
set: (value) => {
|
|
145
|
-
this
|
|
146
|
-
if (this._internalState === 1 /* CREATED */) {
|
|
147
|
-
const hasReachedMaxClients = this.hasReachedMaxClients();
|
|
148
|
-
if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
|
|
149
|
-
this.#_maxClientsReached = false;
|
|
150
|
-
this.#_locked = false;
|
|
151
|
-
this.listing.locked = false;
|
|
152
|
-
}
|
|
153
|
-
if (hasReachedMaxClients) {
|
|
154
|
-
this.#_maxClientsReached = true;
|
|
155
|
-
this.#_locked = true;
|
|
156
|
-
this.listing.locked = true;
|
|
157
|
-
}
|
|
158
|
-
this.listing.maxClients = value;
|
|
159
|
-
this.listing.save();
|
|
160
|
-
}
|
|
185
|
+
this.setMatchmaking({ maxClients: value });
|
|
161
186
|
}
|
|
162
187
|
},
|
|
163
188
|
autoDispose: {
|
|
164
189
|
enumerable: true,
|
|
165
190
|
get: () => this.#_autoDispose,
|
|
166
191
|
set: (value) => {
|
|
167
|
-
if (value !== this.#_autoDispose && this._internalState !==
|
|
192
|
+
if (value !== this.#_autoDispose && this._internalState !== RoomInternalState.DISPOSING) {
|
|
168
193
|
this.#_autoDispose = value;
|
|
169
194
|
this.resetAutoDisposeTimeout();
|
|
170
195
|
}
|
|
@@ -181,6 +206,8 @@ var Room = class _Room {
|
|
|
181
206
|
}
|
|
182
207
|
if (milliseconds !== null && milliseconds !== 0) {
|
|
183
208
|
this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
|
|
209
|
+
} else if (!this._simulationInterval) {
|
|
210
|
+
this.#_patchInterval = setInterval(() => this.clock.tick(), DEFAULT_SIMULATION_INTERVAL);
|
|
184
211
|
}
|
|
185
212
|
}
|
|
186
213
|
}
|
|
@@ -189,7 +216,16 @@ var Room = class _Room {
|
|
|
189
216
|
if (this.#_state) {
|
|
190
217
|
this.state = this.#_state;
|
|
191
218
|
}
|
|
192
|
-
|
|
219
|
+
if (this.messages !== void 0) {
|
|
220
|
+
Object.entries(this.messages).forEach(([messageType, callback]) => {
|
|
221
|
+
if (typeof callback === "function") {
|
|
222
|
+
this.onMessage(messageType, callback.bind(this));
|
|
223
|
+
} else {
|
|
224
|
+
this.onMessage(messageType, callback.format, callback.handler.bind(this));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
this.resetAutoDisposeTimeout(this.seatReservationTimeout);
|
|
193
229
|
this.clock.start();
|
|
194
230
|
}
|
|
195
231
|
/**
|
|
@@ -227,11 +263,27 @@ var Room = class _Room {
|
|
|
227
263
|
* @returns roomId string
|
|
228
264
|
*/
|
|
229
265
|
set roomId(roomId) {
|
|
230
|
-
if (this._internalState !==
|
|
266
|
+
if (this._internalState !== RoomInternalState.CREATING && !isDevMode) {
|
|
231
267
|
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
|
|
232
268
|
}
|
|
233
269
|
this.#_roomId = roomId;
|
|
234
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* This method is called before onJoin() - this is where you should authenticate the client
|
|
273
|
+
* @param client - The client that is authenticating.
|
|
274
|
+
* @param options - The options passed to the client when it is authenticating.
|
|
275
|
+
* @param context - The authentication context, including the token and the client's IP address.
|
|
276
|
+
* @returns The authentication result.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```typescript
|
|
280
|
+
* return {
|
|
281
|
+
* userId: 123,
|
|
282
|
+
* username: "John Doe",
|
|
283
|
+
* email: "john.doe@example.com",
|
|
284
|
+
* };
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
235
287
|
onAuth(client, options, context) {
|
|
236
288
|
return true;
|
|
237
289
|
}
|
|
@@ -246,7 +298,7 @@ var Room = class _Room {
|
|
|
246
298
|
*/
|
|
247
299
|
onBeforeShutdown() {
|
|
248
300
|
this.disconnect(
|
|
249
|
-
isDevMode ?
|
|
301
|
+
isDevMode ? CloseCode.DEVMODE_RESTART : CloseCode.SERVER_SHUTDOWN
|
|
250
302
|
);
|
|
251
303
|
}
|
|
252
304
|
/**
|
|
@@ -255,39 +307,31 @@ var Room = class _Room {
|
|
|
255
307
|
* @returns boolean
|
|
256
308
|
*/
|
|
257
309
|
hasReachedMaxClients() {
|
|
258
|
-
return this.clients.length + Object.keys(this.
|
|
310
|
+
return this.clients.length + Object.keys(this._reservedSeats).length >= this.#_maxClients || this._internalState === RoomInternalState.DISPOSING;
|
|
259
311
|
}
|
|
260
312
|
/**
|
|
261
|
-
*
|
|
262
|
-
* You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
|
|
263
|
-
* The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
|
|
264
|
-
* environment variable if you'd like to change the seat reservation time globally.
|
|
265
|
-
*
|
|
266
|
-
* @default 15 seconds
|
|
267
|
-
*
|
|
268
|
-
* @param seconds - number of seconds.
|
|
269
|
-
* @returns The modified Room object.
|
|
313
|
+
* @deprecated Use `seatReservationTimeout=` instead.
|
|
270
314
|
*/
|
|
271
315
|
setSeatReservationTime(seconds) {
|
|
272
|
-
|
|
316
|
+
console.warn(`DEPRECATED: .setSeatReservationTime(${seconds}) is deprecated. Assign a .seatReservationTimeout property value instead.`);
|
|
317
|
+
this.seatReservationTimeout = seconds;
|
|
273
318
|
return this;
|
|
274
319
|
}
|
|
275
320
|
hasReservedSeat(sessionId, reconnectionToken) {
|
|
276
|
-
const reservedSeat = this.
|
|
321
|
+
const reservedSeat = this._reservedSeats[sessionId];
|
|
277
322
|
if (reservedSeat === void 0) {
|
|
278
323
|
return false;
|
|
279
324
|
}
|
|
280
325
|
if (reservedSeat[3]) {
|
|
281
|
-
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId
|
|
326
|
+
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId;
|
|
282
327
|
} else {
|
|
283
328
|
return reservedSeat[2] === false;
|
|
284
329
|
}
|
|
285
330
|
}
|
|
286
331
|
checkReconnectionToken(reconnectionToken) {
|
|
287
332
|
const sessionId = this._reconnections[reconnectionToken]?.[0];
|
|
288
|
-
const reservedSeat = this.
|
|
333
|
+
const reservedSeat = this._reservedSeats[sessionId];
|
|
289
334
|
if (reservedSeat && reservedSeat[3]) {
|
|
290
|
-
this._reconnectingSessionId.set(sessionId, reconnectionToken);
|
|
291
335
|
return sessionId;
|
|
292
336
|
} else {
|
|
293
337
|
return void 0;
|
|
@@ -332,34 +376,125 @@ var Room = class _Room {
|
|
|
332
376
|
setSerializer(serializer) {
|
|
333
377
|
this._serializer = serializer;
|
|
334
378
|
}
|
|
335
|
-
async setMetadata(meta) {
|
|
336
|
-
if (!this.
|
|
337
|
-
this.
|
|
379
|
+
async setMetadata(meta, persist = true) {
|
|
380
|
+
if (!this._listing.metadata) {
|
|
381
|
+
this._listing.metadata = meta;
|
|
338
382
|
} else {
|
|
339
383
|
for (const field in meta) {
|
|
340
384
|
if (!meta.hasOwnProperty(field)) {
|
|
341
385
|
continue;
|
|
342
386
|
}
|
|
343
|
-
this.
|
|
387
|
+
this._listing.metadata[field] = meta[field];
|
|
344
388
|
}
|
|
345
|
-
if ("markModified" in this.
|
|
346
|
-
this.
|
|
389
|
+
if ("markModified" in this._listing) {
|
|
390
|
+
this._listing.markModified("metadata");
|
|
347
391
|
}
|
|
348
392
|
}
|
|
349
|
-
if (this._internalState ===
|
|
350
|
-
await
|
|
393
|
+
if (persist && this._internalState === RoomInternalState.CREATED) {
|
|
394
|
+
await matchMaker.driver.persist(this._listing);
|
|
395
|
+
this._events.emit("metadata-change");
|
|
351
396
|
}
|
|
352
397
|
}
|
|
353
|
-
async setPrivate(bool = true) {
|
|
354
|
-
if (this.
|
|
355
|
-
this.
|
|
356
|
-
if (this._internalState ===
|
|
357
|
-
await
|
|
398
|
+
async setPrivate(bool = true, persist = true) {
|
|
399
|
+
if (this._listing.private === bool) return;
|
|
400
|
+
this._listing.private = bool;
|
|
401
|
+
if (persist && this._internalState === RoomInternalState.CREATED) {
|
|
402
|
+
await matchMaker.driver.persist(this._listing);
|
|
358
403
|
}
|
|
359
404
|
this._events.emit("visibility-change", bool);
|
|
360
405
|
}
|
|
361
406
|
/**
|
|
362
|
-
*
|
|
407
|
+
* Update multiple matchmaking/listing properties at once with a single persist operation.
|
|
408
|
+
* This is the recommended way to update room listing properties.
|
|
409
|
+
*
|
|
410
|
+
* @param updates - Object containing the properties to update
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```typescript
|
|
414
|
+
* // Update multiple properties at once
|
|
415
|
+
* await this.setMatchmaking({
|
|
416
|
+
* metadata: { difficulty: "hard", rating: 1500 },
|
|
417
|
+
* private: true,
|
|
418
|
+
* locked: true,
|
|
419
|
+
* maxClients: 10
|
|
420
|
+
* });
|
|
421
|
+
* ```
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```typescript
|
|
425
|
+
* // Update only metadata
|
|
426
|
+
* await this.setMatchmaking({
|
|
427
|
+
* metadata: { status: "in_progress" }
|
|
428
|
+
* });
|
|
429
|
+
* ```
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```typescript
|
|
433
|
+
* // Partial metadata update (merges with existing)
|
|
434
|
+
* await this.setMatchmaking({
|
|
435
|
+
* metadata: { ...this.metadata, round: this.metadata.round + 1 }
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
async setMatchmaking(updates) {
|
|
440
|
+
for (const key in updates) {
|
|
441
|
+
if (!updates.hasOwnProperty(key)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
switch (key) {
|
|
445
|
+
case "metadata": {
|
|
446
|
+
this.setMetadata(updates.metadata, false);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
case "private": {
|
|
450
|
+
this.setPrivate(updates.private, false);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case "locked": {
|
|
454
|
+
if (updates[key]) {
|
|
455
|
+
this.lock.call(this, true);
|
|
456
|
+
this._lockedExplicitly = true;
|
|
457
|
+
} else {
|
|
458
|
+
this.unlock.call(this, true);
|
|
459
|
+
this._lockedExplicitly = false;
|
|
460
|
+
}
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "maxClients": {
|
|
464
|
+
this.#_maxClients = updates.maxClients;
|
|
465
|
+
this._listing.maxClients = updates.maxClients;
|
|
466
|
+
const hasReachedMaxClients = this.hasReachedMaxClients();
|
|
467
|
+
if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
|
|
468
|
+
this.#_maxClientsReached = false;
|
|
469
|
+
this.#_locked = false;
|
|
470
|
+
this._listing.locked = false;
|
|
471
|
+
updates.locked = false;
|
|
472
|
+
}
|
|
473
|
+
if (hasReachedMaxClients) {
|
|
474
|
+
this.#_maxClientsReached = true;
|
|
475
|
+
this.#_locked = true;
|
|
476
|
+
this._listing.locked = true;
|
|
477
|
+
updates.locked = true;
|
|
478
|
+
}
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
case "clients": {
|
|
482
|
+
console.warn("setMatchmaking() does not allow updating 'clients' property.");
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
default: {
|
|
486
|
+
this._listing[key] = updates[key];
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (this._internalState === RoomInternalState.CREATED) {
|
|
492
|
+
await matchMaker.driver.update(this._listing, { $set: updates });
|
|
493
|
+
this._events.emit("metadata-change");
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Lock the room. This prevents new clients from joining this room.
|
|
363
498
|
*/
|
|
364
499
|
async lock() {
|
|
365
500
|
this._lockedExplicitly = arguments[0] === void 0;
|
|
@@ -367,13 +502,15 @@ var Room = class _Room {
|
|
|
367
502
|
return;
|
|
368
503
|
}
|
|
369
504
|
this.#_locked = true;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
505
|
+
if (this._lockedExplicitly) {
|
|
506
|
+
await matchMaker.driver.update(this._listing, {
|
|
507
|
+
$set: { locked: this.#_locked }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
373
510
|
this._events.emit("lock");
|
|
374
511
|
}
|
|
375
512
|
/**
|
|
376
|
-
*
|
|
513
|
+
* Unlock the room. This allows new clients to join this room, if maxClients is not reached.
|
|
377
514
|
*/
|
|
378
515
|
async unlock() {
|
|
379
516
|
if (arguments[0] === void 0) {
|
|
@@ -383,19 +520,33 @@ var Room = class _Room {
|
|
|
383
520
|
return;
|
|
384
521
|
}
|
|
385
522
|
this.#_locked = false;
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
523
|
+
if (arguments[0] === void 0) {
|
|
524
|
+
await matchMaker.driver.update(this._listing, {
|
|
525
|
+
$set: { locked: this.#_locked }
|
|
526
|
+
});
|
|
527
|
+
}
|
|
389
528
|
this._events.emit("unlock");
|
|
390
529
|
}
|
|
391
530
|
send(client, messageOrType, messageOrOptions, options) {
|
|
392
531
|
logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
|
|
393
532
|
client.send(messageOrType, messageOrOptions, options);
|
|
394
533
|
}
|
|
395
|
-
|
|
534
|
+
/**
|
|
535
|
+
* Broadcast a message to all connected clients.
|
|
536
|
+
* @param type - The type of the message.
|
|
537
|
+
* @param message - The message to broadcast.
|
|
538
|
+
* @param options - The options for the broadcast.
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
* ```typescript
|
|
542
|
+
* this.broadcast('message', { message: 'Hello, world!' });
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
broadcast(type, ...args) {
|
|
546
|
+
const [message, options] = args;
|
|
396
547
|
if (options && options.afterNextPatch) {
|
|
397
548
|
delete options.afterNextPatch;
|
|
398
|
-
this._afterNextPatchQueue.push(["broadcast",
|
|
549
|
+
this._afterNextPatchQueue.push(["broadcast", [type, ...args]]);
|
|
399
550
|
return;
|
|
400
551
|
}
|
|
401
552
|
this.broadcastMessageType(type, message, options);
|
|
@@ -428,9 +579,30 @@ var Room = class _Room {
|
|
|
428
579
|
this._dequeueAfterPatchMessages();
|
|
429
580
|
return hasChanges;
|
|
430
581
|
}
|
|
431
|
-
onMessage(
|
|
432
|
-
|
|
433
|
-
|
|
582
|
+
onMessage(_messageType, _validationSchema, _callback) {
|
|
583
|
+
const messageType = _messageType.toString();
|
|
584
|
+
const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
|
|
585
|
+
const callback = validationSchema === void 0 ? _validationSchema : _callback;
|
|
586
|
+
const removeListener = this.onMessageEvents.on(messageType, this.onUncaughtException !== void 0 ? wrapTryCatch(callback, this.onUncaughtException.bind(this), OnMessageException, "onMessage", false, _messageType) : callback);
|
|
587
|
+
if (validationSchema !== void 0) {
|
|
588
|
+
this.onMessageValidators[messageType] = validationSchema;
|
|
589
|
+
}
|
|
590
|
+
return () => {
|
|
591
|
+
removeListener();
|
|
592
|
+
if (this.onMessageEvents.events[messageType].length === 0) {
|
|
593
|
+
delete this.onMessageValidators[messageType];
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
onMessageBytes(_messageType, _validationSchema, _callback) {
|
|
598
|
+
const messageType = `_$b${_messageType}`;
|
|
599
|
+
const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
|
|
600
|
+
const callback = validationSchema === void 0 ? _validationSchema : _callback;
|
|
601
|
+
if (validationSchema !== void 0) {
|
|
602
|
+
return this.onMessage(messageType, validationSchema, callback);
|
|
603
|
+
} else {
|
|
604
|
+
return this.onMessage(messageType, callback);
|
|
605
|
+
}
|
|
434
606
|
}
|
|
435
607
|
/**
|
|
436
608
|
* Disconnect all connected clients, and then dispose the room.
|
|
@@ -438,14 +610,14 @@ var Room = class _Room {
|
|
|
438
610
|
* @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
|
|
439
611
|
* @returns Promise<void>
|
|
440
612
|
*/
|
|
441
|
-
disconnect(closeCode =
|
|
442
|
-
if (this._internalState ===
|
|
613
|
+
disconnect(closeCode = CloseCode.CONSENTED) {
|
|
614
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
443
615
|
return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
|
|
444
|
-
} else if (this._internalState ===
|
|
616
|
+
} else if (this._internalState === RoomInternalState.CREATING) {
|
|
445
617
|
throw new Error("cannot disconnect during onCreate()");
|
|
446
618
|
}
|
|
447
|
-
this._internalState =
|
|
448
|
-
|
|
619
|
+
this._internalState = RoomInternalState.DISPOSING;
|
|
620
|
+
matchMaker.driver.remove(this._listing.roomId);
|
|
449
621
|
this.#_autoDispose = true;
|
|
450
622
|
const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
|
|
451
623
|
for (const [_, reconnection] of Object.values(this._reconnections)) {
|
|
@@ -454,38 +626,41 @@ var Room = class _Room {
|
|
|
454
626
|
let numClients = this.clients.length;
|
|
455
627
|
if (numClients > 0) {
|
|
456
628
|
while (numClients--) {
|
|
457
|
-
this
|
|
629
|
+
this.#_forciblyCloseClient(this.clients[numClients], closeCode);
|
|
458
630
|
}
|
|
459
631
|
} else {
|
|
460
632
|
this._events.emit("dispose");
|
|
461
633
|
}
|
|
462
634
|
return delayedDisconnection;
|
|
463
635
|
}
|
|
464
|
-
async
|
|
636
|
+
async _onJoin(client, authContext, connectionOptions) {
|
|
465
637
|
const sessionId = client.sessionId;
|
|
466
638
|
client.reconnectionToken = generateId();
|
|
467
|
-
if (this.
|
|
468
|
-
clearTimeout(this.
|
|
469
|
-
delete this.
|
|
639
|
+
if (this._reservedSeatTimeouts[sessionId]) {
|
|
640
|
+
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
641
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
470
642
|
}
|
|
471
643
|
if (this._autoDisposeTimeout) {
|
|
472
644
|
clearTimeout(this._autoDisposeTimeout);
|
|
473
645
|
this._autoDisposeTimeout = void 0;
|
|
474
646
|
}
|
|
475
|
-
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.
|
|
647
|
+
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this._reservedSeats[sessionId];
|
|
476
648
|
if (isConsumed) {
|
|
477
649
|
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
|
|
478
650
|
}
|
|
479
|
-
this.
|
|
651
|
+
this._reservedSeats[sessionId][2] = true;
|
|
480
652
|
debugMatchMaking("consuming seat reservation, sessionId: '%s' (roomId: %s)", client.sessionId, this.roomId);
|
|
481
653
|
client._afterNextPatchQueue = this._afterNextPatchQueue;
|
|
482
654
|
client.ref["onleave"] = (_) => client.state = ClientState.LEAVING;
|
|
483
655
|
client.ref.once("close", client.ref["onleave"]);
|
|
484
656
|
if (isWaitingReconnection) {
|
|
485
|
-
const
|
|
486
|
-
if (
|
|
657
|
+
const reconnectionToken = connectionOptions?.reconnectionToken;
|
|
658
|
+
if (reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId) {
|
|
487
659
|
this.clients.push(client);
|
|
488
|
-
await this._reconnections[
|
|
660
|
+
await this._reconnections[reconnectionToken]?.[1].resolve(client);
|
|
661
|
+
if (this.onReconnect) {
|
|
662
|
+
await this.onReconnect(client);
|
|
663
|
+
}
|
|
489
664
|
} else {
|
|
490
665
|
const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
|
|
491
666
|
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
|
|
@@ -501,31 +676,31 @@ var Room = class _Room {
|
|
|
501
676
|
throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
|
|
502
677
|
}
|
|
503
678
|
} catch (e) {
|
|
504
|
-
delete this.
|
|
505
|
-
await this
|
|
679
|
+
delete this._reservedSeats[sessionId];
|
|
680
|
+
await this.#_decrementClientCount();
|
|
506
681
|
throw e;
|
|
507
682
|
}
|
|
508
683
|
}
|
|
509
684
|
if (client.state === ClientState.LEAVING) {
|
|
510
|
-
throw new ServerError(
|
|
685
|
+
throw new ServerError(CloseCode.WITH_ERROR, "already disconnected");
|
|
511
686
|
}
|
|
512
687
|
this.clients.push(client);
|
|
513
|
-
Object.defineProperty(this.
|
|
514
|
-
value: this.
|
|
688
|
+
Object.defineProperty(this._reservedSeats, sessionId, {
|
|
689
|
+
value: this._reservedSeats[sessionId],
|
|
515
690
|
enumerable: false
|
|
516
691
|
});
|
|
517
692
|
if (this.onJoin) {
|
|
518
|
-
await this.onJoin(client, joinOptions
|
|
693
|
+
await this.onJoin(client, joinOptions);
|
|
519
694
|
}
|
|
520
695
|
if (client.state === ClientState.LEAVING) {
|
|
521
|
-
throw new
|
|
696
|
+
throw new ServerError(ErrorCode.MATCHMAKE_UNHANDLED, "early_leave");
|
|
522
697
|
} else {
|
|
523
|
-
delete this.
|
|
698
|
+
delete this._reservedSeats[sessionId];
|
|
524
699
|
this._events.emit("join", client);
|
|
525
700
|
}
|
|
526
701
|
} catch (e) {
|
|
527
|
-
await this._onLeave(client,
|
|
528
|
-
delete this.
|
|
702
|
+
await this._onLeave(client, CloseCode.WITH_ERROR);
|
|
703
|
+
delete this._reservedSeats[sessionId];
|
|
529
704
|
if (!e.code) {
|
|
530
705
|
e.code = ErrorCode.APPLICATION_ERROR;
|
|
531
706
|
}
|
|
@@ -540,7 +715,11 @@ var Room = class _Room {
|
|
|
540
715
|
client.raw(getMessageBytes[Protocol.JOIN_ROOM](
|
|
541
716
|
client.reconnectionToken,
|
|
542
717
|
this._serializer.id,
|
|
543
|
-
|
|
718
|
+
/**
|
|
719
|
+
* if skipHandshake is true, we don't need to send the handshake
|
|
720
|
+
* (in case client already has handshake data)
|
|
721
|
+
*/
|
|
722
|
+
connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
|
|
544
723
|
));
|
|
545
724
|
}
|
|
546
725
|
}
|
|
@@ -548,11 +727,19 @@ var Room = class _Room {
|
|
|
548
727
|
* Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
|
|
549
728
|
* If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
|
|
550
729
|
*
|
|
551
|
-
* @param
|
|
552
|
-
* @param seconds -
|
|
730
|
+
* @param client - The client that is allowed to reconnect into the room.
|
|
731
|
+
* @param seconds - The time in seconds that the client is allowed to reconnect into the room.
|
|
553
732
|
*
|
|
554
733
|
* @returns Deferred<Client> - The differed is a promise like type.
|
|
555
734
|
* This type can forcibly reject the promise by calling `.reject()`.
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* onDrop(client: Client, code: CloseCode) {
|
|
739
|
+
* // Allow the client to reconnect into the room with a 15 seconds timeout.
|
|
740
|
+
* this.allowReconnection(client, 15);
|
|
741
|
+
* }
|
|
742
|
+
* ```
|
|
556
743
|
*/
|
|
557
744
|
allowReconnection(previousClient, seconds) {
|
|
558
745
|
if (previousClient._enqueuedMessages !== void 0) {
|
|
@@ -565,7 +752,7 @@ var Room = class _Room {
|
|
|
565
752
|
if (seconds === "manual") {
|
|
566
753
|
seconds = Infinity;
|
|
567
754
|
}
|
|
568
|
-
if (this._internalState ===
|
|
755
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
569
756
|
return Promise.reject(new Error("disposing"));
|
|
570
757
|
}
|
|
571
758
|
const sessionId = previousClient.sessionId;
|
|
@@ -574,13 +761,12 @@ var Room = class _Room {
|
|
|
574
761
|
const reconnection = new Deferred();
|
|
575
762
|
this._reconnections[reconnectionToken] = [sessionId, reconnection];
|
|
576
763
|
if (seconds !== Infinity) {
|
|
577
|
-
this.
|
|
764
|
+
this._reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
|
|
578
765
|
}
|
|
579
766
|
const cleanup = () => {
|
|
580
767
|
delete this._reconnections[reconnectionToken];
|
|
581
|
-
delete this.
|
|
582
|
-
delete this.
|
|
583
|
-
this._reconnectingSessionId.delete(sessionId);
|
|
768
|
+
delete this._reservedSeats[sessionId];
|
|
769
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
584
770
|
};
|
|
585
771
|
reconnection.then((newClient) => {
|
|
586
772
|
newClient.auth = previousClient.auth;
|
|
@@ -589,11 +775,11 @@ var Room = class _Room {
|
|
|
589
775
|
previousClient.state = ClientState.RECONNECTED;
|
|
590
776
|
previousClient.ref = newClient.ref;
|
|
591
777
|
previousClient.reconnectionToken = newClient.reconnectionToken;
|
|
592
|
-
clearTimeout(this.
|
|
593
|
-
|
|
594
|
-
}).catch(() => {
|
|
595
|
-
cleanup();
|
|
778
|
+
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
779
|
+
}, () => {
|
|
596
780
|
this.resetAutoDisposeTimeout();
|
|
781
|
+
}).finally(() => {
|
|
782
|
+
cleanup();
|
|
597
783
|
});
|
|
598
784
|
return reconnection;
|
|
599
785
|
}
|
|
@@ -604,7 +790,7 @@ var Room = class _Room {
|
|
|
604
790
|
}
|
|
605
791
|
this._autoDisposeTimeout = setTimeout(() => {
|
|
606
792
|
this._autoDisposeTimeout = void 0;
|
|
607
|
-
this
|
|
793
|
+
this.#_disposeIfEmpty();
|
|
608
794
|
}, timeoutInSeconds * 1e3);
|
|
609
795
|
}
|
|
610
796
|
broadcastMessageType(type, message, options = {}) {
|
|
@@ -636,36 +822,52 @@ var Room = class _Room {
|
|
|
636
822
|
this._afterNextPatchQueue.splice(0, length);
|
|
637
823
|
}
|
|
638
824
|
}
|
|
639
|
-
async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.
|
|
825
|
+
async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTimeout, allowReconnection = false, devModeReconnectionToken) {
|
|
640
826
|
if (!allowReconnection && this.hasReachedMaxClients()) {
|
|
641
827
|
return false;
|
|
642
828
|
}
|
|
643
|
-
|
|
829
|
+
debugMatchMaking(
|
|
830
|
+
"reserving seat. sessionId: '%s', roomId: '%s', processId: '%s'",
|
|
831
|
+
sessionId,
|
|
832
|
+
this.roomId,
|
|
833
|
+
matchMaker.processId
|
|
834
|
+
);
|
|
835
|
+
this._reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
|
|
644
836
|
if (!allowReconnection) {
|
|
645
|
-
await this
|
|
646
|
-
this.
|
|
647
|
-
delete this.
|
|
648
|
-
delete this.
|
|
649
|
-
await this
|
|
837
|
+
await this.#_incrementClientCount();
|
|
838
|
+
this._reservedSeatTimeouts[sessionId] = setTimeout(async () => {
|
|
839
|
+
delete this._reservedSeats[sessionId];
|
|
840
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
841
|
+
await this.#_decrementClientCount();
|
|
650
842
|
}, seconds * 1e3);
|
|
651
843
|
this.resetAutoDisposeTimeout(seconds);
|
|
652
844
|
}
|
|
653
|
-
if (
|
|
654
|
-
|
|
845
|
+
if (devModeReconnectionToken) {
|
|
846
|
+
const reconnection = new Deferred();
|
|
847
|
+
this._reconnections[devModeReconnectionToken] = [sessionId, reconnection];
|
|
655
848
|
}
|
|
656
849
|
return true;
|
|
657
850
|
}
|
|
658
|
-
|
|
851
|
+
async _reserveMultipleSeats(multipleSessionIds, multipleJoinOptions = true, multipleAuthData = void 0, seconds = this.seatReservationTimeout) {
|
|
852
|
+
let promises = [];
|
|
853
|
+
for (let i = 0; i < multipleSessionIds.length; i++) {
|
|
854
|
+
promises.push(this._reserveSeat(multipleSessionIds[i], multipleJoinOptions[i], multipleAuthData[i], seconds));
|
|
855
|
+
}
|
|
856
|
+
return await Promise.all(promises);
|
|
857
|
+
}
|
|
858
|
+
#_disposeIfEmpty() {
|
|
659
859
|
const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
|
|
660
|
-
this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.
|
|
860
|
+
this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this._reservedSeats).length === 0;
|
|
661
861
|
if (willDispose) {
|
|
662
862
|
this._events.emit("dispose");
|
|
663
863
|
}
|
|
664
864
|
return willDispose;
|
|
665
865
|
}
|
|
666
|
-
async _dispose() {
|
|
667
|
-
this._internalState =
|
|
668
|
-
this.
|
|
866
|
+
async #_dispose() {
|
|
867
|
+
this._internalState = RoomInternalState.DISPOSING;
|
|
868
|
+
if (this._listing?.roomId !== void 0) {
|
|
869
|
+
await matchMaker.driver.remove(this._listing.roomId);
|
|
870
|
+
}
|
|
669
871
|
let userReturnData;
|
|
670
872
|
if (this.onDispose) {
|
|
671
873
|
userReturnData = this.onDispose();
|
|
@@ -690,44 +892,59 @@ var Room = class _Room {
|
|
|
690
892
|
if (client.state === ClientState.LEAVING) {
|
|
691
893
|
return;
|
|
692
894
|
}
|
|
693
|
-
const it = { offset: 1 };
|
|
694
|
-
const code = buffer[0];
|
|
695
895
|
if (!buffer) {
|
|
696
896
|
debugAndPrintError(`${this.roomName} (roomId: ${this.roomId}), couldn't decode message: ${buffer}`);
|
|
697
897
|
return;
|
|
698
898
|
}
|
|
899
|
+
if (this.clock.currentTime - client._lastMessageTime >= 1e3) {
|
|
900
|
+
client._numMessagesLastSecond = 0;
|
|
901
|
+
client._lastMessageTime = this.clock.currentTime;
|
|
902
|
+
} else if (++client._numMessagesLastSecond > this.maxMessagesPerSecond) {
|
|
903
|
+
return this.#_forciblyCloseClient(client, CloseCode.WITH_ERROR);
|
|
904
|
+
}
|
|
905
|
+
const it = { offset: 1 };
|
|
906
|
+
const code = buffer[0];
|
|
699
907
|
if (code === Protocol.ROOM_DATA) {
|
|
700
908
|
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
701
|
-
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
702
909
|
let message;
|
|
703
910
|
try {
|
|
704
911
|
message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
|
|
705
912
|
debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
706
|
-
if (
|
|
707
|
-
message =
|
|
913
|
+
if (this.onMessageValidators[messageType] !== void 0) {
|
|
914
|
+
message = standardValidate(this.onMessageValidators[messageType], message);
|
|
708
915
|
}
|
|
709
916
|
} catch (e) {
|
|
710
917
|
debugAndPrintError(e);
|
|
711
|
-
client.leave(
|
|
918
|
+
client.leave(CloseCode.WITH_ERROR);
|
|
712
919
|
return;
|
|
713
920
|
}
|
|
714
|
-
if (
|
|
715
|
-
|
|
921
|
+
if (this.onMessageEvents.events[messageType]) {
|
|
922
|
+
this.onMessageEvents.emit(messageType, client, message);
|
|
923
|
+
} else if (this.onMessageEvents.events["*"]) {
|
|
924
|
+
this.onMessageEvents.emit("*", client, messageType, message);
|
|
716
925
|
} else {
|
|
717
|
-
|
|
926
|
+
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
718
927
|
}
|
|
719
928
|
} else if (code === Protocol.ROOM_DATA_BYTES) {
|
|
720
929
|
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
721
|
-
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
722
930
|
let message = buffer.subarray(it.offset, buffer.byteLength);
|
|
723
931
|
debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
724
|
-
|
|
725
|
-
|
|
932
|
+
const bytesMessageType = `_$b${messageType}`;
|
|
933
|
+
try {
|
|
934
|
+
if (this.onMessageValidators[bytesMessageType] !== void 0) {
|
|
935
|
+
message = standardValidate(this.onMessageValidators[bytesMessageType], message);
|
|
936
|
+
}
|
|
937
|
+
} catch (e) {
|
|
938
|
+
debugAndPrintError(e);
|
|
939
|
+
client.leave(CloseCode.WITH_ERROR);
|
|
940
|
+
return;
|
|
726
941
|
}
|
|
727
|
-
if (
|
|
728
|
-
|
|
942
|
+
if (this.onMessageEvents.events[bytesMessageType]) {
|
|
943
|
+
this.onMessageEvents.emit(bytesMessageType, client, message);
|
|
944
|
+
} else if (this.onMessageEvents.events["*"]) {
|
|
945
|
+
this.onMessageEvents.emit("*", client, messageType, message);
|
|
729
946
|
} else {
|
|
730
|
-
|
|
947
|
+
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
731
948
|
}
|
|
732
949
|
} else if (code === Protocol.JOIN_ROOM && client.state === ClientState.JOINING) {
|
|
733
950
|
client.state = ClientState.JOINED;
|
|
@@ -739,58 +956,64 @@ var Room = class _Room {
|
|
|
739
956
|
client._enqueuedMessages.forEach((enqueued) => client.raw(enqueued));
|
|
740
957
|
}
|
|
741
958
|
delete client._enqueuedMessages;
|
|
959
|
+
} else if (code === Protocol.PING) {
|
|
960
|
+
client.raw(getMessageBytes[Protocol.PING]());
|
|
742
961
|
} else if (code === Protocol.LEAVE_ROOM) {
|
|
743
|
-
this
|
|
962
|
+
this.#_forciblyCloseClient(client, CloseCode.CONSENTED);
|
|
744
963
|
}
|
|
745
964
|
}
|
|
746
|
-
_forciblyCloseClient(client, closeCode) {
|
|
965
|
+
#_forciblyCloseClient(client, closeCode) {
|
|
747
966
|
client.ref.removeAllListeners("message");
|
|
748
967
|
client.ref.removeListener("close", client.ref["onleave"]);
|
|
749
968
|
this._onLeave(client, closeCode).then(() => client.leave(closeCode));
|
|
750
969
|
}
|
|
751
970
|
async _onLeave(client, code) {
|
|
752
|
-
debugMatchMaking("onLeave, sessionId: '%s' (close code: %d, roomId: %s)", client.sessionId, code, this.roomId);
|
|
753
971
|
client.state = ClientState.LEAVING;
|
|
754
972
|
if (!this.clients.delete(client)) {
|
|
755
973
|
return;
|
|
756
974
|
}
|
|
757
|
-
|
|
975
|
+
const method = code === CloseCode.CONSENTED ? this.onLeave : this.onDrop || this.onLeave;
|
|
976
|
+
if (method) {
|
|
977
|
+
debugMatchMaking(`${method.name}, sessionId: '%s' (close code: %d, roomId: %s)`, client.sessionId, code, this.roomId);
|
|
758
978
|
try {
|
|
759
979
|
this.#_onLeaveConcurrent++;
|
|
760
|
-
await
|
|
980
|
+
await method.call(this, client, code);
|
|
761
981
|
} catch (e) {
|
|
762
|
-
debugAndPrintError(
|
|
982
|
+
debugAndPrintError(`${method.name} error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
|
|
763
983
|
} finally {
|
|
764
984
|
this.#_onLeaveConcurrent--;
|
|
765
985
|
}
|
|
766
986
|
}
|
|
767
987
|
if (this._reconnections[client.reconnectionToken]) {
|
|
768
988
|
this._reconnections[client.reconnectionToken][1].catch(async () => {
|
|
769
|
-
await this
|
|
989
|
+
await this.#_onAfterLeave(client, code, method === this.onDrop);
|
|
770
990
|
});
|
|
771
991
|
} else if (client.state !== ClientState.RECONNECTED) {
|
|
772
|
-
await this
|
|
992
|
+
await this.#_onAfterLeave(client, code, method === this.onDrop);
|
|
773
993
|
}
|
|
774
994
|
}
|
|
775
|
-
async _onAfterLeave(client) {
|
|
776
|
-
|
|
777
|
-
|
|
995
|
+
async #_onAfterLeave(client, code, isDrop = false) {
|
|
996
|
+
if (isDrop && this.onLeave) {
|
|
997
|
+
await this.onLeave(client, code);
|
|
998
|
+
}
|
|
999
|
+
const willDispose = await this.#_decrementClientCount();
|
|
1000
|
+
if (this._reservedSeats[client.sessionId] === void 0) {
|
|
778
1001
|
this._events.emit("leave", client, willDispose);
|
|
779
1002
|
}
|
|
780
1003
|
}
|
|
781
|
-
async _incrementClientCount() {
|
|
1004
|
+
async #_incrementClientCount() {
|
|
782
1005
|
if (!this.#_locked && this.hasReachedMaxClients()) {
|
|
783
1006
|
this.#_maxClientsReached = true;
|
|
784
1007
|
this.lock.call(this, true);
|
|
785
1008
|
}
|
|
786
|
-
await
|
|
1009
|
+
await matchMaker.driver.update(this._listing, {
|
|
787
1010
|
$inc: { clients: 1 },
|
|
788
1011
|
$set: { locked: this.#_locked }
|
|
789
1012
|
});
|
|
790
1013
|
}
|
|
791
|
-
async _decrementClientCount() {
|
|
792
|
-
const willDispose = this
|
|
793
|
-
if (this._internalState ===
|
|
1014
|
+
async #_decrementClientCount() {
|
|
1015
|
+
const willDispose = this.#_disposeIfEmpty();
|
|
1016
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
794
1017
|
return true;
|
|
795
1018
|
}
|
|
796
1019
|
if (!willDispose) {
|
|
@@ -798,7 +1021,7 @@ var Room = class _Room {
|
|
|
798
1021
|
this.#_maxClientsReached = false;
|
|
799
1022
|
this.unlock.call(this, true);
|
|
800
1023
|
}
|
|
801
|
-
await
|
|
1024
|
+
await matchMaker.driver.update(this._listing, {
|
|
802
1025
|
$inc: { clients: -1 },
|
|
803
1026
|
$set: { locked: this.#_locked }
|
|
804
1027
|
});
|
|
@@ -832,8 +1055,27 @@ var Room = class _Room {
|
|
|
832
1055
|
}
|
|
833
1056
|
}
|
|
834
1057
|
};
|
|
1058
|
+
function room(options) {
|
|
1059
|
+
class _ extends Room {
|
|
1060
|
+
constructor() {
|
|
1061
|
+
super();
|
|
1062
|
+
this.messages = options.messages;
|
|
1063
|
+
if (options.state && typeof options.state === "function") {
|
|
1064
|
+
this.state = options.state();
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
for (const key in options) {
|
|
1069
|
+
if (typeof options[key] === "function") {
|
|
1070
|
+
_.prototype[key] = options[key];
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return _;
|
|
1074
|
+
}
|
|
835
1075
|
export {
|
|
836
1076
|
DEFAULT_SEAT_RESERVATION_TIME,
|
|
837
1077
|
Room,
|
|
838
|
-
RoomInternalState
|
|
1078
|
+
RoomInternalState,
|
|
1079
|
+
room,
|
|
1080
|
+
validate
|
|
839
1081
|
};
|