@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.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
6
|
var __export = (target, all) => {
|
|
8
7
|
for (var name in all)
|
|
@@ -16,53 +15,52 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
15
|
}
|
|
17
16
|
return to;
|
|
18
17
|
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
19
|
var Room_exports = {};
|
|
29
20
|
__export(Room_exports, {
|
|
30
21
|
DEFAULT_SEAT_RESERVATION_TIME: () => DEFAULT_SEAT_RESERVATION_TIME,
|
|
31
22
|
Room: () => Room,
|
|
32
|
-
RoomInternalState: () => RoomInternalState
|
|
23
|
+
RoomInternalState: () => RoomInternalState,
|
|
24
|
+
room: () => room,
|
|
25
|
+
validate: () => validate
|
|
33
26
|
});
|
|
34
27
|
module.exports = __toCommonJS(Room_exports);
|
|
35
28
|
var import_msgpackr = require("@colyseus/msgpackr");
|
|
36
29
|
var import_schema = require("@colyseus/schema");
|
|
37
|
-
var import_timer =
|
|
30
|
+
var import_timer = require("@colyseus/timer");
|
|
38
31
|
var import_events = require("events");
|
|
39
|
-
var import_Logger = require("./Logger.
|
|
40
|
-
var import_NoneSerializer = require("./serializer/NoneSerializer.
|
|
41
|
-
var import_SchemaSerializer = require("./serializer/SchemaSerializer.
|
|
42
|
-
var import_Protocol = require("./Protocol.
|
|
43
|
-
var import_Utils = require("./utils/Utils.
|
|
44
|
-
var
|
|
45
|
-
var
|
|
46
|
-
var
|
|
47
|
-
var
|
|
48
|
-
var
|
|
32
|
+
var import_Logger = require("./Logger.ts");
|
|
33
|
+
var import_NoneSerializer = require("./serializer/NoneSerializer.ts");
|
|
34
|
+
var import_SchemaSerializer = require("./serializer/SchemaSerializer.ts");
|
|
35
|
+
var import_Protocol = require("./Protocol.ts");
|
|
36
|
+
var import_Utils = require("./utils/Utils.ts");
|
|
37
|
+
var import_nanoevents = require("./utils/nanoevents.ts");
|
|
38
|
+
var import_DevMode = require("./utils/DevMode.ts");
|
|
39
|
+
var import_Debug = require("./Debug.ts");
|
|
40
|
+
var import_ServerError = require("./errors/ServerError.ts");
|
|
41
|
+
var import_Transport = require("./Transport.ts");
|
|
42
|
+
var import_RoomExceptions = require("./errors/RoomExceptions.ts");
|
|
43
|
+
var import_StandardSchema = require("./utils/StandardSchema.ts");
|
|
44
|
+
var import_core = require("@colyseus/core");
|
|
49
45
|
const DEFAULT_PATCH_RATE = 1e3 / 20;
|
|
50
46
|
const DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
|
|
51
47
|
const noneSerializer = new import_NoneSerializer.NoneSerializer();
|
|
52
48
|
const DEFAULT_SEAT_RESERVATION_TIME = Number(process.env.COLYSEUS_SEAT_RESERVATION_TIME || 15);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
function validate(format, handler) {
|
|
50
|
+
return { format, handler };
|
|
51
|
+
}
|
|
52
|
+
const RoomInternalState = {
|
|
53
|
+
CREATING: 0,
|
|
54
|
+
CREATED: 1,
|
|
55
|
+
DISPOSING: 2
|
|
56
|
+
};
|
|
59
57
|
class Room {
|
|
60
58
|
constructor() {
|
|
61
59
|
/**
|
|
62
60
|
* Timing events tied to the room instance.
|
|
63
61
|
* Intervals and timeouts are cleared when the room is disposed.
|
|
64
62
|
*/
|
|
65
|
-
this.clock = new import_timer.
|
|
63
|
+
this.clock = new import_timer.ClockTimer();
|
|
66
64
|
this.#_onLeaveConcurrent = 0;
|
|
67
65
|
// number of onLeave calls in progress
|
|
68
66
|
/**
|
|
@@ -84,40 +82,52 @@ class Room {
|
|
|
84
82
|
* @default 50ms (20fps)
|
|
85
83
|
*/
|
|
86
84
|
this.patchRate = DEFAULT_PATCH_RATE;
|
|
85
|
+
/**
|
|
86
|
+
* Maximum number of messages a client can send to the server per second.
|
|
87
|
+
* If a client sends more messages than this, it will be disconnected.
|
|
88
|
+
*
|
|
89
|
+
* @default 60
|
|
90
|
+
*/
|
|
91
|
+
this.maxMessagesPerSecond = 60;
|
|
87
92
|
/**
|
|
88
93
|
* The array of connected clients.
|
|
89
94
|
*
|
|
90
|
-
* @see
|
|
95
|
+
* @see [Client instance](https://docs.colyseus.io/room#client)
|
|
91
96
|
*/
|
|
92
97
|
this.clients = new import_Transport.ClientArray();
|
|
93
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Set the number of seconds a room can wait for a client to effectively join the room.
|
|
100
|
+
* You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
|
|
101
|
+
* The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
|
|
102
|
+
* environment variable if you'd like to change the seat reservation time globally.
|
|
103
|
+
*
|
|
104
|
+
* @default 15 seconds
|
|
105
|
+
*/
|
|
106
|
+
this.seatReservationTimeout = DEFAULT_SEAT_RESERVATION_TIME;
|
|
94
107
|
this._events = new import_events.EventEmitter();
|
|
95
|
-
|
|
96
|
-
this.
|
|
97
|
-
this.reservedSeats = {};
|
|
98
|
-
this.reservedSeatTimeouts = {};
|
|
108
|
+
this._reservedSeats = {};
|
|
109
|
+
this._reservedSeatTimeouts = {};
|
|
99
110
|
this._reconnections = {};
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
+
this.onMessageEvents = (0, import_nanoevents.createNanoEvents)();
|
|
112
|
+
this.onMessageValidators = {};
|
|
113
|
+
this.onMessageFallbacks = {
|
|
114
|
+
"__no_message_handler": (client, messageType, _) => {
|
|
115
|
+
const errorMessage = `room onMessage for "${messageType}" not registered.`;
|
|
116
|
+
(0, import_Debug.debugMessage)(`${errorMessage} (roomId: ${this.roomId})`);
|
|
117
|
+
if (import_DevMode.isDevMode) {
|
|
118
|
+
client.error(import_Protocol.ErrorCode.INVALID_PAYLOAD, errorMessage);
|
|
119
|
+
} else {
|
|
120
|
+
client.leave(import_Protocol.CloseCode.WITH_ERROR, errorMessage);
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
};
|
|
114
124
|
this._serializer = noneSerializer;
|
|
115
125
|
this._afterNextPatchQueue = [];
|
|
116
|
-
this._internalState =
|
|
126
|
+
this._internalState = RoomInternalState.CREATING;
|
|
117
127
|
this._lockedExplicitly = false;
|
|
118
128
|
this.#_locked = false;
|
|
119
129
|
this._events.once("dispose", () => {
|
|
120
|
-
this
|
|
130
|
+
this.#_dispose().catch((e) => (0, import_Debug.debugAndPrintError)(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"} (roomId: ${this.roomId})`)).finally(() => this._events.emit("disconnect"));
|
|
121
131
|
});
|
|
122
132
|
if (this.onUncaughtException !== void 0) {
|
|
123
133
|
this.#registerUncaughtExceptionHandlers();
|
|
@@ -133,8 +143,31 @@ class Room {
|
|
|
133
143
|
get locked() {
|
|
134
144
|
return this.#_locked;
|
|
135
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Get the room's matchmaking metadata.
|
|
148
|
+
*/
|
|
136
149
|
get metadata() {
|
|
137
|
-
return this.
|
|
150
|
+
return this._listing.metadata;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Set the room's matchmaking metadata.
|
|
154
|
+
*
|
|
155
|
+
* **Note**: This setter does NOT automatically persist. Use `setMatchmaking()` for automatic persistence.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```typescript
|
|
159
|
+
* class MyRoom extends Room<{ metadata: { difficulty: string; rating: number } }> {
|
|
160
|
+
* async onCreate() {
|
|
161
|
+
* this.metadata = { difficulty: "hard", rating: 1500 };
|
|
162
|
+
* }
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
set metadata(meta) {
|
|
167
|
+
if (this._internalState !== RoomInternalState.CREATING) {
|
|
168
|
+
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.APPLICATION_ERROR, "'metadata' can only be manually set during onCreate(). Use setMatchmaking() instead.");
|
|
169
|
+
}
|
|
170
|
+
this._listing.metadata = meta;
|
|
138
171
|
}
|
|
139
172
|
#_roomId;
|
|
140
173
|
#_roomName;
|
|
@@ -175,29 +208,14 @@ class Room {
|
|
|
175
208
|
enumerable: true,
|
|
176
209
|
get: () => this.#_maxClients,
|
|
177
210
|
set: (value) => {
|
|
178
|
-
this
|
|
179
|
-
if (this._internalState === 1 /* CREATED */) {
|
|
180
|
-
const hasReachedMaxClients = this.hasReachedMaxClients();
|
|
181
|
-
if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
|
|
182
|
-
this.#_maxClientsReached = false;
|
|
183
|
-
this.#_locked = false;
|
|
184
|
-
this.listing.locked = false;
|
|
185
|
-
}
|
|
186
|
-
if (hasReachedMaxClients) {
|
|
187
|
-
this.#_maxClientsReached = true;
|
|
188
|
-
this.#_locked = true;
|
|
189
|
-
this.listing.locked = true;
|
|
190
|
-
}
|
|
191
|
-
this.listing.maxClients = value;
|
|
192
|
-
this.listing.save();
|
|
193
|
-
}
|
|
211
|
+
this.setMatchmaking({ maxClients: value });
|
|
194
212
|
}
|
|
195
213
|
},
|
|
196
214
|
autoDispose: {
|
|
197
215
|
enumerable: true,
|
|
198
216
|
get: () => this.#_autoDispose,
|
|
199
217
|
set: (value) => {
|
|
200
|
-
if (value !== this.#_autoDispose && this._internalState !==
|
|
218
|
+
if (value !== this.#_autoDispose && this._internalState !== RoomInternalState.DISPOSING) {
|
|
201
219
|
this.#_autoDispose = value;
|
|
202
220
|
this.resetAutoDisposeTimeout();
|
|
203
221
|
}
|
|
@@ -214,6 +232,8 @@ class Room {
|
|
|
214
232
|
}
|
|
215
233
|
if (milliseconds !== null && milliseconds !== 0) {
|
|
216
234
|
this.#_patchInterval = setInterval(() => this.broadcastPatch(), milliseconds);
|
|
235
|
+
} else if (!this._simulationInterval) {
|
|
236
|
+
this.#_patchInterval = setInterval(() => this.clock.tick(), DEFAULT_SIMULATION_INTERVAL);
|
|
217
237
|
}
|
|
218
238
|
}
|
|
219
239
|
}
|
|
@@ -222,7 +242,16 @@ class Room {
|
|
|
222
242
|
if (this.#_state) {
|
|
223
243
|
this.state = this.#_state;
|
|
224
244
|
}
|
|
225
|
-
|
|
245
|
+
if (this.messages !== void 0) {
|
|
246
|
+
Object.entries(this.messages).forEach(([messageType, callback]) => {
|
|
247
|
+
if (typeof callback === "function") {
|
|
248
|
+
this.onMessage(messageType, callback.bind(this));
|
|
249
|
+
} else {
|
|
250
|
+
this.onMessage(messageType, callback.format, callback.handler.bind(this));
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
this.resetAutoDisposeTimeout(this.seatReservationTimeout);
|
|
226
255
|
this.clock.start();
|
|
227
256
|
}
|
|
228
257
|
/**
|
|
@@ -260,11 +289,27 @@ class Room {
|
|
|
260
289
|
* @returns roomId string
|
|
261
290
|
*/
|
|
262
291
|
set roomId(roomId) {
|
|
263
|
-
if (this._internalState !==
|
|
292
|
+
if (this._internalState !== RoomInternalState.CREATING && !import_DevMode.isDevMode) {
|
|
264
293
|
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.APPLICATION_ERROR, "'roomId' can only be overridden upon room creation.");
|
|
265
294
|
}
|
|
266
295
|
this.#_roomId = roomId;
|
|
267
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* This method is called before onJoin() - this is where you should authenticate the client
|
|
299
|
+
* @param client - The client that is authenticating.
|
|
300
|
+
* @param options - The options passed to the client when it is authenticating.
|
|
301
|
+
* @param context - The authentication context, including the token and the client's IP address.
|
|
302
|
+
* @returns The authentication result.
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* return {
|
|
307
|
+
* userId: 123,
|
|
308
|
+
* username: "John Doe",
|
|
309
|
+
* email: "john.doe@example.com",
|
|
310
|
+
* };
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
268
313
|
onAuth(client, options, context) {
|
|
269
314
|
return true;
|
|
270
315
|
}
|
|
@@ -279,7 +324,7 @@ class Room {
|
|
|
279
324
|
*/
|
|
280
325
|
onBeforeShutdown() {
|
|
281
326
|
this.disconnect(
|
|
282
|
-
import_DevMode.isDevMode ? import_Protocol.
|
|
327
|
+
import_DevMode.isDevMode ? import_Protocol.CloseCode.DEVMODE_RESTART : import_Protocol.CloseCode.SERVER_SHUTDOWN
|
|
283
328
|
);
|
|
284
329
|
}
|
|
285
330
|
/**
|
|
@@ -288,39 +333,31 @@ class Room {
|
|
|
288
333
|
* @returns boolean
|
|
289
334
|
*/
|
|
290
335
|
hasReachedMaxClients() {
|
|
291
|
-
return this.clients.length + Object.keys(this.
|
|
336
|
+
return this.clients.length + Object.keys(this._reservedSeats).length >= this.#_maxClients || this._internalState === RoomInternalState.DISPOSING;
|
|
292
337
|
}
|
|
293
338
|
/**
|
|
294
|
-
*
|
|
295
|
-
* You should consider how long your `onAuth()` will have to wait for setting a different seat reservation time.
|
|
296
|
-
* The default value is 15 seconds. You may set the `COLYSEUS_SEAT_RESERVATION_TIME`
|
|
297
|
-
* environment variable if you'd like to change the seat reservation time globally.
|
|
298
|
-
*
|
|
299
|
-
* @default 15 seconds
|
|
300
|
-
*
|
|
301
|
-
* @param seconds - number of seconds.
|
|
302
|
-
* @returns The modified Room object.
|
|
339
|
+
* @deprecated Use `seatReservationTimeout=` instead.
|
|
303
340
|
*/
|
|
304
341
|
setSeatReservationTime(seconds) {
|
|
305
|
-
|
|
342
|
+
console.warn(`DEPRECATED: .setSeatReservationTime(${seconds}) is deprecated. Assign a .seatReservationTimeout property value instead.`);
|
|
343
|
+
this.seatReservationTimeout = seconds;
|
|
306
344
|
return this;
|
|
307
345
|
}
|
|
308
346
|
hasReservedSeat(sessionId, reconnectionToken) {
|
|
309
|
-
const reservedSeat = this.
|
|
347
|
+
const reservedSeat = this._reservedSeats[sessionId];
|
|
310
348
|
if (reservedSeat === void 0) {
|
|
311
349
|
return false;
|
|
312
350
|
}
|
|
313
351
|
if (reservedSeat[3]) {
|
|
314
|
-
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId
|
|
352
|
+
return reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId;
|
|
315
353
|
} else {
|
|
316
354
|
return reservedSeat[2] === false;
|
|
317
355
|
}
|
|
318
356
|
}
|
|
319
357
|
checkReconnectionToken(reconnectionToken) {
|
|
320
358
|
const sessionId = this._reconnections[reconnectionToken]?.[0];
|
|
321
|
-
const reservedSeat = this.
|
|
359
|
+
const reservedSeat = this._reservedSeats[sessionId];
|
|
322
360
|
if (reservedSeat && reservedSeat[3]) {
|
|
323
|
-
this._reconnectingSessionId.set(sessionId, reconnectionToken);
|
|
324
361
|
return sessionId;
|
|
325
362
|
} else {
|
|
326
363
|
return void 0;
|
|
@@ -365,34 +402,125 @@ class Room {
|
|
|
365
402
|
setSerializer(serializer) {
|
|
366
403
|
this._serializer = serializer;
|
|
367
404
|
}
|
|
368
|
-
async setMetadata(meta) {
|
|
369
|
-
if (!this.
|
|
370
|
-
this.
|
|
405
|
+
async setMetadata(meta, persist = true) {
|
|
406
|
+
if (!this._listing.metadata) {
|
|
407
|
+
this._listing.metadata = meta;
|
|
371
408
|
} else {
|
|
372
409
|
for (const field in meta) {
|
|
373
410
|
if (!meta.hasOwnProperty(field)) {
|
|
374
411
|
continue;
|
|
375
412
|
}
|
|
376
|
-
this.
|
|
413
|
+
this._listing.metadata[field] = meta[field];
|
|
377
414
|
}
|
|
378
|
-
if ("markModified" in this.
|
|
379
|
-
this.
|
|
415
|
+
if ("markModified" in this._listing) {
|
|
416
|
+
this._listing.markModified("metadata");
|
|
380
417
|
}
|
|
381
418
|
}
|
|
382
|
-
if (this._internalState ===
|
|
383
|
-
await
|
|
419
|
+
if (persist && this._internalState === RoomInternalState.CREATED) {
|
|
420
|
+
await import_core.matchMaker.driver.persist(this._listing);
|
|
421
|
+
this._events.emit("metadata-change");
|
|
384
422
|
}
|
|
385
423
|
}
|
|
386
|
-
async setPrivate(bool = true) {
|
|
387
|
-
if (this.
|
|
388
|
-
this.
|
|
389
|
-
if (this._internalState ===
|
|
390
|
-
await
|
|
424
|
+
async setPrivate(bool = true, persist = true) {
|
|
425
|
+
if (this._listing.private === bool) return;
|
|
426
|
+
this._listing.private = bool;
|
|
427
|
+
if (persist && this._internalState === RoomInternalState.CREATED) {
|
|
428
|
+
await import_core.matchMaker.driver.persist(this._listing);
|
|
391
429
|
}
|
|
392
430
|
this._events.emit("visibility-change", bool);
|
|
393
431
|
}
|
|
394
432
|
/**
|
|
395
|
-
*
|
|
433
|
+
* Update multiple matchmaking/listing properties at once with a single persist operation.
|
|
434
|
+
* This is the recommended way to update room listing properties.
|
|
435
|
+
*
|
|
436
|
+
* @param updates - Object containing the properties to update
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```typescript
|
|
440
|
+
* // Update multiple properties at once
|
|
441
|
+
* await this.setMatchmaking({
|
|
442
|
+
* metadata: { difficulty: "hard", rating: 1500 },
|
|
443
|
+
* private: true,
|
|
444
|
+
* locked: true,
|
|
445
|
+
* maxClients: 10
|
|
446
|
+
* });
|
|
447
|
+
* ```
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
* ```typescript
|
|
451
|
+
* // Update only metadata
|
|
452
|
+
* await this.setMatchmaking({
|
|
453
|
+
* metadata: { status: "in_progress" }
|
|
454
|
+
* });
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* // Partial metadata update (merges with existing)
|
|
460
|
+
* await this.setMatchmaking({
|
|
461
|
+
* metadata: { ...this.metadata, round: this.metadata.round + 1 }
|
|
462
|
+
* });
|
|
463
|
+
* ```
|
|
464
|
+
*/
|
|
465
|
+
async setMatchmaking(updates) {
|
|
466
|
+
for (const key in updates) {
|
|
467
|
+
if (!updates.hasOwnProperty(key)) {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
switch (key) {
|
|
471
|
+
case "metadata": {
|
|
472
|
+
this.setMetadata(updates.metadata, false);
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case "private": {
|
|
476
|
+
this.setPrivate(updates.private, false);
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "locked": {
|
|
480
|
+
if (updates[key]) {
|
|
481
|
+
this.lock.call(this, true);
|
|
482
|
+
this._lockedExplicitly = true;
|
|
483
|
+
} else {
|
|
484
|
+
this.unlock.call(this, true);
|
|
485
|
+
this._lockedExplicitly = false;
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "maxClients": {
|
|
490
|
+
this.#_maxClients = updates.maxClients;
|
|
491
|
+
this._listing.maxClients = updates.maxClients;
|
|
492
|
+
const hasReachedMaxClients = this.hasReachedMaxClients();
|
|
493
|
+
if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
|
|
494
|
+
this.#_maxClientsReached = false;
|
|
495
|
+
this.#_locked = false;
|
|
496
|
+
this._listing.locked = false;
|
|
497
|
+
updates.locked = false;
|
|
498
|
+
}
|
|
499
|
+
if (hasReachedMaxClients) {
|
|
500
|
+
this.#_maxClientsReached = true;
|
|
501
|
+
this.#_locked = true;
|
|
502
|
+
this._listing.locked = true;
|
|
503
|
+
updates.locked = true;
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
case "clients": {
|
|
508
|
+
console.warn("setMatchmaking() does not allow updating 'clients' property.");
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
default: {
|
|
512
|
+
this._listing[key] = updates[key];
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (this._internalState === RoomInternalState.CREATED) {
|
|
518
|
+
await import_core.matchMaker.driver.update(this._listing, { $set: updates });
|
|
519
|
+
this._events.emit("metadata-change");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Lock the room. This prevents new clients from joining this room.
|
|
396
524
|
*/
|
|
397
525
|
async lock() {
|
|
398
526
|
this._lockedExplicitly = arguments[0] === void 0;
|
|
@@ -400,13 +528,15 @@ class Room {
|
|
|
400
528
|
return;
|
|
401
529
|
}
|
|
402
530
|
this.#_locked = true;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
531
|
+
if (this._lockedExplicitly) {
|
|
532
|
+
await import_core.matchMaker.driver.update(this._listing, {
|
|
533
|
+
$set: { locked: this.#_locked }
|
|
534
|
+
});
|
|
535
|
+
}
|
|
406
536
|
this._events.emit("lock");
|
|
407
537
|
}
|
|
408
538
|
/**
|
|
409
|
-
*
|
|
539
|
+
* Unlock the room. This allows new clients to join this room, if maxClients is not reached.
|
|
410
540
|
*/
|
|
411
541
|
async unlock() {
|
|
412
542
|
if (arguments[0] === void 0) {
|
|
@@ -416,19 +546,33 @@ class Room {
|
|
|
416
546
|
return;
|
|
417
547
|
}
|
|
418
548
|
this.#_locked = false;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
549
|
+
if (arguments[0] === void 0) {
|
|
550
|
+
await import_core.matchMaker.driver.update(this._listing, {
|
|
551
|
+
$set: { locked: this.#_locked }
|
|
552
|
+
});
|
|
553
|
+
}
|
|
422
554
|
this._events.emit("unlock");
|
|
423
555
|
}
|
|
424
556
|
send(client, messageOrType, messageOrOptions, options) {
|
|
425
557
|
import_Logger.logger.warn("DEPRECATION WARNING: use client.send(...) instead of this.send(client, ...)");
|
|
426
558
|
client.send(messageOrType, messageOrOptions, options);
|
|
427
559
|
}
|
|
428
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Broadcast a message to all connected clients.
|
|
562
|
+
* @param type - The type of the message.
|
|
563
|
+
* @param message - The message to broadcast.
|
|
564
|
+
* @param options - The options for the broadcast.
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* ```typescript
|
|
568
|
+
* this.broadcast('message', { message: 'Hello, world!' });
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
broadcast(type, ...args) {
|
|
572
|
+
const [message, options] = args;
|
|
429
573
|
if (options && options.afterNextPatch) {
|
|
430
574
|
delete options.afterNextPatch;
|
|
431
|
-
this._afterNextPatchQueue.push(["broadcast",
|
|
575
|
+
this._afterNextPatchQueue.push(["broadcast", [type, ...args]]);
|
|
432
576
|
return;
|
|
433
577
|
}
|
|
434
578
|
this.broadcastMessageType(type, message, options);
|
|
@@ -461,9 +605,30 @@ class Room {
|
|
|
461
605
|
this._dequeueAfterPatchMessages();
|
|
462
606
|
return hasChanges;
|
|
463
607
|
}
|
|
464
|
-
onMessage(
|
|
465
|
-
|
|
466
|
-
|
|
608
|
+
onMessage(_messageType, _validationSchema, _callback) {
|
|
609
|
+
const messageType = _messageType.toString();
|
|
610
|
+
const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
|
|
611
|
+
const callback = validationSchema === void 0 ? _validationSchema : _callback;
|
|
612
|
+
const removeListener = this.onMessageEvents.on(messageType, this.onUncaughtException !== void 0 ? (0, import_Utils.wrapTryCatch)(callback, this.onUncaughtException.bind(this), import_RoomExceptions.OnMessageException, "onMessage", false, _messageType) : callback);
|
|
613
|
+
if (validationSchema !== void 0) {
|
|
614
|
+
this.onMessageValidators[messageType] = validationSchema;
|
|
615
|
+
}
|
|
616
|
+
return () => {
|
|
617
|
+
removeListener();
|
|
618
|
+
if (this.onMessageEvents.events[messageType].length === 0) {
|
|
619
|
+
delete this.onMessageValidators[messageType];
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
onMessageBytes(_messageType, _validationSchema, _callback) {
|
|
624
|
+
const messageType = `_$b${_messageType}`;
|
|
625
|
+
const validationSchema = typeof _callback === "function" ? _validationSchema : void 0;
|
|
626
|
+
const callback = validationSchema === void 0 ? _validationSchema : _callback;
|
|
627
|
+
if (validationSchema !== void 0) {
|
|
628
|
+
return this.onMessage(messageType, validationSchema, callback);
|
|
629
|
+
} else {
|
|
630
|
+
return this.onMessage(messageType, callback);
|
|
631
|
+
}
|
|
467
632
|
}
|
|
468
633
|
/**
|
|
469
634
|
* Disconnect all connected clients, and then dispose the room.
|
|
@@ -471,14 +636,14 @@ class Room {
|
|
|
471
636
|
* @param closeCode WebSocket close code (default = 4000, which is a "consented leave")
|
|
472
637
|
* @returns Promise<void>
|
|
473
638
|
*/
|
|
474
|
-
disconnect(closeCode = import_Protocol.
|
|
475
|
-
if (this._internalState ===
|
|
639
|
+
disconnect(closeCode = import_Protocol.CloseCode.CONSENTED) {
|
|
640
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
476
641
|
return Promise.resolve(`disconnect() ignored: room (${this.roomId}) is already disposing.`);
|
|
477
|
-
} else if (this._internalState ===
|
|
642
|
+
} else if (this._internalState === RoomInternalState.CREATING) {
|
|
478
643
|
throw new Error("cannot disconnect during onCreate()");
|
|
479
644
|
}
|
|
480
|
-
this._internalState =
|
|
481
|
-
|
|
645
|
+
this._internalState = RoomInternalState.DISPOSING;
|
|
646
|
+
import_core.matchMaker.driver.remove(this._listing.roomId);
|
|
482
647
|
this.#_autoDispose = true;
|
|
483
648
|
const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
|
|
484
649
|
for (const [_, reconnection] of Object.values(this._reconnections)) {
|
|
@@ -487,38 +652,41 @@ class Room {
|
|
|
487
652
|
let numClients = this.clients.length;
|
|
488
653
|
if (numClients > 0) {
|
|
489
654
|
while (numClients--) {
|
|
490
|
-
this
|
|
655
|
+
this.#_forciblyCloseClient(this.clients[numClients], closeCode);
|
|
491
656
|
}
|
|
492
657
|
} else {
|
|
493
658
|
this._events.emit("dispose");
|
|
494
659
|
}
|
|
495
660
|
return delayedDisconnection;
|
|
496
661
|
}
|
|
497
|
-
async
|
|
662
|
+
async _onJoin(client, authContext, connectionOptions) {
|
|
498
663
|
const sessionId = client.sessionId;
|
|
499
664
|
client.reconnectionToken = (0, import_Utils.generateId)();
|
|
500
|
-
if (this.
|
|
501
|
-
clearTimeout(this.
|
|
502
|
-
delete this.
|
|
665
|
+
if (this._reservedSeatTimeouts[sessionId]) {
|
|
666
|
+
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
667
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
503
668
|
}
|
|
504
669
|
if (this._autoDisposeTimeout) {
|
|
505
670
|
clearTimeout(this._autoDisposeTimeout);
|
|
506
671
|
this._autoDisposeTimeout = void 0;
|
|
507
672
|
}
|
|
508
|
-
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this.
|
|
673
|
+
const [joinOptions, authData, isConsumed, isWaitingReconnection] = this._reservedSeats[sessionId];
|
|
509
674
|
if (isConsumed) {
|
|
510
675
|
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
|
|
511
676
|
}
|
|
512
|
-
this.
|
|
677
|
+
this._reservedSeats[sessionId][2] = true;
|
|
513
678
|
(0, import_Debug.debugMatchMaking)("consuming seat reservation, sessionId: '%s' (roomId: %s)", client.sessionId, this.roomId);
|
|
514
679
|
client._afterNextPatchQueue = this._afterNextPatchQueue;
|
|
515
680
|
client.ref["onleave"] = (_) => client.state = import_Transport.ClientState.LEAVING;
|
|
516
681
|
client.ref.once("close", client.ref["onleave"]);
|
|
517
682
|
if (isWaitingReconnection) {
|
|
518
|
-
const
|
|
519
|
-
if (
|
|
683
|
+
const reconnectionToken = connectionOptions?.reconnectionToken;
|
|
684
|
+
if (reconnectionToken && this._reconnections[reconnectionToken]?.[0] === sessionId) {
|
|
520
685
|
this.clients.push(client);
|
|
521
|
-
await this._reconnections[
|
|
686
|
+
await this._reconnections[reconnectionToken]?.[1].resolve(client);
|
|
687
|
+
if (this.onReconnect) {
|
|
688
|
+
await this.onReconnect(client);
|
|
689
|
+
}
|
|
522
690
|
} else {
|
|
523
691
|
const errorMessage = process.env.NODE_ENV === "production" ? "already consumed" : "bad reconnection token";
|
|
524
692
|
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_EXPIRED, errorMessage);
|
|
@@ -534,31 +702,31 @@ class Room {
|
|
|
534
702
|
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.AUTH_FAILED, "onAuth failed");
|
|
535
703
|
}
|
|
536
704
|
} catch (e) {
|
|
537
|
-
delete this.
|
|
538
|
-
await this
|
|
705
|
+
delete this._reservedSeats[sessionId];
|
|
706
|
+
await this.#_decrementClientCount();
|
|
539
707
|
throw e;
|
|
540
708
|
}
|
|
541
709
|
}
|
|
542
710
|
if (client.state === import_Transport.ClientState.LEAVING) {
|
|
543
|
-
throw new import_ServerError.ServerError(import_Protocol.
|
|
711
|
+
throw new import_ServerError.ServerError(import_Protocol.CloseCode.WITH_ERROR, "already disconnected");
|
|
544
712
|
}
|
|
545
713
|
this.clients.push(client);
|
|
546
|
-
Object.defineProperty(this.
|
|
547
|
-
value: this.
|
|
714
|
+
Object.defineProperty(this._reservedSeats, sessionId, {
|
|
715
|
+
value: this._reservedSeats[sessionId],
|
|
548
716
|
enumerable: false
|
|
549
717
|
});
|
|
550
718
|
if (this.onJoin) {
|
|
551
|
-
await this.onJoin(client, joinOptions
|
|
719
|
+
await this.onJoin(client, joinOptions);
|
|
552
720
|
}
|
|
553
721
|
if (client.state === import_Transport.ClientState.LEAVING) {
|
|
554
|
-
throw new
|
|
722
|
+
throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_UNHANDLED, "early_leave");
|
|
555
723
|
} else {
|
|
556
|
-
delete this.
|
|
724
|
+
delete this._reservedSeats[sessionId];
|
|
557
725
|
this._events.emit("join", client);
|
|
558
726
|
}
|
|
559
727
|
} catch (e) {
|
|
560
|
-
await this._onLeave(client, import_Protocol.
|
|
561
|
-
delete this.
|
|
728
|
+
await this._onLeave(client, import_Protocol.CloseCode.WITH_ERROR);
|
|
729
|
+
delete this._reservedSeats[sessionId];
|
|
562
730
|
if (!e.code) {
|
|
563
731
|
e.code = import_Protocol.ErrorCode.APPLICATION_ERROR;
|
|
564
732
|
}
|
|
@@ -573,7 +741,11 @@ class Room {
|
|
|
573
741
|
client.raw(import_Protocol.getMessageBytes[import_Protocol.Protocol.JOIN_ROOM](
|
|
574
742
|
client.reconnectionToken,
|
|
575
743
|
this._serializer.id,
|
|
576
|
-
|
|
744
|
+
/**
|
|
745
|
+
* if skipHandshake is true, we don't need to send the handshake
|
|
746
|
+
* (in case client already has handshake data)
|
|
747
|
+
*/
|
|
748
|
+
connectionOptions?.skipHandshake ? void 0 : this._serializer.handshake && this._serializer.handshake()
|
|
577
749
|
));
|
|
578
750
|
}
|
|
579
751
|
}
|
|
@@ -581,11 +753,19 @@ class Room {
|
|
|
581
753
|
* Allow the specified client to reconnect into the room. Must be used inside `onLeave()` method.
|
|
582
754
|
* If seconds is provided, the reconnection is going to be cancelled after the provided amount of seconds.
|
|
583
755
|
*
|
|
584
|
-
* @param
|
|
585
|
-
* @param seconds -
|
|
756
|
+
* @param client - The client that is allowed to reconnect into the room.
|
|
757
|
+
* @param seconds - The time in seconds that the client is allowed to reconnect into the room.
|
|
586
758
|
*
|
|
587
759
|
* @returns Deferred<Client> - The differed is a promise like type.
|
|
588
760
|
* This type can forcibly reject the promise by calling `.reject()`.
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```typescript
|
|
764
|
+
* onDrop(client: Client, code: CloseCode) {
|
|
765
|
+
* // Allow the client to reconnect into the room with a 15 seconds timeout.
|
|
766
|
+
* this.allowReconnection(client, 15);
|
|
767
|
+
* }
|
|
768
|
+
* ```
|
|
589
769
|
*/
|
|
590
770
|
allowReconnection(previousClient, seconds) {
|
|
591
771
|
if (previousClient._enqueuedMessages !== void 0) {
|
|
@@ -598,7 +778,7 @@ class Room {
|
|
|
598
778
|
if (seconds === "manual") {
|
|
599
779
|
seconds = Infinity;
|
|
600
780
|
}
|
|
601
|
-
if (this._internalState ===
|
|
781
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
602
782
|
return Promise.reject(new Error("disposing"));
|
|
603
783
|
}
|
|
604
784
|
const sessionId = previousClient.sessionId;
|
|
@@ -607,13 +787,12 @@ class Room {
|
|
|
607
787
|
const reconnection = new import_Utils.Deferred();
|
|
608
788
|
this._reconnections[reconnectionToken] = [sessionId, reconnection];
|
|
609
789
|
if (seconds !== Infinity) {
|
|
610
|
-
this.
|
|
790
|
+
this._reservedSeatTimeouts[sessionId] = setTimeout(() => reconnection.reject(false), seconds * 1e3);
|
|
611
791
|
}
|
|
612
792
|
const cleanup = () => {
|
|
613
793
|
delete this._reconnections[reconnectionToken];
|
|
614
|
-
delete this.
|
|
615
|
-
delete this.
|
|
616
|
-
this._reconnectingSessionId.delete(sessionId);
|
|
794
|
+
delete this._reservedSeats[sessionId];
|
|
795
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
617
796
|
};
|
|
618
797
|
reconnection.then((newClient) => {
|
|
619
798
|
newClient.auth = previousClient.auth;
|
|
@@ -622,11 +801,11 @@ class Room {
|
|
|
622
801
|
previousClient.state = import_Transport.ClientState.RECONNECTED;
|
|
623
802
|
previousClient.ref = newClient.ref;
|
|
624
803
|
previousClient.reconnectionToken = newClient.reconnectionToken;
|
|
625
|
-
clearTimeout(this.
|
|
626
|
-
|
|
627
|
-
}).catch(() => {
|
|
628
|
-
cleanup();
|
|
804
|
+
clearTimeout(this._reservedSeatTimeouts[sessionId]);
|
|
805
|
+
}, () => {
|
|
629
806
|
this.resetAutoDisposeTimeout();
|
|
807
|
+
}).finally(() => {
|
|
808
|
+
cleanup();
|
|
630
809
|
});
|
|
631
810
|
return reconnection;
|
|
632
811
|
}
|
|
@@ -637,7 +816,7 @@ class Room {
|
|
|
637
816
|
}
|
|
638
817
|
this._autoDisposeTimeout = setTimeout(() => {
|
|
639
818
|
this._autoDisposeTimeout = void 0;
|
|
640
|
-
this
|
|
819
|
+
this.#_disposeIfEmpty();
|
|
641
820
|
}, timeoutInSeconds * 1e3);
|
|
642
821
|
}
|
|
643
822
|
broadcastMessageType(type, message, options = {}) {
|
|
@@ -669,36 +848,52 @@ class Room {
|
|
|
669
848
|
this._afterNextPatchQueue.splice(0, length);
|
|
670
849
|
}
|
|
671
850
|
}
|
|
672
|
-
async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.
|
|
851
|
+
async _reserveSeat(sessionId, joinOptions = true, authData = void 0, seconds = this.seatReservationTimeout, allowReconnection = false, devModeReconnectionToken) {
|
|
673
852
|
if (!allowReconnection && this.hasReachedMaxClients()) {
|
|
674
853
|
return false;
|
|
675
854
|
}
|
|
676
|
-
|
|
855
|
+
(0, import_Debug.debugMatchMaking)(
|
|
856
|
+
"reserving seat. sessionId: '%s', roomId: '%s', processId: '%s'",
|
|
857
|
+
sessionId,
|
|
858
|
+
this.roomId,
|
|
859
|
+
import_core.matchMaker.processId
|
|
860
|
+
);
|
|
861
|
+
this._reservedSeats[sessionId] = [joinOptions, authData, false, allowReconnection];
|
|
677
862
|
if (!allowReconnection) {
|
|
678
|
-
await this
|
|
679
|
-
this.
|
|
680
|
-
delete this.
|
|
681
|
-
delete this.
|
|
682
|
-
await this
|
|
863
|
+
await this.#_incrementClientCount();
|
|
864
|
+
this._reservedSeatTimeouts[sessionId] = setTimeout(async () => {
|
|
865
|
+
delete this._reservedSeats[sessionId];
|
|
866
|
+
delete this._reservedSeatTimeouts[sessionId];
|
|
867
|
+
await this.#_decrementClientCount();
|
|
683
868
|
}, seconds * 1e3);
|
|
684
869
|
this.resetAutoDisposeTimeout(seconds);
|
|
685
870
|
}
|
|
686
|
-
if (
|
|
687
|
-
|
|
871
|
+
if (devModeReconnectionToken) {
|
|
872
|
+
const reconnection = new import_Utils.Deferred();
|
|
873
|
+
this._reconnections[devModeReconnectionToken] = [sessionId, reconnection];
|
|
688
874
|
}
|
|
689
875
|
return true;
|
|
690
876
|
}
|
|
691
|
-
|
|
877
|
+
async _reserveMultipleSeats(multipleSessionIds, multipleJoinOptions = true, multipleAuthData = void 0, seconds = this.seatReservationTimeout) {
|
|
878
|
+
let promises = [];
|
|
879
|
+
for (let i = 0; i < multipleSessionIds.length; i++) {
|
|
880
|
+
promises.push(this._reserveSeat(multipleSessionIds[i], multipleJoinOptions[i], multipleAuthData[i], seconds));
|
|
881
|
+
}
|
|
882
|
+
return await Promise.all(promises);
|
|
883
|
+
}
|
|
884
|
+
#_disposeIfEmpty() {
|
|
692
885
|
const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
|
|
693
|
-
this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.
|
|
886
|
+
this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this._reservedSeats).length === 0;
|
|
694
887
|
if (willDispose) {
|
|
695
888
|
this._events.emit("dispose");
|
|
696
889
|
}
|
|
697
890
|
return willDispose;
|
|
698
891
|
}
|
|
699
|
-
async _dispose() {
|
|
700
|
-
this._internalState =
|
|
701
|
-
this.
|
|
892
|
+
async #_dispose() {
|
|
893
|
+
this._internalState = RoomInternalState.DISPOSING;
|
|
894
|
+
if (this._listing?.roomId !== void 0) {
|
|
895
|
+
await import_core.matchMaker.driver.remove(this._listing.roomId);
|
|
896
|
+
}
|
|
702
897
|
let userReturnData;
|
|
703
898
|
if (this.onDispose) {
|
|
704
899
|
userReturnData = this.onDispose();
|
|
@@ -723,44 +918,59 @@ class Room {
|
|
|
723
918
|
if (client.state === import_Transport.ClientState.LEAVING) {
|
|
724
919
|
return;
|
|
725
920
|
}
|
|
726
|
-
const it = { offset: 1 };
|
|
727
|
-
const code = buffer[0];
|
|
728
921
|
if (!buffer) {
|
|
729
922
|
(0, import_Debug.debugAndPrintError)(`${this.roomName} (roomId: ${this.roomId}), couldn't decode message: ${buffer}`);
|
|
730
923
|
return;
|
|
731
924
|
}
|
|
925
|
+
if (this.clock.currentTime - client._lastMessageTime >= 1e3) {
|
|
926
|
+
client._numMessagesLastSecond = 0;
|
|
927
|
+
client._lastMessageTime = this.clock.currentTime;
|
|
928
|
+
} else if (++client._numMessagesLastSecond > this.maxMessagesPerSecond) {
|
|
929
|
+
return this.#_forciblyCloseClient(client, import_Protocol.CloseCode.WITH_ERROR);
|
|
930
|
+
}
|
|
931
|
+
const it = { offset: 1 };
|
|
932
|
+
const code = buffer[0];
|
|
732
933
|
if (code === import_Protocol.Protocol.ROOM_DATA) {
|
|
733
934
|
const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
|
|
734
|
-
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
735
935
|
let message;
|
|
736
936
|
try {
|
|
737
937
|
message = buffer.byteLength > it.offset ? (0, import_msgpackr.unpack)(buffer.subarray(it.offset, buffer.byteLength)) : void 0;
|
|
738
938
|
(0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
739
|
-
if (
|
|
740
|
-
message =
|
|
939
|
+
if (this.onMessageValidators[messageType] !== void 0) {
|
|
940
|
+
message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[messageType], message);
|
|
741
941
|
}
|
|
742
942
|
} catch (e) {
|
|
743
943
|
(0, import_Debug.debugAndPrintError)(e);
|
|
744
|
-
client.leave(import_Protocol.
|
|
944
|
+
client.leave(import_Protocol.CloseCode.WITH_ERROR);
|
|
745
945
|
return;
|
|
746
946
|
}
|
|
747
|
-
if (
|
|
748
|
-
|
|
947
|
+
if (this.onMessageEvents.events[messageType]) {
|
|
948
|
+
this.onMessageEvents.emit(messageType, client, message);
|
|
949
|
+
} else if (this.onMessageEvents.events["*"]) {
|
|
950
|
+
this.onMessageEvents.emit("*", client, messageType, message);
|
|
749
951
|
} else {
|
|
750
|
-
|
|
952
|
+
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
751
953
|
}
|
|
752
954
|
} else if (code === import_Protocol.Protocol.ROOM_DATA_BYTES) {
|
|
753
955
|
const messageType = import_schema.decode.stringCheck(buffer, it) ? import_schema.decode.string(buffer, it) : import_schema.decode.number(buffer, it);
|
|
754
|
-
const messageTypeHandler = this.onMessageHandlers[messageType];
|
|
755
956
|
let message = buffer.subarray(it.offset, buffer.byteLength);
|
|
756
957
|
(0, import_Debug.debugMessage)("received: '%s' -> %j (roomId: %s)", messageType, message, this.roomId);
|
|
757
|
-
|
|
758
|
-
|
|
958
|
+
const bytesMessageType = `_$b${messageType}`;
|
|
959
|
+
try {
|
|
960
|
+
if (this.onMessageValidators[bytesMessageType] !== void 0) {
|
|
961
|
+
message = (0, import_StandardSchema.standardValidate)(this.onMessageValidators[bytesMessageType], message);
|
|
962
|
+
}
|
|
963
|
+
} catch (e) {
|
|
964
|
+
(0, import_Debug.debugAndPrintError)(e);
|
|
965
|
+
client.leave(import_Protocol.CloseCode.WITH_ERROR);
|
|
966
|
+
return;
|
|
759
967
|
}
|
|
760
|
-
if (
|
|
761
|
-
|
|
968
|
+
if (this.onMessageEvents.events[bytesMessageType]) {
|
|
969
|
+
this.onMessageEvents.emit(bytesMessageType, client, message);
|
|
970
|
+
} else if (this.onMessageEvents.events["*"]) {
|
|
971
|
+
this.onMessageEvents.emit("*", client, messageType, message);
|
|
762
972
|
} else {
|
|
763
|
-
|
|
973
|
+
this.onMessageFallbacks["__no_message_handler"](client, messageType, message);
|
|
764
974
|
}
|
|
765
975
|
} else if (code === import_Protocol.Protocol.JOIN_ROOM && client.state === import_Transport.ClientState.JOINING) {
|
|
766
976
|
client.state = import_Transport.ClientState.JOINED;
|
|
@@ -772,58 +982,64 @@ class Room {
|
|
|
772
982
|
client._enqueuedMessages.forEach((enqueued) => client.raw(enqueued));
|
|
773
983
|
}
|
|
774
984
|
delete client._enqueuedMessages;
|
|
985
|
+
} else if (code === import_Protocol.Protocol.PING) {
|
|
986
|
+
client.raw(import_Protocol.getMessageBytes[import_Protocol.Protocol.PING]());
|
|
775
987
|
} else if (code === import_Protocol.Protocol.LEAVE_ROOM) {
|
|
776
|
-
this
|
|
988
|
+
this.#_forciblyCloseClient(client, import_Protocol.CloseCode.CONSENTED);
|
|
777
989
|
}
|
|
778
990
|
}
|
|
779
|
-
_forciblyCloseClient(client, closeCode) {
|
|
991
|
+
#_forciblyCloseClient(client, closeCode) {
|
|
780
992
|
client.ref.removeAllListeners("message");
|
|
781
993
|
client.ref.removeListener("close", client.ref["onleave"]);
|
|
782
994
|
this._onLeave(client, closeCode).then(() => client.leave(closeCode));
|
|
783
995
|
}
|
|
784
996
|
async _onLeave(client, code) {
|
|
785
|
-
(0, import_Debug.debugMatchMaking)("onLeave, sessionId: '%s' (close code: %d, roomId: %s)", client.sessionId, code, this.roomId);
|
|
786
997
|
client.state = import_Transport.ClientState.LEAVING;
|
|
787
998
|
if (!this.clients.delete(client)) {
|
|
788
999
|
return;
|
|
789
1000
|
}
|
|
790
|
-
|
|
1001
|
+
const method = code === import_Protocol.CloseCode.CONSENTED ? this.onLeave : this.onDrop || this.onLeave;
|
|
1002
|
+
if (method) {
|
|
1003
|
+
(0, import_Debug.debugMatchMaking)(`${method.name}, sessionId: '%s' (close code: %d, roomId: %s)`, client.sessionId, code, this.roomId);
|
|
791
1004
|
try {
|
|
792
1005
|
this.#_onLeaveConcurrent++;
|
|
793
|
-
await
|
|
1006
|
+
await method.call(this, client, code);
|
|
794
1007
|
} catch (e) {
|
|
795
|
-
(0, import_Debug.debugAndPrintError)(
|
|
1008
|
+
(0, import_Debug.debugAndPrintError)(`${method.name} error: ${e && e.message || e || "promise rejected"} (roomId: ${this.roomId})`);
|
|
796
1009
|
} finally {
|
|
797
1010
|
this.#_onLeaveConcurrent--;
|
|
798
1011
|
}
|
|
799
1012
|
}
|
|
800
1013
|
if (this._reconnections[client.reconnectionToken]) {
|
|
801
1014
|
this._reconnections[client.reconnectionToken][1].catch(async () => {
|
|
802
|
-
await this
|
|
1015
|
+
await this.#_onAfterLeave(client, code, method === this.onDrop);
|
|
803
1016
|
});
|
|
804
1017
|
} else if (client.state !== import_Transport.ClientState.RECONNECTED) {
|
|
805
|
-
await this
|
|
1018
|
+
await this.#_onAfterLeave(client, code, method === this.onDrop);
|
|
806
1019
|
}
|
|
807
1020
|
}
|
|
808
|
-
async _onAfterLeave(client) {
|
|
809
|
-
|
|
810
|
-
|
|
1021
|
+
async #_onAfterLeave(client, code, isDrop = false) {
|
|
1022
|
+
if (isDrop && this.onLeave) {
|
|
1023
|
+
await this.onLeave(client, code);
|
|
1024
|
+
}
|
|
1025
|
+
const willDispose = await this.#_decrementClientCount();
|
|
1026
|
+
if (this._reservedSeats[client.sessionId] === void 0) {
|
|
811
1027
|
this._events.emit("leave", client, willDispose);
|
|
812
1028
|
}
|
|
813
1029
|
}
|
|
814
|
-
async _incrementClientCount() {
|
|
1030
|
+
async #_incrementClientCount() {
|
|
815
1031
|
if (!this.#_locked && this.hasReachedMaxClients()) {
|
|
816
1032
|
this.#_maxClientsReached = true;
|
|
817
1033
|
this.lock.call(this, true);
|
|
818
1034
|
}
|
|
819
|
-
await
|
|
1035
|
+
await import_core.matchMaker.driver.update(this._listing, {
|
|
820
1036
|
$inc: { clients: 1 },
|
|
821
1037
|
$set: { locked: this.#_locked }
|
|
822
1038
|
});
|
|
823
1039
|
}
|
|
824
|
-
async _decrementClientCount() {
|
|
825
|
-
const willDispose = this
|
|
826
|
-
if (this._internalState ===
|
|
1040
|
+
async #_decrementClientCount() {
|
|
1041
|
+
const willDispose = this.#_disposeIfEmpty();
|
|
1042
|
+
if (this._internalState === RoomInternalState.DISPOSING) {
|
|
827
1043
|
return true;
|
|
828
1044
|
}
|
|
829
1045
|
if (!willDispose) {
|
|
@@ -831,7 +1047,7 @@ class Room {
|
|
|
831
1047
|
this.#_maxClientsReached = false;
|
|
832
1048
|
this.unlock.call(this, true);
|
|
833
1049
|
}
|
|
834
|
-
await
|
|
1050
|
+
await import_core.matchMaker.driver.update(this._listing, {
|
|
835
1051
|
$inc: { clients: -1 },
|
|
836
1052
|
$set: { locked: this.#_locked }
|
|
837
1053
|
});
|
|
@@ -865,9 +1081,28 @@ class Room {
|
|
|
865
1081
|
}
|
|
866
1082
|
}
|
|
867
1083
|
}
|
|
1084
|
+
function room(options) {
|
|
1085
|
+
class _ extends Room {
|
|
1086
|
+
constructor() {
|
|
1087
|
+
super();
|
|
1088
|
+
this.messages = options.messages;
|
|
1089
|
+
if (options.state && typeof options.state === "function") {
|
|
1090
|
+
this.state = options.state();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
for (const key in options) {
|
|
1095
|
+
if (typeof options[key] === "function") {
|
|
1096
|
+
_.prototype[key] = options[key];
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return _;
|
|
1100
|
+
}
|
|
868
1101
|
// Annotate the CommonJS export names for ESM import in node:
|
|
869
1102
|
0 && (module.exports = {
|
|
870
1103
|
DEFAULT_SEAT_RESERVATION_TIME,
|
|
871
1104
|
Room,
|
|
872
|
-
RoomInternalState
|
|
1105
|
+
RoomInternalState,
|
|
1106
|
+
room,
|
|
1107
|
+
validate
|
|
873
1108
|
});
|