Upgrading to version 0.15 (from 0.14)
See version 0.15 release announcement.
@colyseus/arena
has been renamed to@colyseus/tools
- Schema’s
.triggerAll()
has been deprecated. - Schema callbacks API change
- Schema’s
onChange
behaviour change MapSchema
is now strict on property accessorsclient.reconnect()
API slightly changedallowReconnection()
: second argument is now mandatory@colyseus/loadtest
has been reworked!@colyseus/command
typings update- Built-in
client.auth
is gone!@colyseus/social
fully deprecated RedisDriver
andRedisPresence
constructor change- onLeave
1001
and4000
error 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:
MapSchema
used to be treated as a regular JavaScript object in the early days. Since version 0.14MapSchema
uses a realMap
internally, 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