Skip to content
Docs
Rooms

Permissions & moderation

Resolve game permissions, fill the Room management modal, and kick or ban players.

Most community rooms have some kind of staff: an owner, admins, maybe moderators. disko doesn't decide what those roles are. Instead, it gives your room a few building blocks, and you combine them into whatever roles your room needs:

  • Permissions: decide who may do the things games let you restrict.

  • The Room management modal: give players and staff buttons and settings.

  • Presentation: show who's who, such as an "Admin" badge.

  • Moderation: kick and ban.

An owner-and-admins room, for example, needs nothing more than these pieces. Every call on this page returns a Promise that settles once the change has taken effect.

Permissions

Games declare permissions with a default, such as "pick-teams, restricted by default". In a room that decides nothing, those defaults apply: "everyone", "restricted", or "owner" (the player signed in with the room owner's account).

Your room can take over any permission with a resolver: a function that answers "may this player do this?". room.game.permissions lists what the loaded game declares, each with its name, label and default.

Answers are cached

A game checks permissions constantly, many times a second, and a resolver might be slow (it could look something up in a database). So the game never waits for your resolver. disko asks your resolver ahead of time, at moments when the answer can change, and keeps the answers. The game reads the kept answer instantly.

When a player joins, a game loads, or the room calls refresh, disko asks the room's resolver and keeps the answer. When the game calls game.can, it reads the kept answer immediately, without waiting for the room.

When your room's own facts change, for example when someone becomes an admin, call room.permissions.refresh(...) so disko asks again.

room.js
  • room.permissions.resolve(name, resolver) decides one permission, and room.permissions.resolve(resolver) is a catch-all for every permission the loaded game declares. A named resolver takes precedence, and registering again replaces the resolver. room.permissions.remove(name?) removes a named resolver, or the catch-all without a name.

  • A resolver receives { player, permission, game } and returns true, false, or undefined for the game's default (the declared default, or what the game set for that player with permission.setDefault). Any other value, or an error, applies the default for that decision; errors are not caught and reach Node's uncaughtException.

  • disko asks again when a player joins, when a game loads, when resolvers change, and on room.permissions.refresh({ player?, permission? }). Until the first answer arrives, the game reads the permission as denied.

Room management modal

Every player has a Room button in the play client. It opens a modal with three sections: the player list, the room's options, and actions.

Your room doesn't draw this modal. It describes what's in it (which actions exist, which options, who may see or use them) and the client draws it the same way in every room. So players always find moderation and settings in a familiar place.

  • Player actions appear when someone clicks a player, such as Kick or Give admin.

  • Room actions appear in the Actions section, such as Clear bans.

  • Options are settings, such as which game to play or whether guests may join.

room.js
  • Player actions (room.actions.player(id, definition)) appear in a player's detail view, room actions (room.actions.room(id, definition)) in the Actions section. A definition has a label (1–48 bytes), an optional confirm (true, or { reason: true } for an optional reason field), an optional visible predicate (omitted: everyone) and run.

  • Options come in four formats: room.options.toggle, choice (1–32 choices), number (min, max, optional step) and text (maxLength up to 128, optional secret, whose value is never sent to viewers). Each has a label, the current value, and optional visible, editable (omitted: nobody, so the row is read-only) and change. room.options.set(id, value) changes a value from the room; every viewer sees values live.

  • visible and editable are checked again before run or change, so a stale modal cannot act. A predicate that throws denies. When run or change throws, the action or change is rejected and the viewer sees the error message.

  • Ids are 1–64 bytes, and defining an action with the same id replaces it. room.actions.remove("player" | "room", id) and room.options.remove(id) take entries away. Call room.actions.refresh({ viewer? }) when the facts behind visible change.

Presentation and moderation

Presentation is how a player's name looks everywhere it appears: a color and a short badge, such as "Admin". Moderation is removing players: a kick lets them come back, a ban doesn't.

room.js
  • player.setPresentation({ color, badge }) shows a palette role ("accent", "success", "warning", "danger" or "muted") and a short badge (at most 12 characters) wherever the player's name appears; null clears a field.

  • player.kick(reason?) disconnects a player and shows them the reason. They may rejoin.

  • player.ban({ reason? }) bans and disconnects a player: signed-in players by account, guests by an engine-held network identity the room never sees. The reason is at most 256 bytes.

  • room.bans.list() returns the bans, each with id, kind ("account" or "guest"), the player's name, the reason, a sealed export and lift(). A ban contains no address and no account.

  • Bans last for the room's lifetime. To keep them across a restart, store the sealed exports and pass each to room.bans.import(sealed) when the same room starts again. Exports are sealed with a key derived from the room's hosting token, so they import only with that token.

  • player.isOwner tells whether the player is signed in with the account that owns the room. Guests never are.

See the @disko-game/room reference for every signature.