changes
This commit is contained in:
		@@ -1,7 +1,6 @@
 | 
			
		||||
export class ErrorBase extends Error {
 | 
			
		||||
    constructor(message: string) {
 | 
			
		||||
        super(message);
 | 
			
		||||
        console.log('this.constructor.name :>> ', this.constructor.name);
 | 
			
		||||
        this.name = this.constructor.name;
 | 
			
		||||
        this.stack = (new Error()).stack;
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										7
									
								
								src/common/errors/SessionCreationError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/common/errors/SessionCreationError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { ErrorBase } from "./ErrorBase";
 | 
			
		||||
 | 
			
		||||
export class SessionCreationError extends ErrorBase {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super('Session creation error');
 | 
			
		||||
  }
 | 
			
		||||
} 
 | 
			
		||||
							
								
								
									
										7
									
								
								src/common/errors/SessionNotFoundError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/common/errors/SessionNotFoundError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { ErrorBase } from "./ErrorBase";
 | 
			
		||||
 | 
			
		||||
export class SessionNotFoundError extends ErrorBase {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('Session not found');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,21 @@ export async function wait(ms: number) {
 | 
			
		||||
  return new Promise(resolve => setTimeout(resolve, ms));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const whileNotUndefined = async (fn: Function, maxQueries: number = 20, millis: number = 500): Promise<any> => {
 | 
			
		||||
  return new Promise(async (resolve, reject) => {
 | 
			
		||||
    let result;
 | 
			
		||||
    while (result === undefined) {
 | 
			
		||||
      await wait(millis);
 | 
			
		||||
      result = fn()
 | 
			
		||||
      if (maxQueries-- < 0) {
 | 
			
		||||
        reject()
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    resolve(result);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function askQuestion(question: string): Promise<string> {
 | 
			
		||||
  return new Promise((resolve) => {
 | 
			
		||||
    // console.log(chalk.yellow(question));
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import { Tile } from "./entities/Tile";
 | 
			
		||||
import { LoggingService } from "../common/LoggingService";
 | 
			
		||||
import { printBoard, printLine, uuid, wait } from '../common/utilities';
 | 
			
		||||
import { GameSummary } from './dto/GameSummary';
 | 
			
		||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
 | 
			
		||||
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
 | 
			
		||||
import { GameState } from './dto/GameState';
 | 
			
		||||
 | 
			
		||||
export class DominoesGame {
 | 
			
		||||
@@ -25,13 +25,12 @@ export class DominoesGame {
 | 
			
		||||
  winner: PlayerInterface | null = null;
 | 
			
		||||
  rng: PRNG;
 | 
			
		||||
  handSize: number = 7;
 | 
			
		||||
  notificationManager: PlayerNotificationManager = new PlayerNotificationManager();
 | 
			
		||||
  notificationManager: PlayerNotificationService = new PlayerNotificationService();
 | 
			
		||||
  lastMove: PlayerMove | null = null;
 | 
			
		||||
 | 
			
		||||
  constructor(public players: PlayerInterface[], seed: PRNG) {
 | 
			
		||||
    this.id = uuid();
 | 
			
		||||
    this.logger.info(`Game ID: ${this.id}`);
 | 
			
		||||
    this.logger.info(`Seed: ${this.seed}`);
 | 
			
		||||
    this.rng = seed
 | 
			
		||||
    this.board = new Board(seed);
 | 
			
		||||
    this.initializeGame();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import { PlayerInterface } from "./entities/player/PlayerInterface";
 | 
			
		||||
import { LoggingService } from "../common/LoggingService";  
 | 
			
		||||
import { getRandomSeed, uuid, wait } from "../common/utilities";
 | 
			
		||||
import { MatchSessionState } from "./dto/MatchSessionState";
 | 
			
		||||
import { PlayerNotificationManager } from './PlayerNotificationManager';
 | 
			
		||||
import { PlayerNotificationService } from '../server/services/PlayerNotificationService';
 | 
			
		||||
import seedrandom, { PRNG } from "seedrandom";
 | 
			
		||||
import { NetworkPlayer } from "./entities/player/NetworkPlayer";
 | 
			
		||||
import { PlayerHuman } from "./entities/player/PlayerHuman";
 | 
			
		||||
@@ -16,11 +16,11 @@ export class MatchSession {
 | 
			
		||||
  private waitingForPlayers: boolean = true;
 | 
			
		||||
  private waitingSeconds: number = 0;
 | 
			
		||||
  private logger: LoggingService = new LoggingService();
 | 
			
		||||
  private playerNotificationManager = new PlayerNotificationManager();
 | 
			
		||||
  private playerNotificationManager = new PlayerNotificationService();
 | 
			
		||||
  
 | 
			
		||||
  id: string;
 | 
			
		||||
  matchInProgress: boolean = false;
 | 
			
		||||
  matchWinner: PlayerInterface | null = null;
 | 
			
		||||
  matchWinner?: PlayerInterface = undefined;
 | 
			
		||||
  maxPlayers: number = 4;
 | 
			
		||||
  mode: string = 'classic';
 | 
			
		||||
  players: PlayerInterface[] = [];
 | 
			
		||||
@@ -31,20 +31,19 @@ export class MatchSession {
 | 
			
		||||
  sessionInProgress: boolean = false;
 | 
			
		||||
  state: string = 'created'
 | 
			
		||||
 | 
			
		||||
  constructor(public creator: PlayerInterface, public name?: string) {
 | 
			
		||||
  constructor(public creator: PlayerInterface, public name?: string, seed?: string) {
 | 
			
		||||
    this.seed = seed || getRandomSeed();
 | 
			
		||||
    this.id = uuid();
 | 
			
		||||
    this.name = name || `Game ${this.id}`;
 | 
			
		||||
    this.addPlayer(creator);
 | 
			
		||||
    
 | 
			
		||||
    this.addPlayer(creator);    
 | 
			
		||||
    this.creator = creator;
 | 
			
		||||
 | 
			
		||||
    this.logger.info(`Match session created by: ${creator.name}`);
 | 
			
		||||
    this.logger.info(`Match session ID: ${this.id}`);
 | 
			
		||||
    this.logger.info(`Match session name: ${this.name}`);
 | 
			
		||||
    this.logger.info(`Points to win: ${this.pointsToWin}`);
 | 
			
		||||
    this.sessionInProgress = true;
 | 
			
		||||
    this.matchInProgress = false;
 | 
			
		||||
    this.playerNotificationManager.notifyMatchState(this);
 | 
			
		||||
    this.playerNotificationManager.notifyPlayersState(this.players);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  get numPlayers() {
 | 
			
		||||
@@ -77,14 +76,17 @@ export class MatchSession {
 | 
			
		||||
      this.state = 'started'
 | 
			
		||||
      this.logger.info(`Game #${gameNumber} started`);
 | 
			
		||||
      // this.game.reset()
 | 
			
		||||
      await this.currentGame.start();
 | 
			
		||||
      const gameSummary = await this.currentGame.start();
 | 
			
		||||
      this.logger.debug('gameSummary :>> ', gameSummary);
 | 
			
		||||
      this.setScores();
 | 
			
		||||
      this.checkMatchWinner();
 | 
			
		||||
      this.resetReadiness();
 | 
			
		||||
      this.resetPlayers();
 | 
			
		||||
      this.state = 'waiting'
 | 
			
		||||
      await this.playerNotificationManager.notifyMatchState(this);
 | 
			
		||||
      this.playerNotificationManager.sendEventToPlayers('game-finished', this.players);
 | 
			
		||||
      await this.checkHumanPlayersReady();
 | 
			
		||||
      if (this.matchInProgress) {
 | 
			
		||||
        await this.checkHumanPlayersReady();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.state = 'end'
 | 
			
		||||
    // await this.game.start();
 | 
			
		||||
@@ -103,10 +105,10 @@ export class MatchSession {
 | 
			
		||||
      }, 1000);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  resetReadiness() {
 | 
			
		||||
  
 | 
			
		||||
  resetPlayers() {
 | 
			
		||||
    this.players.forEach(player => {
 | 
			
		||||
      player.ready = false
 | 
			
		||||
      player.reset()
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -114,7 +116,10 @@ export class MatchSession {
 | 
			
		||||
    const scores = Array.from(this.scoreboard.values());
 | 
			
		||||
    const maxScore = Math.max(...scores);
 | 
			
		||||
    if (maxScore >= this.pointsToWin) {
 | 
			
		||||
      this.matchWinner = this.players.find(player => this.scoreboard.get(player.id) === maxScore)!;
 | 
			
		||||
      this.matchWinner = this.players.find(player => this.scoreboard.get(player.name) === maxScore);
 | 
			
		||||
      if (!this.matchWinner) {
 | 
			
		||||
        throw new Error('Match winner not found');
 | 
			
		||||
      }
 | 
			
		||||
      this.logger.info(`Match winner: ${this.matchWinner.name} with ${maxScore} points`);
 | 
			
		||||
      this.matchInProgress = false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -122,17 +127,19 @@ export class MatchSession {
 | 
			
		||||
  resetScoreboard() {
 | 
			
		||||
    this.scoreboard = new Map();
 | 
			
		||||
    this.players.forEach(player => {
 | 
			
		||||
      this.scoreboard.set(player.id, 0);
 | 
			
		||||
      this.scoreboard.set(player.name, 0);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setScores() {
 | 
			
		||||
    const totalPips = this.currentGame?.players.reduce((acc, player) => acc + player.pipsCount(), 0);
 | 
			
		||||
    const totalPips = this.currentGame?.players.reduce((acc, player) => acc + player.pipsCount(), 0) || 0;
 | 
			
		||||
    if (this.currentGame && this.currentGame.winner !== null) {
 | 
			
		||||
      const winner = this.currentGame.winner;
 | 
			
		||||
      this.scoreboard.set(winner.id, this.scoreboard.get(winner.id)! + totalPips!);
 | 
			
		||||
      const currentPips = this.scoreboard.get(winner.name) || 0;
 | 
			
		||||
      this.logger.debug (`${winner.name} has ${currentPips} points`);
 | 
			
		||||
      this.scoreboard.set(winner.name, currentPips + totalPips);
 | 
			
		||||
      if (winner.teamedWith !== null) {
 | 
			
		||||
        this.scoreboard.set(winner.teamedWith.id, this.scoreboard.get(winner.teamedWith.id)! + totalPips!);
 | 
			
		||||
        this.scoreboard.set(winner.teamedWith.name, currentPips + totalPips);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -181,9 +188,7 @@ export class MatchSession {
 | 
			
		||||
    return player;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async start(seed?: string) {
 | 
			
		||||
    this.seed = seed || getRandomSeed();
 | 
			
		||||
    console.log('seed :>> ', this.seed);
 | 
			
		||||
  async start() {
 | 
			
		||||
    if (this.matchInProgress) {
 | 
			
		||||
      throw new Error("Game already in progress");
 | 
			
		||||
    }
 | 
			
		||||
@@ -207,8 +212,9 @@ export class MatchSession {
 | 
			
		||||
    this.logger.info(`${player.name} joined the game!`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setPlayerReady(user: string) {
 | 
			
		||||
    const player = this.players.find(player => player.name === user); 
 | 
			
		||||
  setPlayerReady(userId: string) {
 | 
			
		||||
    this.logger.debug(userId)
 | 
			
		||||
    const player = this.players.find(player => player.id === userId); 
 | 
			
		||||
    if (!player) {
 | 
			
		||||
      throw new Error("Player not found");
 | 
			
		||||
    }
 | 
			
		||||
@@ -242,7 +248,7 @@ export class MatchSession {
 | 
			
		||||
      mode: this.mode,
 | 
			
		||||
      pointsToWin: this.pointsToWin,
 | 
			
		||||
      status: this.sessionInProgress ? 'in progress' : 'waiting',
 | 
			
		||||
      scoreboard: this.scoreboard,
 | 
			
		||||
      scoreboard: [...this.scoreboard.entries()],
 | 
			
		||||
      matchWinner: this.matchWinner?.getState() || null,
 | 
			
		||||
      matchInProgress: this.matchInProgress
 | 
			
		||||
    };
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,15 @@ export class NetworkClientNotifier {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sendEvent(player: NetworkPlayer, event: string, data?: any) {
 | 
			
		||||
    this.io.to(player.socketId).emit(event, data);
 | 
			
		||||
    const eventData = { event, data };
 | 
			
		||||
    this.io.to(player.socketId).emit('game-event', eventData);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sendEventWithAck(player: NetworkPlayer, event: string, data: any, timeoutSecs: number = 900) {
 | 
			
		||||
    const eventData = { event, data };
 | 
			
		||||
    const response = await this.io.to(player.socketId)
 | 
			
		||||
    .timeout(timeoutSecs * 1000).emitWithAck('game-event-ack', eventData);
 | 
			
		||||
    return response[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async broadcast(event: string, data: any) {
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ import { Tile } from './entities/Tile';
 | 
			
		||||
import { NetworkClientNotifier } from './NetworkClientNotifier';
 | 
			
		||||
import { NetworkPlayer } from './entities/player/NetworkPlayer';
 | 
			
		||||
import { PlayerMoveSide, PlayerMoveSideType } from './constants';
 | 
			
		||||
import { SocketDisconnectedError } from '../common/exceptions/SocketDisconnectedError';
 | 
			
		||||
import { SocketDisconnectedError } from '../common/errors/SocketDisconnectedError';
 | 
			
		||||
 | 
			
		||||
export class PlayerInteractionNetwork implements PlayerInteractionInterface {
 | 
			
		||||
    player: PlayerInterface;
 | 
			
		||||
@@ -19,7 +19,7 @@ export class PlayerInteractionNetwork implements PlayerInteractionInterface {
 | 
			
		||||
    async makeMove(board: Board): Promise<PlayerMove | null> {
 | 
			
		||||
      let response = undefined;
 | 
			
		||||
      try {
 | 
			
		||||
        response = await this.clientNotifier.notifyPlayer(this.player as NetworkPlayer, 'makeMove', {
 | 
			
		||||
        response = await this.clientNotifier.sendEventWithAck(this.player as NetworkPlayer, 'ask-client-for-move', {
 | 
			
		||||
          freeHands: board.getFreeEnds(),
 | 
			
		||||
        });
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ export interface MatchSessionState {
 | 
			
		||||
  maxPlayers: number;
 | 
			
		||||
  numPlayers: number;
 | 
			
		||||
  waitingSeconds: number;
 | 
			
		||||
  scoreboard: Map<string, number>;
 | 
			
		||||
  scoreboard: [string, number][];
 | 
			
		||||
  matchWinner: PlayerDto | null;
 | 
			
		||||
  matchInProgress: boolean;
 | 
			
		||||
  playersReady: number
 | 
			
		||||
 
 | 
			
		||||
@@ -6,8 +6,6 @@ import { LoggingService } from "../../../common/LoggingService";
 | 
			
		||||
import { EventEmitter } from "stream";
 | 
			
		||||
import { PlayerInteractionInterface } from "../../PlayerInteractionInterface";
 | 
			
		||||
import { uuid } from "../../../common/utilities";
 | 
			
		||||
import { GameState } from "../../dto/GameState";
 | 
			
		||||
import { MatchSessionState } from "../../dto/MatchSessionState";
 | 
			
		||||
import { PlayerDto } from "../../dto/PlayerDto";
 | 
			
		||||
 | 
			
		||||
export abstract class AbstractPlayer extends EventEmitter implements PlayerInterface {
 | 
			
		||||
@@ -27,20 +25,16 @@ export abstract class AbstractPlayer extends EventEmitter implements PlayerInter
 | 
			
		||||
  abstract chooseTile(board: Board): Promise<Tile>;
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  async notifyGameState(state: GameState): Promise<void> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyPlayerState(state: PlayerDto): Promise<void> {
 | 
			
		||||
  async sendEventWithAck(event: string, data: any): Promise<void> {    
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  async notifyMatchState(state: MatchSessionState): Promise<void> {
 | 
			
		||||
  async sendEvent(event: string, data: any = {}): Promise<void> {    
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForAction(actionId: string): Promise<boolean> {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  async sendEvent(event: string): Promise<void> {    
 | 
			
		||||
  reset(): void {
 | 
			
		||||
    this.hand = [];
 | 
			
		||||
    this.score = 0;
 | 
			
		||||
    this.ready = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  pipsCount(): number {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,55 +4,22 @@ import { PlayerHuman } from "./PlayerHuman";
 | 
			
		||||
import { NetworkClientNotifier } from "../../NetworkClientNotifier";
 | 
			
		||||
import { Tile } from "../Tile";
 | 
			
		||||
import { Board } from "../Board";
 | 
			
		||||
import { GameState } from "../../dto/GameState";
 | 
			
		||||
import { PlayerDto } from "../../dto/PlayerDto";
 | 
			
		||||
import { MatchSessionState } from "../../dto/MatchSessionState";
 | 
			
		||||
import { SocketDisconnectedError } from "../../../common/exceptions/SocketDisconnectedError";
 | 
			
		||||
 | 
			
		||||
export class NetworkPlayer extends PlayerHuman {
 | 
			
		||||
  socketId: string;
 | 
			
		||||
  socketId!: string;
 | 
			
		||||
  playerInteraction: PlayerInteractionInterface = new PlayerInteractionNetwork(this);
 | 
			
		||||
  clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
 | 
			
		||||
  
 | 
			
		||||
  constructor(name: string, socketId: string) {
 | 
			
		||||
    super(name);
 | 
			
		||||
  constructor(id: string, name: string, socketId: string  ) {
 | 
			
		||||
    super(id, name);
 | 
			
		||||
    this.socketId = socketId;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyGameState(state: GameState): Promise<void> {
 | 
			
		||||
    const response = await this.clientNotifier.notifyPlayer(this, 'gameState', state);
 | 
			
		||||
    console.log('game state notified :>> ', response);
 | 
			
		||||
    if (response === undefined || response.status !== 'ok' ) {
 | 
			
		||||
      throw new SocketDisconnectedError();
 | 
			
		||||
    }
 | 
			
		||||
  } 
 | 
			
		||||
 | 
			
		||||
  async notifyPlayerState(state: PlayerDto): Promise<void> {
 | 
			
		||||
    const response = await this.clientNotifier.notifyPlayer(this, 'playerState', state);
 | 
			
		||||
    console.log('player state notified :>> ', response);
 | 
			
		||||
    if (response === undefined || response.status !== 'ok' ) {
 | 
			
		||||
      throw new SocketDisconnectedError();
 | 
			
		||||
    }
 | 
			
		||||
  async sendEvent(event: string, data:any = {}): Promise<void> {
 | 
			
		||||
    this.clientNotifier.sendEvent(this, event, data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyMatchState(state: MatchSessionState): Promise<void> {
 | 
			
		||||
    const response = await this.clientNotifier.notifyPlayer(this, 'matchState', state);
 | 
			
		||||
    console.log('session state notified :>> ', response);
 | 
			
		||||
    if (response === undefined || response.status !== 'ok' ) {
 | 
			
		||||
      throw new SocketDisconnectedError();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  async waitForAction(actionId: string): Promise<boolean> {
 | 
			
		||||
    const response = await this.clientNotifier.notifyPlayer(this, actionId);
 | 
			
		||||
    if (response === undefined || response.status !== 'ok' ) {
 | 
			
		||||
      throw new SocketDisconnectedError();
 | 
			
		||||
    }
 | 
			
		||||
    const { actionResult } = response;
 | 
			
		||||
    return actionResult;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sendEvent(event: string): Promise<void> {
 | 
			
		||||
    this.clientNotifier.sendEvent(this, event);
 | 
			
		||||
  async sendEventWithAck(event: string, data: any): Promise<any> {
 | 
			
		||||
    return await this.clientNotifier.sendEventWithAck(this, event, data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async chooseTile(board: Board): Promise<Tile> {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,9 @@ import { PlayerInteractionInterface } from '../../PlayerInteractionInterface';
 | 
			
		||||
 | 
			
		||||
export class PlayerHuman extends AbstractPlayer {
 | 
			
		||||
  playerInteraction: PlayerInteractionInterface = new PlayerInteractionConsole(this);
 | 
			
		||||
  constructor(name: string) {
 | 
			
		||||
  constructor(id: string, name: string) {
 | 
			
		||||
    super(name);
 | 
			
		||||
    this.id = id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async makeMove(board: Board): Promise<PlayerMove | null> {    
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,10 @@ export interface PlayerInterface {
 | 
			
		||||
  makeMove(gameState: Board): Promise<PlayerMove | null>;
 | 
			
		||||
  chooseTile(board: Board): Promise<Tile>;
 | 
			
		||||
  pipsCount(): number;
 | 
			
		||||
  notifyGameState(state: GameState): Promise<void>;
 | 
			
		||||
  notifyPlayerState(state: PlayerDto): Promise<void>;
 | 
			
		||||
  notifyMatchState(state: MatchSessionState): Promise<void>;
 | 
			
		||||
  waitForAction(actionId: string, data: any): Promise<boolean>;
 | 
			
		||||
  sendEvent(event: string): Promise<void>;
 | 
			
		||||
  reset(): void;
 | 
			
		||||
  
 | 
			
		||||
  sendEvent(event: string, data: any): Promise<void>;
 | 
			
		||||
  sendEventWithAck(event: string, data: any): Promise<any>;
 | 
			
		||||
 | 
			
		||||
  getState(): PlayerDto;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/server/controllers/GameController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/server/controllers/GameController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import { Request, Response } from "express";
 | 
			
		||||
import { BaseController } from "./BaseController";
 | 
			
		||||
import { SessionService } from "../services/SessionService";
 | 
			
		||||
 | 
			
		||||
export class GameController extends BaseController {
 | 
			
		||||
  private sessionService: SessionService = new SessionService();
 | 
			
		||||
 | 
			
		||||
  public async createMatch(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const { user, body } = req;
 | 
			
		||||
      const { sessionName, seed } = body;
 | 
			
		||||
      const sessionId = await this.sessionService.createSession(user, sessionName, seed);
 | 
			
		||||
      res.status(201).json({ sessionId });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.handleError(res, error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public joinMatch(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const { user, body } = req;
 | 
			
		||||
      const { sessionId } = body;
 | 
			
		||||
      this.sessionService.joinSession(user, sessionId);
 | 
			
		||||
      res.status(200).json({ status: 'ok' });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.handleError(res, error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public listMatches(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      this.sessionService.listSessions().then((sessions) => {
 | 
			
		||||
        res.status(200).json(sessions);
 | 
			
		||||
      });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.handleError(res, error);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,11 +9,11 @@ export abstract class BaseMongoManager {
 | 
			
		||||
  protected abstract collection?: string;
 | 
			
		||||
  logger = new LoggingService().logger;
 | 
			
		||||
 | 
			
		||||
  create(data: Entity) {
 | 
			
		||||
  create(data: Entity): Promise<ObjectId | undefined>{
 | 
			
		||||
    return mongoExecute(
 | 
			
		||||
      async ({ collection }) => {
 | 
			
		||||
        await collection?.insertOne(data as any);
 | 
			
		||||
        return data;
 | 
			
		||||
        const result = await collection?.insertOne(data as any);
 | 
			
		||||
        return result?.insertedId;
 | 
			
		||||
      },
 | 
			
		||||
      { colName: this.collection }
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,86 +1,38 @@
 | 
			
		||||
import { MatchSession } from "../../game/MatchSession";
 | 
			
		||||
import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer";
 | 
			
		||||
import { SessionService } from "../services/SessionService";
 | 
			
		||||
 | 
			
		||||
import { ManagerBase } from "./ManagerBase";
 | 
			
		||||
 | 
			
		||||
export class SessionManager extends ManagerBase {
 | 
			
		||||
  private static sessions: any = {};
 | 
			
		||||
  private sessionService: SessionService = new SessionService();
 | 
			
		||||
  private static sessions: Map<string, MatchSession> = new Map();
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.logger.info('SessionController created');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  createSession(data: any, socketId: string): any {
 | 
			
		||||
    const { user, sessionName } = data;
 | 
			
		||||
    const player =  new NetworkPlayer(user, socketId);
 | 
			
		||||
    const session = new MatchSession(player, sessionName);
 | 
			
		||||
    SessionManager.sessions[session.id] = session;
 | 
			
		||||
    this.sessionService.createSession(session);
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      status: 'ok',
 | 
			
		||||
      sessionId: session.id,
 | 
			
		||||
      playerId: player.id
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  joinSession(data: any, socketId: string): any {
 | 
			
		||||
    this.logger.debug('joinSession data :>> ')
 | 
			
		||||
    this.logger.object(data);
 | 
			
		||||
    const { user, sessionId } = data;
 | 
			
		||||
    const session: MatchSession = SessionManager.sessions[sessionId];
 | 
			
		||||
    const player =  new NetworkPlayer(user, socketId);
 | 
			
		||||
    session.addPlayer(player);
 | 
			
		||||
    this.sessionService.updateSession(session);
 | 
			
		||||
    return {
 | 
			
		||||
      status: 'ok',
 | 
			
		||||
      sessionId: session.id,
 | 
			
		||||
      playerId: player.id
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setPlayerReady(data: any): any {
 | 
			
		||||
    const { user, sessionId } = data;
 | 
			
		||||
    const session: MatchSession = SessionManager.sessions[sessionId];
 | 
			
		||||
    session.setPlayerReady(user)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startSession(data: any): any {
 | 
			
		||||
    const sessionId: string = data.sessionId;
 | 
			
		||||
    const seed: string | undefined = data.seed;
 | 
			
		||||
    const session = SessionManager.sessions[sessionId];
 | 
			
		||||
 | 
			
		||||
    if (!session) {
 | 
			
		||||
      return ({
 | 
			
		||||
        status: 'error',
 | 
			
		||||
        message: 'Session not found'
 | 
			
		||||
      });
 | 
			
		||||
    } else if (session.gameInProgress) {
 | 
			
		||||
      return {
 | 
			
		||||
        status: 'error',
 | 
			
		||||
        message: 'Game already in progress'
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      const missingHumans = session.maxPlayers - session.numPlayers;
 | 
			
		||||
      for (let i = 0; i < missingHumans; i++) {
 | 
			
		||||
        session.addPlayer(session.createPlayerAI(i));
 | 
			
		||||
      }
 | 
			
		||||
      session.start(seed);
 | 
			
		||||
      return {
 | 
			
		||||
        status: 'ok'
 | 
			
		||||
      };
 | 
			
		||||
  getSessionPlayer(sessionId: string, userId: string): any {
 | 
			
		||||
    const session: MatchSession | undefined = SessionManager.sessions.get(sessionId);
 | 
			
		||||
    if (session !== undefined) {
 | 
			
		||||
      return session.players.find(player => player.id === userId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  updateSocketId(sessionId: string, userId: string, socketId: string): any {
 | 
			
		||||
    const player = this.getSessionPlayer(sessionId, userId);
 | 
			
		||||
    if (player !== undefined) {
 | 
			
		||||
      player.socketId = socketId;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  setSession(session: MatchSession) {
 | 
			
		||||
    SessionManager.sessions.set(session.id, session);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteSession(session: MatchSession) {
 | 
			
		||||
    SessionManager.sessions.delete(session.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getSession(id: string) {
 | 
			
		||||
    return SessionManager.sessions[id];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteSession(id: string) {
 | 
			
		||||
    delete SessionManager.sessions[id];
 | 
			
		||||
  }
 | 
			
		||||
    return SessionManager.sessions.get(id);
 | 
			
		||||
  }  
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ import { AuthController } from '../controllers/AuthController';
 | 
			
		||||
 | 
			
		||||
import adminRouter from './adminRouter';
 | 
			
		||||
import userRouter from './userRouter';
 | 
			
		||||
import gameRouter from './gameRouter';
 | 
			
		||||
 | 
			
		||||
export default function(): Router {
 | 
			
		||||
  const router = Router();
 | 
			
		||||
@@ -15,8 +16,10 @@ export default function(): Router {
 | 
			
		||||
  router.post('/auth/code', (req: Request, res: Response) => authController.twoFactorCodeAuthentication(req, res));
 | 
			
		||||
  router.post('/login', (req: Request, res: Response) => authController.login(req, res));
 | 
			
		||||
 | 
			
		||||
  router .use('/admin', adminRouter());
 | 
			
		||||
  router .use('/user', userRouter());
 | 
			
		||||
  router.use('/admin', adminRouter());
 | 
			
		||||
  router.use('/user', userRouter());
 | 
			
		||||
  router.use('/game', gameRouter());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return router;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								src/server/router/gameRouter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/server/router/gameRouter.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import { Request, Response, Router } from 'express';
 | 
			
		||||
import { AuthController } from '../controllers/AuthController';
 | 
			
		||||
import { GameController } from '../controllers/GameController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default function(): Router {
 | 
			
		||||
  const router = Router();
 | 
			
		||||
  const gameController = new GameController();
 | 
			
		||||
  const { authenticate } = AuthController
 | 
			
		||||
 | 
			
		||||
  router.post('/match', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.createMatch(req, res));
 | 
			
		||||
  router.patch('/match/join', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.joinMatch(req, res));
 | 
			
		||||
  router.get('/match/list', authenticate({ roles: ['user']}), (req: Request, res: Response) => gameController.listMatches(req, res)); 
 | 
			
		||||
 | 
			
		||||
  return router;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +1,35 @@
 | 
			
		||||
import { DominoesGame } from "./DominoesGame";
 | 
			
		||||
import { MatchSession } from "./MatchSession";
 | 
			
		||||
import { GameState } from "./dto/GameState";
 | 
			
		||||
import { PlayerInterface } from "./entities/player/PlayerInterface";
 | 
			
		||||
import { DominoesGame } from "../../game/DominoesGame";
 | 
			
		||||
import { MatchSession } from "../../game/MatchSession";
 | 
			
		||||
import { GameState } from "../../game/dto/GameState";
 | 
			
		||||
import { PlayerInterface } from "../../game/entities/player/PlayerInterface";
 | 
			
		||||
 | 
			
		||||
export class PlayerNotificationManager {
 | 
			
		||||
export class PlayerNotificationService {
 | 
			
		||||
 | 
			
		||||
  async notifyGameState(game: DominoesGame) {
 | 
			
		||||
    const gameState: GameState = game.getGameState();
 | 
			
		||||
    const { players } = game;
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.notifyGameState(gameState));
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-game-state', gameState));
 | 
			
		||||
    return await Promise.all(promises);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async notifyPlayersState(players: PlayerInterface[]) {
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.notifyPlayerState(player.getState()));
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-player-state', player.getState()));
 | 
			
		||||
    return await Promise.all(promises);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  async notifyMatchState(session: MatchSession) {
 | 
			
		||||
    const { players } = session;
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.notifyMatchState(session.getState()));
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.sendEventWithAck('update-match-session-state', session.getState()));
 | 
			
		||||
    return await Promise.all(promises);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForPlayersAction(actionId: string, data: any = {}, players: PlayerInterface[]) {
 | 
			
		||||
    let promises: Promise<boolean>[] = players.map(player => player.waitForAction(actionId, data));
 | 
			
		||||
  async sendEventToPlayers(event: string, players: PlayerInterface[], data: any = {}) {
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.sendEvent(event, data));
 | 
			
		||||
    return await Promise.all(promises);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async sendEventToPlayers(event: string, players: PlayerInterface[]) {
 | 
			
		||||
    let promises: Promise<void>[] = players.map(player => player.sendEvent(event));
 | 
			
		||||
    return await Promise.all(promises);
 | 
			
		||||
  async sendEvent(event: string, player: PlayerInterface, data: any = {}) {  
 | 
			
		||||
    player.sendEvent(event, data)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,107 @@
 | 
			
		||||
import { SessionCreationError } from "../../common/errors/SessionCreationError";
 | 
			
		||||
import { SessionNotFoundError } from "../../common/errors/SessionNotFoundError";
 | 
			
		||||
import { wait, whileNotUndefined } from "../../common/utilities";
 | 
			
		||||
import { NetworkPlayer } from "../../game/entities/player/NetworkPlayer";
 | 
			
		||||
import { MatchSession } from "../../game/MatchSession";
 | 
			
		||||
import { PlayerNotificationService } from "./PlayerNotificationService";
 | 
			
		||||
import { matchSessionAdapter } from "../db/DbAdapter";
 | 
			
		||||
import { DbMatchSession } from "../db/interfaces";
 | 
			
		||||
import { MatchSessionMongoManager } from "../db/mongo/MatchSessionMongoManager";
 | 
			
		||||
import { SessionManager } from "../managers/SessionManager";
 | 
			
		||||
import { ServiceBase } from "./ServiceBase";
 | 
			
		||||
import { SocketIoService } from "./SocketIoService";
 | 
			
		||||
 | 
			
		||||
export class SessionService extends ServiceBase{
 | 
			
		||||
  private dbManager: MatchSessionMongoManager = new MatchSessionMongoManager();
 | 
			
		||||
  private sessionManager: SessionManager = new SessionManager();
 | 
			
		||||
  private notifyService = new PlayerNotificationService();  
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  public createSession(session: MatchSession): any {
 | 
			
		||||
    this.dbManager.create(matchSessionAdapter(session));
 | 
			
		||||
  public async createSession(user: any, sessionName: string, seed: string ): Promise<string> {
 | 
			
		||||
    let socketClient;
 | 
			
		||||
    try {
 | 
			
		||||
      socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw new SessionCreationError();
 | 
			
		||||
    }
 | 
			
		||||
    const player =  new NetworkPlayer(user._id, user.username, socketClient.socketId);
 | 
			
		||||
    const session = new MatchSession(player, sessionName, seed);
 | 
			
		||||
    const dbSessionId = await this.dbManager.create(matchSessionAdapter(session));
 | 
			
		||||
    if (dbSessionId === undefined) {
 | 
			
		||||
      throw new SessionCreationError();
 | 
			
		||||
    }
 | 
			
		||||
    session.id = dbSessionId.toString();
 | 
			
		||||
    socketClient.sessionId = session.id;
 | 
			
		||||
    this.sessionManager.setSession(session);    
 | 
			
		||||
    this.notifyService.notifyMatchState(session);
 | 
			
		||||
    this.notifyService.notifyPlayersState(session.players);
 | 
			
		||||
    return session.id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public updateSession(session: MatchSession): any {
 | 
			
		||||
  public async joinSession(user: any, sessionId: string): Promise<void> {
 | 
			
		||||
    const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
 | 
			
		||||
    if (session === undefined) {
 | 
			
		||||
      throw new SessionNotFoundError();
 | 
			
		||||
    }    let socketClient;
 | 
			
		||||
    try {
 | 
			
		||||
      socketClient = await whileNotUndefined(() => SocketIoService.getClient(user._id));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw new SessionCreationError();
 | 
			
		||||
    }
 | 
			
		||||
    const player =  new NetworkPlayer(user._id, user.name, socketClient.socketId);
 | 
			
		||||
    session.addPlayer(player);
 | 
			
		||||
    socketClient.sessionId = session.id;
 | 
			
		||||
    this.dbManager.replaceOne({id: session.id}, matchSessionAdapter(session));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public listSessions(): Promise<DbMatchSession[]> {
 | 
			
		||||
    return this.dbManager.listByFilter({});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public updateSocketId(sessionId: string, userId: string, socketId: string): any {
 | 
			
		||||
    this.sessionManager.updateSocketId(sessionId, userId, socketId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setPlayerReady(data: any): any {
 | 
			
		||||
    const { userId, sessionId } = data;
 | 
			
		||||
    const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
 | 
			
		||||
    if (session !== undefined) {
 | 
			
		||||
      session.setPlayerReady(userId)
 | 
			
		||||
      this.notifyService.notifyMatchState(session);
 | 
			
		||||
      this.notifyService.notifyPlayersState(session.players);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  startSession(data: any): any {
 | 
			
		||||
    const sessionId: string = data.sessionId;
 | 
			
		||||
    const seed: string | undefined = data.seed;
 | 
			
		||||
    const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
 | 
			
		||||
 | 
			
		||||
    if (session === undefined) {
 | 
			
		||||
      return ({
 | 
			
		||||
        status: 'error',
 | 
			
		||||
        message: 'Session not found'
 | 
			
		||||
      });
 | 
			
		||||
    } else if (session.matchInProgress) {
 | 
			
		||||
      return {
 | 
			
		||||
        status: 'error',
 | 
			
		||||
        message: 'Game already in progress'
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      const missingHumans = session.maxPlayers - session.numPlayers;
 | 
			
		||||
      for (let i = 0; i < missingHumans; i++) {
 | 
			
		||||
        session.addPlayer(session.createPlayerAI(i));
 | 
			
		||||
      }
 | 
			
		||||
      session.start();
 | 
			
		||||
      return {
 | 
			
		||||
        status: 'ok'
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // public updateSession(session: MatchSession): any {
 | 
			
		||||
  //   this.dbManager.replaceOne({id: session.id}, matchSessionAdapter(session));
 | 
			
		||||
  // }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,14 +2,39 @@ import { Server as HttpServer } from "http";
 | 
			
		||||
import { ServiceBase } from "./ServiceBase";
 | 
			
		||||
import { Server } from "socket.io";
 | 
			
		||||
import { SessionManager } from "../managers/SessionManager";
 | 
			
		||||
import { SecurityManager } from "../managers/SecurityManager";
 | 
			
		||||
import { User } from "../db/interfaces";
 | 
			
		||||
import { Socket } from "socket.io";
 | 
			
		||||
import { SessionService } from "./SessionService";
 | 
			
		||||
 | 
			
		||||
export class SocketIoService  extends ServiceBase{
 | 
			
		||||
  io: Server
 | 
			
		||||
  clients: Map<string, any> = new Map();
 | 
			
		||||
  private static clients: Map<string, any> = new Map();
 | 
			
		||||
  private sessionService: SessionService = new SessionService();
 | 
			
		||||
 | 
			
		||||
  static getClient(id: string) {
 | 
			
		||||
    return this.clients.get(id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  security = new SecurityManager();
 | 
			
		||||
  constructor(private httpServer: HttpServer) {
 | 
			
		||||
    super()
 | 
			
		||||
    this.io = this.socketIo(httpServer);   
 | 
			
		||||
    this.io = this.socketIo(httpServer);  
 | 
			
		||||
    this.io.use(async (socket: Socket, next: any) => {
 | 
			
		||||
      if (socket.handshake.auth && socket.handshake.auth.token) {
 | 
			
		||||
        const token = socket.handshake.auth.token;
 | 
			
		||||
        try {          
 | 
			
		||||
          const user: User = await this.security.verifyJwt(token);
 | 
			
		||||
          socket.user = user;
 | 
			
		||||
          next();
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
          this.logger.error(err);
 | 
			
		||||
          next(new Error('Authentication error'));
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        next(new Error('Authentication error'));
 | 
			
		||||
      }
 | 
			
		||||
    }); 
 | 
			
		||||
    this.initListeners();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -17,8 +42,12 @@ export class SocketIoService  extends ServiceBase{
 | 
			
		||||
    return this.io;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUserId(socket: Socket) {
 | 
			
		||||
    const { user } = socket;
 | 
			
		||||
    return user?._id?.toString();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private initListeners() {
 | 
			
		||||
    const sessionController = new SessionManager();
 | 
			
		||||
    this.io.on('connection', (socket) => {
 | 
			
		||||
      this.logger.debug(`connect ${socket.id}`);
 | 
			
		||||
      if (socket.recovered) {
 | 
			
		||||
@@ -28,59 +57,79 @@ export class SocketIoService  extends ServiceBase{
 | 
			
		||||
        this.logger.debug("socket.data:", socket.data);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.logger.debug("new connection");
 | 
			
		||||
        this.clients.set(socket.id, { alive: true });
 | 
			
		||||
        socket.join('room-general')
 | 
			
		||||
        socket.data.foo = "bar";
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      socket.on('pong', () => {
 | 
			
		||||
        if (this.clients.has(socket.id)) {
 | 
			
		||||
          this.clients.set(socket.id, { alive: true });
 | 
			
		||||
        const { id: socketId, user } = socket;
 | 
			
		||||
        if (user !== undefined && user._id !== undefined) {
 | 
			
		||||
          const userId = user._id.toString();
 | 
			
		||||
          if (!SocketIoService.clients.has(userId)) {
 | 
			
		||||
            SocketIoService.clients.set(userId, { socketId, alive: true, user: socket.user });
 | 
			
		||||
            socket.join('room-general')
 | 
			
		||||
          } else {
 | 
			
		||||
            const client = SocketIoService.clients.get(userId);
 | 
			
		||||
            this.sessionService.updateSocketId(client.sessionId, userId, socketId);
 | 
			
		||||
            client.socketId = socketId;
 | 
			
		||||
            this.logger.debug(`User '${user.username}' already connected. Updating socketId to ${socketId}`);
 | 
			
		||||
            client.alive = true;
 | 
			
		||||
          }            
 | 
			
		||||
        } else {
 | 
			
		||||
          this.logger.error('User not found');
 | 
			
		||||
          socket.disconnect();
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    
 | 
			
		||||
      }    
 | 
			
		||||
      socket.on('disconnect', () => {
 | 
			
		||||
        this.logger.debug('user disconnected');
 | 
			
		||||
        this.clients.delete(socket.id);
 | 
			
		||||
        const id = this.getUserId(socket);
 | 
			
		||||
        if (id) {
 | 
			
		||||
          this.logger.info('user disconnected');
 | 
			
		||||
          SocketIoService.clients.delete(id);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    
 | 
			
		||||
      socket.on('createSession', (data, callback) => {
 | 
			
		||||
        const response = sessionController.createSession(data, socket.id);
 | 
			
		||||
        callback(response);
 | 
			
		||||
      });
 | 
			
		||||
      // socket.on('createSession', (data, callback) => {
 | 
			
		||||
      //   const response = sessionController.createSession(data, socket.id);
 | 
			
		||||
      //   callback(response);
 | 
			
		||||
      // });
 | 
			
		||||
 | 
			
		||||
      socket.on('startSession', (data, callback) => {
 | 
			
		||||
        const response = sessionController.startSession(data);    
 | 
			
		||||
        const response = this.sessionService.startSession(data);    
 | 
			
		||||
        callback(response);
 | 
			
		||||
      });
 | 
			
		||||
    
 | 
			
		||||
       socket.on('joinSession', (data, callback) => {
 | 
			
		||||
        const response = sessionController.joinSession(data, socket.id);
 | 
			
		||||
        callback(response);
 | 
			
		||||
      });
 | 
			
		||||
      //  socket.on('joinSession', (data, callback) => {
 | 
			
		||||
      //   const response = sessionController.joinSession(data, socket.id);
 | 
			
		||||
      //   callback(response);
 | 
			
		||||
      // });
 | 
			
		||||
 | 
			
		||||
      socket.on('playerReady', (data, callback) => {
 | 
			
		||||
        const response = sessionController.setPlayerReady(data);
 | 
			
		||||
        const response = this.sessionService.setPlayerReady(data);
 | 
			
		||||
        callback(response);
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      socket.on('pong', () => {
 | 
			
		||||
        const id = this.getUserId(socket);
 | 
			
		||||
        if (id && SocketIoService.clients.has(id)) {
 | 
			
		||||
          const client = SocketIoService.clients.get(id);
 | 
			
		||||
          SocketIoService.clients.set(id, {...client,  alive: true });
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      
 | 
			
		||||
      this.pingClients()
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private pingClients() {
 | 
			
		||||
    setInterval(() => {
 | 
			
		||||
      for (let [id, client] of this.clients.entries()) {
 | 
			
		||||
      for (let [id, client] of SocketIoService.clients.entries()) {
 | 
			
		||||
        if (!client.alive) {
 | 
			
		||||
          this.logger.debug(`Client ${id} did not respond. Disconnecting.`);
 | 
			
		||||
          this.io.to(id).disconnectSockets(true); // Disconnect client
 | 
			
		||||
          this.clients.delete(id);
 | 
			
		||||
          SocketIoService.clients.delete(id);
 | 
			
		||||
        } else {
 | 
			
		||||
          client.alive = false; // Reset alive status for the next ping
 | 
			
		||||
          this.io.to(id).emit('ping'); // Send ping message
 | 
			
		||||
          this.io.to(client.socketId).emit('ping'); // Send ping message
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }, 30000); 
 | 
			
		||||
    }, 10000); 
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private socketIo(httpServer: HttpServer): Server {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/server/types/socket/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/server/types/socket/index.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import { Socket } from 'socket.io';
 | 
			
		||||
import { User } from '../../db/interfaces';
 | 
			
		||||
 | 
			
		||||
declare module 'socket.io' {
 | 
			
		||||
  interface Socket {
 | 
			
		||||
    user?: User;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user