@colyseus/core 0.16.0-preview.3 → 0.16.0-preview.31
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 +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 +30 -22
- package/build/MatchMaker.js +110 -87
- package/build/MatchMaker.js.map +2 -2
- package/build/MatchMaker.mjs +114 -94
- package/build/MatchMaker.mjs.map +2 -2
- package/build/Protocol.d.ts +3 -4
- package/build/Protocol.js +28 -19
- package/build/Protocol.js.map +2 -2
- package/build/Protocol.mjs +31 -21
- package/build/Protocol.mjs.map +2 -2
- package/build/Room.d.ts +36 -35
- package/build/Room.js +270 -109
- package/build/Room.js.map +2 -2
- package/build/Room.mjs +274 -116
- package/build/Room.mjs.map +2 -2
- package/build/Server.d.ts +6 -7
- package/build/Server.js +40 -12
- package/build/Server.js.map +2 -2
- package/build/Server.mjs +40 -15
- package/build/Server.mjs.map +3 -3
- package/build/Stats.js +1 -1
- package/build/Stats.js.map +2 -2
- package/build/Stats.mjs +6 -5
- package/build/Stats.mjs.map +2 -2
- package/build/Transport.d.ts +24 -11
- 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/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 +20 -19
- package/build/index.js +28 -20
- package/build/index.js.map +2 -2
- package/build/index.mjs +21 -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 +3 -4
- package/build/matchmaker/controller.js +22 -5
- package/build/matchmaker/controller.js.map +2 -2
- package/build/matchmaker/controller.mjs +19 -3
- package/build/matchmaker/controller.mjs.map +2 -2
- package/build/matchmaker/driver/RoomData.d.ts +3 -3
- package/build/matchmaker/driver/RoomData.js +3 -3
- package/build/matchmaker/driver/RoomData.js.map +2 -2
- package/build/matchmaker/driver/RoomData.mjs +2 -2
- package/build/matchmaker/driver/RoomData.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 +7 -7
- package/build/matchmaker/driver/index.js +1 -1
- package/build/matchmaker/driver/index.js.map +2 -2
- package/build/matchmaker/driver/index.mjs +2 -2
- package/build/matchmaker/driver/index.mjs.map +2 -2
- package/build/matchmaker/driver/interfaces.d.ts +7 -11
- package/build/matchmaker/driver/interfaces.js.map +1 -1
- 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 -3
- package/build/presence/LocalPresence.js +83 -5
- package/build/presence/LocalPresence.js.map +3 -3
- package/build/presence/LocalPresence.mjs +83 -8
- 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 +2 -2
- 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 +13 -11
- package/build/serializer/SchemaSerializer.js +13 -10
- package/build/serializer/SchemaSerializer.js.map +2 -2
- package/build/serializer/SchemaSerializer.mjs +17 -13
- package/build/serializer/SchemaSerializer.mjs.map +2 -2
- 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 +4 -2
- package/build/utils/Utils.js +16 -15
- package/build/utils/Utils.js.map +2 -2
- package/build/utils/Utils.mjs +18 -21
- package/build/utils/Utils.mjs.map +2 -2
- package/package.json +9 -3
package/build/Room.mjs
CHANGED
|
@@ -1,57 +1,96 @@
|
|
|
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";
|
|
8
|
-
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";
|
|
13
|
-
import { ClientArray, ClientState } from "./Transport";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
import { logger } from "./Logger.mjs";
|
|
7
|
+
import { NoneSerializer } from "./serializer/NoneSerializer.mjs";
|
|
8
|
+
import { SchemaSerializer } from "./serializer/SchemaSerializer.mjs";
|
|
9
|
+
import { ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
|
|
10
|
+
import { Deferred, generateId } from "./utils/Utils.mjs";
|
|
11
|
+
import { isDevMode } from "./utils/DevMode.mjs";
|
|
12
|
+
import { debugAndPrintError, debugMessage } from "./Debug.mjs";
|
|
13
|
+
import { ServerError } from "./errors/ServerError.mjs";
|
|
14
|
+
import { ClientArray, ClientState } from "./Transport.mjs";
|
|
15
|
+
var DEFAULT_PATCH_RATE = 1e3 / 20;
|
|
16
|
+
var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
|
|
17
|
+
var noneSerializer = new NoneSerializer();
|
|
18
|
+
var DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
18
19
|
var RoomInternalState = /* @__PURE__ */ ((RoomInternalState2) => {
|
|
19
20
|
RoomInternalState2[RoomInternalState2["CREATING"] = 0] = "CREATING";
|
|
20
21
|
RoomInternalState2[RoomInternalState2["CREATED"] = 1] = "CREATED";
|
|
21
22
|
RoomInternalState2[RoomInternalState2["DISPOSING"] = 2] = "DISPOSING";
|
|
22
23
|
return RoomInternalState2;
|
|
23
24
|
})(RoomInternalState || {});
|
|
24
|
-
|
|
25
|
+
var Room = class _Room {
|
|
25
26
|
constructor() {
|
|
27
|
+
/**
|
|
28
|
+
* Timing events tied to the room instance.
|
|
29
|
+
* Intervals and timeouts are cleared when the room is disposed.
|
|
30
|
+
*/
|
|
26
31
|
this.clock = new Clock();
|
|
27
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Maximum number of clients allowed to connect into the room. When room reaches this limit,
|
|
34
|
+
* it is locked automatically. Unless the room was explicitly locked by you via `lock()` method,
|
|
35
|
+
* the room will be unlocked as soon as a client disconnects from it.
|
|
36
|
+
*/
|
|
28
37
|
this.maxClients = Infinity;
|
|
38
|
+
/**
|
|
39
|
+
* Automatically dispose the room when last client disconnects.
|
|
40
|
+
*
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
this.autoDispose = true;
|
|
44
|
+
/**
|
|
45
|
+
* Frequency to send the room state to connected clients, in milliseconds.
|
|
46
|
+
*
|
|
47
|
+
* @default 50ms (20fps)
|
|
48
|
+
*/
|
|
29
49
|
this.patchRate = DEFAULT_PATCH_RATE;
|
|
50
|
+
/**
|
|
51
|
+
* The array of connected clients.
|
|
52
|
+
*
|
|
53
|
+
* @see {@link https://docs.colyseus.io/colyseus/server/room/#client|Client instance}
|
|
54
|
+
*/
|
|
30
55
|
this.clients = new ClientArray();
|
|
56
|
+
/** @internal */
|
|
31
57
|
this._events = new EventEmitter();
|
|
58
|
+
// seat reservation & reconnection
|
|
32
59
|
this.seatReservationTime = DEFAULT_SEAT_RESERVATION_TIME;
|
|
33
60
|
this.reservedSeats = {};
|
|
34
61
|
this.reservedSeatTimeouts = {};
|
|
35
62
|
this._reconnections = {};
|
|
36
63
|
this._reconnectingSessionId = /* @__PURE__ */ new Map();
|
|
37
|
-
this.onMessageHandlers = {
|
|
64
|
+
this.onMessageHandlers = {
|
|
65
|
+
"__no_message_handler": {
|
|
66
|
+
callback: (client, messageType, _) => {
|
|
67
|
+
const errorMessage = `onMessage for "${messageType}" not registered.`;
|
|
68
|
+
debugAndPrintError(errorMessage);
|
|
69
|
+
if (isDevMode) {
|
|
70
|
+
client.error(ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
71
|
+
} else {
|
|
72
|
+
client.leave(Protocol.WS_CLOSE_WITH_ERROR, errorMessage);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
38
77
|
this._serializer = noneSerializer;
|
|
39
78
|
this._afterNextPatchQueue = [];
|
|
40
79
|
this._internalState = 0 /* CREATING */;
|
|
41
80
|
this._locked = false;
|
|
42
81
|
this._lockedExplicitly = false;
|
|
43
82
|
this._maxClientsReached = false;
|
|
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");
|
|
83
|
+
this._events.once("dispose", () => {
|
|
84
|
+
this._dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.message || e || "promise rejected"}`)).finally(() => this._events.emit("disconnect"));
|
|
51
85
|
});
|
|
52
|
-
this.setPatchRate(this.patchRate);
|
|
53
|
-
this.resetAutoDisposeTimeout(this.seatReservationTime);
|
|
54
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* This property will change on these situations:
|
|
89
|
+
* - The maximum number of allowed clients has been reached (`maxClients`)
|
|
90
|
+
* - You manually locked, or unlocked the room using lock() or `unlock()`.
|
|
91
|
+
*
|
|
92
|
+
* @readonly
|
|
93
|
+
*/
|
|
55
94
|
get locked() {
|
|
56
95
|
return this._locked;
|
|
57
96
|
}
|
|
@@ -61,64 +100,149 @@ class Room {
|
|
|
61
100
|
#_roomId;
|
|
62
101
|
#_roomName;
|
|
63
102
|
#_autoDispose;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
#_patchRate;
|
|
104
|
+
#_patchInterval;
|
|
105
|
+
__init() {
|
|
106
|
+
if (this.state) {
|
|
107
|
+
this.setState(this.state);
|
|
108
|
+
}
|
|
109
|
+
this.#_autoDispose = this.autoDispose;
|
|
110
|
+
this.#_patchRate = this.patchRate;
|
|
111
|
+
Object.defineProperties(this, {
|
|
112
|
+
autoDispose: {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
get: () => this.#_autoDispose,
|
|
115
|
+
set: (value) => {
|
|
116
|
+
if (value !== this.#_autoDispose && this._internalState !== 2 /* DISPOSING */) {
|
|
117
|
+
this.#_autoDispose = value;
|
|
118
|
+
this.resetAutoDisposeTimeout();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
patchRate: {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
get: () => this.#_patchRate,
|
|
125
|
+
set: (milliseconds) => {
|
|
126
|
+
this.#_patchRate = milliseconds;
|
|
127
|
+
if (this.#_patchInterval) {
|
|
128
|
+
clearInterval(this.#_patchInterval);
|
|
129
|
+
this.#_patchInterval = void 0;
|
|
130
|
+
}
|
|
131
|
+
if (milliseconds !== null && milliseconds !== 0) {
|
|
132
|
+
this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
this.patchRate = this.#_patchRate;
|
|
138
|
+
this.resetAutoDisposeTimeout(this.seatReservationTime);
|
|
72
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The name of the room you provided as first argument for `gameServer.define()`.
|
|
142
|
+
*
|
|
143
|
+
* @returns roomName string
|
|
144
|
+
*/
|
|
73
145
|
get roomName() {
|
|
74
146
|
return this.#_roomName;
|
|
75
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Setting the name of the room. Overwriting this property is restricted.
|
|
150
|
+
*
|
|
151
|
+
* @param roomName
|
|
152
|
+
*/
|
|
76
153
|
set roomName(roomName) {
|
|
77
154
|
if (this.#_roomName) {
|
|
78
155
|
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomName' cannot be overwritten.");
|
|
79
156
|
}
|
|
80
157
|
this.#_roomName = roomName;
|
|
81
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* A unique, auto-generated, 9-character-long id of the room.
|
|
161
|
+
* You may replace `this.roomId` during `onCreate()`.
|
|
162
|
+
*
|
|
163
|
+
* @returns roomId string
|
|
164
|
+
*/
|
|
82
165
|
get roomId() {
|
|
83
166
|
return this.#_roomId;
|
|
84
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Setting the roomId, is restricted in room lifetime except upon room creation.
|
|
170
|
+
*
|
|
171
|
+
* @param roomId
|
|
172
|
+
* @returns roomId string
|
|
173
|
+
*/
|
|
85
174
|
set roomId(roomId) {
|
|
86
175
|
if (this._internalState !== 0 /* CREATING */ && !isDevMode) {
|
|
87
176
|
throw new ServerError(ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
|
|
88
177
|
}
|
|
89
178
|
this.#_roomId = roomId;
|
|
90
179
|
}
|
|
180
|
+
// TODO: flag as @deprecated on v0.16
|
|
181
|
+
// TOOD: remove instance level `onAuth` on 1.0
|
|
182
|
+
/**
|
|
183
|
+
* onAuth at the instance level will be deprecated in the future.
|
|
184
|
+
* Please use "static onAuth(token, req) instead
|
|
185
|
+
*/
|
|
91
186
|
onAuth(client, options, request) {
|
|
92
187
|
return true;
|
|
93
188
|
}
|
|
94
189
|
static async onAuth(token, req) {
|
|
95
190
|
return true;
|
|
96
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Returns whether the sum of connected clients and reserved seats exceeds maximum number of clients.
|
|
194
|
+
*
|
|
195
|
+
* @returns boolean
|
|
196
|
+
*/
|
|
97
197
|
hasReachedMaxClients() {
|
|
98
198
|
return this.clients.length + Object.keys(this.reservedSeats).length >= this.maxClients || this._internalState === 2 /* DISPOSING */;
|
|
99
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Set the number of seconds a room can wait for a client to effectively join the room.
|
|
202
|
+
* You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
|
|
203
|
+
* The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
|
|
204
|
+
* environment variable if you'd like to change the seat reservation time globally.
|
|
205
|
+
*
|
|
206
|
+
* @default 15 seconds
|
|
207
|
+
*
|
|
208
|
+
* @param seconds - number of seconds.
|
|
209
|
+
* @returns The modified Room object.
|
|
210
|
+
*/
|
|
100
211
|
setSeatReservationTime(seconds) {
|
|
101
212
|
this.seatReservationTime = seconds;
|
|
102
213
|
return this;
|
|
103
214
|
}
|
|
104
215
|
hasReservedSeat(sessionId, reconnectionToken) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return
|
|
216
|
+
const reservedSeat = this.reservedSeats[sessionId];
|
|
217
|
+
if (reservedSeat === void 0) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (reservedSeat[3]) {
|
|
221
|
+
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId && this._reconnectingSessionId.has(sessionId);
|
|
108
222
|
} else {
|
|
109
|
-
return
|
|
223
|
+
return reservedSeat[2] === false;
|
|
110
224
|
}
|
|
111
225
|
}
|
|
112
226
|
checkReconnectionToken(reconnectionToken) {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
227
|
+
const sessionId = this._reconnections[reconnectionToken]?.[0];
|
|
228
|
+
const reservedSeat = this.reservedSeats[sessionId];
|
|
229
|
+
if (reservedSeat && reservedSeat[3]) {
|
|
116
230
|
this._reconnectingSessionId.set(sessionId, reconnectionToken);
|
|
117
231
|
return sessionId;
|
|
118
232
|
} else {
|
|
119
233
|
return void 0;
|
|
120
234
|
}
|
|
121
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* (Optional) Set a simulation interval that can change the state of the game.
|
|
238
|
+
* The simulation interval is your game loop.
|
|
239
|
+
*
|
|
240
|
+
* @default 16.6ms (60fps)
|
|
241
|
+
*
|
|
242
|
+
* @param onTickCallback - You can implement your physics or world updates here!
|
|
243
|
+
* This is a good place to update the room state.
|
|
244
|
+
* @param delay - Interval delay on executing `onTickCallback` in milliseconds.
|
|
245
|
+
*/
|
|
122
246
|
setSimulationInterval(onTickCallback, delay = DEFAULT_SIMULATION_INTERVAL) {
|
|
123
247
|
if (this._simulationInterval) {
|
|
124
248
|
clearInterval(this._simulationInterval);
|
|
@@ -130,15 +254,11 @@ class Room {
|
|
|
130
254
|
}, delay);
|
|
131
255
|
}
|
|
132
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* @deprecated Use `.patchRate=` instead.
|
|
259
|
+
*/
|
|
133
260
|
setPatchRate(milliseconds) {
|
|
134
261
|
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
262
|
}
|
|
143
263
|
setState(newState) {
|
|
144
264
|
this.clock.start();
|
|
@@ -172,14 +292,16 @@ class Room {
|
|
|
172
292
|
}
|
|
173
293
|
}
|
|
174
294
|
async setPrivate(bool = true) {
|
|
175
|
-
if (this.listing.private === bool)
|
|
176
|
-
return;
|
|
295
|
+
if (this.listing.private === bool) return;
|
|
177
296
|
this.listing.private = bool;
|
|
178
297
|
if (this._internalState === 1 /* CREATED */) {
|
|
179
298
|
await this.listing.save();
|
|
180
299
|
}
|
|
181
300
|
this._events.emit("visibility-change", bool);
|
|
182
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Locking the room will remove it from the pool of available rooms for new clients to connect to.
|
|
304
|
+
*/
|
|
183
305
|
async lock() {
|
|
184
306
|
this._lockedExplicitly = arguments[0] === void 0;
|
|
185
307
|
if (this._locked) {
|
|
@@ -191,6 +313,9 @@ class Room {
|
|
|
191
313
|
});
|
|
192
314
|
this._events.emit("lock");
|
|
193
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Unlocking the room returns it to the pool of available rooms for new clients to connect to.
|
|
318
|
+
*/
|
|
194
319
|
async unlock() {
|
|
195
320
|
if (arguments[0] === void 0) {
|
|
196
321
|
this._lockedExplicitly = false;
|
|
@@ -208,16 +333,28 @@ class Room {
|
|
|
208
333
|
logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
|
|
209
334
|
client.send(messageOrType, messageOrOptions, options);
|
|
210
335
|
}
|
|
211
|
-
broadcast(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (opts && opts.afterNextPatch) {
|
|
215
|
-
delete opts.afterNextPatch;
|
|
336
|
+
broadcast(type, message, options) {
|
|
337
|
+
if (options && options.afterNextPatch) {
|
|
338
|
+
delete options.afterNextPatch;
|
|
216
339
|
this._afterNextPatchQueue.push(["broadcast", arguments]);
|
|
217
340
|
return;
|
|
218
341
|
}
|
|
219
|
-
this.broadcastMessageType(
|
|
342
|
+
this.broadcastMessageType(type, message, options);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Broadcast bytes (UInt8Arrays) to a particular room
|
|
346
|
+
*/
|
|
347
|
+
broadcastBytes(type, message, options) {
|
|
348
|
+
if (options && options.afterNextPatch) {
|
|
349
|
+
delete options.afterNextPatch;
|
|
350
|
+
this._afterNextPatchQueue.push(["broadcastBytes", arguments]);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.broadcastMessageType(type, message, options);
|
|
220
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* Checks whether mutations have occurred in the state, and broadcast them to all connected clients.
|
|
357
|
+
*/
|
|
221
358
|
broadcastPatch() {
|
|
222
359
|
if (this.onBeforePatch) {
|
|
223
360
|
this.onBeforePatch(this.state);
|
|
@@ -232,10 +369,16 @@ class Room {
|
|
|
232
369
|
this._dequeueAfterPatchMessages();
|
|
233
370
|
return hasChanges;
|
|
234
371
|
}
|
|
235
|
-
onMessage(messageType, callback) {
|
|
236
|
-
this.onMessageHandlers[messageType] = callback;
|
|
372
|
+
onMessage(messageType, callback, validate) {
|
|
373
|
+
this.onMessageHandlers[messageType] = { callback, validate };
|
|
237
374
|
return () => delete this.onMessageHandlers[messageType];
|
|
238
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Disconnect all connected clients, and then dispose the room.
|
|
378
|
+
*
|
|
379
|
+
* @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
|
|
380
|
+
* @returns Promise<void>
|
|
381
|
+
*/
|
|
239
382
|
disconnect(closeCode = Protocol.WS_CLOSE_CONSENTED) {
|
|
240
383
|
if (this._internalState === 2 /* DISPOSING */) {
|
|
241
384
|
return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
|
|
@@ -261,7 +404,7 @@ class Room {
|
|
|
261
404
|
}
|
|
262
405
|
async ["_onJoin"](client, req) {
|
|
263
406
|
const sessionId = client.sessionId;
|
|
264
|
-
client.
|
|
407
|
+
client.reconnectionToken = generateId();
|
|
265
408
|
if (this.reservedSeatTimeouts[sessionId]) {
|
|
266
409
|
clearTimeout(this.reservedSeatTimeouts[sessionId]);
|
|
267
410
|
delete this.reservedSeatTimeouts[sessionId];
|
|
@@ -270,23 +413,28 @@ class Room {
|
|
|
270
413
|
clearTimeout(this._autoDisposeTimeout);
|
|
271
414
|
this._autoDisposeTimeout = void 0;
|
|
272
415
|
}
|
|
273
|
-
const [joinOptions, authData] = this.reservedSeats[sessionId];
|
|
274
|
-
if (
|
|
416
|
+
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.reservedSeats[sessionId];
|
|
417
|
+
if (isConsumed) {
|
|
275
418
|
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
|
|
276
419
|
}
|
|
277
|
-
this.reservedSeats[sessionId]
|
|
420
|
+
this.reservedSeats[sessionId][2] = true;
|
|
278
421
|
client._afterNextPatchQueue = this._afterNextPatchQueue;
|
|
279
422
|
client.ref["onleave"] = (_) => client.state = ClientState.LEAVING;
|
|
280
423
|
client.ref.once("close", client.ref["onleave"]);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
424
|
+
if (isWaitingReconnection) {
|
|
425
|
+
const previousReconnectionToken = this._reconnectingSessionId.get(sessionId);
|
|
426
|
+
if (previousReconnectionToken) {
|
|
427
|
+
this.clients.push(client);
|
|
428
|
+
await this._reconnections[previousReconnectionToken]?.[1].resolve(client);
|
|
429
|
+
} else {
|
|
430
|
+
const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
|
|
431
|
+
throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
|
|
432
|
+
}
|
|
285
433
|
} else {
|
|
286
434
|
try {
|
|
287
435
|
if (authData) {
|
|
288
436
|
client.auth = authData;
|
|
289
|
-
} else if (this.onAuth !==
|
|
437
|
+
} else if (this.onAuth !== _Room.prototype.onAuth) {
|
|
290
438
|
client.auth = await this.onAuth(client, joinOptions, req);
|
|
291
439
|
if (!client.auth) {
|
|
292
440
|
throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
|
|
@@ -324,15 +472,25 @@ class Room {
|
|
|
324
472
|
client.ref.once("close", client.ref["onleave"]);
|
|
325
473
|
client.ref.on("message", this._onMessage.bind(this, client));
|
|
326
474
|
client.raw(getMessageBytes[Protocol.JOIN_ROOM](
|
|
327
|
-
client.
|
|
475
|
+
client.reconnectionToken,
|
|
328
476
|
this._serializer.id,
|
|
329
477
|
this._serializer.handshake && this._serializer.handshake()
|
|
330
478
|
));
|
|
331
479
|
}
|
|
332
480
|
}
|
|
481
|
+
/**
|
|
482
|
+
* Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
|
|
483
|
+
* If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
|
|
484
|
+
*
|
|
485
|
+
* @param previousClient - The client which is to be waiting until re-connection happens.
|
|
486
|
+
* @param seconds - Timeout period on re-connection in seconds.
|
|
487
|
+
*
|
|
488
|
+
* @returns Deferred<Client> - The differed is a promise like type.
|
|
489
|
+
* This type can forcibly reject the promise by calling `.reject()`.
|
|
490
|
+
*/
|
|
333
491
|
allowReconnection(previousClient, seconds) {
|
|
334
492
|
if (previousClient._enqueuedMessages !== void 0) {
|
|
335
|
-
return;
|
|
493
|
+
return Deferred.reject("client not joined");
|
|
336
494
|
}
|
|
337
495
|
if (seconds === void 0) {
|
|
338
496
|
console.warn('DEPRECATED: allowReconnection() requires a second argument. Using "manual" mode.');
|
|
@@ -343,10 +501,10 @@ class Room {
|
|
|
343
501
|
}
|
|
344
502
|
if (this._internalState === 2 /* DISPOSING */) {
|
|
345
503
|
this._disposeIfEmpty();
|
|
346
|
-
|
|
504
|
+
return Deferred.reject("disconnecting");
|
|
347
505
|
}
|
|
348
506
|
const sessionId = previousClient.sessionId;
|
|
349
|
-
const reconnectionToken = previousClient.
|
|
507
|
+
const reconnectionToken = previousClient.reconnectionToken;
|
|
350
508
|
this._reserveSeat(sessionId, true, previousClient.auth, seconds, true);
|
|
351
509
|
const reconnection = new Deferred();
|
|
352
510
|
this._reconnections[reconnectionToken] = [sessionId, reconnection];
|
|
@@ -384,7 +542,7 @@ class Room {
|
|
|
384
542
|
}
|
|
385
543
|
broadcastMessageType(type, message, options = {}) {
|
|
386
544
|
debugMessage("broadcast: %O", message);
|
|
387
|
-
const encodedMessage = getMessageBytes.raw(Protocol.ROOM_DATA, type, message);
|
|
545
|
+
const encodedMessage = message instanceof Uint8Array ? getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, void 0, message) : getMessageBytes.raw(Protocol.ROOM_DATA, type, message);
|
|
388
546
|
const except = typeof options.except !== "undefined" ? Array.isArray(options.except) ? options.except : [options.except] : void 0;
|
|
389
547
|
let numClients = this.clients.length;
|
|
390
548
|
while (numClients--) {
|
|
@@ -395,7 +553,7 @@ class Room {
|
|
|
395
553
|
}
|
|
396
554
|
}
|
|
397
555
|
sendFullState(client) {
|
|
398
|
-
client.
|
|
556
|
+
client.raw(this._serializer.getFullState(client));
|
|
399
557
|
}
|
|
400
558
|
_dequeueAfterPatchMessages() {
|
|
401
559
|
const length = this._afterNextPatchQueue.length;
|
|
@@ -415,7 +573,7 @@ class Room {
|
|
|
415
573
|
if (!allowReconnection && this.hasReachedMaxClients()) {
|
|
416
574
|
return false;
|
|
417
575
|
}
|
|
418
|
-
this.reservedSeats[sessionId] = [joinOptions, authData];
|
|
576
|
+
this.reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
|
|
419
577
|
if (!allowReconnection) {
|
|
420
578
|
await this._incrementClientCount();
|
|
421
579
|
this.reservedSeatTimeouts[sessionId] = setTimeout(async () => {
|
|
@@ -439,14 +597,14 @@ class Room {
|
|
|
439
597
|
}
|
|
440
598
|
async _dispose() {
|
|
441
599
|
this._internalState = 2 /* DISPOSING */;
|
|
442
|
-
|
|
600
|
+
this.listing.remove();
|
|
443
601
|
let userReturnData;
|
|
444
602
|
if (this.onDispose) {
|
|
445
603
|
userReturnData = this.onDispose();
|
|
446
604
|
}
|
|
447
|
-
if (this
|
|
448
|
-
clearInterval(this
|
|
449
|
-
this
|
|
605
|
+
if (this.#_patchInterval) {
|
|
606
|
+
clearInterval(this.#_patchInterval);
|
|
607
|
+
this.#_patchInterval = void 0;
|
|
450
608
|
}
|
|
451
609
|
if (this._simulationInterval) {
|
|
452
610
|
clearInterval(this._simulationInterval);
|
|
@@ -460,59 +618,52 @@ class Room {
|
|
|
460
618
|
this.clock.stop();
|
|
461
619
|
return await (userReturnData || Promise.resolve());
|
|
462
620
|
}
|
|
463
|
-
_onMessage(client,
|
|
621
|
+
_onMessage(client, buffer) {
|
|
464
622
|
if (client.state === ClientState.LEAVING) {
|
|
465
623
|
return;
|
|
466
624
|
}
|
|
467
|
-
const it = { offset:
|
|
468
|
-
const code =
|
|
469
|
-
if (!
|
|
470
|
-
debugAndPrintError(`${this.roomName} (${this.roomId}), couldn't decode message: ${
|
|
625
|
+
const it = { offset: 1 };
|
|
626
|
+
const code = buffer[0];
|
|
627
|
+
if (!buffer) {
|
|
628
|
+
debugAndPrintError(`${this.roomName} (${this.roomId}), couldn't decode message: ${buffer}`);
|
|
471
629
|
return;
|
|
472
630
|
}
|
|
473
631
|
if (code === Protocol.ROOM_DATA) {
|
|
474
|
-
const messageType = decode.stringCheck(
|
|
632
|
+
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
633
|
+
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
475
634
|
let message;
|
|
476
635
|
try {
|
|
477
|
-
message =
|
|
636
|
+
message = buffer.byteLength > it.offset ? unpack(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
|
|
478
637
|
debugMessage("received: '%s' -> %j", messageType, message);
|
|
638
|
+
if (messageTypeHandler?.validate !== void 0) {
|
|
639
|
+
message = messageTypeHandler.validate(message);
|
|
640
|
+
}
|
|
479
641
|
} catch (e) {
|
|
480
642
|
debugAndPrintError(e);
|
|
481
643
|
client.leave(Protocol.WS_CLOSE_WITH_ERROR);
|
|
482
644
|
return;
|
|
483
645
|
}
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
} else if (this.onMessageHandlers["*"]) {
|
|
487
|
-
this.onMessageHandlers["*"](client, messageType, message);
|
|
646
|
+
if (messageTypeHandler) {
|
|
647
|
+
messageTypeHandler.callback(client, message);
|
|
488
648
|
} 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
|
-
}
|
|
649
|
+
(this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
|
|
496
650
|
}
|
|
497
651
|
} else if (code === Protocol.ROOM_DATA_BYTES) {
|
|
498
|
-
const messageType = decode.stringCheck(
|
|
499
|
-
const
|
|
652
|
+
const messageType = decode.stringCheck(buffer, it) ? decode.string(buffer, it) : decode.number(buffer, it);
|
|
653
|
+
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
654
|
+
let message = buffer.subarray(it.offset, buffer.byteLength);
|
|
500
655
|
debugMessage("received: '%s' -> %j", messageType, message);
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
|
|
656
|
+
if (messageTypeHandler?.validate !== void 0) {
|
|
657
|
+
message = messageTypeHandler.validate(message);
|
|
658
|
+
}
|
|
659
|
+
if (messageTypeHandler) {
|
|
660
|
+
messageTypeHandler.callback(client, message);
|
|
505
661
|
} 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
|
-
}
|
|
662
|
+
(this.onMessageHandlers["*"] || this.onMessageHandlers["__no_message_handler"]).callback(client, messageType, message);
|
|
513
663
|
}
|
|
514
664
|
} else if (code === Protocol.JOIN_ROOM && client.state === ClientState.JOINING) {
|
|
515
665
|
client.state = ClientState.JOINED;
|
|
666
|
+
client._joinedAt = this.clock.elapsedTime;
|
|
516
667
|
if (this.state) {
|
|
517
668
|
this.sendFullState(client);
|
|
518
669
|
}
|
|
@@ -541,11 +692,18 @@ class Room {
|
|
|
541
692
|
}
|
|
542
693
|
}
|
|
543
694
|
}
|
|
544
|
-
if (client.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
695
|
+
if (this._reconnections[client.reconnectionToken]) {
|
|
696
|
+
this._reconnections[client.reconnectionToken][1].catch(async () => {
|
|
697
|
+
await this._onAfterLeave(client);
|
|
698
|
+
});
|
|
699
|
+
} else if (client.state !== ClientState.RECONNECTED) {
|
|
700
|
+
await this._onAfterLeave(client);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async _onAfterLeave(client) {
|
|
704
|
+
const willDispose = await this._decrementClientCount();
|
|
705
|
+
if (this.reservedSeats[client.sessionId] === void 0) {
|
|
706
|
+
this._events.emit("leave", client, willDispose);
|
|
549
707
|
}
|
|
550
708
|
}
|
|
551
709
|
async _incrementClientCount() {
|
|
@@ -575,7 +733,7 @@ class Room {
|
|
|
575
733
|
}
|
|
576
734
|
return willDispose;
|
|
577
735
|
}
|
|
578
|
-
}
|
|
736
|
+
};
|
|
579
737
|
export {
|
|
580
738
|
DEFAULT_SEAT_RESERVATION_TIME,
|
|
581
739
|
Room,
|