Giter Club home page Giter Club logo

p2p's Introduction

@boardgame.io/p2p

npm version Unit Tests Coverage Status Gitter

Experimental peer-to-peer multiplayer transport for boardgame.io

This package provides an experimental multiplayer transport implementation, which establishes a peer-to-peer connection between clients. Instead of using a Node.js server to maintain authoritative match state and communicate between clients, a host client maintains the authoritative state in their browser and manages connections between all the connected peers.

Installation

npm install @boardgame.io/p2p

Usage

import { Client } from 'boardgame.io/client';
import { P2P } from '@boardgame.io/p2p';
import { MyGame } from './game';

const matchID = 'random-id-string';

// Host clients maintain the authoritative game state and manage
// communication between all other peers.
const hostClient = Client({
  game: MyGame,
  matchID,
  playerID: '0',
  multiplayer: P2P({ isHost: true }),
});

// Peer clients look up a host using the `matchID` and communicate
// with the host much like they would with a server.
const peerClient = Client({
  game: MyGame,
  matchID,
  playerID: '1',
  multiplayer: P2P(),
});

API

P2P(options?)

You can configure the peer-to-peer transport by passing an optional object. It can contain the following fields.

  • isHost

    • type: boolean
    • default: false

    Controls whether or not this instance is a host and controls the authoritative game state. Only one client should have isHost: true.

  • onError

    • type: (error: Error) => void
    • default: undefined

    Callback to subscribe to PeerJS’s 'error' event.

  • peerOptions

    Passed to PeerJS when creating a new Peer connection. See PeerJS docs for full list of options →

Authenticating players

By default, any client can connect with any playerID and successfully submit moves as that player. If you want to avoid this, you must set credentials on your boardgame.io clients. The first client to connect with a given playerID will authenticate and all future connections for that playerID must also provide the same credentials to successfully connect.

The transport uses public-key encryption, deterministically generating keys from the value of credentials. For optimal security, use the included helper method to generate credentials:

import { Client } from 'boardgame.io/client';
import { P2P, generateCredentials } from '@boardgame.io/p2p';

const credentials = generateCredentials();

const peerClient = Client({
  multiplayer: P2P(),
  credentials,
  // ...
});

Notes

What does experimental mean?

This package currently works but is liable to change as what is required for peer-to-peer scenarios is better understood. Please try it out and send us feedback, bug reports and feature requests, but be aware that it may change in breaking ways until a more stable API is established.

Why would I want to use this?

Deploying a Node server to enable multiplayer play can be a serious logistical hurdle and is often more expensive than serving a static website. This transport enables multiplayer play without a game server. If you’re looking for a casual way to play with friends that can be pretty attractive.

What are the drawbacks?

No lobby or matchmaking. You have to have a matchID to find and connect to the other players. One pattern might be for a host to generate a random matchID on your site. Then they could share the matchID with friends via their preferred instant messaging service for example.

Additionally, currently if the host goes offline, the match will stop and potentially all match state will be lost. In the future it may be possible to decentralise this and allow other players to step in as hosts in this case.

How does this work?

Under the hood this transport uses PeerJS, a library that helps simplify creating peer-to-peer connections. When a host starts running, it registers with a so-called “handshake” server using a matchID to identify itself. Then when other clients connect, they can use the same matchID to request a connection to the host from the handshake server. Once that peer-to-peer connection is established between clients, all future communication will take place directly between clients and no longer pass via a server.

Unless configured otherwise, this transport will use PeerJS’s default handshake server to negotiate the initial connection between peers. Consider running your own handshake server or donating to PeerJS to help support theirs.

Contributing

Bug reports, suggestions, and pull requests are very welcome! If you run into any problems or have questions, please open an issue.

Please also note the code of conduct and be kind to each other.

License

The code in this repository is provided under the MIT License.

p2p's People

Contributors

delucis avatar dependabot[bot] avatar joust avatar xlnx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

p2p's Issues

Hostless Mode

I think we should try to make the library as p2p as possible.
To achieve true p2p one thing we need to get rid of this the need for a host.

In boardgame.io a host is the one entity that is trusted by all clients.
The responsibility of the host are the following:

  • authenticate clients
  • create randoms
  • store clients private state
  • verify valid moves

To get rid of the host, these responsibility need to be handled trustless from within all clients.

Authentication

Authentication will be handled like in any other p2p network through public-key cryptography.

And this is the only part where we need some sort of trust to a single entity.
Not to verify identities but to initiate a game and to invite other players to that game.

Ones the game has started that trust is no longer needed.
Every client can verify moves from any other client, because their communication will be signed by the users private key.

Create randoms

This is one of the trickier challenges to overcome.
In boardgame.io the random values need to be random, that means nobody should be able to control the outcome of the random value. Secondly the random value need to be secret so nobody but the client ,who created the random value, knows what that value is. Because that value might be used to draw a random card or do anything else that is random and secret to the client.

To generate a random value like mentioned above.
The clients that needs a random value, sends a request to all clients. Part of that request is a random value that is encrypted by that client. Because the value is send before any other parts of the random value are known, there is no way to choose the value to get a wanted outcome. Secondly because this value is encrypted by the client, the other client cant influence the outcome by choosing a value that fits their needs. This also means nobody but the client who asked for the random value can know what that value is.

Store clients private state

Clients private state is encrypted and replicated to all clients.

Verify valid moves

This is an another interesting problem. Since there a certain parts that needs to be secret during the game. Verification can only happen in certain situations. For example when a random card is drawn. That move can only be verified after that card should no longer be kept secret. There maybe a way to find these things automatically. But to allow for most compatibility. The moves in a game can only be verified after the game has ended. So when ctx.events.endGame() is called every client publishs their encryption key(s) these keys can then be used by any client to find out if the game has been cheated in. This is an important charakteristik of a p2p network. Trust needs to be replaced by verification.

Draw a card and keep it hidden

There is an another Problem that need to be adressed since some games will require that mechanism. To draw a random card from a pile and keep that hidden. Its not enough to keep the random value hidden. The card will need to be removed from the pile. Observing the removal will give a way what card was drawn. To make that work, every card in the pile will need to be encrypted with a diffrent key. And these keys will need to be splitted and every client gets one part of the key. So when a card is drawn. Every client sends their part to the client that has drawn the card. That way only the client that has drawn the card has all parts of the key and can decrypt and see what card was drawn.
I think this part will need to be investigated futher. Because the encryption of the card will need made without ever giving away the full encryption key.

EDIT: It seems like somebody far smarter has already solved this issue. Its called Mental Poker But we would need to evaluate if full distribution is performat enough.

Run `playerView` on state sent to clients

playerView is not yet implemented when sending state updates to clients:

p2p/src/index.ts

Lines 86 to 98 in f84a73a

this.master = new Master(game, db, {
send: ({ playerID, ...data }) => {
for (const [client] of this.clients) {
if (client.metadata.playerID === playerID) client.send(data);
}
},
sendAll: (data) => {
for (const [client] of this.clients) {
client.send(data);
}
},
});
}

Prerequisite: expose getFilterPlayerView in boardgame.io Done in boardgameio/boardgame.io@20817aa

Retry connections

If a peer tries to connect before the host is online, the connection will fail and never succeed even once the host does come online. Some kind of polling retry logic would make sense here to pick up hosts that (re)connect late.

Player ID protection

Currently anyone with the match ID can play as any other player. Some system to support credentials seems like a good idea.

Auto-host mode

Using PeerJS’s 'unavailable-id' error type, it might be possible to automatically decide which client is the host.

  1. All clients would initially attempt to claim the host ID.

  2. If a client receives the 'unavailable-id' error, that indicates a peer has already taken the role of host and the client should attempt to connect to the existing host.

API could be P2P({ isHost: 'auto' }).

This has the advantage of the web app not needing to decide who hosts in advance. Just fire up the P2P transport and the first client to connect will host.

The disadvantage would likely be primarily for longer running games once #3 is implemented. With state persistence it would probably be desirable to have a static host. Some form of #4 could mitigate that: any client can host and all clients persist state.

State persistence

The P2PHost currently uses an in-memory storage implementation to hold match state. This means navigating, closing a tab, or just stopping/hiding the client will usually result in that state being lost.

We should use a persistent storage layer (e.g. using localStorage or indexedDB) and offer some options for how long matches should persist for as well as some way to delete matches.

Is there a p2p equivalent of getFirstAvailablePlayerID?

This is the only trouble I've run into using the experimental p2p stuff. I'd like players to be assigned sequential playerIDs as they join. If anyone could point me in the right direction, I would really appreciate it.

Decentralization

Currently a single client acts as the host and all communications pass through them.

The host could provide connected clients with the peer IDs of all other clients so that they can also establish connections between each other (a “full-mesh topology”). At that point, various other possibilities arise: each client could be responsible for processing and emitting their own state updates instead of sending an action to be processed and emitted by the host. Then if the host drops out, the clients (via their existing connections) could agree a new host amongst themselves and keep playing.

A variation on this would be the host just transmitting a list of IDs for all connected peers to allow them to establish connections only in the case the host goes offline.

Decentralization would also mean storing match state in all clients, which would also be more robust.

This should probably be an optional feature.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.