@colyseus/core 0.16.0-preview.9 → 0.16.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/README.md +5 -5
- package/build/Debug.js +6 -2
- package/build/Debug.js.map +2 -2
- package/build/Debug.mjs +11 -10
- package/build/Debug.mjs.map +2 -2
- package/build/IPC.d.ts +1 -1
- package/build/IPC.js +3 -3
- package/build/IPC.js.map +2 -2
- package/build/IPC.mjs +4 -3
- package/build/IPC.mjs.map +2 -2
- package/build/Logger.mjs +4 -3
- package/build/Logger.mjs.map +1 -1
- package/build/MatchMaker.d.ts +35 -30
- package/build/MatchMaker.js +150 -100
- package/build/MatchMaker.js.map +2 -2
- package/build/MatchMaker.mjs +154 -107
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.d.ts +3 -4
- package/build/Protocol.js +33 -19
- package/build/Protocol.js.map +2 -2
- package/build/Protocol.mjs +36 -21
- package/build/Protocol.mjs.map +2 -2
- package/build/Room.d.ts +64 -40
- package/build/Room.js +408 -151
- package/build/Room.js.map +2 -2
- package/build/Room.mjs +412 -158
- package/build/Room.mjs.map +2 -2
- package/build/Server.d.ts +8 -7
- package/build/Server.js +51 -18
- package/build/Server.js.map +2 -2
- package/build/Server.mjs +51 -21
- package/build/Server.mjs.map +3 -3
- package/build/Stats.d.ts +2 -0
- package/build/Stats.js +38 -3
- package/build/Stats.js.map +2 -2
- package/build/Stats.mjs +30 -6
- package/build/Stats.mjs.map +2 -2
- package/build/Transport.d.ts +8 -7
- package/build/Transport.js +1 -1
- package/build/Transport.js.map +2 -2
- package/build/Transport.mjs +6 -5
- package/build/Transport.mjs.map +2 -2
- package/build/discovery/index.d.ts +1 -1
- package/build/discovery/index.js.map +2 -2
- package/build/discovery/index.mjs +3 -2
- package/build/discovery/index.mjs.map +2 -2
- package/build/errors/RoomExceptions.d.ts +39 -0
- package/build/errors/RoomExceptions.js +100 -0
- package/build/errors/RoomExceptions.js.map +7 -0
- package/build/errors/RoomExceptions.mjs +71 -0
- package/build/errors/RoomExceptions.mjs.map +7 -0
- package/build/errors/SeatReservationError.mjs +3 -2
- package/build/errors/SeatReservationError.mjs.map +1 -1
- package/build/errors/ServerError.js +1 -1
- package/build/errors/ServerError.js.map +1 -1
- package/build/errors/ServerError.mjs +5 -4
- package/build/errors/ServerError.mjs.map +2 -2
- package/build/index.d.ts +21 -19
- package/build/index.js +47 -20
- package/build/index.js.map +2 -2
- package/build/index.mjs +41 -19
- package/build/index.mjs.map +2 -2
- package/build/matchmaker/Lobby.d.ts +3 -3
- package/build/matchmaker/Lobby.js +6 -3
- package/build/matchmaker/Lobby.js.map +2 -2
- package/build/matchmaker/Lobby.mjs +4 -4
- package/build/matchmaker/Lobby.mjs.map +2 -2
- package/build/matchmaker/RegisteredHandler.d.ts +6 -7
- package/build/matchmaker/RegisteredHandler.js +7 -10
- package/build/matchmaker/RegisteredHandler.js.map +2 -2
- package/build/matchmaker/RegisteredHandler.mjs +11 -13
- package/build/matchmaker/RegisteredHandler.mjs.map +2 -2
- package/build/matchmaker/controller.d.ts +4 -5
- package/build/matchmaker/controller.js +22 -15
- package/build/matchmaker/controller.js.map +2 -2
- package/build/matchmaker/controller.mjs +19 -13
- package/build/matchmaker/controller.mjs.map +2 -2
- package/build/matchmaker/driver/api.d.ts +104 -0
- package/build/matchmaker/driver/api.js +29 -0
- package/build/matchmaker/driver/api.js.map +7 -0
- package/build/matchmaker/driver/api.mjs +7 -0
- package/build/matchmaker/driver/api.mjs.map +7 -0
- package/build/matchmaker/driver/index.d.ts +2 -2
- package/build/matchmaker/driver/index.js +2 -2
- package/build/matchmaker/driver/index.js.map +2 -2
- package/build/matchmaker/driver/index.mjs +5 -4
- package/build/matchmaker/driver/index.mjs.map +2 -2
- package/build/matchmaker/driver/local/LocalDriver.d.ts +13 -0
- package/build/matchmaker/driver/local/LocalDriver.js +65 -0
- package/build/matchmaker/driver/local/LocalDriver.js.map +7 -0
- package/build/matchmaker/driver/local/LocalDriver.mjs +43 -0
- package/build/matchmaker/driver/local/LocalDriver.mjs.map +7 -0
- package/build/matchmaker/driver/local/Query.d.ts +9 -0
- package/build/matchmaker/driver/local/Query.js +78 -0
- package/build/matchmaker/driver/local/Query.js.map +7 -0
- package/build/matchmaker/driver/local/Query.mjs +56 -0
- package/build/matchmaker/driver/local/Query.mjs.map +7 -0
- package/build/matchmaker/driver/local/RoomData.d.ts +19 -0
- package/build/matchmaker/driver/local/RoomData.js +79 -0
- package/build/matchmaker/driver/local/RoomData.js.map +7 -0
- package/build/matchmaker/driver/local/RoomData.mjs +57 -0
- package/build/matchmaker/driver/local/RoomData.mjs.map +7 -0
- package/build/presence/LocalPresence.d.ts +10 -6
- package/build/presence/LocalPresence.js +85 -24
- package/build/presence/LocalPresence.js.map +3 -3
- package/build/presence/LocalPresence.mjs +85 -27
- package/build/presence/LocalPresence.mjs.map +3 -3
- package/build/presence/Presence.d.ts +38 -2
- package/build/presence/Presence.js.map +1 -1
- package/build/rooms/LobbyRoom.d.ts +6 -6
- package/build/rooms/LobbyRoom.js +8 -3
- package/build/rooms/LobbyRoom.js.map +2 -2
- package/build/rooms/LobbyRoom.mjs +7 -5
- package/build/rooms/LobbyRoom.mjs.map +2 -2
- package/build/rooms/RelayRoom.d.ts +3 -3
- package/build/rooms/RelayRoom.js +3 -1
- package/build/rooms/RelayRoom.js.map +2 -2
- package/build/rooms/RelayRoom.mjs +10 -7
- package/build/rooms/RelayRoom.mjs.map +2 -2
- package/build/serializer/NoneSerializer.d.ts +2 -2
- package/build/serializer/NoneSerializer.js.map +1 -1
- package/build/serializer/NoneSerializer.mjs +3 -2
- package/build/serializer/NoneSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializer.d.ts +16 -15
- package/build/serializer/SchemaSerializer.js +12 -10
- package/build/serializer/SchemaSerializer.js.map +2 -2
- package/build/serializer/SchemaSerializer.mjs +16 -13
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- package/build/serializer/SchemaSerializerDebug.d.ts +7 -0
- package/build/serializer/SchemaSerializerDebug.js +0 -0
- package/build/serializer/SchemaSerializerDebug.js.map +7 -0
- package/build/serializer/SchemaSerializerDebug.mjs +0 -0
- package/build/serializer/SchemaSerializerDebug.mjs.map +7 -0
- package/build/serializer/Serializer.d.ts +1 -2
- package/build/serializer/Serializer.js.map +1 -1
- package/build/utils/DevMode.d.ts +2 -2
- package/build/utils/DevMode.js +8 -4
- package/build/utils/DevMode.js.map +2 -2
- package/build/utils/DevMode.mjs +7 -6
- package/build/utils/DevMode.mjs.map +2 -2
- package/build/utils/Utils.d.ts +8 -3
- package/build/utils/Utils.js +41 -17
- package/build/utils/Utils.js.map +2 -2
- package/build/utils/Utils.mjs +40 -21
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +17 -6
package/build/Room.mjs
CHANGED
|
@@ -1,153 +1,332 @@
|
|
|
1
|
-
|
|
1
|
+
// packages/core/src/Room.ts
|
|
2
|
+
import { unpack } from "@colyseus/msgpackr";
|
|
2
3
|
import { decode, $changes } from "@colyseus/schema";
|
|
3
|
-
import Clock from "@
|
|
4
|
+
import Clock from "@colyseus/timer";
|
|
4
5
|
import { EventEmitter } from "events";
|
|
5
|
-
import { logger } from "./Logger";
|
|
6
|
-
import { NoneSerializer } from "./serializer/NoneSerializer";
|
|
7
|
-
import { SchemaSerializer } from "./serializer/SchemaSerializer";
|
|
6
|
+
import { logger } from "./Logger.mjs";
|
|
7
|
+
import { NoneSerializer } from "./serializer/NoneSerializer.mjs";
|
|
8
|
+
import { SchemaSerializer } from "./serializer/SchemaSerializer.mjs";
|
|
8
9
|
import { ErrorCode, getMessageBytes, Protocol } from "./Protocol";
|
|
9
|
-
import { Deferred, generateId } from "./utils/Utils";
|
|
10
|
-
import { isDevMode } from "./utils/DevMode";
|
|
11
|
-
import { debugAndPrintError, debugMessage } from "./Debug";
|
|
12
|
-
import { ServerError } from "./errors/ServerError";
|
|
10
|
+
import { Deferred, generateId, wrapTryCatch } from "./utils/Utils.mjs";
|
|
11
|
+
import { isDevMode } from "./utils/DevMode.mjs";
|
|
12
|
+
import { debugAndPrintError, debugMatchMaking, debugMessage } from "./Debug.mjs";
|
|
13
|
+
import { ServerError } from "./errors/ServerError.mjs";
|
|
13
14
|
import { ClientArray, ClientState } from "./Transport";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
import { OnAuthException, OnCreateException, OnDisposeException, OnJoinException, OnLeaveException, OnMessageException, SimulationIntervalException, TimedEventException } from "./errors/RoomExceptions.mjs";
|
|
16
|
+
var DEFAULT_PATCH_RATE = 1e3 / 20;
|
|
17
|
+
var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
|
|
18
|
+
var noneSerializer = new NoneSerializer();
|
|
19
|
+
var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
18
20
|
var RoomInternalState = /* @__PURE__ */ ((RoomInternalState2) => {
|
|
19
21
|
RoomInternalState2[RoomInternalState2["CREATING"] = 0] = "CREATING";
|
|
20
22
|
RoomInternalState2[RoomInternalState2["CREATED"] = 1] = "CREATED";
|
|
21
23
|
RoomInternalState2[RoomInternalState2["DISPOSING"] = 2] = "DISPOSING";
|
|
22
24
|
return RoomInternalState2;
|
|
23
25
|
})(RoomInternalState || {});
|
|
24
|
-
|
|
26
|
+
var Room = class _Room {
|
|
25
27
|
constructor() {
|
|
28
|
+
/**
|
|
29
|
+
* Timing events tied to the room instance.
|
|
30
|
+
* Intervals and timeouts are cleared when the room is disposed.
|
|
31
|
+
*/
|
|
26
32
|
this.clock = new Clock();
|
|
27
|
-
this.#
|
|
33
|
+
this.#_onLeaveConcurrent = 0;
|
|
34
|
+
// number of onLeave calls in progress
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of clients allowed to connect into the room. When room reaches this limit,
|
|
37
|
+
* it is locked automatically. Unless the room was explicitly locked by you via `lock()` method,
|
|
38
|
+
* the room will be unlocked as soon as a client disconnects from it.
|
|
39
|
+
*/
|
|
28
40
|
this.maxClients = Infinity;
|
|
41
|
+
this.#_maxClientsReached = false;
|
|
42
|
+
/**
|
|
43
|
+
* Automatically dispose the room when last client disconnects.
|
|
44
|
+
*
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
this.autoDispose = true;
|
|
48
|
+
/**
|
|
49
|
+
* Frequency to send the room state to connected clients, in milliseconds.
|
|
50
|
+
*
|
|
51
|
+
* @default 50ms (20fps)
|
|
52
|
+
*/
|
|
29
53
|
this.patchRate = DEFAULT_PATCH_RATE;
|
|
54
|
+
/**
|
|
55
|
+
* The array of connected clients.
|
|
56
|
+
*
|
|
57
|
+
* @see {@link https://docs.colyseus.io/colyseus/server/room/#client|Client instance}
|
|
58
|
+
*/
|
|
30
59
|
this.clients = new ClientArray();
|
|
60
|
+
/** @internal */
|
|
31
61
|
this._events = new EventEmitter();
|
|
62
|
+
// seat reservation & reconnection
|
|
32
63
|
this.seatReservationTime = DEFAULT_SEAT_RESERVATION_TIME;
|
|
33
64
|
this.reservedSeats = {};
|
|
34
65
|
this.reservedSeatTimeouts = {};
|
|
35
66
|
this._reconnections = {};
|
|
36
67
|
this._reconnectingSessionId = /* @__PURE__ */ new Map();
|
|
37
|
-
this.onMessageHandlers = {
|
|
68
|
+
this.onMessageHandlers = {
|
|
69
|
+
"__no_message_handler": {
|
|
70
|
+
callback: (client, messageType, _) => {
|
|
71
|
+
const errorMessage = `room onMessage for "${messageType}" not registered.`;
|
|
72
|
+
debugAndPrintError(`${errorMessage} (roomId: ${this.roomId})`);
|
|
73
|
+
if (isDevMode) {
|
|
74
|
+
client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
75
|
+
} else {
|
|
76
|
+
client.leave(Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
38
81
|
this._serializer = noneSerializer;
|
|
39
82
|
this._afterNextPatchQueue = [];
|
|
40
83
|
this._internalState = 0 /* CREATING */;
|
|
41
|
-
this._locked = false;
|
|
42
84
|
this._lockedExplicitly = false;
|
|
43
|
-
this
|
|
44
|
-
this._events.once("dispose",
|
|
45
|
-
|
|
46
|
-
await this._dispose();
|
|
47
|
-
} catch (e) {
|
|
48
|
-
debugAndPrintError(`onDispose error: ${e && e.message || e || "promise rejected"}`);
|
|
49
|
-
}
|
|
50
|
-
this._events.emit("disconnect");
|
|
85
|
+
this.#_locked = false;
|
|
86
|
+
this._events.once("dispose", () => {
|
|
87
|
+
this._dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
|
|
51
88
|
});
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
if (this.onUncaughtException !== void 0) {
|
|
90
|
+
this.#registerUncaughtExceptionHandlers();
|
|
91
|
+
}
|
|
54
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* This property will change on these situations:
|
|
95
|
+
* - The maximum number of allowed clients has been reached (`maxClients`)
|
|
96
|
+
* - You manually locked, or unlocked the room using lock() or `unlock()`.
|
|
97
|
+
*
|
|
98
|
+
* @readonly
|
|
99
|
+
*/
|
|
55
100
|
get locked() {
|
|
56
|
-
return this
|
|
101
|
+
return this.#_locked;
|
|
57
102
|
}
|
|
58
103
|
get metadata() {
|
|
59
104
|
return this.listing.metadata;
|
|
60
105
|
}
|
|
61
106
|
#_roomId;
|
|
62
107
|
#_roomName;
|
|
108
|
+
#_onLeaveConcurrent;
|
|
109
|
+
#_maxClientsReached;
|
|
110
|
+
#_maxClients;
|
|
63
111
|
#_autoDispose;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
112
|
+
#_patchRate;
|
|
113
|
+
#_patchInterval;
|
|
114
|
+
#_state;
|
|
115
|
+
#_locked;
|
|
116
|
+
/**
|
|
117
|
+
* This method is called by the MatchMaker before onCreate()
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
__init() {
|
|
121
|
+
this.#_state = this.state;
|
|
122
|
+
this.#_autoDispose = this.autoDispose;
|
|
123
|
+
this.#_patchRate = this.patchRate;
|
|
124
|
+
this.#_maxClients = this.maxClients;
|
|
125
|
+
Object.defineProperties(this, {
|
|
126
|
+
state: {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get: () => this.#_state,
|
|
129
|
+
set: (newState) => {
|
|
130
|
+
if (newState[$changes] !== void 0) {
|
|
131
|
+
this.setSerializer(new SchemaSerializer());
|
|
132
|
+
} else if ("_definition" in newState) {
|
|
133
|
+
throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
|
|
134
|
+
} else if ($changes === void 0) {
|
|
135
|
+
throw new Error("Multiple @colyseus/schema versions detected. Please make sure you don't have multiple versions of @colyseus/schema installed.");
|
|
136
|
+
}
|
|
137
|
+
this._serializer.reset(newState);
|
|
138
|
+
this.#_state = newState;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
maxClients: {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
get: () => this.#_maxClients,
|
|
144
|
+
set: (value) => {
|
|
145
|
+
this.#_maxClients = value;
|
|
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
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
autoDispose: {
|
|
164
|
+
enumerable: true,
|
|
165
|
+
get: () => this.#_autoDispose,
|
|
166
|
+
set: (value) => {
|
|
167
|
+
if (value !== this.#_autoDispose && this._internalState !== 2 /* DISPOSING */) {
|
|
168
|
+
this.#_autoDispose = value;
|
|
169
|
+
this.resetAutoDisposeTimeout();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
patchRate: {
|
|
174
|
+
enumerable: true,
|
|
175
|
+
get: () => this.#_patchRate,
|
|
176
|
+
set: (milliseconds) => {
|
|
177
|
+
this.#_patchRate = milliseconds;
|
|
178
|
+
if (this.#_patchInterval) {
|
|
179
|
+
clearInterval(this.#_patchInterval);
|
|
180
|
+
this.#_patchInterval = void 0;
|
|
181
|
+
}
|
|
182
|
+
if (milliseconds !== null && milliseconds !== 0) {
|
|
183
|
+
this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
this.patchRate = this.#_patchRate;
|
|
189
|
+
if (this.#_state) {
|
|
190
|
+
this.state = this.#_state;
|
|
71
191
|
}
|
|
192
|
+
this.resetAutoDisposeTimeout(this.seatReservationTime);
|
|
193
|
+
this.clock.start();
|
|
72
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* The name of the room you provided as first argument for `gameServer.define()`.
|
|
197
|
+
*
|
|
198
|
+
* @returns roomName string
|
|
199
|
+
*/
|
|
73
200
|
get roomName() {
|
|
74
201
|
return this.#_roomName;
|
|
75
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Setting the name of the room. Overwriting this property is restricted.
|
|
205
|
+
*
|
|
206
|
+
* @param roomName
|
|
207
|
+
*/
|
|
76
208
|
set roomName(roomName) {
|
|
77
209
|
if (this.#_roomName) {
|
|
78
210
|
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomName' cannot be overwritten.");
|
|
79
211
|
}
|
|
80
212
|
this.#_roomName = roomName;
|
|
81
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* A unique, auto-generated, 9-character-long id of the room.
|
|
216
|
+
* You may replace `this.roomId` during `onCreate()`.
|
|
217
|
+
*
|
|
218
|
+
* @returns roomId string
|
|
219
|
+
*/
|
|
82
220
|
get roomId() {
|
|
83
221
|
return this.#_roomId;
|
|
84
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Setting the roomId, is restricted in room lifetime except upon room creation.
|
|
225
|
+
*
|
|
226
|
+
* @param roomId
|
|
227
|
+
* @returns roomId string
|
|
228
|
+
*/
|
|
85
229
|
set roomId(roomId) {
|
|
86
230
|
if (this._internalState !== 0 /* CREATING */ && !isDevMode) {
|
|
87
231
|
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
|
|
88
232
|
}
|
|
89
233
|
this.#_roomId = roomId;
|
|
90
234
|
}
|
|
91
|
-
onAuth(client, options,
|
|
235
|
+
onAuth(client, options, context) {
|
|
92
236
|
return true;
|
|
93
237
|
}
|
|
94
|
-
static async onAuth(token,
|
|
238
|
+
static async onAuth(token, options, context) {
|
|
95
239
|
return true;
|
|
96
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* This method is called during graceful shutdown of the server process
|
|
243
|
+
* You may override this method to dispose the room in your own way.
|
|
244
|
+
*
|
|
245
|
+
* Once process reaches room count of 0, the room process will be terminated.
|
|
246
|
+
*/
|
|
247
|
+
onBeforeShutdown() {
|
|
248
|
+
this.disconnect(
|
|
249
|
+
isDevMode ? Protocol.WS_CLOSE_DEVMODE_RESTART : Protocol.WS_CLOSE_CONSENTED
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Returns whether the sum of connected clients and reserved seats exceeds maximum number of clients.
|
|
254
|
+
*
|
|
255
|
+
* @returns boolean
|
|
256
|
+
*/
|
|
97
257
|
hasReachedMaxClients() {
|
|
98
258
|
return this.clients.length + Object.keys(this.reservedSeats).length >= this.maxClients || this._internalState === 2 /* DISPOSING */;
|
|
99
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Set the number of seconds a room can wait for a client to effectively join the room.
|
|
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.
|
|
270
|
+
*/
|
|
100
271
|
setSeatReservationTime(seconds) {
|
|
101
272
|
this.seatReservationTime = seconds;
|
|
102
273
|
return this;
|
|
103
274
|
}
|
|
104
275
|
hasReservedSeat(sessionId, reconnectionToken) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return
|
|
276
|
+
const reservedSeat = this.reservedSeats[sessionId];
|
|
277
|
+
if (reservedSeat === void 0) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (reservedSeat[3]) {
|
|
281
|
+
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId && this._reconnectingSessionId.has(sessionId);
|
|
108
282
|
} else {
|
|
109
|
-
return
|
|
283
|
+
return reservedSeat[2] === false;
|
|
110
284
|
}
|
|
111
285
|
}
|
|
112
286
|
checkReconnectionToken(reconnectionToken) {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
287
|
+
const sessionId = this._reconnections[reconnectionToken]?.[0];
|
|
288
|
+
const reservedSeat = this.reservedSeats[sessionId];
|
|
289
|
+
if (reservedSeat && reservedSeat[3]) {
|
|
116
290
|
this._reconnectingSessionId.set(sessionId, reconnectionToken);
|
|
117
291
|
return sessionId;
|
|
118
292
|
} else {
|
|
119
293
|
return void 0;
|
|
120
294
|
}
|
|
121
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* (Optional) Set a simulation interval that can change the state of the game.
|
|
298
|
+
* The simulation interval is your game loop.
|
|
299
|
+
*
|
|
300
|
+
* @default 16.6ms (60fps)
|
|
301
|
+
*
|
|
302
|
+
* @param onTickCallback - You can implement your physics or world updates here!
|
|
303
|
+
* This is a good place to update the room state.
|
|
304
|
+
* @param delay - Interval delay on executing `onTickCallback` in milliseconds.
|
|
305
|
+
*/
|
|
122
306
|
setSimulationInterval(onTickCallback, delay = DEFAULT_SIMULATION_INTERVAL) {
|
|
123
307
|
if (this._simulationInterval) {
|
|
124
308
|
clearInterval(this._simulationInterval);
|
|
125
309
|
}
|
|
126
310
|
if (onTickCallback) {
|
|
311
|
+
if (this.onUncaughtException !== void 0) {
|
|
312
|
+
onTickCallback = wrapTryCatch(onTickCallback, this.onUncaughtException.bind(this), SimulationIntervalException, "setSimulationInterval");
|
|
313
|
+
}
|
|
127
314
|
this._simulationInterval = setInterval(() => {
|
|
128
315
|
this.clock.tick();
|
|
129
316
|
onTickCallback(this.clock.deltaTime);
|
|
130
317
|
}, delay);
|
|
131
318
|
}
|
|
132
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* @deprecated Use `.patchRate=` instead.
|
|
322
|
+
*/
|
|
133
323
|
setPatchRate(milliseconds) {
|
|
134
324
|
this.patchRate = milliseconds;
|
|
135
|
-
if (this._patchInterval) {
|
|
136
|
-
clearInterval(this._patchInterval);
|
|
137
|
-
this._patchInterval = void 0;
|
|
138
|
-
}
|
|
139
|
-
if (milliseconds !== null && milliseconds !== 0) {
|
|
140
|
-
this._patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
|
|
141
|
-
}
|
|
142
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* @deprecated Use `.state =` instead.
|
|
328
|
+
*/
|
|
143
329
|
setState(newState) {
|
|
144
|
-
this.clock.start();
|
|
145
|
-
if (newState[$changes] !== void 0) {
|
|
146
|
-
this.setSerializer(new SchemaSerializer());
|
|
147
|
-
} else if ($changes === void 0) {
|
|
148
|
-
throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
|
|
149
|
-
}
|
|
150
|
-
this._serializer.reset(newState);
|
|
151
330
|
this.state = newState;
|
|
152
331
|
}
|
|
153
332
|
setSerializer(serializer) {
|
|
@@ -172,35 +351,40 @@ class Room {
|
|
|
172
351
|
}
|
|
173
352
|
}
|
|
174
353
|
async setPrivate(bool = true) {
|
|
175
|
-
if (this.listing.private === bool)
|
|
176
|
-
return;
|
|
354
|
+
if (this.listing.private === bool) return;
|
|
177
355
|
this.listing.private = bool;
|
|
178
356
|
if (this._internalState === 1 /* CREATED */) {
|
|
179
357
|
await this.listing.save();
|
|
180
358
|
}
|
|
181
359
|
this._events.emit("visibility-change", bool);
|
|
182
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* Locking the room will remove it from the pool of available rooms for new clients to connect to.
|
|
363
|
+
*/
|
|
183
364
|
async lock() {
|
|
184
365
|
this._lockedExplicitly = arguments[0] === void 0;
|
|
185
|
-
if (this
|
|
366
|
+
if (this.#_locked) {
|
|
186
367
|
return;
|
|
187
368
|
}
|
|
188
|
-
this
|
|
369
|
+
this.#_locked = true;
|
|
189
370
|
await this.listing.updateOne({
|
|
190
|
-
$set: { locked: this
|
|
371
|
+
$set: { locked: this.#_locked }
|
|
191
372
|
});
|
|
192
373
|
this._events.emit("lock");
|
|
193
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Unlocking the room returns it to the pool of available rooms for new clients to connect to.
|
|
377
|
+
*/
|
|
194
378
|
async unlock() {
|
|
195
379
|
if (arguments[0] === void 0) {
|
|
196
380
|
this._lockedExplicitly = false;
|
|
197
381
|
}
|
|
198
|
-
if (!this
|
|
382
|
+
if (!this.#_locked) {
|
|
199
383
|
return;
|
|
200
384
|
}
|
|
201
|
-
this
|
|
385
|
+
this.#_locked = false;
|
|
202
386
|
await this.listing.updateOne({
|
|
203
|
-
$set: { locked: this
|
|
387
|
+
$set: { locked: this.#_locked }
|
|
204
388
|
});
|
|
205
389
|
this._events.emit("unlock");
|
|
206
390
|
}
|
|
@@ -208,16 +392,28 @@ class Room {
|
|
|
208
392
|
logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
|
|
209
393
|
client.send(messageOrType, messageOrOptions, options);
|
|
210
394
|
}
|
|
211
|
-
broadcast(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (opts && opts.afterNextPatch) {
|
|
215
|
-
delete opts.afterNextPatch;
|
|
395
|
+
broadcast(type, message, options) {
|
|
396
|
+
if (options && options.afterNextPatch) {
|
|
397
|
+
delete options.afterNextPatch;
|
|
216
398
|
this._afterNextPatchQueue.push(["broadcast", arguments]);
|
|
217
399
|
return;
|
|
218
400
|
}
|
|
219
|
-
this.broadcastMessageType(
|
|
401
|
+
this.broadcastMessageType(type, message, options);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Broadcast bytes (UInt8Arrays) to a particular room
|
|
405
|
+
*/
|
|
406
|
+
broadcastBytes(type, message, options) {
|
|
407
|
+
if (options && options.afterNextPatch) {
|
|
408
|
+
delete options.afterNextPatch;
|
|
409
|
+
this._afterNextPatchQueue.push(["broadcastBytes", arguments]);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
this.broadcastMessageType(type, message, options);
|
|
220
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Checks whether mutations have occurred in the state, and broadcast them to all connected clients.
|
|
416
|
+
*/
|
|
221
417
|
broadcastPatch() {
|
|
222
418
|
if (this.onBeforePatch) {
|
|
223
419
|
this.onBeforePatch(this.state);
|
|
@@ -232,10 +428,16 @@ class Room {
|
|
|
232
428
|
this._dequeueAfterPatchMessages();
|
|
233
429
|
return hasChanges;
|
|
234
430
|
}
|
|
235
|
-
onMessage(messageType, callback) {
|
|
236
|
-
this.onMessageHandlers[messageType] = callback;
|
|
431
|
+
onMessage(messageType, callback, validate) {
|
|
432
|
+
this.onMessageHandlers[messageType] = this.onUncaughtException !== void 0 ? { validate, callback: wrapTryCatch(callback, this.onUncaughtException.bind(this), OnMessageException, "onMessage", false, messageType) } : { validate, callback };
|
|
237
433
|
return () => delete this.onMessageHandlers[messageType];
|
|
238
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* Disconnect all connected clients, and then dispose the room.
|
|
437
|
+
*
|
|
438
|
+
* @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
|
|
439
|
+
* @returns Promise<void>
|
|
440
|
+
*/
|
|
239
441
|
disconnect(closeCode = Protocol.WS_CLOSE_CONSENTED) {
|
|
240
442
|
if (this._internalState === 2 /* DISPOSING */) {
|
|
241
443
|
return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
|
|
@@ -247,7 +449,7 @@ class Room {
|
|
|
247
449
|
this.#_autoDispose = true;
|
|
248
450
|
const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
|
|
249
451
|
for (const [_, reconnection] of Object.values(this._reconnections)) {
|
|
250
|
-
reconnection.reject();
|
|
452
|
+
reconnection.reject(new Error("disconnecting"));
|
|
251
453
|
}
|
|
252
454
|
let numClients = this.clients.length;
|
|
253
455
|
if (numClients > 0) {
|
|
@@ -259,7 +461,7 @@ class Room {
|
|
|
259
461
|
}
|
|
260
462
|
return delayedDisconnection;
|
|
261
463
|
}
|
|
262
|
-
async ["_onJoin"](client,
|
|
464
|
+
async ["_onJoin"](client, authContext) {
|
|
263
465
|
const sessionId = client.sessionId;
|
|
264
466
|
client.reconnectionToken = generateId();
|
|
265
467
|
if (this.reservedSeatTimeouts[sessionId]) {
|
|
@@ -270,26 +472,38 @@ class Room {
|
|
|
270
472
|
clearTimeout(this._autoDisposeTimeout);
|
|
271
473
|
this._autoDisposeTimeout = void 0;
|
|
272
474
|
}
|
|
273
|
-
const [joinOptions, authData] = this.reservedSeats[sessionId];
|
|
274
|
-
if (
|
|
475
|
+
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.reservedSeats[sessionId];
|
|
476
|
+
if (isConsumed) {
|
|
275
477
|
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
|
|
276
478
|
}
|
|
277
|
-
this.reservedSeats[sessionId]
|
|
479
|
+
this.reservedSeats[sessionId][2] = true;
|
|
480
|
+
debugMatchMaking("consuming seat reservation, sessionId: '%s' (roomId: %s)", client.sessionId, this.roomId);
|
|
278
481
|
client._afterNextPatchQueue = this._afterNextPatchQueue;
|
|
279
482
|
client.ref["onleave"] = (_) => client.state = ClientState.LEAVING;
|
|
280
483
|
client.ref.once("close", client.ref["onleave"]);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
484
|
+
if (isWaitingReconnection) {
|
|
485
|
+
const previousReconnectionToken = this._reconnectingSessionId.get(sessionId);
|
|
486
|
+
if (previousReconnectionToken) {
|
|
487
|
+
this.clients.push(client);
|
|
488
|
+
await this._reconnections[previousReconnectionToken]?.[1].resolve(client);
|
|
489
|
+
} else {
|
|
490
|
+
const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
|
|
491
|
+
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
|
|
492
|
+
}
|
|
285
493
|
} else {
|
|
286
494
|
try {
|
|
287
495
|
if (authData) {
|
|
288
496
|
client.auth = authData;
|
|
289
|
-
} else if (this.onAuth !==
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
497
|
+
} else if (this.onAuth !== _Room.prototype.onAuth) {
|
|
498
|
+
try {
|
|
499
|
+
client.auth = await this.onAuth(client, joinOptions, authContext);
|
|
500
|
+
if (!client.auth) {
|
|
501
|
+
throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
|
|
502
|
+
}
|
|
503
|
+
} catch (e) {
|
|
504
|
+
delete this.reservedSeats[sessionId];
|
|
505
|
+
await this._decrementClientCount();
|
|
506
|
+
throw e;
|
|
293
507
|
}
|
|
294
508
|
}
|
|
295
509
|
if (client.state === ClientState.LEAVING) {
|
|
@@ -303,15 +517,15 @@ class Room {
|
|
|
303
517
|
if (this.onJoin) {
|
|
304
518
|
await this.onJoin(client, joinOptions, client.auth);
|
|
305
519
|
}
|
|
306
|
-
this._events.emit("join", client);
|
|
307
|
-
delete this.reservedSeats[sessionId];
|
|
308
520
|
if (client.state === ClientState.LEAVING) {
|
|
309
|
-
|
|
521
|
+
throw new Error("early_leave");
|
|
522
|
+
} else {
|
|
523
|
+
delete this.reservedSeats[sessionId];
|
|
524
|
+
this._events.emit("join", client);
|
|
310
525
|
}
|
|
311
526
|
} catch (e) {
|
|
312
|
-
this.
|
|
527
|
+
await this._onLeave(client, Protocol.WS_CLOSE_GOING_AWAY);
|
|
313
528
|
delete this.reservedSeats[sessionId];
|
|
314
|
-
this._decrementClientCount();
|
|
315
529
|
if (!e.code) {
|
|
316
530
|
e.code = ErrorCode.APPLICATION_ERROR;
|
|
317
531
|
}
|
|
@@ -330,9 +544,19 @@ class Room {
|
|
|
330
544
|
));
|
|
331
545
|
}
|
|
332
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
|
|
549
|
+
* If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
|
|
550
|
+
*
|
|
551
|
+
* @param previousClient - The client which is to be waiting until re-connection happens.
|
|
552
|
+
* @param seconds - Timeout period on re-connection in seconds.
|
|
553
|
+
*
|
|
554
|
+
* @returns Deferred<Client> - The differed is a promise like type.
|
|
555
|
+
* This type can forcibly reject the promise by calling `.reject()`.
|
|
556
|
+
*/
|
|
333
557
|
allowReconnection(previousClient, seconds) {
|
|
334
558
|
if (previousClient._enqueuedMessages !== void 0) {
|
|
335
|
-
return;
|
|
559
|
+
return Promise.reject(new Error("not joined"));
|
|
336
560
|
}
|
|
337
561
|
if (seconds === void 0) {
|
|
338
562
|
console.warn('DEPRECATED: allowReconnection() requires a second argument. Using "manual" mode.');
|
|
@@ -342,8 +566,7 @@ class Room {
|
|
|
342
566
|
seconds = Infinity;
|
|
343
567
|
}
|
|
344
568
|
if (this._internalState === 2 /* DISPOSING */) {
|
|
345
|
-
|
|
346
|
-
throw new Error("disconnecting");
|
|
569
|
+
return Promise.reject(new Error("disposing"));
|
|
347
570
|
}
|
|
348
571
|
const sessionId = previousClient.sessionId;
|
|
349
572
|
const reconnectionToken = previousClient.reconnectionToken;
|
|
@@ -362,8 +585,9 @@ class Room {
|
|
|
362
585
|
reconnection.then((newClient) => {
|
|
363
586
|
newClient.auth = previousClient.auth;
|
|
364
587
|
newClient.userData = previousClient.userData;
|
|
365
|
-
previousClient.ref = newClient.ref;
|
|
366
588
|
previousClient.state = ClientState.RECONNECTED;
|
|
589
|
+
previousClient.ref = newClient.ref;
|
|
590
|
+
previousClient.reconnectionToken = newClient.reconnectionToken;
|
|
367
591
|
clearTimeout(this.reservedSeatTimeouts[sessionId]);
|
|
368
592
|
cleanup();
|
|
369
593
|
}).catch(() => {
|
|
@@ -383,8 +607,8 @@ class Room {
|
|
|
383
607
|
}, timeoutInSeconds * 1e3);
|
|
384
608
|
}
|
|
385
609
|
broadcastMessageType(type, message, options = {}) {
|
|
386
|
-
debugMessage("broadcast: %O", message);
|
|
387
|
-
const encodedMessage = getMessageBytes.raw(Protocol.ROOM_DATA, type, message);
|
|
610
|
+
debugMessage("broadcast: %O (roomId: %s)", message, this.roomId);
|
|
611
|
+
const encodedMessage = message instanceof Uint8Array ? getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, void 0, message) : getMessageBytes.raw(Protocol.ROOM_DATA, type, message);
|
|
388
612
|
const except = typeof options.except !== "undefined" ? Array.isArray(options.except) ? options.except : [options.except] : void 0;
|
|
389
613
|
let numClients = this.clients.length;
|
|
390
614
|
while (numClients--) {
|
|
@@ -415,7 +639,7 @@ class Room {
|
|
|
415
639
|
if (!allowReconnection && this.hasReachedMaxClients()) {
|
|
416
640
|
return false;
|
|
417
641
|
}
|
|
418
|
-
this.reservedSeats[sessionId] = [joinOptions, authData];
|
|
642
|
+
this.reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
|
|
419
643
|
if (!allowReconnection) {
|
|
420
644
|
await this._incrementClientCount();
|
|
421
645
|
this.reservedSeatTimeouts[sessionId] = setTimeout(async () => {
|
|
@@ -431,7 +655,8 @@ class Room {
|
|
|
431
655
|
return true;
|
|
432
656
|
}
|
|
433
657
|
_disposeIfEmpty() {
|
|
434
|
-
const willDispose = this.#
|
|
658
|
+
const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
|
|
659
|
+
this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
|
|
435
660
|
if (willDispose) {
|
|
436
661
|
this._events.emit("dispose");
|
|
437
662
|
}
|
|
@@ -439,14 +664,14 @@ class Room {
|
|
|
439
664
|
}
|
|
440
665
|
async _dispose() {
|
|
441
666
|
this._internalState = 2 /* DISPOSING */;
|
|
442
|
-
|
|
667
|
+
this.listing.remove();
|
|
443
668
|
let userReturnData;
|
|
444
669
|
if (this.onDispose) {
|
|
445
670
|
userReturnData = this.onDispose();
|
|
446
671
|
}
|
|
447
|
-
if (this
|
|
448
|
-
clearInterval(this
|
|
449
|
-
this
|
|
672
|
+
if (this.#_patchInterval) {
|
|
673
|
+
clearInterval(this.#_patchInterval);
|
|
674
|
+
this.#_patchInterval = void 0;
|
|
450
675
|
}
|
|
451
676
|
if (this._simulationInterval) {
|
|
452
677
|
clearInterval(this._simulationInterval);
|
|
@@ -460,56 +685,48 @@ class Room {
|
|
|
460
685
|
this.clock.stop();
|
|
461
686
|
return await (userReturnData || Promise.resolve());
|
|
462
687
|
}
|
|
463
|
-
_onMessage(client,
|
|
688
|
+
_onMessage(client, buffer) {
|
|
464
689
|
if (client.state === ClientState.LEAVING) {
|
|
465
690
|
return;
|
|
466
691
|
}
|
|
467
|
-
const it = { offset:
|
|
468
|
-
const code =
|
|
469
|
-
if (!
|
|
470
|
-
debugAndPrintError(`${this.roomName} (${this.roomId}), couldn't decode message: ${
|
|
692
|
+
const it = { offset: 1 };
|
|
693
|
+
const code = buffer[0];
|
|
694
|
+
if (!buffer) {
|
|
695
|
+
debugAndPrintError(`${this.roomName} (roomId: ${this.roomId}), couldn't decode message: ${buffer}`);
|
|
471
696
|
return;
|
|
472
697
|
}
|
|
473
698
|
if (code === Protocol.ROOM_DATA) {
|
|
474
|
-
const messageType = decode.stringCheck(
|
|
699
|
+
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
700
|
+
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
475
701
|
let message;
|
|
476
702
|
try {
|
|
477
|
-
message =
|
|
478
|
-
debugMessage("received: '%s' -> %j", messageType, message);
|
|
703
|
+
message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
|
|
704
|
+
debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
705
|
+
if (messageTypeHandler?.validate !== void 0) {
|
|
706
|
+
message = messageTypeHandler.validate(message);
|
|
707
|
+
}
|
|
479
708
|
} catch (e) {
|
|
480
709
|
debugAndPrintError(e);
|
|
481
710
|
client.leave(Protocol.WS_CLOSE_WITH_ERROR);
|
|
482
711
|
return;
|
|
483
712
|
}
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
} else if (this.onMessageHandlers["*"]) {
|
|
487
|
-
this.onMessageHandlers["*"](client, messageType, message);
|
|
713
|
+
if (messageTypeHandler) {
|
|
714
|
+
messageTypeHandler.callback(client, message);
|
|
488
715
|
} else {
|
|
489
|
-
|
|
490
|
-
debugAndPrintError(errorMessage);
|
|
491
|
-
if (isDevMode) {
|
|
492
|
-
client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
493
|
-
} else {
|
|
494
|
-
client.leave(Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
|
|
495
|
-
}
|
|
716
|
+
(this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
|
|
496
717
|
}
|
|
497
718
|
} else if (code === Protocol.ROOM_DATA_BYTES) {
|
|
498
|
-
const messageType = decode.stringCheck(
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
719
|
+
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
720
|
+
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
721
|
+
let message = buffer.subarray(it.offset, buffer.byteLength);
|
|
722
|
+
debugMessage("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
723
|
+
if (messageTypeHandler?.validate !== void 0) {
|
|
724
|
+
message = messageTypeHandler.validate(message);
|
|
725
|
+
}
|
|
726
|
+
if (messageTypeHandler) {
|
|
727
|
+
messageTypeHandler.callback(client, message);
|
|
505
728
|
} else {
|
|
506
|
-
|
|
507
|
-
debugAndPrintError(errorMessage);
|
|
508
|
-
if (isDevMode) {
|
|
509
|
-
client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
510
|
-
} else {
|
|
511
|
-
client.leave(Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
|
|
512
|
-
}
|
|
729
|
+
(this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
|
|
513
730
|
}
|
|
514
731
|
} else if (code === Protocol.JOIN_ROOM && client.state === ClientState.JOINING) {
|
|
515
732
|
client.state = ClientState.JOINED;
|
|
@@ -531,32 +748,43 @@ class Room {
|
|
|
531
748
|
this._onLeave(client, closeCode).then(() => client.leave(closeCode));
|
|
532
749
|
}
|
|
533
750
|
async _onLeave(client, code) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
try {
|
|
539
|
-
await this.onLeave(client, code === Protocol.WS_CLOSE_CONSENTED);
|
|
540
|
-
} catch (e) {
|
|
541
|
-
debugAndPrintError(`onLeave error: ${e && e.message || e || "promise rejected"}`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
751
|
+
debugMatchMaking("onLeave, sessionId: '%s' (close code: %d, roomId: %s)", client.sessionId, code, this.roomId);
|
|
752
|
+
client.state = ClientState.LEAVING;
|
|
753
|
+
if (!this.clients.delete(client)) {
|
|
754
|
+
return;
|
|
544
755
|
}
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
this.
|
|
756
|
+
if (this.onLeave) {
|
|
757
|
+
try {
|
|
758
|
+
this.#_onLeaveConcurrent++;
|
|
759
|
+
await this.onLeave(client, code === Protocol.WS_CLOSE_CONSENTED);
|
|
760
|
+
} catch (e) {
|
|
761
|
+
debugAndPrintError(`onLeave error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
|
|
762
|
+
} finally {
|
|
763
|
+
this.#_onLeaveConcurrent--;
|
|
549
764
|
}
|
|
550
765
|
}
|
|
766
|
+
if (this._reconnections[client.reconnectionToken]) {
|
|
767
|
+
this._reconnections[client.reconnectionToken][1].catch(async () => {
|
|
768
|
+
await this._onAfterLeave(client);
|
|
769
|
+
});
|
|
770
|
+
} else if (client.state !== ClientState.RECONNECTED) {
|
|
771
|
+
await this._onAfterLeave(client);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async _onAfterLeave(client) {
|
|
775
|
+
const willDispose = await this._decrementClientCount();
|
|
776
|
+
if (this.reservedSeats[client.sessionId] === void 0) {
|
|
777
|
+
this._events.emit("leave", client, willDispose);
|
|
778
|
+
}
|
|
551
779
|
}
|
|
552
780
|
async _incrementClientCount() {
|
|
553
|
-
if (!this
|
|
554
|
-
this
|
|
781
|
+
if (!this.#_locked && this.hasReachedMaxClients()) {
|
|
782
|
+
this.#_maxClientsReached = true;
|
|
555
783
|
this.lock.call(this, true);
|
|
556
784
|
}
|
|
557
785
|
await this.listing.updateOne({
|
|
558
786
|
$inc: { clients: 1 },
|
|
559
|
-
$set: { locked: this
|
|
787
|
+
$set: { locked: this.#_locked }
|
|
560
788
|
});
|
|
561
789
|
}
|
|
562
790
|
async _decrementClientCount() {
|
|
@@ -565,18 +793,44 @@ class Room {
|
|
|
565
793
|
return true;
|
|
566
794
|
}
|
|
567
795
|
if (!willDispose) {
|
|
568
|
-
if (this
|
|
569
|
-
this
|
|
796
|
+
if (this.#_maxClientsReached && !this._lockedExplicitly) {
|
|
797
|
+
this.#_maxClientsReached = false;
|
|
570
798
|
this.unlock.call(this, true);
|
|
571
799
|
}
|
|
572
800
|
await this.listing.updateOne({
|
|
573
801
|
$inc: { clients: -1 },
|
|
574
|
-
$set: { locked: this
|
|
802
|
+
$set: { locked: this.#_locked }
|
|
575
803
|
});
|
|
576
804
|
}
|
|
577
805
|
return willDispose;
|
|
578
806
|
}
|
|
579
|
-
|
|
807
|
+
#registerUncaughtExceptionHandlers() {
|
|
808
|
+
const onUncaughtException = this.onUncaughtException.bind(this);
|
|
809
|
+
const originalSetTimeout = this.clock.setTimeout;
|
|
810
|
+
this.clock.setTimeout = (cb, timeout, ...args) => {
|
|
811
|
+
return originalSetTimeout.call(this.clock, wrapTryCatch(cb, onUncaughtException, TimedEventException, "setTimeout"), timeout, ...args);
|
|
812
|
+
};
|
|
813
|
+
const originalSetInterval = this.clock.setInterval;
|
|
814
|
+
this.clock.setInterval = (cb, timeout, ...args) => {
|
|
815
|
+
return originalSetInterval.call(this.clock, wrapTryCatch(cb, onUncaughtException, TimedEventException, "setInterval"), timeout, ...args);
|
|
816
|
+
};
|
|
817
|
+
if (this.onCreate !== void 0) {
|
|
818
|
+
this.onCreate = wrapTryCatch(this.onCreate.bind(this), onUncaughtException, OnCreateException, "onCreate", true);
|
|
819
|
+
}
|
|
820
|
+
if (this.onAuth !== void 0) {
|
|
821
|
+
this.onAuth = wrapTryCatch(this.onAuth.bind(this), onUncaughtException, OnAuthException, "onAuth", true);
|
|
822
|
+
}
|
|
823
|
+
if (this.onJoin !== void 0) {
|
|
824
|
+
this.onJoin = wrapTryCatch(this.onJoin.bind(this), onUncaughtException, OnJoinException, "onJoin", true);
|
|
825
|
+
}
|
|
826
|
+
if (this.onLeave !== void 0) {
|
|
827
|
+
this.onLeave = wrapTryCatch(this.onLeave.bind(this), onUncaughtException, OnLeaveException, "onLeave", true);
|
|
828
|
+
}
|
|
829
|
+
if (this.onDispose !== void 0) {
|
|
830
|
+
this.onDispose = wrapTryCatch(this.onDispose.bind(this), onUncaughtException, OnDisposeException, "onDispose");
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
580
834
|
export {
|
|
581
835
|
DEFAULT_SEAT_RESERVATION_TIME,
|
|
582
836
|
Room,
|