From c5f0c44d268c40f3808e4725e3d9d6e33747f7a2 Mon Sep 17 00:00:00 2001 From: Jordan Pawlett Date: Fri, 1 May 2020 19:50:36 +0100 Subject: [PATCH] Refacor game service to be atomic and scalable. WIP, still needs redis cron jobs --- .../src/admin-gateway-service.ts | 6 + services/games-service/src/game.ts | 333 ++++++++------ services/games-service/src/games-service.ts | 136 ++++-- services/games-service/src/turn.ts | 157 ++++--- services/games-service/yarn.lock | 431 +++++++++++++----- .../src/websocket-gateway-service.ts | 2 +- 6 files changed, 708 insertions(+), 357 deletions(-) diff --git a/services/admin-gateway-service/src/admin-gateway-service.ts b/services/admin-gateway-service/src/admin-gateway-service.ts index 7bbc422f..f043b503 100644 --- a/services/admin-gateway-service/src/admin-gateway-service.ts +++ b/services/admin-gateway-service/src/admin-gateway-service.ts @@ -107,6 +107,12 @@ export default class AdminGatewayService extends Service { 'PUT /games/start': 'games.start', 'POST /games/cards': 'games.submit', 'POST /games/winner': 'games.winner', + 'GET /games/:id': 'games.get', + 'GET /games': 'games.list', + 'POST /games/search': 'games.find', + 'POST /games': 'games.create', + 'PATCH /games/:id': 'games.update', + 'DELETE /games/:id': 'games.remove', }, mappingPolicy: 'restrict', bodyParsers: { diff --git a/services/games-service/src/game.ts b/services/games-service/src/game.ts index 83d42190..88f316b6 100644 --- a/services/games-service/src/game.ts +++ b/services/games-service/src/game.ts @@ -1,6 +1,6 @@ import { ServiceBroker, LoggerInstance } from 'moleculer'; -import TurnHandler, { GameState, TurnDataWithState } from './turn'; +import TurnHandler, { GameState, TurnDataWithState, TurnData } from './turn'; // turn-setup -> playing cards -> selecting winner -> repeat. -> end-game. /** @@ -48,57 +48,136 @@ export interface GamePlayer { cards?: string[]; } +export interface GameInterface { + _id: string; + room: Room; + players: { [id: string]: GamePlayer }; + gameState: GameState; + prevTurnData: TurnDataWithState; + turns: TurnDataWithState[]; + whiteCards: string[]; + blackCards: string[]; + turnData: TurnData; + selectedCards: { [id: string]: string[] }; +} + export default class Game extends TurnHandler { - private gameTimeout: NodeJS.Timer = null; - private players: { [id: string]: GamePlayer } = this.initalizePlayers(this._room); - private gameState = GameState.TURN_SETUP; - protected lastGameState = null; + private gameTimeout: { [gameId: string]: NodeJS.Timer } = {}; - constructor(private _room: Room, broker: ServiceBroker, logger: LoggerInstance) { + constructor(broker: ServiceBroker, logger: LoggerInstance) { super(broker, logger); + } + + private setGameTimeout(gameId: string, cb: (game: GameInterface) => void, timeout: number = 60 * 1000) { + if (this.gameTimeout[gameId]) { + clearTimeout(this.gameTimeout[gameId]); + } - this.onGameStart(); + this.gameTimeout[gameId] = setTimeout(async () => { + const game = await this.broker.call('games.get', { id: gameId, populate: ['room'] }); + cb(game); + }, timeout); + } + + public destroyGame(id: string) { + return this.broker.call('games.remove', { id }); } - get room() { - return this._room; + public async onTurnUpdated(updatedTurn: TurnDataWithState) { + try { + // Try update the games prevState. + // tslint:disable-next-line: max-line-length + await this.broker.call('games.update', { id: updatedTurn.gameId, prevTurnData: updatedTurn, gameState: updatedTurn.state }); + } catch (e) { + this.logger.error(e); + } + + this.logger.info('Turn update found, setting timer for next turn', updatedTurn.state); + switch (updatedTurn.state) { + case GameState.TURN_SETUP: + return this.setGameTimeout(updatedTurn.gameId, (game) => this.handleNextTurn(game), 10000); + case GameState.PICKING_CARDS: + return this.setGameTimeout(updatedTurn.gameId, (game) => this.handleWinnerSelection(game)); + case GameState.SELECTING_WINNER: + return this.setGameTimeout(updatedTurn.gameId, (game) => this.handleNoWinner(game, 'The Czar did not pick a winner! They have failed us all...')); + case GameState.ENEDED: + return this.setGameTimeout(updatedTurn.gameId, (game) => { + // kick everyone out and end the game; + this.destroyGame(game._id); + }); + default: + this.logger.error('Not sure which state to call'); + return; + } } - private onGameStart() { + private initalizePlayers(room: Room): { [id: string]: GamePlayer } { + return room.players.reduce((acc, curr) => { + acc[curr] = { _id: curr, cards: [], isCzar: false, score: 0 }; + return acc; + }, {}); + } + public onGameStart(room: Room) { + const players = this.initalizePlayers(room); + const initalTurnData = { + czar: null, + blackCard: null, + turn: 0, + totalTime: 60 * 1000 + }; + const initalGameState = GameState.TURN_SETUP; const gameData: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - ...this.turnData, + gameId: '', + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + ...initalTurnData, selectedCards: {}, winner: null, winningCards: [], - state: this.gameState, + state: initalGameState }; - return this.fetchCards(this._room.options.decks) + return this.fetchCards(room.options.decks) + .then(({ whiteCards, blackCards }) => { + return this.broker.call('games.create', { + room: room._id, + players, + gameState: initalGameState, + prevTurnData: initalGameState, + turns: [], + whiteCards, + blackCards, + turnData: initalTurnData, + selectedCards: {}, + }); + }) + .then((game: GameInterface) => { + gameData.gameId = game._id; + this.logger.info('game created'); + return this.broker.call('rooms.update', { id: room._id, status: 'started' }); + }) .then(() => { - this.logger.info('Game started, sending data', gameData); - this.lastGameState = gameData; - this.broker.emit('games.updated', gameData); - this.handleNextTurn(); + this.logger.info('Game started, sending data'); + return this.broker.emit('games.turn.updated', gameData); + }) + .catch(err => { + this.logger.error(err); }); } - private initalizePlayers(room: Room): { [id: string]: GamePlayer } { - return room.players.reduce((acc, curr) => { - acc[curr] = { _id: curr, cards: [], isCzar: false, score: 0 }; - return acc; - }, {}); - } + private async endGame(game: GameInterface) { + if (this.gameTimeout[game._id]) { + clearTimeout(this.gameTimeout[game._id]); + } - private async endGame() { + this.logger.info('End game triggered'); + const { players, turnData, room } = game; // emit end game, with score tally and info. - this.gameState = GameState.ENEDED; let winners = { _ids: [], score: 0 }; - Object.values(this.players).forEach(({ _id, score }) => { + Object.values(players).forEach(({ _id, score }) => { // Player has a largest score. Take all the glory! if (score > winners.score) { winners = { _ids: [_id], score }; @@ -112,127 +191,101 @@ export default class Game extends TurnHandler { }); const gameData: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - ...this.turnData, + gameId: game._id, + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + ...turnData, selectedCards: {}, winner: winners._ids, winningCards: [], state: GameState.ENEDED, }; - this.lastGameState = gameData; - await this.broker.emit('games.updated', gameData); - return this.broker.call('rooms.update', { id: this.room._id, status: 'finished' }) + await this.broker.emit('games.turn.updated', gameData); + return this.broker.call('rooms.update', { id: room._id, status: 'finished' }) .then(() => this.logger.info('Game ended', gameData)) .catch((err) => { this.logger.error(err); }); } - private setGameTimeout(cb: () => void, timeout: number = 60 * 1000) { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); - } - - this.gameTimeout = setTimeout(() => { - cb(); - }, timeout); - } - - private handleNextTurn() { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); + private handleNextTurn(game: GameInterface) { + if (this.gameTimeout[game._id]) { + clearTimeout(this.gameTimeout[game._id]); } + const { players, room } = game; // Target should actually be based on the first user score to get to that. - const isTargetReached = Object.values(this.players).some(player => player.score >= this._room.options.target); + const isTargetReached = Object.values(players).some(player => player.score >= room.options.target); if (isTargetReached) { - this.endGame(); + this.endGame(game); return; } - return this.startTurn(this.players) - .then(async data => { - this.gameState = GameState.PICKING_CARDS; - - const withState: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - selectedCards: {}, - winner: null, - winningCards: [], - ...data, - state: GameState.PICKING_CARDS, - }; - - this.logger.info('Starting turn', withState); - this.lastGameState = withState; - await this.broker.emit('games.updated', withState); - - this.setGameTimeout(() => this.handleWinnerSelection()); + // mutate players by reference + Object.values(players).forEach(player => player.isCzar = false); + return this.startTurn(game) + .then(dataWithState => { + this.logger.info('Starting round', dataWithState); + return this.broker.emit('games.turn.updated', dataWithState); }) .catch(err => { this.logger.error(err); }); } - private async handleWinnerSelection() { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); + private async handleWinnerSelection(game: GameInterface) { + if (this.gameTimeout[game._id]) { + clearTimeout(this.gameTimeout[game._id]); } + const { selectedCards, players, turnData, room } = game; // If no users selected any cards to play, skip. - if (!Object.keys(this.selectedCards).length) { - this.handleNoWinner('No one selected any cards. Everyone loses!'); + if (!Object.keys(selectedCards).length) { + this.handleNoWinner(game, 'No one selected any cards. Everyone loses!'); return; } this.logger.info('Round time up. Entering winner selection stage'); - const populatedSelectedCards = await this.populatedSelectedCards(); + const populatedSelectedCards = await this.populatedSelectedCards(selectedCards); // Send all cards for everyone to view. - this.gameState = GameState.SELECTING_WINNER; const gameData: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - ...this.turnData, + gameId: game._id, + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + ...turnData, selectedCards: populatedSelectedCards, winner: null, winningCards: [], state: GameState.SELECTING_WINNER, }; - this.lastGameState = gameData; - await this.broker.emit('games.updated', gameData); - - // If the czar doesn't pick a winner within 60 seconds. Move on. - this.setGameTimeout(() => this.handleNoWinner('The Czar did not pick a winner! They have failed us all...')); + await this.broker.emit('games.turn.updated', gameData); } - public onHandSubmitted(playerId: string, whiteCards: string[]) { + public async onHandSubmitted(game: GameInterface, playerId: string, whiteCards: string[]) { + const { gameState } = game; // Ignore cards if the game state is no longer picking cards. - if (this.gameState !== GameState.PICKING_CARDS) { + if (gameState !== GameState.PICKING_CARDS) { throw new Error('Not allowed to select cards at this time'); } - this.submitCards(playerId, whiteCards); - // TODO: emit placement 'card selected' for each selection to display on the front-end. - const playersCards = this.players[playerId].cards; - // make a new array of cards, excluding the ones the player just played. - this.players[playerId].cards = playersCards.filter(card => !whiteCards.includes(card)); - if (this.hasEveryoneSelected(this.players)) { - this.handleWinnerSelection(); + const updatedGame = await this.submitCards(game, playerId, whiteCards); + if (this.hasEveryoneSelected(updatedGame)) { + this.handleWinnerSelection(updatedGame); } } - private async handleNoWinner(reason?: string) { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); + private async handleNoWinner(game: GameInterface, reason?: string) { + if (this.gameTimeout[game._id]) { + clearTimeout(this.gameTimeout[game._id]); } + const { players, turnData, room, turns } = game; const gameData: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - ...this.turnData, + gameId: game._id, + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + ...turnData, selectedCards: {}, winner: null, winningCards: [], @@ -241,37 +294,40 @@ export default class Game extends TurnHandler { }; // Store the end state of each round in a collection. - this.turns.push(gameData); - this.lastGameState = gameData; - await this.broker.emit('games.updated', gameData); - this.setGameTimeout(() => this.handleNextTurn(), 10000); + turns.push(gameData); + await this.broker.call('games.update', { id: game._id, turns }); + await this.broker.emit('games.turn.updated', gameData); } - public async onWinnerSelected(winner: string, clientId: string) { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); + public async onWinnerSelected(game: GameInterface, winner: string, clientId: string) { + if (this.gameTimeout[game._id]) { + clearTimeout(this.gameTimeout[game._id]); } - if (clientId !== this.turnData.czar) { + const { turnData, gameState, players, turns, room, selectedCards } = game; + if (clientId !== turnData.czar) { throw new Error('Only the czar is allowed to select the winner'); } // Only allow one winning card to be selected. - if (this.gameState !== GameState.SELECTING_WINNER) { + if (gameState !== GameState.SELECTING_WINNER) { throw new Error('This is not the correct round to select a winner'); } - const winningCards = await this.selectWinner(winner); + const winningCards = await this.selectWinner(selectedCards, winner); + // reset selected cards. // emit winning cards. - this.players[winner].score += 1; - this.gameState = GameState.TURN_SETUP; - const populatedSelectedCards = await this.populatedSelectedCards(); + const winningPlayer = players[winner]; + if (winningPlayer) { + winningPlayer.score += 1; + } + const populatedSelectedCards = await this.populatedSelectedCards(selectedCards); - // This object should be stored in a turns array in the db, for history functionality. const gameData: TurnDataWithState = { - players: Object.values(this.players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), - roomId: this.room._id, - ...this.turnData, + gameId: game._id, + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + ...turnData, selectedCards: populatedSelectedCards, winner: winner, winningCards, @@ -279,48 +335,53 @@ export default class Game extends TurnHandler { }; // Store the end state of each round in a collection. - this.turns.push(gameData); - this.lastGameState = gameData; + turns.push(gameData); + await this.broker.call('games.update', { id: game._id, turns, players }); // Emit the winning card, and winning player, for front-end display - await this.broker.emit('games.updated', gameData); - - this.setGameTimeout(() => this.handleNextTurn(), 10000); + await this.broker.emit('games.turn.updated', gameData); } - public onPlayerLeave(playerId: string) { - const player = this.players[playerId]; - delete this.players[playerId]; - - if (!Object.keys(this.players).length) { - this.endGame(); + public async onPlayerLeave(game: GameInterface, playerId: string, adapter: any, onUpdated: any) { + const player = game.players[playerId]; + + // delete this.players[playerId]; + const prop = `players.${playerId}`; + const newGameObj = await adapter.updateById(game._id, { $unset: { [prop]: 1 } }); + onUpdated(newGameObj); + newGameObj._id = newGameObj._id.toString(); + // if that was the last player to leave. End the game. + if (!Object.keys(newGameObj.players).length) { + this.destroyGame(game._id); return; } + newGameObj.room = { _id: newGameObj.room }; // If the czar leaves the game. End the turn. if (player.isCzar) { - clearTimeout(this.gameTimeout); - this.handleNoWinner('The Czar left the game'); + this.handleNoWinner(newGameObj, 'The Czar left the game'); } } - public async onPlayerJoin(playerId: string) { - // Ensure the new player is including in the match. - this.players[playerId] = { _id: playerId, cards: [], isCzar: false, score: 0 }; - await this.dealWhiteCards(this.players[playerId]); + public async onPlayerJoin(gameId: string, playerId: string) { + // Ensure the new player is included in the match. + const newPlayer = { _id: playerId, cards: [], isCzar: false, score: 0 }; + const playersProp = `players.${playerId}`; + return this.broker.call('games.update', { id: gameId, [playersProp]: newPlayer }); // Implement this. // if (this.lastGameState) { // emit update to only the player that joined. + // await this.dealWhiteCards(newPlayer, game.whiteCards); // } } public destroy() { - if (this.gameTimeout) { - clearTimeout(this.gameTimeout); - } + // if (this.gameTimeout) { + // clearTimeout(this.gameTimeout); + // } - this.players = {}; + // this.players = {}; // handle firing game removed update. } diff --git a/services/games-service/src/games-service.ts b/services/games-service/src/games-service.ts index 28a1e7ed..783d7a6e 100644 --- a/services/games-service/src/games-service.ts +++ b/services/games-service/src/games-service.ts @@ -2,11 +2,11 @@ import { Service, ServiceBroker, Context, NodeHealthStatus } from 'moleculer'; import CacheCleaner from '@cards-against-formality/cache-clean-mixin'; import dbMixin from '@cards-against-formality/db-mixin'; -import Game, { Room } from './game'; +import Game, { Room, GameInterface } from './game'; -export default class DecksService extends Service { +export default class GameService extends Service { - private games: Game[] = []; + private gameService: Game = null; constructor(_broker: ServiceBroker) { super(_broker); @@ -17,11 +17,19 @@ export default class DecksService extends Service { mixins: [ dbMixin('games'), CacheCleaner([ + 'cache.clean.games', 'cache.clean.cards', 'cache.clean.decks', 'cache.clean.rooms', ]) ], + settings: { + populates: { + room: { + action: 'rooms.get', + } + } + }, actions: { health: this.health, start: { @@ -50,7 +58,15 @@ export default class DecksService extends Service { events: { 'rooms.player.joined': this.handlePlayerJoined, 'rooms.player.left': this.handlePlayerLeft, - 'rooms.removed': this.handleRoomRemoved + 'rooms.removed': this.handleRoomRemoved, + 'games.turn.updated': ctx => this.gameService.onTurnUpdated(ctx.params) + }, + entityCreated: this.entityCreated, + entityUpdated: this.entityUpdated, + entityRemoved: this.entityRemoved, + started: () => { + this.gameService = new Game(this.broker, this.logger); + return null; } }, ); @@ -70,54 +86,74 @@ export default class DecksService extends Service { throw new Error('Only the host can start the game'); } - return ctx.call('rooms.update', { id: roomId, status: 'started' }) - .then(room => { - this.games.push(new Game(room, this.broker, this.logger)); - return { message: 'Game successfully started' }; - }) + return this.gameService.onGameStart(_room) + .then(() => ({ message: 'Game successfully started' })) .catch(err => { this.logger.error(err); throw new Error('Failed to start game'); }); } - private submitCards(ctx: Context<{ clientId: string; roomId: string; cards: string[] }>) { + private getGameMatchingRoom(ctx: Context, roomId: string): Promise { + return ctx.call(`${this.name}.find`, { query: { room: roomId }, populate: ['room'] }) + .then((games: GameInterface[]) => { + if (!games?.length) { + throw new Error('Unable to find game'); + } + this.logger.info(games[0].room); + return games[0]; + }) + .catch(err => { + this.logger.error(err); + throw err; + }); + } + + private async submitCards(ctx: Context<{ clientId: string; roomId: string; cards: string[] }>) { const { roomId, cards, clientId } = ctx.params; - const foundGame = this.games.find(game => game.room._id === roomId); - foundGame.onHandSubmitted(clientId, cards); + + const game: any = await this.getGameMatchingRoom(ctx, roomId); + await this.gameService.onHandSubmitted(game, clientId, cards); return { message: 'Cards successfully submitted' }; } - private selectWinner(ctx: Context<{ clientId: string; roomId: string; winnerId: string }>) { + private async selectWinner(ctx: Context<{ clientId: string; roomId: string; winnerId: string }>) { const { clientId, roomId, winnerId } = ctx.params; - const foundGame = this.games.find(game => game.room._id === roomId); - foundGame.onWinnerSelected(winnerId, clientId); + const game: any = await this.getGameMatchingRoom(ctx, roomId); + await this.gameService.onWinnerSelected(game, winnerId, clientId); return { message: 'Winner selected' }; } - private handlePlayerJoined(ctx: Context<{ clientId: string; roomId: string }>) { + private async handlePlayerJoined(ctx: Context<{ clientId: string; roomId: string }>) { const { clientId, roomId } = ctx.params; - const foundGame = this.games.find(game => game.room._id === roomId); - if (foundGame) { - foundGame.onPlayerJoin(clientId); - } + return this.getGameMatchingRoom(ctx, roomId) + .then((game) => { + return this.gameService.onPlayerJoin(game._id, clientId); + }) + .catch(err => { + // game must not have started yet. + }); } private handlePlayerLeft(ctx: Context<{ clientId: string; roomId: string }>) { const { clientId, roomId } = ctx.params; - const foundGame = this.games.find(game => game.room._id === roomId); - if (foundGame) { - foundGame.onPlayerLeave(clientId); - } + return this.getGameMatchingRoom(ctx, roomId) + .then((game) => { + return this.gameService.onPlayerLeave( + game, + clientId, + this.adapter, + json => this.entityChanged('updated', json, ctx).then(() => json) + ); + }) + .catch(err => { + // game must not have started yet. + }); } - private handleRoomRemoved(ctx: Context<{ _id: string }>) { - const index = this.games.findIndex(game => game.room._id === ctx.params._id); - if (index >= 0) { - this.logger.info('Found game, destroying game room'); - this.games[index].destroy(); - this.games.splice(index, 1); - } + private async handleRoomRemoved(ctx: Context<{ _id: string }>) { + const game: any = await this.getGameMatchingRoom(ctx, ctx.params._id); + return this.gameService.destroyGame(game._id); } /** @@ -132,4 +168,42 @@ export default class DecksService extends Service { return ctx.call('$node.health'); } + /** + * Emit an event when a Card is created. + * + * @private + * @param {*} json + * @param {Context} ctx + * @returns + * @memberof ClientsService + */ + private entityCreated(json: any, ctx: Context) { + return ctx.emit(`${this.name}.created`, json); + } + + /** + * Emit an event when a card is updated. + * + * @private + * @param {*} json + * @param {Context} ctx + * @returns + * @memberof ClientsService + */ + private entityUpdated(json: any, ctx: Context) { + return ctx.emit(`${this.name}.updated`, json); + } + + /** + * Emit an event when a Card is removed. + * + * @private + * @param {*} json + * @param {Context} ctx + * @returns + * @memberof ClientsService + */ + private entityRemoved(json: any, ctx: Context) { + return ctx.emit(`${this.name}.removed`, json); + } } diff --git a/services/games-service/src/turn.ts b/services/games-service/src/turn.ts index b42479d3..49e0de25 100644 --- a/services/games-service/src/turn.ts +++ b/services/games-service/src/turn.ts @@ -1,4 +1,4 @@ -import { GamePlayer } from './game'; +import { GamePlayer, GameInterface } from './game'; import { ServiceBroker, LoggerInstance } from 'moleculer'; export interface Card { @@ -20,13 +20,14 @@ export interface TurnData { blackCard: Card; turn: number; totalTime: number; + selectedCards: { [id: string]: Card[] }; } export interface TurnDataWithState extends TurnData { + gameId: string; state: GameState; players: GamePlayer[]; roomId: string; - selectedCards: { [id: string]: Card[] }; winner: string | string[]; winningCards: Card[]; errorMessage?: string; @@ -34,53 +35,34 @@ export interface TurnDataWithState extends TurnData { export default class TurnHandler { - protected turns: TurnDataWithState[] = []; - private whiteCards: string[] = []; - private blackCards: string[] = []; - private _turnData: TurnData = { - czar: null, - blackCard: null, - turn: 0, - totalTime: 60 * 1000 - }; - public selectedCards: { [id: string]: string[] } = {}; - constructor(protected broker: ServiceBroker, protected logger: LoggerInstance) { } - get turnData() { - return this._turnData; - } - - get turn() { - return this.turnData.turn; - } - - protected async fetchCards(deckIds: string[]) { - try { - // tslint:disable-next-line: max-line-length - const decks: Array<{ whiteCards: string[]; blackCards: string[] }> = await this.broker.call('decks.get', { id: deckIds }); - decks.forEach(deck => { - const { whiteCards, blackCards } = deck; - this.whiteCards.push(...whiteCards); - this.blackCards.push(...blackCards); + protected fetchCards(deckIds: string[]): Promise<{ whiteCards: string[]; blackCards: string[] }> { + const _whiteCards = []; + const _blackCards = []; + return this.broker.call, any>('decks.get', { id: deckIds }) + .then(decks => { + decks.forEach(deck => { + const { whiteCards, blackCards } = deck; + _whiteCards.push(...whiteCards); + _blackCards.push(...blackCards); + }); + return { whiteCards: _whiteCards, blackCards: _blackCards }; }); - } catch (e) { - this.logger.error('Fatal: failed to fetch decks to initialize game'); - } } private getRandomIndex(upperLimit: number): number { - return Math.floor(Math.random() * upperLimit); + return Math.round(Math.random() * upperLimit); } - private pickCzar(players: { [id: string]: GamePlayer }): string { + private pickCzar(turns: TurnDataWithState[], players: { [id: string]: GamePlayer }): string { // get the previous rounds czar. - const turnsLength = this.turns.length; + const turnsLength = turns.length; let prevCzar; if (turnsLength) { - const prevTurn = this.turns[turnsLength - 1]; + const prevTurn = turns[turnsLength - 1]; prevCzar = prevTurn.czar; } @@ -105,19 +87,19 @@ export default class TurnHandler { return selectedPlayer._id; } - private pickBlackCard(): Promise { - const index = this.getRandomIndex(this.blackCards.length - 1); + private pickBlackCard(blackCards: string[]): Promise { + const index = this.getRandomIndex(blackCards.length - 1); // Remove the card so it cannot be chosen again. - this.blackCards.splice(index, 1); - const id = this.blackCards[index]; + blackCards.splice(index, 1); + const id = blackCards[index]; return this.broker.call('cards.get', { id }); } - private pickWhiteCard(): string { - const index = this.getRandomIndex(this.whiteCards.length - 1); + private pickWhiteCard(whiteCards: string[]): string { + const index = this.getRandomIndex(whiteCards.length - 1); // Remove the card so it cannot be chosen again. - this.whiteCards.splice(index, 1); - return this.whiteCards[index]; + whiteCards.splice(index, 1); + return whiteCards[index]; } private emitCardsToPlayer(player: GamePlayer) { @@ -127,74 +109,99 @@ export default class TurnHandler { } // Given a player, deal all the white cards to it. - protected dealWhiteCards(player: GamePlayer): Promise { + protected dealWhiteCards(player: GamePlayer, whiteCards: string[]): Promise { const cardsNeeded = 10 - player.cards.length; if (!cardsNeeded) { return this.emitCardsToPlayer(player); } while (player.cards?.length !== 10) { - player.cards.push(this.pickWhiteCard()); + player.cards.push(this.pickWhiteCard(whiteCards)); } return this.emitCardsToPlayer(player); } - private async ensurePlayersHaveCards(players: { [id: string]: GamePlayer }) { - const cardPlayers = Object.values(players).map(player => this.dealWhiteCards(player)); + private async ensurePlayersHaveCards(players: { [id: string]: GamePlayer }, whiteCards: string[]) { + const cardPlayers = Object.values(players).map(player => this.dealWhiteCards(player, whiteCards)); return Promise.all(cardPlayers); } - protected async startTurn(players: { [id: string]: GamePlayer }): Promise { - this._turnData.blackCard = null; - this._turnData.czar = null; - + protected async startTurn(game: GameInterface): Promise { + const { turnData, players, room, turns, whiteCards, blackCards } = game; // mutate by reference. ensure we reset the czar. Object.values(players).forEach(player => player.isCzar = false); - this.selectedCards = {}; - this._turnData.turn += 1; - this._turnData.czar = this.pickCzar(players); - this._turnData.blackCard = await this.pickBlackCard(); - await this.ensurePlayersHaveCards(players); - return this._turnData; - } - - protected submitCards(clientId: string, cards: string[]): void { - if (this.turnData.czar === clientId) { + turnData.turn += 1; + // players mutated by reference. + turnData.czar = this.pickCzar(turns, players); + // mutate black and white cards by reference + this.logger.info('Black card length before', blackCards.length); + turnData.blackCard = await this.pickBlackCard(blackCards); + this.logger.info('Black card length after', blackCards.length); + await this.ensurePlayersHaveCards(players, whiteCards); + + // tslint:disable-next-line: max-line-length + await this.broker.call('games.update', { id: game._id, selectedCards: {}, players, whiteCards, blackCards, turnData }); + return { + gameId: game._id, + players: Object.values(players).map(({ _id, score, isCzar }) => ({ _id, score, isCzar })), + roomId: room._id, + selectedCards: {}, + winner: null, + winningCards: [], + ...turnData, + state: GameState.PICKING_CARDS, + }; + } + + protected async submitCards(game: GameInterface, clientId: string, cards: string[]): Promise { + const { selectedCards, turnData, players } = game; + + if (turnData.czar === clientId) { throw new Error('The czar is not allowed to play the round.'); } - if (clientId in this.selectedCards) { + if (clientId in selectedCards) { throw new Error('You have already submitted your cards.'); } - if (this.turnData.blackCard.pick !== cards.length) { - throw new Error(`You must select exactly ${this.turnData.blackCard.pick} cards.`); + if (turnData.blackCard.pick !== cards.length) { + throw new Error(`You must select exactly ${turnData.blackCard.pick} cards.`); } - this.selectedCards[clientId] = cards; + // TODO: emit placement 'card selected' for each selection to display on the front-end. + const playersCards = players[clientId].cards; + // make a new array of cards, excluding the ones the player just played. + const newCards = playersCards.filter(card => !cards.includes(card)); + + const playersProp = `players.${clientId}.cards`; + const selectedCardsProp = `selectedCards.${clientId}`; + // NOT CONFIDENT THAT THIS WILL WORK. DOUBLE CHECK + await this.broker.call('games.update', { + id: game._id, [playersProp]: newCards, [selectedCardsProp]: cards + }); + return this.broker.call('games.get', { id: game._id, populate: ['room'] }); } - protected selectWinner(winner: string): Promise { - const winningCards = this.selectedCards[winner]; - // reset selected cards. - this.selectedCards = {}; + protected selectWinner(selectedCards: { [id: string]: string[] }, winner: string): Promise { + const winningCards = selectedCards[winner]; return this.broker.call('cards.get', { id: winningCards }); } - protected async populatedSelectedCards() { - const allSelectedCards = Object.values(this.selectedCards).flat(1); + protected async populatedSelectedCards(selectedCards: { [id: string]: string[] }) { + const allSelectedCards = Object.values(selectedCards).flat(1); const cards: Card[] = await this.broker.call('cards.get', { id: allSelectedCards }); - const entries = Object.entries(this.selectedCards).map(([key, value]) => { + const entries = Object.entries(selectedCards).map(([key, value]) => { const populatedCards = value.map(v => cards.find(c => c._id === v)); return [key, populatedCards]; }); return Object.fromEntries(entries); } - protected hasEveryoneSelected(players: { [id: string]: GamePlayer }): boolean { + protected hasEveryoneSelected(game: GameInterface): boolean { + const { players, selectedCards, turnData } = game; // ensure every player, has a property in the selected cards map. return Object.keys(players).every(player => { - return player in this.selectedCards || this.turnData.czar === player; + return player in selectedCards || turnData.czar === player; }); } } diff --git a/services/games-service/yarn.lock b/services/games-service/yarn.lock index ddec9165..19a08eb1 100644 --- a/services/games-service/yarn.lock +++ b/services/games-service/yarn.lock @@ -10,9 +10,9 @@ "@babel/highlight" "^7.8.3" "@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== + version "7.9.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" + integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== "@babel/highlight@^7.8.3": version "7.9.0" @@ -55,21 +55,21 @@ integrity sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug== "@types/jsonwebtoken@^8.3.8": - version "8.3.8" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.8.tgz#b27c9156dde2049ae03e56528a53ef5a8294aa82" - integrity sha512-g2ke5+AR/RKYpQxd+HJ2yisLHGuOV0uourOcPtKlcT5Zqv4wFg9vKhFpXEztN4H/6Y6RSUKioz/2PTFPP30CTA== + version "8.3.9" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.9.tgz#48da9a49997e4eb046733e6878f583d7448f0594" + integrity sha512-00rI8GbOKuRtoYxltFSRTVUXCRLbuYwln2/nUMPtFU9JGS7if+nnmLjeoFGmqsNCmblPLAaeQ/zMLVsHr6T5bg== dependencies: "@types/node" "*" "@types/node@*": - version "13.9.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.3.tgz#6356df2647de9eac569f9a52eda3480fa9e70b4d" - integrity sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA== + version "13.13.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" + integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== "@types/node@^10.12.12": - version "10.17.17" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8" - integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q== + version "10.17.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.21.tgz#c00e9603399126925806bed2d9a1e37da506965e" + integrity sha512-PQKsydPxYxF1DsAFWmunaxd3sOi3iMt6Zmx/tgaagHYmwJ/9cRH91hQkeJZaUGWbvn0K5HlSVEXkn5U/llWPpQ== "@types/strip-bom@^3.0.0": version "3.0.0" @@ -86,6 +86,11 @@ abab@^2.0.0: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + acorn-globals@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" @@ -110,9 +115,9 @@ acorn@^6.0.1: integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== ajv@^6.10.2, ajv@^6.5.5: - version "6.12.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" - integrity sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw== + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -171,6 +176,19 @@ append-transform@^0.4.0: dependencies: default-require-extensions "^1.0.0" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -580,7 +598,7 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -bson@^1.1.1: +bson@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.4.tgz#f76870d799f15b854dffb7ee32f0a874797f7e89" integrity sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q== @@ -670,6 +688,11 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" @@ -710,9 +733,9 @@ cli-cursor@^2.1.0: restore-cursor "^2.0.0" cli-spinners@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77" - integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ== + version "2.3.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.3.0.tgz#0632239a4b5aa4c958610142c34bb7a651fc8df5" + integrity sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w== cli-width@^1.0.1: version "1.1.1" @@ -797,6 +820,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + convert-source-map@^1.4.0, convert-source-map@^1.5.1: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" @@ -900,7 +928,7 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -924,6 +952,11 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -977,6 +1010,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + denque@^1.1.0, denque@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" @@ -989,6 +1027,11 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" @@ -1045,7 +1088,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: version "1.17.5" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== @@ -1322,9 +1365,9 @@ fast-levenshtein@~2.0.6: integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= fastest-validator@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fastest-validator/-/fastest-validator-1.2.0.tgz#84ca8bd8a5501071885fbcf3a7a9656fa5150d47" - integrity sha512-cdO1pb0OGFdz9Hy69Bj+dn6+lfq51S3h2QzYc+vLMJnbaEK6Y1Sg81ylQlhMBz4Ubz40rTD66sZwwmKu2MeUbQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/fastest-validator/-/fastest-validator-1.3.0.tgz#f6813f1bd50ec8d78e99dff5b6596e9f91f878e4" + integrity sha512-MJZrtZl6bur73n/t54nfIx0UH6nx9UsI1olZhMmD3WuIpJLyEPUU+x0PWKitPuytE+JuHTJ4liY3Tqia4ma/xw== fb-watchman@^2.0.0: version "2.0.1" @@ -1440,6 +1483,13 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1458,6 +1508,20 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" @@ -1520,9 +1584,9 @@ globals@^9.18.0: integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== graceful-fs@^4.1.11, graceful-fs@^4.1.2: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== growly@^1.3.0: version "1.3.0" @@ -1530,13 +1594,14 @@ growly@^1.3.0: integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= handlebars@^4.0.3: - version "4.7.3" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee" - integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg== + version "4.7.6" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" + integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== dependencies: + minimist "^1.2.5" neo-async "^2.6.0" - optimist "^0.6.1" source-map "^0.6.1" + wordwrap "^1.0.0" optionalDependencies: uglify-js "^3.1.4" @@ -1575,6 +1640,11 @@ has-symbols@^1.0.0, has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -1642,13 +1712,20 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -1692,6 +1769,11 @@ inherits@2, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + inquirer@0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.11.0.tgz#7448bfa924092af311d47173bbab990cae2bb027" @@ -1723,9 +1805,9 @@ invert-kv@^2.0.0: integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== ioredis@^4.16.2: - version "4.16.2" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.16.2.tgz#29ae301885e493c4642d2fb1998761c576e27462" - integrity sha512-hlRK9q9K8pWpYIxUh079dWUWECiGNdI7+/AR21pgeqIBXQzjVKFnz0wXvmhEQZV3Hvv4saQpvJww9SkjwvPXZA== + version "4.16.3" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.16.3.tgz#6a6b85830206fd98353b7ff8536521f17943be53" + integrity sha512-Ejvcs2yW19Vq8AipvbtfcX3Ig8XG9EAyFOvGbhI/Q1QoVOK9ZdgY092kdOyOWIYBnPHjfjMJhU9qhsnp0i0K1w== dependencies: cluster-key-slot "^1.1.0" debug "^4.1.1" @@ -2438,9 +2520,9 @@ json-stringify-safe@~5.0.1: integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= json5@2.x: - version "2.1.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e" - integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ== + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== dependencies: minimist "^1.2.5" @@ -2754,17 +2836,17 @@ micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -mime-db@1.43.0: - version "1.43.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" - integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== +mime-db@1.44.0: + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.26" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" - integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== dependencies: - mime-db "1.43.0" + mime-db "1.44.0" mimic-fn@^1.0.0: version "1.2.0" @@ -2788,10 +2870,20 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8= +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" mixin-deep@^1.2.0: version "1.3.2" @@ -2801,10 +2893,10 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.x, mkdirp@^0.5.1, mkdirp@~0.5.1: - version "0.5.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" - integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== +mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" @@ -2860,12 +2952,12 @@ moleculer@^0.14.6: node-fetch "^2.6.0" mongodb@^3.3.2: - version "3.5.5" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.5.tgz#1334c3e5a384469ac7ef0dea69d59acc829a496a" - integrity sha512-GCjDxR3UOltDq00Zcpzql6dQo1sVry60OXJY3TDmFc2SWFY6c8Gn1Ardidc5jDirvJrx2GC3knGOImKphbSL3A== + version "3.5.7" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.5.7.tgz#6dcfff3bdbf67a53263dcca1647c265eea1d065d" + integrity sha512-lMtleRT+vIgY/JhhTn1nyGwnSMmJkJELp+4ZbrjctrnBxuLbj6rmLuJFz8W2xUzUqWmqoyVxJLYuC58ZKpcTYQ== dependencies: bl "^2.2.0" - bson "^1.1.1" + bson "^1.1.4" denque "^1.4.1" require_optional "^1.0.1" safe-buffer "^5.1.2" @@ -2893,9 +2985,9 @@ mute-stream@0.0.5: integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= nan@^2.12.1: - version "2.14.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" - integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nanomatch@^1.2.9: version "1.2.13" @@ -2915,9 +3007,9 @@ nanomatch@^1.2.9: to-regex "^3.0.1" nats@^1.4.0: - version "1.4.6" - resolved "https://registry.yarnpkg.com/nats/-/nats-1.4.6.tgz#3ca1d9bce8615a07cdd9d46baace594141d5b669" - integrity sha512-ixzuW3UJYW1rUHIPR4LRrnVpL9RXtYHdbGPOgzqYOiOKirKviFPY3gNE/EOVKB3XL1rlJt7vWxEPd+jttLCqkw== + version "1.4.9" + resolved "https://registry.yarnpkg.com/nats/-/nats-1.4.9.tgz#ad86a920bd44110193edc25068c8e91a358f7a81" + integrity sha512-D0qTg2v0jir32TCJfGkxRwOba4/9vqJJmr4GtghR/J9AF6E0y1BdCeXxnD2G+hruSPnKUbF0NzaQVPbSg5fdOg== dependencies: nuid "^1.1.4" ts-nkeys "^1.0.16" @@ -2938,6 +3030,15 @@ nedb@^1.8.0: mkdirp "~0.5.1" underscore "~1.4.4" +needle@^2.2.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.1.tgz#14af48732463d7475696f937626b1b993247a56a" + integrity sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + neo-async@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" @@ -2989,6 +3090,30 @@ node-notifier@^5.2.1, node-notifier@^5.4.0: shellwords "^0.1.1" which "^1.3.0" +node-pre-gyp@*: + version "0.14.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" + integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4.4.2" + +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -3006,6 +3131,27 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3013,6 +3159,16 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + nuid@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/nuid/-/nuid-1.1.4.tgz#6145710b6cc9ef0df7b94af09c6d750925939097" @@ -3116,14 +3272,6 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -3162,11 +3310,19 @@ os-locale@^3.1.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -3345,9 +3501,9 @@ prompts@^0.1.9: sisteransi "^0.1.1" psl@^1.1.28: - version "1.7.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" - integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== pump@^3.0.0: version "3.0.0" @@ -3376,6 +3532,16 @@ randomatic@^3.0.0: kind-of "^6.0.0" math-random "^1.0.1" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -3393,7 +3559,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@^2.0.1, readable-stream@^2.3.5: +readable-stream@^2.0.1, readable-stream@^2.0.6, readable-stream@^2.3.5: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -3582,9 +3748,9 @@ resolve@1.1.7: integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= resolve@1.x, resolve@^1.0.0, resolve@^1.10.0, resolve@^1.3.2: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" @@ -3688,7 +3854,7 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -3721,9 +3887,9 @@ shellwords@^0.1.1: integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== sisteransi@^0.1.1: version "0.1.1" @@ -3792,10 +3958,10 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map-support@^0.5.12, source-map-support@^0.5.6: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== +source-map-support@^0.5.12, source-map-support@^0.5.17, source-map-support@^0.5.6: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -3831,9 +3997,9 @@ spdx-correct@^3.0.0: spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" - integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.0" @@ -3915,7 +4081,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -3932,21 +4098,39 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string.prototype.trimend@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" string_decoder@~1.1.1: version "1.1.1" @@ -4000,7 +4184,7 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@^2.0.0: +strip-json-comments@^2.0.0, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -4039,6 +4223,19 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + test-exclude@^4.2.1: version "4.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.3.tgz#a9a5e64474e4398339245a0a769ad7c2f4a97c20" @@ -4179,14 +4376,14 @@ ts-node-dev@^1.0.0-pre.40: tsconfig "^7.0.0" ts-node@*: - version "8.8.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.1.tgz#7c4d3e9ed33aa703b64b28d7f9d194768be5064d" - integrity sha512-10DE9ONho06QORKAaCBpPiFCdW+tZJuY/84tyypGtl6r+/C7Asq0dhqbRZURuUlLQtZxxDvT8eoj8cGW0ha6Bg== + version "8.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.9.1.tgz#2f857f46c47e91dcd28a14e052482eb14cfd65a5" + integrity sha512-yrq6ODsxEFTLz0R3BX2myf0WBCSQh9A+py8PBo1dCzWIOcvisbyH6akNKqDHMgXePF2kir5mm5JXJTH3OUJYOQ== dependencies: arg "^4.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.6" + source-map-support "^0.5.17" yn "3.1.1" ts-node@^7.0.1: @@ -4284,12 +4481,11 @@ typescript@^3.2.2: integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== uglify-js@^3.1.4: - version "3.8.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.8.0.tgz#f3541ae97b2f048d7e7e3aa4f39fd8a1f5d7a805" - integrity sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ== + version "3.9.1" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.9.1.tgz#a56a71c8caa2d36b5556cc1fd57df01ae3491539" + integrity sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA== dependencies: commander "~2.20.3" - source-map "~0.6.1" underscore@~1.4.4: version "1.4.4" @@ -4460,15 +4656,22 @@ which@^1.2.12, which@^1.2.9, which@^1.3.0: dependencies: isexe "^2.0.0" +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= wrap-ansi@^2.0.0: version "2.1.0" @@ -4514,7 +4717,7 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= -yallist@^3.0.2: +yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/services/websocket-gateway-service/src/websocket-gateway-service.ts b/services/websocket-gateway-service/src/websocket-gateway-service.ts index c4c8004b..6a91f0d1 100644 --- a/services/websocket-gateway-service/src/websocket-gateway-service.ts +++ b/services/websocket-gateway-service/src/websocket-gateway-service.ts @@ -81,7 +81,7 @@ export default class WebsocketGatewayService extends Service { 'rooms.created': ctx => this.emitRoomUpdate(ctx, 'created'), 'rooms.updated': ctx => this.emitRoomUpdate(ctx, 'updated'), 'rooms.removed': ctx => this.emitRoomUpdate(ctx, 'removed'), - 'games.updated': ctx => this.handleGameUpdate(ctx), + 'games.turn.updated': ctx => this.handleGameUpdate(ctx), 'games.deal': ctx => this.handleCardDealing(ctx) } }