Room API
The Room
class is meant to implement a game session, and/or serve as the communication channel between a group of clients.
- Rooms are created on demand during matchmaking by default
- Room classes must be exposed using
.define()
import http from "http";
import { Room, Client } from "colyseus";
export class MyRoom extends Room {
// (optional) Validate client auth token before joining/creating the room
static async onAuth (token: string, request: http.IncomingMessage) { }
// When room is initialized
onCreate (options: any) { }
// When client successfully join the room
onJoin (client: Client, options: any, auth: any) { }
// When a client leaves the room
onLeave (client: Client, consented: boolean) { }
// Cleanup callback, called after there are no more clients in the room. (see `autoDispose`)
onDispose () { }
}
Room Lifecycle Events
- The room lifecycle events are called automatically.
- Optional
async
/await
is supported on every lifecycle event.
On Create
Called once, when the room is created by the matchmaker. options
is the merged values specified on Server#define() with the options provided from the SDK at .joinOrCreate()
or .join()
.
onCreate (options) {
/**
* This is a good place to initialize your room state.
*/
}
The server may overwrite options during .define()
for authortity over client-provided options:
// (server-side)
// ...
gameServer.define("my_room", MyRoom, {
map: "cs_assault"
})
On this example, the map
option is "cs_assault"
during onCreate()
, and "de_dust2"
during onJoin()
.
On Auth
The onAuth
method is called before onJoin
, and it is responsible for validating the client’s request to join a room.
async onAuth (client, options, context) {
/**
* This is a good place to validate the client's auth token.
*/
}
Arguments
client
: A reference to the client that is trying to join the room.options
: Options provided by the client-side SDK.context
: The request context, containing:.token
- the authentication token sent by the client..headers
- the headers sent by the client..ip
- the IP address of the client.
See Authentication for more details.
On Join
Triggered when a client successfully joins the room, after a successful onAuth
.
Parameters:
client
: A reference to the client that joined the room.options
: Options provided by the Client SDK. (Merged with default values provided by Server#define())auth
: (optional) auth data returned byonAuth
method.
async onJoin (client, options, auth?) {
/**
* This is a good place to add the client to the state.
*/
}
See Client SDK on .joinOrCreate()
and .join()
.
On Leave
Triggered when a client leaves the room.
If the client leaves the room by closing the browser tab, the onLeave
method is called with consented
set to false
. If the client leaves the room by calling .leave()
, the onLeave
method is called with consented
set to true
.
async onLeave (client, consented) {
/**
* This is a good place to remove the client from the state.
*/
}
You may define this function as async
:
onLeave(client, consented) {
if (this.state.players.has(client.sessionId)) {
this.state.players.delete(client.sessionId);
}
}
At Graceful Shutdown, onLeave
is called with consented
set to true
for all clients.
On Dispose
The onDispose()
method is called before the room is destroyed, which happens when:
- there are no more clients left in the room, and
autoDispose
is set totrue
(default) - you manually call
.disconnect()
.
async onDispose() {
/**
* This is a good place to perform cleanup tasks.
*/
}
You may define async onDispose()
an asynchronous method in order to persist some data in the database. In fact, this is a great place to persist player’s data in the database after a game match ends.
At Graceful Shutdown, onDispose
is called for all rooms.
On Unhandled Exception
Opt-in to catch unhandled exceptions in your room. This method is called when an unhandled exception occurs in any of the lifecycle methods.
onUncaughtException (err: Error, methodName: string) {
console.error("An error ocurred in", methodName, ":", err);
err.cause // original unhandled error
err.message // original error message
}
See Exception Handling for more details.
On Before Patch
The onBeforePatch
lifecycle hook is triggered before state synchronization, at patch rate frequency. (see setPatchRate())
onBeforePatch() {
/*
* Here you can mutate something in the state just before it is encoded &
* synchronized with all clients
*/
}
On Cache Room (devMode
)
An optional hook to cache external data when devMode
is enabled.
(See restoring data outside the room’s state)
export class MyRoom extends Room<MyRoomState> {
// ...
onCacheRoom() {
return { foo: "bar" };
}
}
On Restore Room (devMode
)
An optional hook to reprocess/restore data which was returned and stored from the previous hook onCacheRoom
when Development Mode is enabled.
export class MyRoom extends Room<MyRoomState> {
// ...
onRestoreRoom(cachedData: any): void {
console.log("ROOM HAS BEEN RESTORED!", cachedData);
this.state.players.forEach(player => {
player.method(cachedData["foo"]);
});
}
}
On Before Shutdown
The onBeforeShutdown
lifecycle hook is called as part of the Graceful Shutdown process. The process will only truly shutdown after all rooms have been disposed.
By default, the room will disconnect all clients and dispose itself immediately.
You may customize how the room should behave during the shutdown process:
onBeforeShutdown() {
//
// Notify users that process is shutting down, they may need to save their progress and join a new room
//
this.broadcast("going-down", "Server is shutting down. Please save your progress and join a new room.");
//
// Disconnect all clients after 5 minutes
//
this.clock.setTimeout(() => this.disconnect(), 5 * 60 * 1000);
}
See graceful shutdown for more details.
Public methods
Room handlers have these methods available.
On Message
Register a callback to process a type of message sent by the client-side.
this.onMessage (type, callback, validation)
- The
type
argument can be eitherstring
ornumber
. - You can only define a single callback per message type. (Defining a callback more than once will result in overriding the previous one)
Callback for specific type of message
onCreate () {
this.onMessage("action", (client, payload) => {
console.log(client.sessionId, "sent 'action' message: ", payload);
});
}
Use room.send(type, payload)
from the client SDK to send messages to the server.
Fallback for all messages
You can register a single callback as a fallback to handle other types of messages.
onCreate () {
this.onMessage("action", (client, payload) => {
//
// Triggers when 'action' message is sent.
//
});
this.onMessage("*", (client, type, payload) => {
//
// Triggers when any other type of message is sent,
// excluding "action", which has its own specific handler defined above.
//
console.log(client.sessionId, "sent", type, payload);
});
}
Message input validation
You may provide a validation function as the third argument to onMessage
. This function will be called before the message is processed, and if it throws an error, the message will be ignored.
The data returned by the validation function will be passed as payload
on the message handler.
onCreate () {
this.onMessage("action", (client, payload) => {
//
// payload.x and payload.y are guaranteed to be numbers here.
//
console.log({ x: payload.x, y: payload.y });
}, (payload) => {
if (typeof(payload.x) !== "number" || typeof(payload.y) !== "number") {
throw new Error("Invalid payload");
}
return payload;
});
}
Set State
Set the synchronizable room state. See State Synchronization and Schema for more details.
import { Room } from "colyseus";
import { MyState } from "./MyState";
export class MyRoom extends Room {
state = new MyState();
}
The room’s state
is mutable. You should not reassign the state
object, but rather mutate it directly when updating the state.
The .setState()
method is deprecated. Use this.state
directly instead.
Set Simulation Interval
Optional: Set a simulation interval that can change the state of the game. The simulation interval is your game loop. Default simulation interval: 16.6ms (60fps)
this.setSimulationInterval (callback[, milliseconds=16.6])
onCreate () {
this.setSimulationInterval((deltaTime) => this.update(deltaTime));
}
update (deltaTime) {
// implement your physics or world updates here!
// this is a good place to update the room state
}
Set Patch Rate
Set frequency the patched state should be sent to all clients. Default is 50ms
(20fps)
this.setPatchRate (milliseconds)
Set Private
Set the room listing as private - or revert it to public, if false
is provided.
this.setPrivate (bool)
Set Metadata
Set metadata to this room. Each room instance may have metadata attached to it - the only purpose for attaching metadata is to differentiate one room from another when getting the list of available rooms via Lobby Room or Match-maker API.
this.setMetadata (metadata)
onCreate(options) {
this.setMetadata({ friendlyFire: options.friendlyFire });
}
Set Seat Reservation Time
Set the number of seconds a room can wait for a client to effectively join the room. You should consider how long your onAuth()
will have to wait for setting a different seat reservation time. The default value is 15 seconds.
this.setSeatReservationTime (seconds)
You may set the COLYSEUS_SEAT_RESERVATION_TIME
environment variable if you’d like to change the seat reservation time globally.
Broadcast Message
Send a message to all connected clients.
this.broadcast (type, payload, options?)
Available options are:
except
: aClient
, or array ofClient
instances not to send the message toafterNextPatch
: waits until next patch to broadcast the message
Broadcasting a message to all clients:
onCreate() {
this.onMessage("action", (client, payload) => {
// broadcast a message to all clients
this.broadcast("action-taken", "an action has been taken!");
});
}
The client will receive the message in the onMessage()
callback.
Lock Room
Locking the room will remove it from the pool of available rooms for new clients to connect to.
this.lock ()
Unlock Room
Unlocking the room returns it to the pool of available rooms for new clients to connect to.
this.unlock ()
Allow Reconnection
Allow the specified client to reconnect
into the room. Must be used inside onLeave()
method.
this.allowReconnection (client, seconds)
client
: the disconnectingClient
instanceseconds
: number of seconds to wait for client to perform.reconnect()
, or"manual"
, to allow for manual reconnection rejection (see second example)
Return type:
allowReconnection()
returns aDeferred<Client>
instance.- The returned
Deferred
instance is a promise-like structure, you can forcibly reject the reconnection by calling.reject()
on it. (see second example) Deferred
type can forcibly reject the promise by calling.reject()
(see second example)
Example: Rejecting the reconnection after a 20 second timeout.
async onLeave (client: Client, consented: boolean) {
// flag client as inactive for other users
this.state.players.get(client.sessionId).connected = false;
try {
if (consented) {
throw new Error("consented leave");
}
// allow disconnected client to reconnect into this room until 20 seconds
await this.allowReconnection(client, 20);
// client returned! let's re-activate it.
this.state.players.get(client.sessionId).connected = true;
} catch (e) {
// 20 seconds expired. let's remove the client.
this.state.players.delete(client.sessionId);
}
}
Disconnect
Disconnect all clients, then dispose.
this.disconnect ()
Broadcast Patch
You may not need this! - This method is called automatically by the framework.
This method will check whether mutations have occurred in the state
, and broadcast them to all connected clients.
this.broadcastPatch ()
If you’d like to have control over when to broadcast patches, you can do this by disabling the default patch interval:
onCreate() {
// disable automatic patches
this.setPatchRate(null);
// ensure clock timers are enabled
this.setSimulationInterval(() => {/* */});
this.clock.setInterval(() => {
// only broadcast patches if your custom conditions are met.
if (yourCondition) {
this.broadcastPatch();
}
}, 2000);
}
Public properties
roomId
A unique, auto-generated, 9-character-long identifier of the room.
You may replace this.roomId
during onCreate()
.
Using a custom roomId
Check out the guide How-to » Customize room id
roomName
The name of the room you provided as first argument for gameServer.define()
.
state
The synchronized state of the room.
clients
The array of connected clients. See Client instance.
Sending a message to a specific clients.
// ...
this.clients.forEach((client) => {
if (client.userData.team === "red") {
client.send("hello", "world");
}
});
// ...
Getting a client by sessionId
.
// ...
const client = this.clients.getById("UEsBFUBhK");
// ...
maxClients
Maximum number of clients allowed to connect into the room. When room reaches this limit, it is locked automatically. Unless the room was explicitly locked by you via lock() method, the room will be unlocked as soon as a client disconnects from it.
patchRate
Frequency to send the room state to connected clients, in milliseconds. Default is 50
ms (20fps)
autoDispose
Automatically dispose the room when last client disconnects. Default is true
locked
(read-only)
This property will change on these situations:
- The maximum number of allowed clients has been reached (
maxClients
) - You manually locked, or unlocked the room using
lock()
orunlock()
.
clock
It is recommended to use the clock
instance for setTimeout
and setInterval
methods, as timers and intervals are automatically cleared when the room is disposed - preventing memory leaks.
// ...
onCreate() {
this.clock.setTimeout(() => {
console.log("This message will be printed after 5 seconds");
}, 5000);
this.clock.setInterval(() => {
console.log("Current time:", this.clock.currentTime);
}, 1000);
}
// ...
See Timing Events.
presence
The presence
is used as a shared in-memory database for your cluster, and for pub/sub operations between rooms.
You may access the global presence
instance from your Room code.
// ...
onCreate() {
// publish an event to all rooms listening to "event-from-another-room"
this.presence.publish("event-name-from-another-room", { hello: "world" });
// subscribe to events from another room
this.presence.subscribe("event-name-from-another-room", (payload) => {
console.log("Received event from another room!", payload);
});
// set arbitrary value to the presence
this.presence.set("arbitrary-key", "value");
}
// ...
See Presence API.
Client
The client
instance from the server-side is responsible for the transport layer between the server and the client. It should not be confused with the Client
from the client-side SDK, as they have completely different purposes.
You operate on client
instances from this.clients
, Room#onJoin()
, Room#onLeave()
and Room#onMessage()
.
Properties
sessionId
Unique identifier of the client connection.
// ...
onJoin(client, options) {
console.log(client.sessionId);
}
// ...
In the client-side, you can find the sessionId
in the room
instance.
userData
The client.userData
can be used to store player-specific data easily accessible via the client
instance. This property is meant for convenience.
// ...
onJoin(client, options) {
client.userData = { team: (this.clients.length % 2 === 0) ? "red" : "blue" };
}
onLeave(client) {
console.log(client.userData.playerNumber);
}
// ...
auth
The client.auth
property holds the data returned by the onAuth()
method.
onAuth(token, request) {
return { userId: "123" };
}
onJoin(client, options) {
console.log(client.auth.userId);
}
See Authentication for more details.
Methods
Send Message
Send a type of message to the client. Messages are encoded with MsgPack and can hold any JSON-seriazeable data structure.
client.send(type, payload)
The type
can be either a string
or a number
.
//
// sending message of string type ("powerup")
//
client.send("powerup", { kind: "ammo" });
//
// sending message of number type (1)
//
client.send(1, { kind: "ammo"});
Send Message (in bytes)
Send a raw byte array message to the client.
client.sendBytes(type, bytes)
The type
can be either a string
or a number
.
This is useful if you’d like to manually encode a message, rather than the default encoding (MsgPack).
//
// sending message of string type ("powerup")
//
client.sendBytes("powerup", [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);
//
// sending message of number type (1)
//
client.sendBytes(1, [ 172, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 ]);
Send Error
Send an error with code and message to the client. The client can handle it on onError
client.error(code, payload)
Leave Room
Force disconnection of the client
with the room. You may send a custom code
when closing the connection, with values betweeen 4000
and 4999
(see table of WebSocket close codes)
client.leave(code?: number)
This will trigger room.onLeave
event on the client-side.
Table of WebSocket close codes
Close code (uint16) | Codename | Internal | Customizable | Description |
---|---|---|---|---|
0 - 999 | Yes | No | Unused | |
1000 | CLOSE_NORMAL | No | No | Successful operation / regular socket shutdown |
1001 | CLOSE_GOING_AWAY | No | No | Client is leaving (browser tab closing) |
1002 | CLOSE_PROTOCOL_ERROR | Yes | No | Endpoint received a malformed frame |
1003 | CLOSE_UNSUPPORTED | Yes | No | Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame) |
1004 | Yes | No | Reserved | |
1005 | CLOSED_NO_STATUS | Yes | No | Expected close status, received none |
1006 | CLOSE_ABNORMAL | Yes | No | No close code frame has been receieved |
1007 | Unsupported payload | Yes | No | Endpoint received inconsistent message (e.g. malformed UTF-8) |
1008 | Policy violation | No | No | Generic code used for situations other than 1003 and 1009 |
1009 | CLOSE_TOO_LARGE | No | No | Endpoint won’t process large frame |
1010 | Mandatory extension | No | No | Client wanted an extension which server did not negotiate |
1011 | Server error | No | No | Internal server error while operating |
1012 | Service restart | No | No | Server/service is restarting |
1013 | Try again later | No | No | Temporary server condition forced blocking client’s request |
1014 | Bad gateway | No | No | Server acting as gateway received an invalid response |
1015 | TLS handshake fail | Yes | No | Transport Layer Security handshake failure |
1016 - 1999 | Yes | No | Reserved for future use by the WebSocket standard. | |
2000 - 2999 | Yes | Yes | Reserved for use by WebSocket extensions | |
3000 - 3999 | No | Yes | Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve. | |
4000 - 4999 | No | Yes | Available for applications |