boardgame.io icon indicating copy to clipboard operation
boardgame.io copied to clipboard

Control over spectator access

Open delucis opened this issue 2 years ago • 4 comments

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 to update for players)
  • /games/{name}/{id}/stop-spectating: remove a spectator from a match (equivalent to leave 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;
}

delucis avatar Sep 15 '21 09:09 delucis

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.

SamyGarib avatar Sep 15 '21 16:09 SamyGarib

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 avatar Sep 16 '21 08:09 lunedis

@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.

delucis avatar Sep 16 '21 09:09 delucis

@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.

delucis avatar Sep 16 '21 09:09 delucis