Releases: colyseus/colyseus
0.16.0
Colyseus 0.16 is here ✨
- Announcement Post: https://colyseus.io/blog/colyseus-016-is-here/
- Migration guide: https://docs.colyseus.io/upgrading/0.16
Changelog
SchemaSerializer
now uses@colyseus/schema
3.0 (colyseus/schema#173 / #709)@colyseus/schema
v3.0:- Introduce a
StateView
build block that allows to filter data per client (See docs) - Deprecate
@filter()
and@filterChildren()
- De-couple encode and decode methods from
Schema
instances - De-couple client-side callbacks from
Schema
instances- Client SDKs: New API for attaching client-side schema callbacks (See docs)
- Client SDKs: Possibility to replace/extend the callback system with your own (See docs)
- Fix re-using schema instances on many places & missing callbacks (colyseus/schema#151)
- Fix triggering
onAdd
callbacks twice when nested (colyseus/schema#147)
- Fix known issues from ArraySchema (#641)
- Avoid global type Context (colyseus/colyseus-unity-sdk#131)
- Expose APIs to:
- Allow to encode/decode arbitrary structures (no need to inherit from
Schema
class) - Allow to customize the change tracking, encoding and decoding at the byte level
- (See Advanced Usage)
- Allow to encode/decode arbitrary structures (no need to inherit from
- Introduce a
onAuth
now receives "context" as third argument instead of "req". (See docs)- Experimental
WebTransport
implementation (See docs) - Improve buffer / memory allocation for sending messages
- Allow to set state from class definition (
state = new MyState()
) - Deprecate sending schema-encoded messages
- Expose previously undocumented
client._reconnectionToken
asclient.reconnectionToken
matchMaker.getRoomById()
no longer returns aRoom
instance. It now returns the cached data on the driver.matchMaker.getLocalRoomById()
has been introduced to retrieve theRoom
instance instead.
client.getAvailableRooms()
have been removed- Do not expose private
Client
properties to end-users (added aClientPrivate
interface for internal use)
A huge thank you to our vibrant community, sponsors, and Colyseus Cloud users for making this journey possible!
💖 Special shoutout to Poki, Pixels.xyz, Bloxd, 0x&, and all our supporters 💖
0.15.57
(The bundled colyseus
package number will now start to follow the same @colyseus/core
version number)
Graceful Shutdown
- Introduces
Room.onBeforeShutdown()
and improved Graceful Shutdown process (see docs) - Introduces
Room.onUncaughtException()
for automatically handling try/catch within your room code (see docs)
The new Graceful Shutdown process better allow to configure rolling updates. Keeping active rooms connected while deploying a new version in the same server is now possible!
@colyseus/schema
is now a peer dependency!
As @colyseus/schema
has been moved to a peerDependency
of the framework - you must now specify its version in your project:
"dependencies": {
"@colyseus/schema": "^2.0.36"
}
0.16.0-preview
0.16.0-preview
(Only the JavaScript SDK is supported at the moment. Other SDKs will updated in the upcoming months. The final 0.16
will only come out once all SDKs are supported.)
See the migration guide to 0.16
(Work-in-progress)
Featured changes
- Uses
@colyseus/schema
version3.0
(Which fixes colyseus/schema#155, #702, #689, colyseus/schema#152, colyseus/schema#108, colyseus/schema#94, colyseus/schema#107, colyseus/schema#170, https://github.com/orgs/colyseus/discussions/589, colyseus/colyseus-unity-sdk#131) - Fixes previously known
ArraySchema
issues (#641) - Introduce the
StateView
build block which allows filtering state data per client - Introduce an experimental way to customize encoding and decoding
Schema
instances (see docs) - De-couple encode and decode methods from
Schema
instances - Improve buffer / memory allocation for sending room messages
- Introduce experimental
@colyseus/h3-transport
forWebTransport
support. (#272)- (The JavaScript SDK auto-detects it and uses
WebTransport
instead ofWebSockets
for room connections)
- (The JavaScript SDK auto-detects it and uses
- Allow to extend the client-side callback system (see docs)
Breaking changes
- Renamed
matchMaker.find(conditions)
method tomatchMaker.query(conditions, sortOptions)
- The previously undocumented
client._reconnectionToken
variable has been renamed toclient.reconnectionToken
@filter()
and@filterChildren()
have been removed from@colyseus/schema
. (If you need to filter data from the state per client, please use theStateView
)- Deprecation: sending schema-encoded messages has been removed
Quality of life changes
- The room properties
patchRate
,autoDispose
andstate
can now be assigned at the class level:
class MyRoom extends Room {
patchRate = 1000;
autoDispose = false;
state = new MyState();
}
If you have any issues, please share either as an issue on GitHub or on this thread on Discord 🙏
0.15.17
Maintenance release
Improved how “stale” rooms are cleared when using multiple processes:
The Problem
On previous versions, whenever a new process starts up, all existing rooms would be "health-checked", and removed in cause of failure - one by one. Depending on the number of rooms available, this could cause stress on Redis, and end up clearing rooms that shouldn't be cleared.
The Solution
Now, rather than "health-checking" all the rooms, each process is "health-checked" instead. Then, in case of failure, rooms are cleared from the cache in batches, instead of one-by-one.
Also, now whenever a user tries to “join or create” and a “stale” room is found, the remote process is immediately health-checked and the “join or create” is re-tried. When this happens, the request will take more time to complete, but it won’t result in an error to join. (previously, the {roomId} is already full
error used to appear in such scenario!)
Internal changes
- Setting the
autoDipose
property will now reset the auto-dispose timeout. d3ff4a5 - Fixed global stats counter during
room.disconnect()
and early “leaves” (#719) room.disconnect()
returns a resolved promise if room is already disposingpresence.hdel()
now returns aboolean
if successful or not- Added
driver.cleanup(processId)
method to remove cached rooms byprocessId
devMode
bug fix: preventroomHistory.clients is not iterable
error
0.15.15
Introducing Authentication Module
Documentation | Live demo | Demo Source Code
Server-side: onAuth()
changes
static onAuth(token, req)
method should be implemented asstatic
from now ononAuth(client, options, req)
as an instance method still works, but will be deprecated in the future.
This change allows validating the token earlier in the connection process, without needing an instance of the room available.
This way the auth token is read from the first matchmaking request header instead of as query param in the second WebSocket connection.
Server-side: The @colyseus/auth
module
- Uses JWT to secure communication with client SDK
- Provides authentication via express routes:
/auth/register
- user registration (email + password)/auth/login
- login (email + password)/auth/anonymous
- anonymous login/auth/userdata
- fetch user data
- Provides OAuth express routes (Thanks to @simov's work on
grant
module - a MIT-licensed "OAuth Proxy")/auth/provider/:providerId
- redirect to provider/auth/provider/:providerId/callback
- reply callback from the provider
Database interaction must be implemented by end-user
End-user should implement the following callbacks:
auth.settings.onFindUserByEmail = async (email) => {/* query user by email */}
auth.settings.onRegisterWithEmailAndPassword = async (email, password, options) => {/* insert user */}
auth.settings.onRegisterAnonymously = async (options: T) => {/* insert anonymous user */}
auth.oauth.onCallback(async (data, provider) => {/* query or insert user by OAuth provider */})
End-user may customize the following callbacks. (They come with a default implementation.)
auth.settings.onParseToken = (jwt: JwtPayload) => jwt
auth.settings.onGenerateToken = async (userdata: unknown) => await JWT.sign(userdata)
auth.settings.onHashPassword = async (password: string) => Hash.make(password)
Server-side Usage
import { auth } from "@colyseus/auth";
auth.settings.onFindUserByEmail = async (email) => {
//
// Your database query to find user by email address
//
}
auth.settings.onRegisterWithEmailAndPassword = async (email, password, options) => {
//
// Your database query to insert the user with email + password
// (The "password" is already hashed here)
//
}
// Configure Discord as OAuth provider
auth.oauth.addProvider('discord', {
key: "YOUR_DISCORD_KEY",
secret: "YOUR_DISCORD_SECRET",
scope: ['identify', 'email'],
});
auth.oauth.onCallback(async (data, provider) => {
//
// Your database query/insert. This callback must return the userdata.
// data.raw -> { token_type, access_token, expires_in, refresh_token, scope }
// data.profile -> { id, username, avatar, discriminator, public_flags, premium_type, flags, banner, accent_color, global_name, avatar_decoration_data, banner_color, mfa_enabled, locale, email }
//
});
//
// app.config.ts: bind auth express routes
//
initializeExpress: (app) => {
// bind auth + oauth express routes
app.use(auth.prefix, auth.routes());
// custom protected request
app.get("/profile", auth.middleware(), (req: Request, res) => {
res.json(req.auth);
});
},
Client-side: client.auth
API:
client.auth.registerWithEmailAndPassword(email, password, options?)
client.auth.signInWithEmailAndPassword(email, password)
client.auth.signInAnonymously(options?)
client.auth.signInWithProvider(providerName)
- open popup for OAuth providerclient.auth.sendPasswordResetEmail(email)
client.auth.getUserData()
- requests/auth/userdata
from the serverclient.auth.onChange(callback)
- define a callback that is triggered when internal auth state changes (it only triggers as a response fromclient.auth
method calls, there's no realtime subscription here!)client.auth.signOut()
- clear local auth tokenclient.auth.token
- auth token getter and setter
Client-side: client.http
API:
If client.auth.token
is set, requests from client.http
will forward it as "Authentication: Bearer {token}"
header. The match-making requests are now using client.http
as well.
client.http.get(path, options)
client.http.post(path, options)
client.http.del(path, options)
client.http.put(path, options)
Playground update (@colyseus/playground
)
The playground tool has been updated to allow customizing the client's "Auth Token".
(PRs introducing the authentication module #657, colyseus/colyseus.js#133, colyseus/docs#150)
Smaller fixes
A big thanks to @afrokick for fixing all these below 👏
0.15.13
Major changes
- Introduced an internal
Stats
module, responsible for handling theroomcount
key on Redis. - The
roomcount
hash key on Redis now holds its values separated by comma (processId
->"count,ccu"
) - The internal
Stats
module is exposed viamatchMaker.stats
, so end-user can call:matchMaker.stats.fetchAll()
->Promise<Array<{ roomCount: number; ccu: number; processId: string }>>
matchMaker.stats.getGlobalCCU()
->Promise<number>
- Added
selectProcessIdToCreateRoom
callback option toServer
constructor.
The default value for selectProcessIdToCreateRoom
behaves exactly as previous versions, but now it consumes the new matchMaker.stats.fetchAll()
API:
selectProcessIdToCreateRoom = async function (roomName: string, clientOptions: any) {
return (await matchMaker.stats.fetchAll())
.sort((p1, p2) => p1.roomCount > p2.roomCount ? 1 : -1)[0]
.processId;
}
Breaking changes
- The
roomcount
Redis key format has changed. - The
_ccu
Redis key does not exist anymore (UsematchMaker.stats.getGlobalCCU()
instead)
Smaller changes
It has been reported (by @CookedApps) that reconnection messages were flooding production logs. These logs are now filtered out on production environment.
Thanks @hunkydoryrepair and @CookedApps
0.15.10
Merged PRs from @theweiweiway & @hunkydoryrepair
0.15.9
0.15.8
Bun support 
Bun support is experimental, please report any issues you may find!
# Create a new Colyseus project
bunx create-colyseus-app@latest ./my-server
# Enter the project directory
cd my-server
# Install Bun transport & Run the server
bun add @colyseus/bun-websockets
bun run src/index.ts
(Bun is a new JavaScript runtime that is fast and aims to replace Node.js.)
Breaking change for @colyseus/proxy
In order to support bun
, the internal-ip
module has been removed from the core.
If you are still using version 0.15 + @colyseus/proxy
, you should use the following code to continue using the proxy:
npm install --save internal-ip
import ip from 'internal-ip';
// ...
process.env.SELF_HOSTNAME = await ip.v4();
(Since 0.15, the usage of the proxy is not recommended anymore)