Skip to content

Commit

Permalink
feat: Add plain JS lobby client (#728)
Browse files Browse the repository at this point in the history
* feat(server): Create types for Lobby API data

Closes #707

This creates types for data returned by the Lobby API and uses them to 
type the response bodies in the server-side router.

* refactor(types): Include min and max players options in Game interface

* refactor(lobby): Convert connection class to Typescript

* feat(lobby): Create a plain JS LobbyClient

LobbyClient is a lightweight wrapper around `fetch` calls to a 
boardgame.io Lobby API server. Apart from the server’s base URL, it is 
stateless and serves mainly to allow argument validation and to abstract 
away a few request details. Unlike plain fetch, requests will throw 
errors if they don’t return `ok` (i.e. status 2xx).

* refactor(lobby): Use new LobbyClient class in React lobby connection

* fix(lobby): Include missing import for React types

* docs(api): Update Lobby docs to convert game to match more thoroughly

* docs: Correct inline documentation block

* docs: Update Lobby docs

- Add examples with the plain JS client
- Move the server config details to the Server doc

* feat(lobby): Add support for listMatches filtering to client

#740 added the ability to pass query string parameters to the 
listMatches API endpoint to filter the matches returned. This adds 
support for building the relevant query string to the lobby client’s 
listMatches method.
  • Loading branch information
delucis authored Jul 10, 2020
1 parent 3c8777b commit 43c7fbb
Show file tree
Hide file tree
Showing 11 changed files with 930 additions and 170 deletions.
227 changes: 157 additions & 70 deletions docs/documentation/api/Lobby.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,123 @@
# Lobby

### React components
The [Server](/api/Server) hosts the Lobby REST API that can be used to create
and join matches. It is particularly useful when you want to
authenticate clients to prove that they have the right to send
actions on behalf of a player.

Authenticated matches are created with server-side tokens for each player.
You can create a match with the `create` API call, and join a player to a
match with the `join` API call.

A match that is authenticated will not accept moves from a client on behalf
of a player without the appropriate credential token.

Use the `create` API call to create a match that requires credential tokens.
When you call the `join` API, you can retrieve the credential token for a
particular player.

## Clients

<!-- tabs:start -->
### **Plain JS**

boardgame.io provides a lightweight wrapper around the Fetch API to simplify
using a Lobby API server from the client.


```js
import { LobbyClient } from 'boardgame.io/client';

You can use the lobby component with the code below:
const lobbyClient = new LobbyClient({ server: 'http://localhost:8000' });

lobbyClient.listGames()
.then(console.log) // => ['chess', 'tic-tac-toe']
.catch(console.error);
```

### **React**

The React lobby component provides a more high-level client, including UI
for listing, joining, and creating matches.

```js
import { Lobby } from 'boardgame.io/react';
import { TicTacToe } from './Game';
import { TicTacToeBoard } from './Board';

<Lobby
gameServer={`https://${window.location.hostname}:8000`}
lobbyServer={`https://${window.location.hostname}:8000`}
gameComponents={importedGames}
gameComponents={[
{ game: TicTacToe, board: TicTacToeBoard }
]}
/>;
```

`importedGames` is an array of objects with these fields:
`gameComponents` expects an array of objects with these fields:

- `game`: The boardgame.io `Game` definition.
- `game`: A boardgame.io `Game` definition.
- `board`: The React component that will render the board.

### Server-side API
<!-- tabs:end -->

The [Server](/api/Server) hosts the Lobby REST API that can be used to create and join matches. It is particularly useful when you want to
authenticate clients to prove that they have the right to send
actions on behalf of a player.
## REST API

### Listing available game types

#### GET `/games`

Returns an array of names for the games this server is running.

#### Using a LobbyClient instance

```js
const games = await lobbyClient.listGames();
```

Authenticated games are created with server-side tokens for each player. You can create a match with the `create` API call, and join a player to a match with the `join` API call.
### Listing all matches for a given game

A game that is authenticated will not accept moves from a client on behalf of a player without the appropriate credential token.
#### GET `/games/{name}`

Use the `create` API call to create a match that requires credential tokens. When you call the `join` API, you can retrieve the credential token for a particular player.
Returns all match instances of the game named `name`.

#### Configuration
Returns an array of `matches`. Each instance has fields:

You can pass `lobbyConfig` to configure the Lobby API
during server startup:
- `matchID`: the ID of the match instance.

- `players`: the list of seats and players that have joined the game, if any.

- `setupData` (optional): custom object that was passed to the game `setup` function.

#### Using a LobbyClient instance

```js
server.run({ port: 8000, lobbyConfig });
const { matches } = await lobbyClient.listMatches('tic-tac-toe');
```

Options are:
### Getting a specific match by its ID

#### GET `/games/{name}/{id}`

- `apiPort`: If specified, it runs the Lobby API in a separate Koa server on this port. Otherwise, it shares the same Koa server runnning on the default boardgame.io `port`.
- `apiCallback`: Called when the Koa server is ready. Only applicable if `apiPort` is specified.
Returns a match instance given its matchID.

Returns a match instance. Each instance has fields:

- `matchID`: the ID of the match instance.

- `players`: the list of seats and players that have joined the match, if any.

#### Creating a match
- `setupData` (optional): custom object that was passed to the game `setup` function.

#### Using a LobbyClient instance

```js
const match = await lobbyClient.getMatch('tic-tac-toe', 'matchID');
```

##### POST `/games/{name}/create`
### Creating a match

#### POST `/games/{name}/create`

Creates a new authenticated match for a game named `name`.

Expand All @@ -61,85 +131,93 @@ Accepts three parameters:

Returns `matchID`, which is the ID of the newly created game instance.

#### Joining a game
#### Using a LobbyClient instance

```js
const { matchID } = await lobbyClient.createMatch('tic-tac-toe', {
numPlayers: 2
});
```

### Joining a match

##### POST `/games/{name}/{id}/join`
#### POST `/games/{name}/{id}/join`

Allows a player to join a particular match instance `id` of a game named `name`.

Accepts three JSON body parameters:

- `playerID` (required): the ordinal player in the game that is being joined (0, 1...).
- `playerID` (required): the ordinal player in the match that is being joined (`'0'`, `'1'`...).

- `playerName` (required): the display name of the player joining the game.
- `playerName` (required): the display name of the player joining the match.

- `data` (optional): additional information associated to the player.
- `data` (optional): additional metadata to associate with the player.

Returns `playerCredentials` which is the token this player will require to authenticate their actions in the future.

#### Update a player's information
#### Using a LobbyClient instance

##### POST `/games/{name}/{id}/update`
```js
const { playerCredentials } = await lobbyClient.joinMatch(
'tic-tac-toe',
'matchID',
{
playerID: '0',
playerName: 'Alice',
}
);
```

### Updating a player’s metadata

#### POST `/games/{name}/{id}/update`

Rename and/or update additional information of a user in the match instance `id` of a game named `name` previously joined by the player.
Rename and/or update additional metadata for a player in the match instance `id` of a game named `name` previously joined by the player.

Accepts four parameters, requires at least one of the two optional parameters:

- `playerID` (required): the ID used by the player in the game (0,1...).
- `playerID` (required): the ID used by the player in the match (0,1...).

- `crendentials` (required): the authentication token of the player.
- `credentials` (required): the authentication token of the player.

- `newName` (optional): the new name of the player.

- `data` (optional): additional information associated to the player.
- `data` (optional): additional metadata to associate with the player.

#### Using a LobbyClient instance

```js
await lobbyClient.updatePlayer('tic-tac-toe', 'matchID', {
playerID: '0',
credentials: 'playerCredentials',
newName: 'Al',
});
```

#### Leaving a match
### Leaving a match

##### POST `/games/{name}/{id}/leave`
#### POST `/games/{name}/{id}/leave`

Leave the match instance `id` of a game named `name` previously joined by the player.

Accepts two parameters, all required:

- `playerID`: the ID used by the player in the game (0, 1...).
- `playerID`: the ID used by the player in the match (0, 1...).

- `credentials`: the authentication token of the player.

#### Listing all match instances of a given game

##### GET `/games/{name}`

Returns all match instances of the game named `name`.

Returns an array of `matches`. Each instance has fields:

- `matchID`: the ID of the match instance.

- `players`: the list of seats and players that have joined the game, if any.
#### Using a LobbyClient instance

- `setupData` (optional): custom object that was passed to the game `setup` function.

#### Getting specific instance of a match by its ID

##### GET `/games/{name}/{id}`

Returns a match instance given its matchID.

Returns a match instance. Each instance has fields:

- `matchID`: the ID of the match instance.

- `players`: the list of seats and players that have joined the game, if any.

- `setupData` (optional): custom object that was passed to the game `setup` function.

#### Client Authentication

All actions for an authenticated game require an additional payload field `credentials`, which must be the given secret associated with the player.
```js
await lobbyClient.leaveMatch('tic-tac-toe', 'matchID', {
playerID: '0',
credentials: 'playerCredentials',
});
```

#### Playing again
### Playing again

##### POST `/games/{name}/{id}/playAgain`
#### POST `/games/{name}/{id}/playAgain`

- `{name}` (required): the name of the game being played again.

Expand All @@ -149,12 +227,21 @@ Given a previous match, generates a match ID where users should go if they want

Accepts these parameters:

- `playerID` (required): the player ID of the player on the previous game.
- `playerID` (required): the player ID of the player in the previous match.

- `credentials` (required): player's credentials.

`numPlayers` (optional): the number of players. Defaults to the `numPlayers` value of the previous room.
- `numPlayers` (optional): the number of players. Defaults to the `numPlayers` value of the previous match.

`setupData` (optional): custom object that was passed to the game `setup` function. Defaults to the `setupData` object of the previous room.
- `setupData` (optional): custom object that was passed to the game `setup` function. Defaults to the `setupData` object of the previous room.

Returns `nextMatchID`, which is the ID of the newly created match that the user should go to play again.

#### Using a LobbyClient instance

```js
const { nextMatchID } = await lobbyClient.playAgain('tic-tac-toe', 'matchID', {
playerID: '0',
credentials: 'playerCredentials',
});
```
21 changes: 21 additions & 0 deletions docs/documentation/api/Server.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ server.run(8000);
server.run(8000, () => console.log("server running..."));
```

#### With custom Lobby settings

You can pass `lobbyConfig` to configure the Lobby API during server startup:

```js
const lobbyConfig = {
apiPort: 8080,
apiCallback: () => console.log('Running Lobby API on port 8080...'),
};

server.run({ port: 8000, lobbyConfig });
```

Options are:

- `apiPort`: If specified, it runs the Lobby API in a separate Koa server on
this port. Otherwise, it shares the same Koa server running on the default
boardgame.io `port`.
- `apiCallback`: Called when the Koa server is ready. Only applicable if
`apiPort` is specified.

#### With HTTPS

```js
Expand Down
3 changes: 2 additions & 1 deletion packages/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
*/

import { Client } from '../src/client/client';
import { LobbyClient } from '../src/lobby/client';

export { Client };
export { Client, LobbyClient };
Loading

0 comments on commit 43c7fbb

Please sign in to comment.