Compare commits
	
		
			4 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2346acc096 | ||
| 
						 | 
					8fb6fac75d | ||
| 
						 | 
					f52bea91ac | ||
| 
						 | 
					0329e84548 | 
							
								
								
									
										8
									
								
								.hmrc
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								.hmrc
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
				
			|||||||
  "path": "G:\\Other\\Development\\Projects\\[ideas]\\domino",
 | 
					  "path": "G:\\Other\\Development\\Projects\\[ideas]\\domino",
 | 
				
			||||||
  "name": "domino-server",
 | 
					  "name": "domino-server",
 | 
				
			||||||
  "initialVersion": "0.1.1",
 | 
					  "initialVersion": "0.1.1",
 | 
				
			||||||
  "version": "0.1.3",
 | 
					  "version": "0.1.5",
 | 
				
			||||||
  "docker": {
 | 
					  "docker": {
 | 
				
			||||||
    "useRegistry": true,
 | 
					    "useRegistry": true,
 | 
				
			||||||
    "registry": "192.168.1.115:5000",
 | 
					    "registry": "192.168.1.115:5000",
 | 
				
			||||||
@@ -75,7 +75,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "_backup": {
 | 
					  "_backup": {
 | 
				
			||||||
    "name": "domino-server",
 | 
					    "name": "domino-server",
 | 
				
			||||||
    "version": "0.1.2",
 | 
					    "version": "0.1.4",
 | 
				
			||||||
    "description": "",
 | 
					    "description": "",
 | 
				
			||||||
    "main": "index.js",
 | 
					    "main": "index.js",
 | 
				
			||||||
    "engines": {
 | 
					    "engines": {
 | 
				
			||||||
@@ -88,8 +88,8 @@
 | 
				
			|||||||
      "test": "node --env-file=.env -r ts-node/register src/test.ts",
 | 
					      "test": "node --env-file=.env -r ts-node/register src/test.ts",
 | 
				
			||||||
      "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
 | 
					      "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
 | 
				
			||||||
      "docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
 | 
					      "docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
 | 
				
			||||||
      "docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.2",
 | 
					      "docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.4",
 | 
				
			||||||
      "docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.2",
 | 
					      "docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.4",
 | 
				
			||||||
      "publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
 | 
					      "publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "keywords": [],
 | 
					    "keywords": [],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Unreleased
 | 
					## Unreleased
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.1.5 - 2024-07-25
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					- socket.io room management
 | 
				
			||||||
 | 
					### Fixed 
 | 
				
			||||||
 | 
					- Blocked game not waiting for all turns to complete
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.1.4 - 2024-07-20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 0.1.3 - 2024-07-18
 | 
					## 0.1.3 - 2024-07-18
 | 
				
			||||||
### Added
 | 
					### Added
 | 
				
			||||||
- game by rounds
 | 
					- game by rounds
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "domino-server",
 | 
					  "name": "domino-server",
 | 
				
			||||||
  "version": "0.1.3",
 | 
					  "version": "0.1.5",
 | 
				
			||||||
  "description": "",
 | 
					  "description": "",
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "engines": {
 | 
					  "engines": {
 | 
				
			||||||
@@ -13,8 +13,8 @@
 | 
				
			|||||||
    "test": "node --env-file=.env -r ts-node/register src/test.ts",
 | 
					    "test": "node --env-file=.env -r ts-node/register src/test.ts",
 | 
				
			||||||
    "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
 | 
					    "test:watch": "node --env-file=.env --watch -r ts-node/register src/test.ts",
 | 
				
			||||||
    "docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
 | 
					    "docker-build": "docker build -t 192.168.1.115:5000/arhuako/domino-server:latest .",
 | 
				
			||||||
    "docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.3",
 | 
					    "docker-tag": "docker tag 192.168.1.115:5000/arhuako/domino-server:latest 192.168.1.115:5000/arhuako/domino-server:0.1.5",
 | 
				
			||||||
    "docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.3",
 | 
					    "docker-push": "docker push 192.168.1.115:5000/arhuako/domino-server:latest && docker push 192.168.1.115:5000/arhuako/domino-server:0.1.5",
 | 
				
			||||||
    "publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
 | 
					    "publish": "npm run docker-build && npm run docker-tag && npm run docker-push"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [],
 | 
					  "keywords": [],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,11 @@
 | 
				
			|||||||
<h1>Changelog</h1>
 | 
					<h1>Changelog</h1>
 | 
				
			||||||
<p>All notable changes to this project will be documented in this file.</p>
 | 
					<p>All notable changes to this project will be documented in this file.</p>
 | 
				
			||||||
 | 
					<h2>0.1.5 - 2024-07-24</h2>
 | 
				
			||||||
 | 
					<h3>Added</h3>
 | 
				
			||||||
 | 
					<ul>
 | 
				
			||||||
 | 
					<li>socket.io room management</li>
 | 
				
			||||||
 | 
					</ul>
 | 
				
			||||||
 | 
					<h2>0.1.4 - 2024-07-20</h2>
 | 
				
			||||||
<h2>0.1.3 - 2024-07-18</h2>
 | 
					<h2>0.1.3 - 2024-07-18</h2>
 | 
				
			||||||
<h3>Added</h3>
 | 
					<h3>Added</h3>
 | 
				
			||||||
<ul>
 | 
					<ul>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
  <meta charset="UTF-8">
 | 
					 | 
				
			||||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					 | 
				
			||||||
  <title>Domino Tiles</title>
 | 
					 | 
				
			||||||
  <link rel="stylesheet" href="styles.css">
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
  <div class="domino" data-top="1" data-bottom="5">
 | 
					 | 
				
			||||||
    <div class="half top"></div>
 | 
					 | 
				
			||||||
    <div class="half bottom"></div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <div class="domino" data-top="1" data-bottom="5">
 | 
					 | 
				
			||||||
    <div class="half top"></div>
 | 
					 | 
				
			||||||
    <div class="half bottom"></div>
 | 
					 | 
				
			||||||
  </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  <script src="script.js"></script>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
@@ -1,35 +0,0 @@
 | 
				
			|||||||
document.querySelectorAll('.domino').forEach(domino => {
 | 
					 | 
				
			||||||
  const topValue = domino.getAttribute('data-top');
 | 
					 | 
				
			||||||
  const bottomValue = domino.getAttribute('data-bottom');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  addPips(domino.querySelector('.top'), topValue);
 | 
					 | 
				
			||||||
  addPips(domino.querySelector('.bottom'), bottomValue);
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function addPips(half, value) {
 | 
					 | 
				
			||||||
  const pipPositions = getPipPositions(value);
 | 
					 | 
				
			||||||
  pipPositions.forEach(pos => {
 | 
					 | 
				
			||||||
    const pip = document.createElement('div');
 | 
					 | 
				
			||||||
    pip.className = 'pip';
 | 
					 | 
				
			||||||
    pip.style.left = pos[0];
 | 
					 | 
				
			||||||
    pip.style.top = pos[1];
 | 
					 | 
				
			||||||
    half.appendChild(pip);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function getPipPositions(value) {
 | 
					 | 
				
			||||||
  const p1 = '17%';
 | 
					 | 
				
			||||||
  const p2 = '42%';
 | 
					 | 
				
			||||||
  const p3 = '67%';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const positions = {
 | 
					 | 
				
			||||||
    0: [],
 | 
					 | 
				
			||||||
    1: [[p2, p2]],
 | 
					 | 
				
			||||||
    2: [[p1, p1], [p3, p3]],
 | 
					 | 
				
			||||||
    3: [[p1, p1], [p2, p2], [p3, p3]],
 | 
					 | 
				
			||||||
    4: [[p1, p1], [p1, p3], [p3, p1], [p3, p3]],
 | 
					 | 
				
			||||||
    5: [[p1, p1], [p1, p3], [p2, p2], [p3, p1], [p3, p3]],
 | 
					 | 
				
			||||||
    6: [[p1, p1], [p1, p3], [p2, p1], [p2, p3], [p3, p1], [p3, p3]],
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  return positions[value];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,77 +0,0 @@
 | 
				
			|||||||
body {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  height: 100vh;
 | 
					 | 
				
			||||||
  background: #f0f0f0;
 | 
					 | 
				
			||||||
  font-family: Arial, sans-serif;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.domino {
 | 
					 | 
				
			||||||
  width: 100px;
 | 
					 | 
				
			||||||
  height: 200px;
 | 
					 | 
				
			||||||
  background: white;
 | 
					 | 
				
			||||||
  border: 2px solid black;
 | 
					 | 
				
			||||||
  border-radius: 10px;
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.half {
 | 
					 | 
				
			||||||
  flex: 1;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.top {
 | 
					 | 
				
			||||||
  border-bottom: 2px solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip {
 | 
					 | 
				
			||||||
  width: 15px;
 | 
					 | 
				
			||||||
  height: 15px;
 | 
					 | 
				
			||||||
  background: black;
 | 
					 | 
				
			||||||
  border-radius: 50%;
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="1"] {
 | 
					 | 
				
			||||||
  left: 50%;
 | 
					 | 
				
			||||||
  top: 50%;
 | 
					 | 
				
			||||||
  transform: translate(-50%, -50%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="2"] {
 | 
					 | 
				
			||||||
  left: 25%;
 | 
					 | 
				
			||||||
  top: 25%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="3"] {
 | 
					 | 
				
			||||||
  right: 25%;
 | 
					 | 
				
			||||||
  top: 25%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="4"] {
 | 
					 | 
				
			||||||
  left: 25%;
 | 
					 | 
				
			||||||
  bottom: 25%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="5"] {
 | 
					 | 
				
			||||||
  right: 25%;
 | 
					 | 
				
			||||||
  bottom: 25%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="6"] {
 | 
					 | 
				
			||||||
  left: 50%;
 | 
					 | 
				
			||||||
  top: 50%;
 | 
					 | 
				
			||||||
  transform: translate(-50%, -50%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.pip[data-number="7"] {
 | 
					 | 
				
			||||||
  right: 50%;
 | 
					 | 
				
			||||||
  top: 50%;
 | 
					 | 
				
			||||||
  transform: translate(50%, -50%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -97,7 +97,7 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
      tiles.push(...player.hand);
 | 
					      tiles.push(...player.hand);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const canPlay = tiles.some(tile => tile.pips[0] === freeEnds[0] || tile.pips[1] === freeEnds[0] || tile.pips[0] === freeEnds[1] || tile.pips[1] === freeEnds[1]);
 | 
					    const canPlay = tiles.some(tile => tile.pips[0] === freeEnds[0] || tile.pips[1] === freeEnds[0] || tile.pips[0] === freeEnds[1] || tile.pips[1] === freeEnds[1]);
 | 
				
			||||||
    return !canPlay;
 | 
					    return  this.blockedCount >= 4 && !canPlay;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  isGameOver(): boolean {
 | 
					  isGameOver(): boolean {
 | 
				
			||||||
@@ -144,7 +144,7 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
  playTurn() {
 | 
					  playTurn() {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const player = this.players[this.currentPlayerIndex];
 | 
					      const player = this.players[this.currentPlayerIndex];
 | 
				
			||||||
      this.notificationService.sendEventToPlayers('server:next-turn', this.players, this.getGameState());
 | 
					      this.notificationService.sendEventToPlayers('server:next-turn', this.players, this.getState());
 | 
				
			||||||
      this.logger.debug(`${player.name}'s turn (${player.hand.length} tiles)`);
 | 
					      this.logger.debug(`${player.name}'s turn (${player.hand.length} tiles)`);
 | 
				
			||||||
      this.printPlayerHand(player);
 | 
					      this.printPlayerHand(player);
 | 
				
			||||||
      printBoard(this.board)
 | 
					      printBoard(this.board)
 | 
				
			||||||
@@ -222,6 +222,7 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
      players: this.players.map(player => player.getState(true)),
 | 
					      players: this.players.map(player => player.getState(true)),
 | 
				
			||||||
      board: this.board.tiles.map(tile => tile.getState(true)),
 | 
					      board: this.board.tiles.map(tile => tile.getState(true)),
 | 
				
			||||||
      boneyard: this.board.boneyard.map(tile => tile.getState(true)),
 | 
					      boneyard: this.board.boneyard.map(tile => tile.getState(true)),
 | 
				
			||||||
 | 
					      movements: this.board.movements
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.emit('game-over', summary);
 | 
					    this.emit('game-over', summary);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -264,7 +265,7 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
      this.deal();
 | 
					      this.deal();
 | 
				
			||||||
      const extractStates =  (p: PlayerInterface) => ({
 | 
					      const extractStates =  (p: PlayerInterface) => ({
 | 
				
			||||||
        player:  p.getState(true),
 | 
					        player:  p.getState(true),
 | 
				
			||||||
        gameState: this.getGameState()
 | 
					        gameState: this.getState()
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      await this.notificationService.sendEventToPlayers('server:hand-dealt', this.players, extractStates);
 | 
					      await this.notificationService.sendEventToPlayers('server:hand-dealt', this.players, extractStates);
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
@@ -325,11 +326,14 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getGameState(): GameState {
 | 
					  getState(): GameState {
 | 
				
			||||||
    const currentPlayer = this.players[this.currentPlayerIndex]
 | 
					    const currentPlayer = this.players[this.currentPlayerIndex]
 | 
				
			||||||
 | 
					    const lastMove = this.lastMove?.getState() || null;
 | 
				
			||||||
 | 
					    const movements = this.board.movements.map(move => move.getState());
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      id: uuid(),
 | 
					      id: uuid(),
 | 
				
			||||||
      lastMove: this.lastMove,
 | 
					      lastMove,
 | 
				
			||||||
      gameInProgress: this.gameInProgress,
 | 
					      gameInProgress: this.gameInProgress,
 | 
				
			||||||
      winner: this.winner,
 | 
					      winner: this.winner,
 | 
				
			||||||
      tileSelectionPhase: this.tileSelectionPhase, 
 | 
					      tileSelectionPhase: this.tileSelectionPhase, 
 | 
				
			||||||
@@ -337,10 +341,11 @@ export class DominoesGame extends EventEmitter {
 | 
				
			|||||||
      gameTied: this.gameTied,
 | 
					      gameTied: this.gameTied,
 | 
				
			||||||
      gameId: this.id,
 | 
					      gameId: this.id,
 | 
				
			||||||
      boneyard: this.board.boneyard.map(tile => tile.getState(false)),
 | 
					      boneyard: this.board.boneyard.map(tile => tile.getState(false)),
 | 
				
			||||||
      players: this.players.map(player => player.getState()),
 | 
					      players: this.players.map(player => player.getState(false)),
 | 
				
			||||||
      currentPlayer: currentPlayer.getState(),
 | 
					      currentPlayer: currentPlayer.getState(),
 | 
				
			||||||
      board: this.board.tiles.map(tile => tile.getState(true)),
 | 
					      board: this.board.tiles.map(tile => tile.getState(true)),
 | 
				
			||||||
      boardFreeEnds: this.board.getFreeEnds(),
 | 
					      boardFreeEnds: this.board.getFreeEnds(),
 | 
				
			||||||
 | 
					      movements
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ export class MatchSession {
 | 
				
			|||||||
  private clientsReady: string[] = [];
 | 
					  private clientsReady: string[] = [];
 | 
				
			||||||
  private gameSummaries: GameSummary[] = [];
 | 
					  private gameSummaries: GameSummary[] = [];
 | 
				
			||||||
  private sessionService: SessionService = new SessionService();
 | 
					  private sessionService: SessionService = new SessionService();
 | 
				
			||||||
 | 
					  private room: string;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
  matchInProgress: boolean = false;
 | 
					  matchInProgress: boolean = false;
 | 
				
			||||||
@@ -46,6 +47,7 @@ export class MatchSession {
 | 
				
			|||||||
    const { sessionName, seed, winType, winTarget } = options;
 | 
					    const { sessionName, seed, winType, winTarget } = options;
 | 
				
			||||||
    this.seed = seed || getRandomSeed();
 | 
					    this.seed = seed || getRandomSeed();
 | 
				
			||||||
    this.id = uuid();
 | 
					    this.id = uuid();
 | 
				
			||||||
 | 
					    this.room = `room-${this.id}`;
 | 
				
			||||||
    this.name = sessionName || `Match ${this.id}`;
 | 
					    this.name = sessionName || `Match ${this.id}`;
 | 
				
			||||||
    this.addPlayerToSession(creator);    
 | 
					    this.addPlayerToSession(creator);    
 | 
				
			||||||
    this.creator = creator;
 | 
					    this.creator = creator;
 | 
				
			||||||
@@ -187,7 +189,6 @@ export class MatchSession {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.status = 'waiting'
 | 
					        this.status = 'waiting'
 | 
				
			||||||
        // await this.playerNotificationManager.notifyMatchState(this);
 | 
					 | 
				
			||||||
        this.notificationService.sendEventToPlayers('server:game-finished', this.players, {
 | 
					        this.notificationService.sendEventToPlayers('server:game-finished', this.players, {
 | 
				
			||||||
          lastGame: gameSummary,
 | 
					          lastGame: gameSummary,
 | 
				
			||||||
          sessionState: this.getState(true)
 | 
					          sessionState: this.getState(true)
 | 
				
			||||||
@@ -397,6 +398,7 @@ export class MatchSession {
 | 
				
			|||||||
      matchInProgress: this.matchInProgress,
 | 
					      matchInProgress: this.matchInProgress,
 | 
				
			||||||
      gameSummaries: this.gameSummaries,
 | 
					      gameSummaries: this.gameSummaries,
 | 
				
			||||||
      options: this.options,
 | 
					      options: this.options,
 | 
				
			||||||
 | 
					      room: this.room,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
import { PlayerMove } from "../entities/PlayerMove";
 | 
					import { PlayerDto, PlayerMoveDto } from "./PlayerDto";
 | 
				
			||||||
import { PlayerDto } from "./PlayerDto";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GameState {
 | 
					export interface GameState {
 | 
				
			||||||
  id: string;
 | 
					  id: string;
 | 
				
			||||||
@@ -14,5 +13,6 @@ export interface GameState {
 | 
				
			|||||||
  gameId: string;
 | 
					  gameId: string;
 | 
				
			||||||
  tileSelectionPhase: boolean;
 | 
					  tileSelectionPhase: boolean;
 | 
				
			||||||
  boardFreeEnds: number[];
 | 
					  boardFreeEnds: number[];
 | 
				
			||||||
  lastMove: PlayerMove | null;
 | 
					  lastMove: PlayerMoveDto | null;
 | 
				
			||||||
 | 
					  movements: PlayerMoveDto[];
 | 
				
			||||||
}  
 | 
					}  
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { Score } from "../../server/db/interfaces";
 | 
					import { Score } from "../../server/db/interfaces";
 | 
				
			||||||
 | 
					import { PlayerMove } from "../entities/PlayerMove";
 | 
				
			||||||
import { PlayerDto, TileDto } from "./PlayerDto";
 | 
					import { PlayerDto, TileDto } from "./PlayerDto";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GameSummary {
 | 
					export interface GameSummary {
 | 
				
			||||||
@@ -10,4 +11,5 @@ export interface GameSummary {
 | 
				
			|||||||
  players?: PlayerDto[];
 | 
					  players?: PlayerDto[];
 | 
				
			||||||
  board: TileDto[]
 | 
					  board: TileDto[]
 | 
				
			||||||
  boneyard: TileDto[]
 | 
					  boneyard: TileDto[]
 | 
				
			||||||
 | 
					  movements: PlayerMove[]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -10,4 +10,5 @@ export interface MatchSessionOptions {
 | 
				
			|||||||
  seed: string
 | 
					  seed: string
 | 
				
			||||||
  sessionName: string
 | 
					  sessionName: string
 | 
				
			||||||
  numPlayers: 1 | 2 | 3 | 4
 | 
					  numPlayers: 1 | 2 | 3 | 4
 | 
				
			||||||
 | 
					  turnWaitSeconds: number
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import { Score } from "../../server/db/interfaces";
 | 
					import { Score } from "../../server/db/interfaces";
 | 
				
			||||||
 | 
					import { GameState } from "./GameState";
 | 
				
			||||||
import { GameSummary } from "./GameSummary";
 | 
					import { GameSummary } from "./GameSummary";
 | 
				
			||||||
import { MatchSessionOptions } from "./MatchSessionOptions";
 | 
					import { MatchSessionOptions } from "./MatchSessionOptions";
 | 
				
			||||||
import { PlayerDto } from "./PlayerDto";
 | 
					import { PlayerDto } from "./PlayerDto";
 | 
				
			||||||
@@ -22,4 +23,6 @@ export interface MatchSessionState {
 | 
				
			|||||||
  playersReady: number,
 | 
					  playersReady: number,
 | 
				
			||||||
  gameSummaries: GameSummary[];
 | 
					  gameSummaries: GameSummary[];
 | 
				
			||||||
  options: MatchSessionOptions
 | 
					  options: MatchSessionOptions
 | 
				
			||||||
 | 
					  currentGame?: GameState
 | 
				
			||||||
 | 
					  room: string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -14,4 +14,12 @@ export interface PlayerDto {
 | 
				
			|||||||
  teamedWith?: string;
 | 
					  teamedWith?: string;
 | 
				
			||||||
  ready: boolean;
 | 
					  ready: boolean;
 | 
				
			||||||
  isHuman: boolean;
 | 
					  isHuman: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface PlayerMoveDto {
 | 
				
			||||||
 | 
					  id: string;
 | 
				
			||||||
 | 
					  tile: TileDto
 | 
				
			||||||
 | 
					  type: string | null;
 | 
				
			||||||
 | 
					  playerId: string;
 | 
				
			||||||
 | 
					  direction?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -9,6 +9,7 @@ export class Board {
 | 
				
			|||||||
  tiles: Tile[] = [];
 | 
					  tiles: Tile[] = [];
 | 
				
			||||||
  boneyard: Tile[] = [];
 | 
					  boneyard: Tile[] = [];
 | 
				
			||||||
  logger = new LoggingService();
 | 
					  logger = new LoggingService();
 | 
				
			||||||
 | 
					  movements: PlayerMove[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(private rng: PRNG) {}
 | 
					  constructor(private rng: PRNG) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,6 +65,7 @@ export class Board {
 | 
				
			|||||||
      this.playTileRight(tile);
 | 
					      this.playTileRight(tile);
 | 
				
			||||||
      // printLine(`${tile} -- right`);       
 | 
					      // printLine(`${tile} -- right`);       
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    this.movements.push(playerMove);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  playTileLeft(tile: Tile) {
 | 
					  playTileLeft(tile: Tile) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { uuid } from "../../common/utilities";
 | 
					import { uuid } from "../../common/utilities";
 | 
				
			||||||
import { PlayerMoveSideType } from "../constants";
 | 
					import { PlayerMoveSideType } from "../constants";
 | 
				
			||||||
 | 
					import { PlayerMoveDto } from "../dto/PlayerDto";
 | 
				
			||||||
import { Tile } from "./Tile";
 | 
					import { Tile } from "./Tile";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PlayerMove {
 | 
					export class PlayerMove {
 | 
				
			||||||
@@ -9,4 +10,14 @@ export class PlayerMove {
 | 
				
			|||||||
  toString() {
 | 
					  toString() {
 | 
				
			||||||
    return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`;
 | 
					    return `PlayerMove:([${this.tile.pips[0]}|${this.tile.pips[1]}] ${this.type})`;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getState(): PlayerMoveDto {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      id: this.id,
 | 
				
			||||||
 | 
					      tile: this.tile.getState(),
 | 
				
			||||||
 | 
					      type: this.type,
 | 
				
			||||||
 | 
					      playerId: this.playerId,
 | 
				
			||||||
 | 
					      direction: this.direction,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -6,42 +6,83 @@ import { TemporalTokenMongoManager } from '../db/mongo/TemporalTokenMongoManager
 | 
				
			|||||||
import { BaseController } from './BaseController';
 | 
					import { BaseController } from './BaseController';
 | 
				
			||||||
import { NextFunction, Request, Response } from 'express';
 | 
					import { NextFunction, Request, Response } from 'express';
 | 
				
			||||||
import { AuthenticationOption, Token, User } from '../db/interfaces';
 | 
					import { AuthenticationOption, Token, User } from '../db/interfaces';
 | 
				
			||||||
 | 
					import { UserSessionMongoManager } from '../db/mongo/UserSessionMongoManager';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AuthController extends BaseController {
 | 
					export class AuthController extends BaseController {
 | 
				
			||||||
  security = new SecurityManager();
 | 
					  security = new SecurityManager();
 | 
				
			||||||
  usersManager = new UsersMongoManager();
 | 
					  usersManager = new UsersMongoManager();
 | 
				
			||||||
  temporalTokenManager = new TemporalTokenMongoManager();
 | 
					  temporalTokenManager = new TemporalTokenMongoManager();
 | 
				
			||||||
 | 
					  userSessionManager = new UserSessionMongoManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async login(req: Request, res: Response): Promise<void> {
 | 
					  async login(req: Request, res: Response): Promise<void> {
 | 
				
			||||||
    const { log } = req;
 | 
					    const { log } = req;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      let token = null
 | 
					 | 
				
			||||||
      const { username, password } = req.body;
 | 
					      const { username, password } = req.body;
 | 
				
			||||||
      this.logger.debug('login', username, password);
 | 
					 | 
				
			||||||
      const { valid: isValidPassword, user } = await this._checkPassword(username, password);
 | 
					      const { valid: isValidPassword, user } = await this._checkPassword(username, password);
 | 
				
			||||||
      this.logger.debug('isValidPassword', isValidPassword);
 | 
					      if (!isValidPassword || user === null) {
 | 
				
			||||||
      if (!isValidPassword) {
 | 
					 | 
				
			||||||
        res.status(401).json({ error: 'Unauthorized' }).end();
 | 
					        res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
        log.error('Unauthorized login attempt for user: ', username);
 | 
					        log.error('Unauthorized login attempt for user: ', username);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }      
 | 
					      }      
 | 
				
			||||||
      this._jwtSignUser(user, res)
 | 
					      delete user.hash;
 | 
				
			||||||
 | 
					      let sessionId = await this.userSessionManager.renewSession(user.id);
 | 
				
			||||||
 | 
					      const token = await this.security.signJwt({ sessionId, user}, false);
 | 
				
			||||||
 | 
					      if (token === null) {
 | 
				
			||||||
 | 
					        res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const refreshToken = await this.security.signJwt({ sessionId, user}, true);
 | 
				
			||||||
 | 
					      res.status(200).json({ token, refreshToken }).end();
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      this.handleError(res, error);
 | 
					      this.handleError(res, error);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  _jwtSignUser(user: User | null, res: Response) {
 | 
					  async refresh(req: Request, res: Response): Promise<void> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const { refreshToken } = req.body;
 | 
				
			||||||
 | 
					      if (!refreshToken) {
 | 
				
			||||||
 | 
					        res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const data = await this.security.verifyJwt(refreshToken);
 | 
				
			||||||
 | 
					      const { uuid: sessionId } = await this.userSessionManager.getByUuid(data.sessionId) || { uuid: undefined };
 | 
				
			||||||
 | 
					      if (!data.sessionId || !data.user || data.sessionId !== sessionId) {
 | 
				
			||||||
 | 
					        res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const newToken = await this.security.signJwt(data, false);
 | 
				
			||||||
 | 
					      res.status(200).json({ token: newToken }).end();
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async _jwtSignUser(user: User | null, res: Response, isFromRefresh: boolean = false) {
 | 
				
			||||||
    if (user === null) {
 | 
					    if (user === null) {
 | 
				
			||||||
      res.status(401).json({ error: 'Unauthorized' }).end();
 | 
					      res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    delete user.hash;
 | 
					    delete user.hash;
 | 
				
			||||||
    const token = this.security.signJwt(user);
 | 
					    let uuid;
 | 
				
			||||||
 | 
					    if (!isFromRefresh) {
 | 
				
			||||||
 | 
					      uuid = await this.userSessionManager.renewSession(user.id);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      const userSession = await this.userSessionManager.getUserSession(user.id);
 | 
				
			||||||
 | 
					      uuid = userSession?.uuid;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const token = this.security.signJwt({ sessionId: uuid, user}, false);
 | 
				
			||||||
    if (token === null) {
 | 
					    if (token === null) {
 | 
				
			||||||
      res.status(401).json({ error: 'Unauthorized' }).end();
 | 
					      res.status(401).json({ error: 'Unauthorized' }).end();
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      res.status(200).json({ token }).end();
 | 
					      const data: {
 | 
				
			||||||
 | 
					        token: string,
 | 
				
			||||||
 | 
					        refreshToken?: string
 | 
				
			||||||
 | 
					      } = { token };
 | 
				
			||||||
 | 
					      if (!isFromRefresh) {
 | 
				
			||||||
 | 
					        data.refreshToken = this.security.signJwt({ sessionId: uuid, user}, true);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      res.status(200).json(data).end();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return;
 | 
					    return;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -159,21 +200,27 @@ export class AuthController extends BaseController {
 | 
				
			|||||||
  static authenticate(options: AuthenticationOption = {}) {
 | 
					  static authenticate(options: AuthenticationOption = {}) {
 | 
				
			||||||
    return async function(req: Request, res: Response, next: NextFunction) {
 | 
					    return async function(req: Request, res: Response, next: NextFunction) {
 | 
				
			||||||
      const security = new SecurityManager();
 | 
					      const security = new SecurityManager();
 | 
				
			||||||
 | 
					      const userSessionManager = new UserSessionMongoManager();
 | 
				
			||||||
      const token = req.headers.authorization;
 | 
					      const token = req.headers.authorization;
 | 
				
			||||||
      const { roles = [] } = options;
 | 
					      const { roles = [] } = options;
 | 
				
			||||||
      if (!token) {
 | 
					      if (!token) {
 | 
				
			||||||
        return res.status(401).json({ error: 'Unauthorized' });
 | 
					        return res.status(401).json({ error: 'Unauthorized' });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        const user: User = await security.verifyJwt(token);
 | 
					        const parsed = await security.verifyJwt(token);
 | 
				
			||||||
 | 
					        const user: User = parsed.user;
 | 
				
			||||||
 | 
					        const userSession = await userSessionManager.getUserSession(user.id);
 | 
				
			||||||
        const validRoles = await AuthController.checkRoles(user, roles);
 | 
					        const validRoles = await AuthController.checkRoles(user, roles);
 | 
				
			||||||
        if (!validRoles) {
 | 
					        if (!validRoles) {
 | 
				
			||||||
          return res.status(403).json({ error: 'Forbidden' });
 | 
					          return res.status(403).json({ error: 'Forbidden' });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (userSession?.uuid !== parsed.sessionId) {
 | 
				
			||||||
 | 
					          return res.status(401).json({ error: 'Unauthorized' });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        req.user = user;
 | 
					        req.user = user;
 | 
				
			||||||
        next();      
 | 
					        next();      
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
        return res.status(403).json({ error: 'Forbidden' });
 | 
					        return res.status(401).json({ error: 'Token expired' });
 | 
				
			||||||
      }   
 | 
					      }   
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -194,7 +241,7 @@ export class AuthController extends BaseController {
 | 
				
			|||||||
        req.token = apiToken;
 | 
					        req.token = apiToken;
 | 
				
			||||||
        next();
 | 
					        next();
 | 
				
			||||||
      } catch (error) {
 | 
					      } catch (error) {
 | 
				
			||||||
        return res.status(403).json({ error: 'Forbidden' });
 | 
					        return res.status(401).json({ error: 'Unauthorized' });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										50
									
								
								src/server/controllers/UpdaterController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/server/controllers/UpdaterController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import { Request, Response } from "express";
 | 
				
			||||||
 | 
					import { BaseController } from "./BaseController";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const json = {
 | 
				
			||||||
 | 
					  "version": "0.2.0",
 | 
				
			||||||
 | 
					  "notes": `- v0.2.0
 | 
				
			||||||
 | 
					  Added: session expiration and renew handling
 | 
				
			||||||
 | 
					  Added: Allow unique session per user
 | 
				
			||||||
 | 
					  Added: Socket.io room management
 | 
				
			||||||
 | 
					  Added: Game: added turn waiting timer
 | 
				
			||||||
 | 
					  Added: Game: added timed button on game summaries
 | 
				
			||||||
 | 
					  Added: Added new header
 | 
				
			||||||
 | 
					  Added: New sound library (Howlerjs)
 | 
				
			||||||
 | 
					  Fixed: pp data persistance
 | 
				
			||||||
 | 
					  Fixed: Some other Fixes
 | 
				
			||||||
 | 
					- v0.1.12
 | 
				
			||||||
 | 
					  Added: I18n translations
 | 
				
			||||||
 | 
					  Added: Win conditions
 | 
				
			||||||
 | 
					  Fixed: Multiplayer join button not accessible
 | 
				
			||||||
 | 
					- v0.1.8
 | 
				
			||||||
 | 
					  Added: Updater
 | 
				
			||||||
 | 
					  Added: Refresh authentication when expires
 | 
				
			||||||
 | 
					  Added: Match summary page phase 1`,
 | 
				
			||||||
 | 
					  "pub_date": "2024-07-20T10:25:57Z",
 | 
				
			||||||
 | 
					  "platforms": {
 | 
				
			||||||
 | 
					    "windows-x86_64": {
 | 
				
			||||||
 | 
					      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVTdDh5VEM1Y1hnUUEzRXpPWDhLQ0JDSjU0UDhwdnFqLzQ0NEV1RFRudSsyS3AwcmdpbDd2SmVkV1VwdmNXWVdnbkF2S3ZPMWZKTDRzdHZ0djVjZWNENk51TGlWbjRsWXdJPQp0cnVzdGVkIGNvbW1lbnQ6IHRpbWVzdGFtcDoxNzIxOTE4NDU2CWZpbGU6ZG9taW5vLWNsaWVudF8wLjIuMF94NjQtc2V0dXAubnNpcy56aXAKTnpuMkJueXBJaGVEYkpMS2phMm5ybkp5aHRKVUtLbHJYaVVrYXRXMXg1R1owQjN2VzRVVmlOMnJMWUlIMzBCSE5WeHpIYUY5dmVzTG5SdDEyNGNPQXc9PQo=",
 | 
				
			||||||
 | 
					      "url": "https://domserv.xintanalabs.net/updates/domino-client_0.2.0_x64-setup.nsis.zip"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UpdaterController extends BaseController {
 | 
				
			||||||
 | 
					  async checkUpdate(req: Request, res: Response): Promise<any> {
 | 
				
			||||||
 | 
					    this.logger.info('Checking for updates');
 | 
				
			||||||
 | 
					    return res.json(json).status(200).end();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // return res.status(204).end();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // async startMatchSession(data: any): Promise<any> {
 | 
				
			||||||
 | 
					  //   const response = await this.sessionManager.startSession(data);
 | 
				
			||||||
 | 
					  //   return response;
 | 
				
			||||||
 | 
					  // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // async joinMatchSession(data: any, socketId: string): Promise<any> {
 | 
				
			||||||
 | 
					  //   const response = await this.sessionManager.joinSession(data, socketId);
 | 
				
			||||||
 | 
					  //   return response;
 | 
				
			||||||
 | 
					  // }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,6 +18,7 @@ export function matchSessionAdapter(session: MatchSession, showPips: boolean = f
 | 
				
			|||||||
        status: state.status,        
 | 
					        status: state.status,        
 | 
				
			||||||
        matchInProgress: state.matchInProgress,
 | 
					        matchInProgress: state.matchInProgress,
 | 
				
			||||||
        gameSummaries: state.gameSummaries,
 | 
					        gameSummaries: state.gameSummaries,
 | 
				
			||||||
 | 
					        room: state.room
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -61,6 +61,7 @@ export interface DbMatchSession extends EntityMongo {
 | 
				
			|||||||
  status: string;
 | 
					  status: string;
 | 
				
			||||||
  matchInProgress: boolean;
 | 
					  matchInProgress: boolean;
 | 
				
			||||||
  gameSummaries: GameSummary[];
 | 
					  gameSummaries: GameSummary[];
 | 
				
			||||||
 | 
					  room: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface DbUser extends EntityMongo {
 | 
					export interface DbUser extends EntityMongo {
 | 
				
			||||||
@@ -115,4 +116,9 @@ export interface DbListResponse{
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  sort?: any;
 | 
					  sort?: any;
 | 
				
			||||||
  data: EntityMongo[];
 | 
					  data: EntityMongo[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface UserSession extends EntityMongo {
 | 
				
			||||||
 | 
					  userId: string;
 | 
				
			||||||
 | 
					  uuid: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/server/db/mongo/UserSessionMongoManager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/server/db/mongo/UserSessionMongoManager.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					import { uuid } from "../../../common/utilities";
 | 
				
			||||||
 | 
					import { UserSession } from "../interfaces";
 | 
				
			||||||
 | 
					import { BaseMongoManager } from "./common/BaseMongoManager";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserSessionMongoManager extends BaseMongoManager {
 | 
				
			||||||
 | 
					  collection = 'userSessions';
 | 
				
			||||||
 | 
					  constructor() {
 | 
				
			||||||
 | 
					    super();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async createSession(userId: string): Promise<string> {
 | 
				
			||||||
 | 
					    const uid: string = uuid();
 | 
				
			||||||
 | 
					    const userSession: UserSession = {
 | 
				
			||||||
 | 
					      userId,
 | 
				
			||||||
 | 
					      uuid: uid
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    await this.create(userSession);
 | 
				
			||||||
 | 
					    return uid;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async removeSession(userId: string): Promise<number> {
 | 
				
			||||||
 | 
					    return this.deleteByFilter({ userId });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async renewSession(userId: string): Promise<string> {
 | 
				
			||||||
 | 
					    await this.removeSession(userId);
 | 
				
			||||||
 | 
					    return this.createSession(userId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async getUserSession(userId: string): Promise<UserSession | null> {
 | 
				
			||||||
 | 
					    return this.getByFilter({ userId }) as Promise<UserSession | null>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async getByUuid(uuid: string): Promise<UserSession | null> {
 | 
				
			||||||
 | 
					    return this.getByFilter({ uuid }) as Promise<UserSession | null>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -24,6 +24,7 @@ app.use(express.text());
 | 
				
			|||||||
app.use(express.urlencoded({extended: true }));
 | 
					app.use(express.urlencoded({extended: true }));
 | 
				
			||||||
app.use(useRouter())
 | 
					app.use(useRouter())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(express.static(join(process.cwd(), 'public')));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.get('/', (req, res) => {
 | 
					app.get('/', (req, res) => {
 | 
				
			||||||
  res.sendFile(join(__dirname, 'index.html'));
 | 
					  res.sendFile(join(__dirname, 'index.html'));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,9 @@ import crypto from 'crypto';
 | 
				
			|||||||
import jwt from 'jsonwebtoken';
 | 
					import jwt from 'jsonwebtoken';
 | 
				
			||||||
import bcrypt from 'bcryptjs';
 | 
					import bcrypt from 'bcryptjs';
 | 
				
			||||||
import { User } from '../db/interfaces';
 | 
					import { User } from '../db/interfaces';
 | 
				
			||||||
 | 
					import { ManagerBase } from './ManagerBase';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SecurityManager {
 | 
					export class SecurityManager extends ManagerBase {
 | 
				
			||||||
  saltRounds = Number(process.env.SALT_ROUNDS);
 | 
					  saltRounds = Number(process.env.SALT_ROUNDS);
 | 
				
			||||||
  jwtSecretKey = process.env.JWT_SECRET_KEY || '';
 | 
					  jwtSecretKey = process.env.JWT_SECRET_KEY || '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,18 +21,21 @@ export class SecurityManager {
 | 
				
			|||||||
    return crypto.randomBytes(32).toString('hex');
 | 
					    return crypto.randomBytes(32).toString('hex');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  signJwt(data: any) {
 | 
					  signJwt(data: any, longTerm: boolean = false): string {
 | 
				
			||||||
    return jwt.sign(data, this.jwtSecretKey, { expiresIn: '3h' });
 | 
					    const expiresIn: string = longTerm ? '7d' : '3h'
 | 
				
			||||||
 | 
					    delete data.iat;
 | 
				
			||||||
 | 
					    delete data.exp;
 | 
				
			||||||
 | 
					    return jwt.sign(data, this.jwtSecretKey, { expiresIn });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO: verificar esto
 | 
					  // TODO: verificar esto
 | 
				
			||||||
  async verifyJwt(token: string): Promise<User> {
 | 
					  async verifyJwt(token: string): Promise<any> {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      jwt.verify(token, this.jwtSecretKey, (err, decoded) => {
 | 
					      jwt.verify(token, this.jwtSecretKey, (err, decoded) => {
 | 
				
			||||||
        if (err) {
 | 
					        if (err) {
 | 
				
			||||||
          reject(err);
 | 
					          reject(err);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          resolve(decoded as User);
 | 
					          resolve(decoded);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Request, Response, Router } from 'express';
 | 
					import { Request, Response, Router } from 'express';
 | 
				
			||||||
import { AuthController } from '../controllers/AuthController';
 | 
					import { AuthController } from '../controllers/AuthController';
 | 
				
			||||||
 | 
					import { UpdaterController } from '../controllers/UpdaterController';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import adminRouter from './adminRouter';
 | 
					import adminRouter from './adminRouter';
 | 
				
			||||||
import userRouter from './userRouter';
 | 
					import userRouter from './userRouter';
 | 
				
			||||||
@@ -8,13 +9,19 @@ import gameRouter from './gameRouter';
 | 
				
			|||||||
export default function(): Router {
 | 
					export default function(): Router {
 | 
				
			||||||
  const router = Router();
 | 
					  const router = Router();
 | 
				
			||||||
  const authController = new AuthController();
 | 
					  const authController = new AuthController();
 | 
				
			||||||
 | 
					  const updaterController = new UpdaterController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  router.get('/version', async function(req: Request, res: Response){
 | 
					  router.get('/version', async function(req: Request, res: Response){
 | 
				
			||||||
      res.send('1.0.0').end();
 | 
					      res.json({
 | 
				
			||||||
 | 
					        app: 'domino',
 | 
				
			||||||
 | 
					        version: '0.1.4-test',
 | 
				
			||||||
 | 
					      }).end();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  router.post('/auth/code', (req: Request, res: Response) => authController.twoFactorCodeAuthentication(req, res));
 | 
					  router.post('/auth/code', (req: Request, res: Response) => authController.twoFactorCodeAuthentication(req, res));
 | 
				
			||||||
  router.post('/login', (req: Request, res: Response) => authController.login(req, res));
 | 
					  router.post('/login', (req: Request, res: Response) => authController.login(req, res));
 | 
				
			||||||
 | 
					  router.post('/refresh', (req: Request, res: Response) => authController.refresh(req, res));
 | 
				
			||||||
 | 
					  router.get('/updater/:target/:arch/:currentVersion', (req: Request, res: Response) => updaterController.checkUpdate(req, res));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  router.use('/admin', adminRouter());
 | 
					  router.use('/admin', adminRouter());
 | 
				
			||||||
  router.use('/user', userRouter());
 | 
					  router.use('/user', userRouter());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -97,7 +97,6 @@ export class InteractionService extends ServiceBase{
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					 | 
				
			||||||
  private  onPlayerReady(data: any): any {
 | 
					  private  onPlayerReady(data: any): any {
 | 
				
			||||||
    const { userId, sessionId } = data;
 | 
					    const { userId, sessionId } = data;
 | 
				
			||||||
    const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
 | 
					    const session: MatchSession | undefined = this.sessionManager.getSession(sessionId);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,23 +10,23 @@ export class PlayerNotificationService extends ServiceBase {
 | 
				
			|||||||
  clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
 | 
					  clientNotifier: NetworkClientNotifier = new NetworkClientNotifier();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  notifyGameState(game: DominoesGame) {
 | 
					  notifyGameState(game: DominoesGame) {
 | 
				
			||||||
    const gameState: GameState = game.getGameState();
 | 
					    const gameState: GameState = game.getState();
 | 
				
			||||||
    const { players } = game;
 | 
					    const { players } = game;
 | 
				
			||||||
    players.map(player => player.sendEvent('update-game-state', gameState));
 | 
					    players.filter(p => p instanceof NetworkPlayer).map(player => player.sendEvent('update-game-state', gameState));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  notifyPlayersState(players: PlayerInterface[]) {
 | 
					  notifyPlayersState(players: PlayerInterface[]) {
 | 
				
			||||||
    players.map(player => player.sendEvent('update-player-state', player.getState()));
 | 
					    players.filter(p => p instanceof NetworkPlayer).map(player => player.sendEvent('update-player-state', player.getState()));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  notifyMatchState(session: MatchSession) {
 | 
					  notifyMatchState(session: MatchSession) {
 | 
				
			||||||
    const { players } = session;
 | 
					    const { players } = session;
 | 
				
			||||||
    players.map(player => player.sendEvent('update-match-session-state', session.getState()));
 | 
					    players.filter(p => p instanceof NetworkPlayer).forEach(player => player.sendEvent('update-match-session-state', session.getState()));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async sendEventToPlayers(event: string, players: PlayerInterface[], data: Function | any = {}) {
 | 
					  async sendEventToPlayers(event: string, players: PlayerInterface[], data: Function | any = {}) {
 | 
				
			||||||
    players.forEach((player) => {
 | 
					    players.filter(p => p instanceof NetworkPlayer).forEach((player) => {
 | 
				
			||||||
      let dataTosend = data;
 | 
					      let dataTosend = data;
 | 
				
			||||||
      if (typeof data === 'function') {
 | 
					      if (typeof data === 'function') {
 | 
				
			||||||
        dataTosend = data(player);
 | 
					        dataTosend = data(player);
 | 
				
			||||||
@@ -37,6 +37,8 @@ export class PlayerNotificationService extends ServiceBase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  sendEvent(event: string, player: PlayerInterface, data: any = {}) {  
 | 
					  sendEvent(event: string, player: PlayerInterface, data: any = {}) {  
 | 
				
			||||||
    this.logger.debug(`Sending event '${event}' to player ${player.id}`);
 | 
					    this.logger.debug(`Sending event '${event}' to player ${player.id}`);
 | 
				
			||||||
    this.clientNotifier.sendEvent(player as NetworkPlayer, event, data);  
 | 
					    if (player instanceof NetworkPlayer) {
 | 
				
			||||||
 | 
					      this.clientNotifier.sendEvent(player as NetworkPlayer, event, data);  
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,7 +2,6 @@ import { Server as HttpServer } from "http";
 | 
				
			|||||||
import { ServiceBase } from "./ServiceBase";
 | 
					import { ServiceBase } from "./ServiceBase";
 | 
				
			||||||
import { Server } from "socket.io";
 | 
					import { Server } from "socket.io";
 | 
				
			||||||
import { SecurityManager } from "../managers/SecurityManager";
 | 
					import { SecurityManager } from "../managers/SecurityManager";
 | 
				
			||||||
import { User } from "../db/interfaces";
 | 
					 | 
				
			||||||
import { Socket } from "socket.io";
 | 
					import { Socket } from "socket.io";
 | 
				
			||||||
import { InteractionService } from "./InteractionService";
 | 
					import { InteractionService } from "./InteractionService";
 | 
				
			||||||
import { ClientEvents } from "../../game/constants";
 | 
					import { ClientEvents } from "../../game/constants";
 | 
				
			||||||
@@ -24,7 +23,7 @@ export class SocketIoService  extends ServiceBase {
 | 
				
			|||||||
      if (socket.handshake.auth && socket.handshake.auth.token) {
 | 
					      if (socket.handshake.auth && socket.handshake.auth.token) {
 | 
				
			||||||
        const token = socket.handshake.auth.token;
 | 
					        const token = socket.handshake.auth.token;
 | 
				
			||||||
        try {          
 | 
					        try {          
 | 
				
			||||||
          const user: User = await this.security.verifyJwt(token);
 | 
					          const { user } = await this.security.verifyJwt(token);
 | 
				
			||||||
          socket.user = user;
 | 
					          socket.user = user;
 | 
				
			||||||
          next();
 | 
					          next();
 | 
				
			||||||
        } catch (err) {
 | 
					        } catch (err) {
 | 
				
			||||||
@@ -61,8 +60,7 @@ export class SocketIoService  extends ServiceBase {
 | 
				
			|||||||
        if (user !== undefined && user._id !== undefined) {
 | 
					        if (user !== undefined && user._id !== undefined) {
 | 
				
			||||||
          const userId = user._id.toString();
 | 
					          const userId = user._id.toString();
 | 
				
			||||||
          if (!SocketIoService.clients.has(userId)) {
 | 
					          if (!SocketIoService.clients.has(userId)) {
 | 
				
			||||||
            SocketIoService.clients.set(userId, { socketId, alive: true, user: socket.user });
 | 
					            SocketIoService.clients.set(userId, { socketId, alive: true, user: socket.user  });
 | 
				
			||||||
            socket.join('room-general')
 | 
					 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            const client = SocketIoService.clients.get(userId);
 | 
					            const client = SocketIoService.clients.get(userId);
 | 
				
			||||||
            this.interactionService.updateSocketId(client.sessionId, userId, socketId);
 | 
					            this.interactionService.updateSocketId(client.sessionId, userId, socketId);
 | 
				
			||||||
@@ -83,6 +81,11 @@ export class SocketIoService  extends ServiceBase {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      socket.on('client:join-room', (room) => {
 | 
				
			||||||
 | 
					        socket.join(room);
 | 
				
			||||||
 | 
					        this.logger.debug(`User ${socket.user?.username} joined room ${room}`);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      socket.on(ClientEvents.CLIENT_EVENT, (data) => {
 | 
					      socket.on(ClientEvents.CLIENT_EVENT, (data) => {
 | 
				
			||||||
        this.interactionService.handleClientEvent(data);
 | 
					        this.interactionService.handleClientEvent(data);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user