boardgame.io
boardgame.io copied to clipboard
Control over spectator access
Currently a client with a matchID
can connect using an undefined or null playerID
and receive state updates as a “spectator”. They are not permitted to make moves or send chat messages.
It might be useful to support the following additional scenarios.
Use cases
1. Disallow spectators entirely
Allow matches to be played without any spectators able to view match state. We already support an unlisted
option in the Lobby API to limit the possibility for matches to be discovered, but this setting would allow a match to be listed publicly, but then still be inaccessible for anyone who didn’t join as a player.
2. Allow spectators to send chat messages
We decided against supporting spectator chat in the initial feature implementation. Partly this was because spectators are unauthenticated so we have no way of checking their identity. However, some users do want to allow spectators to join the chat.
3. Allow only some spectators
Maybe the players want some control over who is allowed to spectate, but not disallow them completely. This would require spectators to “authenticate” somehow.
4. Show spectator connection status
Currently match metadata lets clients know which of the various players is currently online via the isConnected
field. If spectators have a more specific identity, seeing who is connected could be helpful. Otherwise, with or without identities, a count of the connected spectators could be included in match metadata.
Implementation
Add option to the create match endpoint to control spectator access
Accept an additional spectatorAccess
flag when creating a match via the Lobby API. This would be stored in the match metadata and used to evaluate requests from spectators to receive game state. Presumably in this logic authenticating sync requests.
Based on current usage and the above use cases, spectatorAccess
could be something like the following enum (defaulting to OPEN
to maintain current behaviour):
enum SpectatorAccessLevel {
OPEN, // current behaviour, anyone can request spectator match state
OPEN_REGISTRATION, // spectators must first register via the lobby to request match state
PROTECTED_REGISTRATION, // a password is required to register via the lobby
PRIVATE // no spectators are allowed
}
When using the PROTECTED_REGISTRATION
mode, match creation requests could include a spectatorPIN
to set the password required to register as a spectator.
Spectator registration in the Lobby API
While the OPEN
and PRIVATE
flags above are a hard toggle, the middle access levels introduce a concept of “registered” spectators. Much like players use the /games/{name}/{id}/join
endpoint to join a seat in a specific match, this would introduce a /games/{name}/{id}/spectate
endpoint for a spectator to register. When registering, they could submit a name
and arbitrary data
. They would receive credentials in return, like players do, and a unique ID. The ID & credentials could then be used to request match state (and optionally to chat, see below). [A unique ID would help distinguish two spectators much like playerID
.]
With an OPEN_REGISTRATION
access level, anyone could register. With PROTECTED_REGISTRATION
, a client would need to provide the correct password/PIN to successfully register.
Registered spectators could be stored in a spectators
field in match metadata equivalent to how players
are stored currently.
We may also want some endpoints in addition to spectate
:
-
/games/{name}/{id}/update-spectator
: allow updating a spectator’s name or metadata (equivalent toupdate
for players) -
/games/{name}/{id}/stop-spectating
: remove a spectator from a match (equivalent toleave
for players)
Spectator chat
With the registration model, it would be possible to enable chat for spectators as there are now some guarantees about sender identity. The chat message authentication logic would need to be updated to reflect this.
A server-level option to enable spectator chat would be a good idea. Enabling/disabling it per match via the create
endpoint is also an option.
Connection status
Update the onConnectionChange
handler to increment/decrement a spectatorCount
field when spectators connect/disconnect and set isConnected
flags in spectators
when using a registration model.
Summary of additions to match metadata
interface AdditionsToMatchMetadata {
spectatorAccess: SpectatorAccessLevel;
spectatorPIN?: string;
spectators: Record<string, {
id: string;
name: string;
isConnected: boolean;
data: any
}>;
spectatorCount: number;
}
This feature is perfect as it is presented, at least for my use case. In regards to the user info, I put a lot more info for a player in matchData than just the name: (external) id, imagePath, ranking, peer2peer preferences, etc. This helps the UI to render valuable information, as well as to provide some context actions. It would be useful if the spectator's service allows that, as the join service does.
In my opinion, the "name" variable shouldn't be unique. This only works for services that use the name as the user-id, but this case limits users, generating friction in the sign-in process: an already full-of-friction process.
Another thing to consider is how "secret state" is handled in regards to spectators.
Consider a card game where each person has a separate deck of cards and some cards on their hands.
Neither the persons nor the spectators know what order the deck is in (only the master). Only the persons themselves know what their hand cards is.
For some use cases, you might want spectators to be able to see both hands (referee / tournament view). Or you want them to see only the hand of the person they are spectating? (spectating a player instead of the whole game is a concept that is not present yet). Or is the solution to see neither of the hand cards as a spectator?
Those are all features that could be considered when talking about working on the spectating feature.
@lunedis As spectators are already allowed access (this issue is about restricting that access), some of this is already supported. All state is run through a game’s playerView
function before being sent to a client to strip out anything that shouldn’t be shared for that specific client (see the Secret State docs). I suppose playerView
might be misleadingly named given it is also used to generate a spectator’s view.
The more specific use cases like a “referee” view would probably need something more than what this issue proposes, because such a role would presumably have to be quite privileged — not just anyone should be able to connect and see other players’ hands if they are generally secret. It would introduce some kind of hierarchical role/permissions concept that may be beyond the current lobby architecture.
@SamyGarib Good points.
The spectators should support arbitrary data
just like players. And I guess it might be helpful to have an update-spectator
endpoint for updating that metadata equivalent to the update
endpoint for players.
We can probably use a server-generated random ID for spectators instead of a unique name then to identify them.