Upgrading to version 0.15 (from 0.14)
See version 0.15 release announcement.
@colyseus/arenahas been renamed to@colyseus/tools- Schema’s
.triggerAll()has been deprecated. - Schema callbacks API change
- Schema’s
onChangebehaviour change MapSchemais now strict on property accessorsclient.reconnect()API slightly changedallowReconnection(): second argument is now mandatory@colyseus/loadtesthas been reworked!@colyseus/commandtypings update- Built-in
client.authis gone!@colyseus/socialfully deprecated RedisDriverandRedisPresenceconstructor change- onLeave
1001and4000error codes range
@colyseus/arena has been renamed to @colyseus/tools
You can remove @colyseus/arena and install @colyseus/tools:
npm install --save @colyseus/tools// 0.14 (old)
import Arena from "@colyseus/arena";
export default Arena({...})
// 0.15 (new)
import config from "@colyseus/tools";
export default config({...})You can also rename the arena.config.ts file to app.config.ts for consistency.
Schema’s .triggerAll() has been deprecated
Now, whenever you register an onAdd() callback, it is called immediately for already existing items - thus, making the usage of .triggerAll() not necessary anymore.
You can prevent the onAdd() callback from being triggered automatically, though, if necessary.
state.players.onAdd(() => {
/*
* a player has been added
*/
}, false);By providing false in the second argument, the onAdd() callback is not going to be triggered for already existing items.
Schema callbacks API slightly changed
Now, instead of assigning a single callback per onAdd/onChange/onRemove, you attach them by calling as a method instead. You can attach more than one callback this way.
See example below:
=== “TypeScript”
// 0.14 (old)
state.players.onAdd = function(value, key) {/* do stuff */};
state.players.onChange = function(value, key) {/* do stuff */};
state.players.onRemove = function(value, key) {/* do stuff */};
// 0.15 (new)
state.players.onAdd(function(value, key) {/* do stuff */});
state.players.onChange(function(value, key) {/* do stuff */});
state.players.onRemove(function(value, key) {/* do stuff */});=== “C#”
// 0.14 (old)
state.players.OnAdd += (key, value) => {/* do stuff */};
state.players.OnChange += (key, value) => {/* do stuff */};
state.players.OnRemove += (key, value) => {/* do stuff */};
// 0.15 (new)
state.players.OnAdd((key, value) => {/* do stuff */})
state.players.OnChange((key, value) => {/* do stuff */})
state.players.OnRemove((key, value) => {/* do stuff */})=== “Lua”
-- 0.14 (old)
state.players.on_add = function(value, key) --[[ do stuff ]] end
state.players.on_change = function(value, key) --[[ do stuff ]] end
state.players.on_remove = function(value, key) --[[ do stuff ]] end
-- 0.15 (new)
-- ATTENTION: this is a method call. make sure to use `:` instead of `.` here.
state.players:on_add(function(value, key) --[[ do stuff ]] end)
state.players:on_change(function(value, key) --[[ do stuff ]] end)
state.players:on_remove(function(value, key) --[[ do stuff ]] end)=== “Haxe”
// 0.14 (old)
state.players.onAdd = function(value, key) {/* do stuff */};
state.players.onChange = function(value, key) {/* do stuff */};
state.players.onRemove = function(value, key) {/* do stuff */};
// 0.15 (new)
state.players.onAdd(function(value, key) {/* do stuff */});
state.players.onChange(function(value, key) {/* do stuff */});
state.players.onRemove(function(value, key) {/* do stuff */});The return value of onAdd()/onChange()/onRemove() is a function that can detach the callback that have been added.
=== “TypeScript”
const detachCallback = state.players.onAdd(function(value, key) {/* do stuff */});
// detaches the onAdd callback.
detachCallback();=== “C#”
var detachCallback = state.players.OnAdd((key, value) => {/* do stuff */})
// detaches the onAdd callback.
detachCallback();=== “Lua”
local detach_callback = state.players:on_add(function(value, key) --[[ do stuff ]] end)
-- detaches the onAdd callback.
detach_callback();=== “Haxe”
var detachCallback = state.players.onAdd(function(value, key) {/* do stuff */});
// detaches the onAdd callback.
detachCallback();Schema’s onChange behaviour change
On Schema instances
The list of changes inside the onChange callback is not provided anymore.
You can still use onChange to detect wheter the instance object had changes,
but you should use .listen() to listen for its particular properties:
// 0.14 (old)
state.player.onChange = function(changes) {
console.log("player changed!");
changes.forEach((change) => {
if (change.field === "fieldname") {
console.log(change.value);
}
})
}
// 0.15 (new)
state.player.onChange(() =>{
console.log("player changed!");
});
state.player.listen("field", (value) => {
console.log(value);
})On ArraySchema and MapSchema
Previously, the onChange callback wouldn’t be fired during onAdd and onRemove when attached to a collection of items (MapSchema, ArraySchema, etc).
Now, onChange is triggered alongside with onAdd and onRemove.
MapSchema is now strict on property accessors
Only JavaScript/TypeScript is affected. If you use a client-side SDK other than JavaScript/TypeScript, no change is needed on the client-side for you.
// 0.14 (old)
this.state.players[client.sessionId] = new Player();
// 0.15 (new)
this.state.players.set(client.sessionId, new Player());Reasoning:
MapSchemaused to be treated as a regular JavaScript object in the early days. Since version 0.14MapSchemauses a realMapinternally, with a “proxy” compatibility layer in order to avoid breaking existing projects. Now the “proxy” layer has been removed, improving the performance slightly.
client.reconnect() API slightly changed
The previous reconnection implementation had a security vulnerability, although very unlikely to be explored, we had to update its implementation to make it secure.
// 0.14 (old)
client.reconnect(cachedRoomId, cachedSessionId)
// 0.15 (new)
client.reconnect(cachedReconnectionToken)Insterad of providing the previously active room.roomId and room.sessionId for reconnection, you only provide the room.reconnectionToken instead.
Reconnection tokens are unique and private for each client.
allowReconnection(): second argument is now mandatory
Previously, by omitting the second argument of allowReconnection(), you were in control over when to cancel the possibility for reconnection.
To make this intent more explicit, it is now mandatory to provide the second argument either as "manual", or the number of seconds to wait for reconnection:
=== “Manual”
async onLeave (client: Client, consented: boolean) {
// ...
try {
if (consented) { throw new Error("consented leave"); }
//
// Get reconnection token
// NOTE: do not use `await` here yet!
//
const reconnection = this.allowReconnection(client, "manual");
//
// here is the custom logic for rejecting the reconnection.
// for demonstration purposes of the API, an interval is created
// rejecting the reconnection if the player has missed 2 rounds,
// (assuming he's playing a turn-based game)
//
// in a real scenario, you would store the `reconnection` in
// your Player instance, for example, and perform this check during your
// game loop logic
//
const currentRound = this.state.currentRound;
const interval = setInterval(() => {
if ((this.state.currentRound - currentRound) > 2) {
// manually reject the client reconnection
reconnection.reject();
clearInterval(interval);
}
}, 1000);
// now it's time to `await` for the reconnection
await reconnection;
// client returned! let's re-activate it.
// ...
} catch (e) {
// reconnection has been rejected. let's remove the client.
// ...
}
}=== “Number of seconds”
async onLeave (client: Client, consented: boolean) {
try {
if (consented) { throw new Error("consented leave"); }
// allow disconnected client to reconnect into this room until 20 seconds
await this.allowReconnection(client, 20);
} catch (e) {
// 20 seconds expired. reconnection not successful.
}
}@colyseus/loadtest has been reworked!
The loadtest tool has been reworked to allow for more complex scripting, so your loadtest scripts will need to be slightly rewritten, as the new format looks like this:
import { Client, Room } from "colyseus.js";
import { cli, Options } from "@colyseus/loadtest";
async function main(options: Options) {
const client = new Client(options.endpoint);
const room: Room = await client.joinOrCreate(options.roomName, {/*
your join options here...
*/});
console.log("joined successfully!");
room.onMessage("message-type", (payload) => {
// logic
});
room.onStateChange((state) => {
console.log("state change:", state);
});
room.onLeave((code) => {
console.log("left");
});
}
// execute
cli(main);@colyseus/command typings update
On latest version of @colyseus/command (0.2.0), instead of providing the state as a generic when extending the Command, you now provide your whole Room type:
import { Command } from "@colyseus/command";
export class MyRoom extends Room<State> {/* ... */}
-export class MyCommand extends Command<State> {/* ... */}
+export class MyCommand extends Command<MyRoom> {/* ... */}Built-in client.auth is gone! @colyseus/social fully deprecated.
The documentation has been discouraging the use of @colyseus/social since version 0.14.
Now @colyseus/social has been officially deprecated. If you really rely on it please reach out to the devs on Discord on how to proceed if you still need to use it.
RedisDriver and RedisPresence constructor change
Providing a URI as connection string for RedisDriver or RedisPresence has
changed to:
-new RedisDriver({ url: "redis://xyz:123" })
+new RedisDriver("redis://xyz:123")onLeave 1001 and 4000 error codes range
If you had a condition like this:
if (1001 <= code) {
// process any abnormal shutdown
}Now you need to validate the higher limit as well since we have the custom error codes starting on 4000.
if (1001 <= code && 1015 >= code) {
// process any abnormal shutdown
}See https://docs.colyseus.io/client/?h=4000#onleave and https://docs.colyseus.io/server/room/#table-of-websocket-close-codes