Room Access Rules
Rooms use explicit room-level access checks under rooms[namespace].access.
Configuration
import { defineConfig } from '@edgebase/shared';
export default defineConfig({
rooms: {
game: {
access: {
metadata: (auth, roomId) => auth !== null,
join: (auth, roomId) => auth?.custom?.plan === 'pro',
action: (auth, roomId, actionType) => {
if (!auth) return false;
if (actionType === 'BAN_PLAYER') return auth.role === 'admin';
return true;
},
},
handlers: {
lifecycle: {
onJoin(sender, room) {
room.sendMessage('joined', { userId: sender.userId });
},
},
actions: {
MOVE(payload, room, sender) {
room.setPlayerState(sender.userId, (state) => ({
...state,
position: payload,
}));
},
},
},
},
},
});
Release Mode
Rooms are fail-closed in release mode.
- If
publicis not enabled - and the matching
access.*function is missing - then
metadata,join, andactionare denied
Use public only as an explicit opt-in:
rooms: {
lobby: {
public: {
metadata: true,
join: true,
},
},
}
access vs handlers
access.metadata- decides whether room metadata can be fetched
access.join- decides whether a player can enter the room
access.action- decides whether a client action is allowed
handlers.lifecycle.onJoin- runs after join is accepted
handlers.actions- handles accepted action payloads
handlers.timers- handles server timers
Using auth.meta
Room access receives the same enriched auth context as HTTP routes.
export default defineConfig({
auth: {
handlers: {
hooks: {
enrich: async (auth) => ({
workspaceId: await lookupWorkspace(auth.id),
}),
},
},
},
rooms: {
board: {
access: {
join: (auth) => Boolean(auth?.meta?.workspaceId),
},
},
},
});
Room Sender
Lifecycle and action handlers receive a sender object with:
userIdconnectionIdrole
Use auth inside access. Use sender inside handlers after the join has been accepted.