Skip to content

Releases: colyseus/colyseus

0.16.0

18 Feb 20:28
Compare
Choose a tag to compare

Colyseus 0.16 is here ✨

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)
  • 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 as client.reconnectionToken
  • matchMaker.getRoomById() no longer returns a Room instance. It now returns the cached data on the driver.
    • matchMaker.getLocalRoomById() has been introduced to retrieve the Room instance instead.
  • client.getAvailableRooms() have been removed
  • Do not expose private Client properties to end-users (added a ClientPrivate 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

03 Dec 19:19
Compare
Choose a tag to compare

(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

25 Jul 00:45
Compare
Choose a tag to compare
0.16.0-preview Pre-release
Pre-release

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

Breaking changes

  • Renamed matchMaker.find(conditions) method to matchMaker.query(conditions, sortOptions)
  • The previously undocumented client._reconnectionToken variable has been renamed to client.reconnectionToken
  • @filter() and @filterChildren() have been removed from @colyseus/schema. (If you need to filter data from the state per client, please use the StateView)
  • Deprecation: sending schema-encoded messages has been removed

Quality of life changes

  • The room properties patchRate, autoDispose and state 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

26 Apr 19:41
Compare
Choose a tag to compare

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 disposing
  • presence.hdel() now returns a boolean if successful or not
  • Added driver.cleanup(processId) method to remove cached rooms by processId
  • devMode bug fix: prevent roomHistory.clients is not iterable error

0.15.15

27 Dec 21:06
Compare
Choose a tag to compare

Introducing Authentication Module

Documentation | Live demo | Demo Source Code

Server-side: onAuth() changes

  • static onAuth(token, req) method should be implemented as static from now on
  • onAuth(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 provider
  • client.auth.sendPasswordResetEmail(email)
  • client.auth.getUserData() - requests /auth/userdata from the server
  • client.auth.onChange(callback) - define a callback that is triggered when internal auth state changes (it only triggers as a response from client.auth method calls, there's no realtime subscription here!)
  • client.auth.signOut() - clear local auth token
  • client.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".

playground-auth-tools

(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 👏

  • simulateLatency doesn't go back (Issue: #570, PR: #663)
  • BunWebSockets transport not setting CORS headers on matchmaking requests (Issue: #662, PR: #664)
  • Wrong type on RelayRoom (Issue: #665, PR: #667)
  • Changing room to private doesn't update realtime listing (Issue: #617, PR: #668)

0.15.13

18 Nov 02:49
Compare
Choose a tag to compare

Major changes

  • Introduced an internal Stats module, responsible for handling the roomcount 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 via matchMaker.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 to Server 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 (Use matchMaker.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.

image


Thanks @hunkydoryrepair and @CookedApps

0.15.10

03 Nov 23:03
Compare
Choose a tag to compare

Merged PRs from @theweiweiway & @hunkydoryrepair

  • maintain correct room count #649
  • REMOTE_ROOM_SHORT_TIMEOUT default to 2000 instead of 500 #650

0.15.9

29 Sep 14:03
Compare
Choose a tag to compare

Fix #633, thanks @hunkydoryrepair for reporting. (regression from 5b69715)

0.15.8

12 Sep 15:40
Compare
Choose a tag to compare

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)

0.15.6

07 Aug 14:05
Compare
Choose a tag to compare
  • Fixes .joinById() not properly returning MATCHMAKE_INVALID_ROOM_ID when using Redis driver.

(Thanks @CookedApps for reporting #608)