Migrating Versions0.17

Migrating to version 0.17 (from 0.16)

⚠️

This guide may not be complete. Please report if you find any missing information.

See 0.17 release notes for more details.

Upgrading packages

Get all the latest 0.17.x versions for the package names you already have in your package.json:

package.json
{
    "dependencies": {
        "colyseus": "^0.17.8",
        "@colyseus/core": "^0.17.32",
        "@colyseus/tools": "^0.17.18",
        "@colyseus/ws-transport": "^0.17.9",
        "@colyseus/monitor": "^0.17.7", // (optional)
        "@colyseus/playground": "^0.17.10", // (optional)
        "@colyseus/redis-driver": "^0.17.6", // (optional)
        "@colyseus/redis-presence": "^0.17.6", // (optional)
        "@colyseus/auth": "^0.17.6", // (optional)
        "@colyseus/loadtest": "^0.17.8", // (optional)
        "@colyseus/testing": "^0.17.11", // (optional)
        "@colyseus/uwebsockets-transport": "^0.17.13", // (optional)
    }
}

The @colyseus/schema package is now at version 4.0.x

package.json
-   "@colyseus/schema": "^3.0.x",
+   "@colyseus/schema": "^4.0.8",

In the front-end, replace colyseus.js with the @colyseus/sdk package:

package.json
-   "colyseus.js": "^0.16.x",
+   "@colyseus/sdk": "^0.17.26",

uWebSockets.js (@colyseus/uwebsockets-transport)

If you use uWebSockets.js, it is now required to add uwebsockets-express as a dependency to your project.

package.json
-   "uwebsockets-express": "^1.3.x",
+   "uwebsockets-express": "^1.4.1",

(If you’d like to migrate to Express v5, you should use uwebsockets-express@2.0.1 instead.)

We are soft-deprecating how to structure the app.config.ts file. The previous syntax is still supported, but we recommend using the new one. Eventually, the previous syntax will be removed.

app.config.ts
-import config from "@colyseus/tools";
+import { defineServer, defineRoom } from "colyseus";
+import { MyRoom } from "./rooms/MyRoom";
 
-export default config({
+const server = defineServer({
-    initializeGameServer: (gameServer) => {
-        gameServer.define("my_room", MyRoom);
-    },
+    rooms: {
+        my_room: defineRoom(MyRoom),
+    },
-    initializeTransport: () => new uWebSocketsTransport(),
+    transport: new uWebSocketsTransport(),
-    initializeExpress: (app) => {}
+    express: (app) => {}
-    options: {/* presence: new RedisPresence(), driver: new RedisDriver(), etc. */}
+    /* presence: new RedisPresence(), driver: new RedisDriver(), etc. */
});

(The create-colyseus-app templates are now using this new structure.)

Breaking Changes

Backend

Matchmaker: Seat reservation format has changed

If you use the Matchmaker API for reserving seats (matchMaker.reserveSeatFor(room)), and manually access the .room object within the seat reservation object, you will need to update your code to access from the new format.

{
    "sessionId": "zzzzzzzzz",
-    "room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", ...other room data...}
+    "name": "battle",
+    "roomId": "xxxxxxxxx",
+    "processId": "yyyyyyyyy",
+    "sessionId": "zzzzzzzzz",
+    "publicAddress": "subdomain.domain.com",
}

Accessing the .roomId from the seat reservation object has changed:

const seatReservation = await matchMaker.reserveSeatFor(room);
-const roomId = seatReservation.room.roomId;
+const roomId = seatReservation.roomId;

The "room" object has been removed from the seat reservation object.

We basically removed redundant and unecessary information from the seat reservation object.

TypeScript: Room generic types have changed:

The Room<State, Metadata> generic type has changed to Room<{ state: S, metadata: M, client: C }>.

If you only use Room<State>, you can remove the generic types, and assign the state instance to the state property.

MyRoom.ts
-export class MyRoom extends Room<MyState> {
+export class MyRoom extends Room {
+    state = new MyState();

Alternatively, if you need to initialize the state in onCreate() (e.g., when passing arguments to the state constructor), you can use the new generic type format:

MyRoom.ts
export class MyRoom extends Room<{ state: MyState }> {
    onCreate(options) {
        this.state = new MyState(options.someArg);
    }
}

LobbyRoom: updateLobby() calls are not required anymore

There is no need to manually call updateLobby() after .setMetadata() anymore. This is done automatically for you.

Room’s this.presence.subscribe() / this.presence.unsubscribe()

There is no need to manually unsubscribe from presence channels in the onDispose() method anymore. Subscriptions are removed automatically when the room is disposed.

TypeScript: Client generic types have changed:

The Client<UserData, AuthData> generic types have changed to Client<{ userData: U, auth: A, messages: M }>.

interface UserData {
    your: any;
    custom: any;
    data: any;
}
interface AuthData {
    id: string;
    name: string;
}
 
-onJoin(client: Client<UserData, AuthData>) {
+onJoin(client: Client<{ userData: UserData, auth: AuthData }>) {
    console.log(client.userData);
    console.log(client.auth);
    console.log(client.messages);
}

Alternatively, you can create your own Client type alias:

import { type Client as MyClient } from "colyseus";
type Client = MyClient<{ userData: UserData, auth: AuthData }>;
// ...
onJoin(client: Client) {
    console.log(client.userData.custom);
    console.log(client.auth.name);
}

TypeScript: ClientArray generic types have changed:

Similar to Client, the ClientArray<UserData, AuthData> generic types have changed to ClientArray<Client<{ userData: U, auth: A, messages: M }>>.

-clients: ClientArray<UserData, AuthData>;
+clients: ClientArray<Client<{ userData: UserData, auth: AuthData }>>;

.setSeatReservationTime() method has moved:

The .setSeatReservationTime() method has moved to the .seatReservationTimeout property.

-   this.setSeatReservationTime(15);
+   this.seatReservationTimeout = 15;

Room onLeave()’s consented parameter have changed

The consented: boolean parameter have changed to code: number:

-   async onLeave (client: Client, consented: boolean) {
+   async onLeave (client: Client, code: number) {
}

In order to keep the same behavior, you can use the following:

import { CloseCode } from "colyseus";
 
async onLeave (client: Client, code: number) {
    const consented = (code === CloseCode.CONSENTED);
    // ... your existing code ...
}

Internal and undocumented Room properties have changed

If you were accessing or modifying internal undocumented properties of the Room class, you may need to update your code.

Example
-this.resetAutoDisposeTimeout();
+this["resetAutoDisposeTimeout"]();

List of renamed protected members:

Old MemberNew Member
this.resetAutoDisposeTimeoutthis["resetAutoDisposeTimeout"]
this.listingthis["_listing"]
this.reservedSeatsthis["_reservedSeats"]
this.reservedSeatTimeoutsthis["_reservedSeatTimeouts"]
this._eventsthis["_events"]
this._reconnectionsthis["_reconnections"]

Fallback for all messages

If you were using the onMessageHandlers property directly to register a fallback for all messages, you need to update your code to use the onMessageFallbacks property instead.

Example
-this.onMessageHandlers["__no_message_handler"] = {
-    callback: (client, type, payload) => {
-       // ... your existing code ...
-    }
-}}
+this["onMessageFallbacks"]["__no_message_handler"] = (client, type, payload) => {
+    // ... your existing code ...
+}

Protocol.WS_* constants have been moved to CloseCode

-import { Protocol } from "colyseus";
+import { CloseCode } from "colyseus";
Old (Protocol.WS_*)New (CloseCode.*)
WS_CLOSE_NORMALNORMAL_CLOSURE
WS_CLOSE_GOING_AWAYGOING_AWAY
WS_CLOSE_CONSENTEDCONSENTED
WS_CLOSE_WITH_ERRORWITH_ERROR
WS_CLOSE_DEVMODE_RESTARTMAY_TRY_RECONNECT
WS_SERVER_DISCONNECTSERVER_SHUTDOWN
WS_TOO_MANY_CLIENTS(removed)
🚫

Important: WebSocket Close Codes Conflict

Colyseus now uses close codes in the 4000-4010 range:

  • 4000 - CONSENTED
  • 4001 - SERVER_SHUTDOWN
  • 4002 - WITH_ERROR
  • 4003 - FAILED_TO_RECONNECT
  • 4010 - MAY_TRY_RECONNECT

If your application was previously using codes in the 4xxx range for custom purposes (e.g., account banned, kicked, etc.), you will need to update your code to use different values to avoid conflicts.

The WebSocket specification reserves 4000-4999 for application use, but Colyseus now uses part of this range internally.

Frontend

Accessing refId of Schema instances

You could previously access the refId of Schema instances via the room.serializer.decoder.root.refIds object.

-room.serializer.decoder.root.refIds.get(schemaInstance)

You may now access the refId of a Schema instance via the [$refId] property.

+import { $refId } from "@colyseus/schema";
+const refId = schemaInstance[$refId]