⚔ Multiplayer Framework for Node.js
Documentation | Live demo | Demo Source Code
onAuth()
changesstatic onAuth(token, req)
method should be implemented as static
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.
@colyseus/auth
module/auth/register
- user registration (email + password)/auth/login
- login (email + password)/auth/anonymous
- anonymous login/auth/userdata
- fetch user datagrant
module - a MIT-licensed "OAuth Proxy")
/auth/provider/:providerId
- redirect to provider/auth/provider/:providerId/callback
- reply callback from the providerEnd-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)
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.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 from client.auth
method calls, there's no realtime subscription here!)client.auth.signOut()
- clear local auth tokenclient.auth.token
- auth token getter and setterclient.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)
@colyseus/playground
)The playground tool has been updated to allow customizing the client's "Auth Token".
(PRs introducing the authentication module https://github.com/colyseus/colyseus/pull/657, https://github.com/colyseus/colyseus.js/pull/133, https://github.com/colyseus/docs/pull/150)
A big thanks to @afrokick for fixing all these below 👏
Stats
module, responsible for handling the roomcount
key on Redis.roomcount
hash key on Redis now holds its values separated by comma (processId
->"count,ccu"
)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>
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;
}
roomcount
Redis key format has changed._ccu
Redis key does not exist anymore (Use matchMaker.stats.getGlobalCCU()
instead)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
Merged PRs from @theweiweiway & @hunkydoryrepair
REMOTE_ROOM_SHORT_TIMEOUT
default to 2000
instead of 500
https://github.com/colyseus/colyseus/pull/650
Fix https://github.com/colyseus/colyseus/issues/633, thanks @hunkydoryrepair for reporting. (regression from https://github.com/colyseus/colyseus/commit/5b69715e1796bce8a4374ae4f0dcd2b8c2ffcc65)
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.)
@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)
.joinById()
not properly returning MATCHMAKE_INVALID_ROOM_ID
when using Redis driver.(Thanks @CookedApps for reporting https://github.com/colyseus/colyseus/issues/608)
client.userData
and client.auth
via Client<UserData, AuthData>
"trying to send data to inactive client"
logs as they were not helpful.@colyseus/loadtest
- add "disconnected" count below "connected" count.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<UserData> 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
New features
devMode
flag for restoring rooms and reconnecting clients during development. (see docs)@colyseus/proxy
. (see docs)msgpackr
- #528, see benchmarks)onBeforePatch
lifecycle hook. (#385)sessionId
(#443)logger:
Server option) (#442)DEBUG=colyseus:message
) (#465, see docs)Breaking changes
@colyseus/arena
package has been renamed to @colyseus/tools
client.reconnect()
vulnerability fixed & API slightly changed
allowReconnection()
: second argument is now mandatory
@colyseus/loadtest
has been reworked!
@colyseus/command
typings update
.triggerAll()
has been deprecated.
onChange
behaviour change
MapSchema
is now strict on property accessors
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)""
(empty string) or null
when using gameServer.filterBy()
option. (#342)Breaking changes:
MongooseDriver
should now be imported from @colyseus/mongoose-driver
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
- https://github.com/colyseus/colyseus/pull/412, thanks @vitalyrotari!@colyseus/monitor
- moved from its own repository into this one@colyseus/arena
- moved from its own repository into this oneThe @colyseus/schema has received a major restructuring internally, here's what has changed at a glance:
@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!MapSchema
now uses a real Map
underneath instead of plain objects.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!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! ❤