Skip to content

Commit

Permalink
Surface game metadata and player nicknames in client / react props (#436
Browse files Browse the repository at this point in the history
)

* Surface game metadata and player nicknames in client / react props

* Add gameMetadata to Client documentation

* Do not send credentials to clients in metadata, update documentation, add tests

* remove unnecessary code from client.js

* Address review
  • Loading branch information
jasonharrison authored and nicolodavis committed Aug 15, 2019
1 parent ca3378c commit a0d5f36
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 9 deletions.
25 changes: 22 additions & 3 deletions docs/documentation/api/Client.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,28 @@ The `Board` component will receive the following as `props`:

10. `playerID`: The player ID associated with the client.

11. `isActive`: `true` if the client is able to currently make
11. `gameMetadata`: An object containing the players that have joined the game from a [room](/api/Lobby.md).

Example:

````json
{
"players": {
"0": {
"id": 0,
"name": "Alice"
},
"1": {
"id": 1,
"name": "Bob"
}
}
}```

12. `isActive`: `true` if the client is able to currently make
a move or interact with the game.

12. `isMultiplayer`: `true` if it is a multiplayer game.
13. `isMultiplayer`: `true` if it is a multiplayer game.

13. `isConnected`: `true` if connection to the server is active.
14. `isConnected`: `true` if connection to the server is active.
````
5 changes: 5 additions & 0 deletions src/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ class _ClientImpl {
isConnected: true,
onAction: () => {},
subscribe: () => {},
subscribeGameMetadata: _metadata => {}, // eslint-disable-line no-unused-vars
connect: () => {},
updateGameID: () => {},
updatePlayerID: () => {},
Expand Down Expand Up @@ -286,6 +287,10 @@ class _ClientImpl {
}

this.createDispatchers();

this.transport.subscribeGameMetadata(metadata => {
this.gameMetadata = metadata;
});
}

subscribe(fn) {
Expand Down
15 changes: 13 additions & 2 deletions src/client/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,13 @@ describe('multiplayer', () => {

describe('custom transport', () => {
class CustomTransport {
custom = true;
constructor() {
this.callback = null;
}

subscribeGameMetadata(fn) {
this.callback = fn;
}
}

let client;
Expand All @@ -209,7 +215,12 @@ describe('multiplayer', () => {

test('correct transport used', () => {
expect(client.transport).toBeInstanceOf(CustomTransport);
expect(client.transport.custom).toBe(true);
});

test('metadata callback', () => {
const metadata = { m: true };
client.transport.callback(metadata);
expect(client.gameMetadata).toEqual(metadata);
});
});

Expand Down
1 change: 1 addition & 0 deletions src/client/react-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export function Client(opts) {
reset: this.client.reset,
undo: this.client.undo,
redo: this.client.redo,
gameMetadata: this.client.gameMetadata,
});
}

Expand Down
1 change: 1 addition & 0 deletions src/client/react.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export function Client(opts) {
reset: this.client.reset,
undo: this.client.undo,
redo: this.client.redo,
gameMetadata: this.client.gameMetadata,
});
}

Expand Down
2 changes: 2 additions & 0 deletions src/client/transport/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export class Local {
*/
subscribe() {}

subscribeGameMetadata(_metadata) {} // eslint-disable-line no-unused-vars

/**
* Updates the game id.
* @param {string} id - The new game id.
Expand Down
8 changes: 7 additions & 1 deletion src/client/transport/socketio.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class SocketIO {
this.gameID = this.gameName + ':' + this.gameID;
this.isConnected = false;
this.callback = () => {};
this.gameMetadataCallback = () => {};
}

/**
Expand Down Expand Up @@ -96,10 +97,11 @@ export class SocketIO {

// Called when the client first connects to the master
// and requests the current game state.
this.socket.on('sync', (gameID, state, log) => {
this.socket.on('sync', (gameID, state, log, gameMetadata) => {
if (gameID == this.gameID) {
const action = ActionCreators.sync(state, log);
this.store.dispatch(action);
this.gameMetadataCallback(gameMetadata);
}
});

Expand All @@ -124,6 +126,10 @@ export class SocketIO {
this.callback = fn;
}

subscribeGameMetadata(fn) {
this.gameMetadataCallback = fn;
}

/**
* Updates the game id.
* @param {string} id - The new game id.
Expand Down
12 changes: 9 additions & 3 deletions src/master/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,20 @@ export class Master {
async onSync(gameID, playerID, numPlayers) {
const key = gameID;

let state;
let state, gameMetadata, filteredGameMetadata;

if (this.executeSynchronously) {
state = this.storageAPI.get(key);
gameMetadata = this.storageAPI.get(GameMetadataKey(gameID));
} else {
state = await this.storageAPI.get(key);
gameMetadata = await this.storageAPI.get(GameMetadataKey(gameID));
}
if (gameMetadata) {
filteredGameMetadata = Object.values(gameMetadata.players).map(player => {
return { id: player.id, name: player.name };
});
}

// If the game doesn't exist, then create one on demand.
// TODO: Move this out of the sync call.
if (state === undefined) {
Expand Down Expand Up @@ -301,7 +307,7 @@ export class Master {
this.transportAPI.send({
playerID,
type: 'sync',
args: [gameID, filteredState, log],
args: [gameID, filteredState, log, filteredGameMetadata],
});

return;
Expand Down
30 changes: 30 additions & 0 deletions src/master/master.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@ describe('sync', () => {
await master.onSync('gameID', '0', 2);
expect(send).toHaveBeenCalled();
});

test('should not have metadata', async () => {
await master.onSync('gameID', '0', 2);
// [0][0] = first call, first argument
expect(send.mock.calls[0][0].args[3]).toBeUndefined();
});

test('should have metadata', async () => {
const db = new InMemory();
const dbMetadata = {
players: {
'0': {
id: 0,
credentials: 'qS2m4Ujb_',
name: 'Alice',
},
'1': {
id: 1,
credentials: 'nIQtXFybDD',
name: 'Bob',
},
},
};
db.set('gameID:metadata', dbMetadata);
const masterWithMetadata = new Master(game, db, TransportAPI(send));
await masterWithMetadata.onSync('gameID', '0', 2);

const expectedMetadata = [{ id: 0, name: 'Alice' }, { id: 1, name: 'Bob' }];
expect(send.mock.calls[0][0].args[3]).toMatchObject(expectedMetadata);
});
});

describe('update', () => {
Expand Down

0 comments on commit a0d5f36

Please sign in to comment.