Skip to content

Releases: colyseus/colyseus

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)

0.15.5

21 Jun 13:05
Compare
Choose a tag to compare
  • #563 - Allow to specify types of client.userData and client.auth via Client<UserData, AuthData>
  • Fixes #555 and #532 - Zombie Rooms with multiple servers w/ RedisDriver
  • Prevent "trying to send data to inactive client" logs as they were not helpful.
  • @colyseus/loadtest - add "disconnected" count below "connected" count.

Specifying types for client.userData and client.auth

You can now specify the types of client.userData and client.auth via Client<UserData, AuthData>:

import { ClientArray, Room } from "colyseus";

interface UserData {
  keyboard: {[key: string]: boolean};
}
interface AuthData {
  userId: number;
}

class MyRoom extends Room {
  clients: ClientArray<UserData, AuthData>

  onCreate() {
    this.onMessage("key", (client, key) => {
      // client.userData is now of type UserData
      client.userData.keyboard[key] = true;
    });
  }

  async onAuth(client, options) {
    // client.auth is now of type AuthData
    client.auth = await fetchUserFromDB(options);
    return client.auth.userId > 0;
  }
}

(If TypeScript complains on "Property 'clients' has no initializer and is not definitely assigned in the constructor", you can either declare clients!: ClientArray or set "strictPropertyInitialization":false on tsconfig (not recommended))


Updating your packages:

npm install --save @colyseus/core@latest
npm install --save @colyseus/ws-transport@latest
npm install --save @colyseus/redis-driver@latest
npm install --save-dev @colyseus/loadtest@latest

If you have these packages installed, you can also update them:

npm install --save @colyseus/uwebsockets-transport
npm install --save @colyseus/mongoose-driver

0.15

04 May 13:11
Compare
Choose a tag to compare

See the migration guide to 0.15! (from 0.14)

New features

  • Introducing a devMode flag for restoring rooms and reconnecting clients during development. (see docs)
  • Introducing a new scalability option that does not rely on @colyseus/proxy. (see docs)
  • Switched to a faster MessagePack implementation (msgpackr - #528, see benchmarks)
  • New Room's onBeforePatch lifecycle hook. (#385)
  • Schema: single property callback is now available on all SDKs (see docs)
  • Possibility to access a client instance directly via its sessionId (#443)
  • Support for custom loggers (via logger: Server option) (#442)
  • Introduced log flag for incoming and outgoing messages (DEBUG=colyseus:message) (#465, see docs)

Breaking changes

Bug fixes / Improvements

  • MongooseDriver is not the default recommendation anymore. Use RedisDriver instead.
  • "redis" module has been replaced by "ioredis" for cluster support on both RedisPresence and RedisDriver (#452)
  • Some room properties are now fully private (#441)
  • Fixed an issue where matchmaking filter returning all available rooms when filtering criteria is ""(empty string) or null when using gameServer.filterBy() option. (#342)

0.14.18

12 Jul 21:18
9279adf
Compare
Choose a tag to compare

Migrating guide to 0.14.18 https://docs.colyseus.io/migrating/0.14.18/

Breaking changes:

  • The MongooseDriver should now be imported from @colyseus/mongoose-driver
  • That's it! If you happen to face any issues please let us know! 🙏

This is a maintenance update - now the colyseus package has been broken down into isolated modules to avoid tying specific modules too hard with the framework.
Examples of this are the new RedisDriver (thanks @vitalyrotari) and the new uWebSocketsTransport transport option!

Here's the list of current packages in the colyseus/colyseus repository now:

  • colyseus - aggregates previously existing features through their respective modules
    • @colyseus/core - all framework abstractions and features
    • @colyseus/ws-transport - default WebSocket transport (through ws module)
    • @colyseus/redis-presence - provides RedisPresence
    • @colyseus/mongoose-driver - provides MongooseDriver
  • @colyseus/uwebsockets-transport - New transport option!, uses uWebSockets.js instead of ws 🥳
  • @colyseus/redis-driver - #412, thanks @vitalyrotari!
  • @colyseus/monitor - moved from its own repository into this one
  • @colyseus/arena - moved from its own repository into this one

0.14.0

20 Oct 18:34
Compare
Choose a tag to compare

Check out the migration guide from 0.13 to 0.14 here.

The @colyseus/schema has received a major restructuring internally, here's what has changed at a glance:

  • Instance-sharing is now supported! You may use the same reference of a schema instance multiple times for synchronization without having to .clone() it.
  • Better @filter() implementation, and the addition of @filterChildren(). Although filters are still not recommended for fast-paced games, it should serve turn-based scenarios pretty well!
  • The MapSchema now uses a real Map underneath instead of plain objects.
  • It is not recommended to implement constructor() inside your schemas. Use .assign({ ... }) instead.

Besides the schema changes, here are some small changes in the framework itself:

  • simulateLatency(): this utility has been included to help you simulate networking delays during development.
  • broadcastPatch(): this internal method has been exposed, so if you wanna have control over when to send state patches to connected clients, now you can!
  • Bugfix: messages from "disconnecting" clients are now being dropped. (During some disconnection scenarios, the "client" instance of a particular "sessionId" was already removed from the server, but messages from that "sessionId" were still arriving at the server.)

The website was re-branded to focus on the maintenance and development of the framework, as well as giving more exposure to the Discord community!

Thank you all for the support! ❤