UNPKG

boardgame.io

Version:
158 lines (138 loc) 4.35 kB
/* * Copyright 2018 The boardgame.io Authors * * Use of this source code is governed by a MIT-style * license that can be found in the LICENSE file or at * https://opensource.org/licenses/MIT. */ import IO from 'koa-socket-2'; import { ServerOptions as SocketOptions } from 'socket.io'; import { ServerOptions as HttpsOptions } from 'https'; import { Master, TransportAPI as MasterTransport, AuthFn, } from '../../master/master'; import { PlayerID } from '../../types'; const PING_TIMEOUT = 20 * 1e3; const PING_INTERVAL = 10 * 1e3; /** * API that's exposed by SocketIO for the Master to send * information to the clients. */ export function TransportAPI( gameID: string, socket, clientInfo: Map<any, any>, roomInfo: Map<any, any> ): MasterTransport { /** * Send a message to a specific client. */ const send: MasterTransport['send'] = ({ type, playerID, args }) => { const clients = roomInfo.get(gameID).values(); for (const client of clients) { const info = clientInfo.get(client); if (info.playerID == playerID) { if (socket.id == client) { socket.emit.apply(socket, [type, ...args]); } else { socket.to(info.socket.id).emit.apply(socket, [type, ...args]); } } } }; /** * Send a message to all clients. */ const sendAll: MasterTransport['sendAll'] = makePlayerData => { roomInfo.get(gameID).forEach(c => { const playerID: PlayerID = clientInfo.get(c).playerID; const data = makePlayerData(playerID); send({ playerID, ...data }); }); }; return { send, sendAll }; } export interface SocketOpts { auth?: boolean | AuthFn; https?: HttpsOptions; socketOpts?: SocketOptions; socketAdapter?: any; } /** * Transport interface that uses socket.io */ export class SocketIO { protected clientInfo: Map<any, any>; protected roomInfo: Map<any, any>; private auth: boolean | AuthFn; private https: HttpsOptions; private socketAdapter: any; private socketOpts: SocketOptions; constructor({ auth = true, https, socketAdapter, socketOpts }: SocketOpts = {}) { this.clientInfo = new Map(); this.roomInfo = new Map(); this.auth = auth; this.https = https; this.socketAdapter = socketAdapter; this.socketOpts = socketOpts; } init(app, games) { const io = new IO({ ioOptions: { pingTimeout: PING_TIMEOUT, pingInterval: PING_INTERVAL, ...this.socketOpts, }, }); app.context.io = io; io.attach(app, !!this.https, this.https); if (this.socketAdapter) { io.adapter(this.socketAdapter); } for (const game of games) { const nsp = app._io.of(game.name); nsp.on('connection', socket => { socket.on('update', async (action, stateID, gameID, playerID) => { const master = new Master( game, app.context.db, TransportAPI(gameID, socket, this.clientInfo, this.roomInfo), this.auth ); await master.onUpdate(action, stateID, gameID, playerID); }); socket.on('sync', async (gameID, playerID, numPlayers) => { socket.join(gameID); // Remove client from any previous game that it was a part of. if (this.clientInfo.has(socket.id)) { const { gameID: oldGameID } = this.clientInfo.get(socket.id); this.roomInfo.get(oldGameID).delete(socket.id); } let roomClients = this.roomInfo.get(gameID); if (roomClients === undefined) { roomClients = new Set(); this.roomInfo.set(gameID, roomClients); } roomClients.add(socket.id); this.clientInfo.set(socket.id, { gameID, playerID, socket }); const master = new Master( game, app.context.db, TransportAPI(gameID, socket, this.clientInfo, this.roomInfo), this.auth ); await master.onSync(gameID, playerID, numPlayers); }); socket.on('disconnect', () => { if (this.clientInfo.has(socket.id)) { const { gameID } = this.clientInfo.get(socket.id); this.roomInfo.get(gameID).delete(socket.id); this.clientInfo.delete(socket.id); } }); }); } } }