From 4457cf5ad5269758b6eb5d0f998d8804ec90733d Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Thu, 20 Jan 2022 22:18:23 -0500 Subject: [PATCH 1/2] update dependency --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6c5fd3e..7087139 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2133,9 +2133,9 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "engine.io": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.0.tgz", - "integrity": "sha512-ErhZOVu2xweCjEfYcTdkCnEYUiZgkAcBBAhW4jbIvNG8SLU3orAqoJCiytZjYF7eTpVmmCrLDjLIEaPlUAs1uw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", + "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", From 4e4aee3fe8114a527477e932c2ddaa603148d96b Mon Sep 17 00:00:00 2001 From: AlecM33 Date: Fri, 21 Jan 2022 01:33:49 -0500 Subject: [PATCH 2/2] revert conversion of join to XHR call --- client/src/scripts/game.js | 141 ++++++++++++++-------------------- server/api/GamesAPI.js | 34 ++++---- server/modules/GameManager.js | 82 ++++++++++---------- 3 files changed, 113 insertions(+), 144 deletions(-) diff --git a/client/src/scripts/game.js b/client/src/scripts/game.js index 1c14957..f9eb058 100644 --- a/client/src/scripts/game.js +++ b/client/src/scripts/game.js @@ -12,26 +12,6 @@ import { XHRUtility } from '../modules/XHRUtility.js'; const game = () => { injectNavbar(); - const socket = io('/in-game'); - const timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url)); - const gameTimerManager = new GameTimerManager(stateBucket, socket); - const gameStateRenderer = new GameStateRenderer(stateBucket, socket); - socket.on('disconnect', () => { - toast('Disconnected. Attempting reconnect...', 'error', true, false); - }); - socket.on('connect', () => { - if (!stateBucket.joinRequestInFlight) { - prepareGamePage( - stateBucket, - stateBucket.accessCode, - gameTimerManager, - gameStateRenderer, - timerWorker, - socket, - UserUtility.validateAnonUserSignature(stateBucket.environment) - ); - } - }); XHRUtility.xhr( '/api/games/environment', 'GET', @@ -40,81 +20,74 @@ const game = () => { ) .then((res) => { stateBucket.environment = res.content; - joinGame(res, gameTimerManager, gameStateRenderer, socket, timerWorker); + const socket = io('/in-game'); + const timerWorker = new Worker(new URL('../modules/Timer.js', import.meta.url)); + const gameTimerManager = new GameTimerManager(stateBucket, socket); + const gameStateRenderer = new GameStateRenderer(stateBucket, socket); + socket.on('connect', () => { + syncWithGame( + stateBucket, + gameTimerManager, + gameStateRenderer, + timerWorker, + socket, + UserUtility.validateAnonUserSignature(res.content) + ); + }); + socket.on('disconnect', () => { + toast('Disconnected. Attempting reconnect...', 'error', true, false); + }); + setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager); }).catch((res) => { toast(res.content, 'error', true); }); }; -function joinGame (environmentResponse, gameTimerManager, gameStateRenderer, socket, timerWorker) { - let cookie = UserUtility.validateAnonUserSignature(environmentResponse.content); +function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie) { const splitUrl = window.location.href.split('/game/'); const accessCode = splitUrl[1]; if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) { - XHRUtility.xhr( - '/api/games/' + accessCode + '/players', - 'PATCH', - null, - JSON.stringify({ cookie: cookie }) - ) - .then((res) => { - UserUtility.setAnonymousUserId(res.content, environmentResponse.content); - stateBucket.accessCode = accessCode; - stateBucket.joinRequestInFlight = false; - cookie = res.content; - setClientSocketHandlers(stateBucket, gameStateRenderer, socket, timerWorker, gameTimerManager); - prepareGamePage(stateBucket, accessCode, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie); - }).catch((res) => { - if (res.status === 404) { - window.location = '/not-found?reason=' + encodeURIComponent('game-not-found'); - } else if (res.status >= 500) { - toast( - 'The server is experiencing problems. Please try again later', - 'error', - true - ); - } - }); + socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, cookie, function (gameState) { + cookie = gameState.client.cookie; + UserUtility.setAnonymousUserId(cookie, stateBucket.environment); + stateBucket.currentGameState = gameState; + document.querySelector('.spinner-container')?.remove(); + document.querySelector('.spinner-background')?.remove(); + document.getElementById('game-content').innerHTML = templates.INITIAL_GAME_DOM; + toast('You are connected.', 'success', true, true, 2); + processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker); + if (!gameState.client.hasEnteredName) { + document.getElementById('prompt').innerHTML = templates.NAME_CHANGE_MODAL; + document.getElementById('change-name-form').onsubmit = (e) => { + e.preventDefault(); + const name = document.getElementById('player-new-name').value; + if (validateName(name)) { + socket.emit(globals.COMMANDS.CHANGE_NAME, gameState.accessCode, { + name: name, + personId: gameState.client.id + }, (result) => { + switch (result) { + case 'taken': + toast('This name is already taken.', 'error', true, true, 8); + break; + case 'changed': + ModalManager.dispelModal('change-name-modal', 'change-name-modal-background'); + toast('Name set.', 'success', true, true, 5); + propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id); + processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker); + } + }); + } else { + toast('Name must be between 1 and 30 characters.', 'error', true, true, 8); + } + }; + } + }); + } else { + window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code'); } } -function prepareGamePage (stateBucket, accessCode, gameTimerManager, gameStateRenderer, timerWorker, socket, cookie) { - socket.emit(globals.COMMANDS.FETCH_GAME_STATE, accessCode, cookie, function (gameState) { - stateBucket.currentGameState = gameState; - document.querySelector('.spinner-container')?.remove(); - document.querySelector('.spinner-background')?.remove(); - document.getElementById('game-content').innerHTML = templates.INITIAL_GAME_DOM; - toast('You are connected.', 'success', true, true, 2); - processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker); - if (!gameState.client.hasEnteredName) { - document.getElementById('prompt').innerHTML = templates.NAME_CHANGE_MODAL; - document.getElementById('change-name-form').onsubmit = (e) => { - e.preventDefault(); - const name = document.getElementById('player-new-name').value; - if (validateName(name)) { - socket.emit(globals.COMMANDS.CHANGE_NAME, gameState.accessCode, { - name: name, - personId: gameState.client.id - }, (result) => { - switch (result) { - case 'taken': - toast('This name is already taken.', 'error', true, true, 8); - break; - case 'changed': - ModalManager.dispelModal('change-name-modal', 'change-name-modal-background'); - toast('Name set.', 'success', true, true, 5); - propagateNameChange(stateBucket.currentGameState, name, stateBucket.currentGameState.client.id); - processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker); - } - }); - } else { - toast('Name must be between 1 and 30 characters.', 'error', true, true, 8); - } - }; - } - }); -} - function processGameState (currentGameState, userId, socket, gameStateRenderer, gameTimerManager, timerWorker, refreshPrompt = true) { displayClientInfo(currentGameState.client.name, currentGameState.client.userType); if (refreshPrompt) { diff --git a/server/api/GamesAPI.js b/server/api/GamesAPI.js index acd50b8..2d89f3d 100644 --- a/server/api/GamesAPI.js +++ b/server/api/GamesAPI.js @@ -19,7 +19,7 @@ const apiLimiter = rateLimit({ const corsOptions = process.env.NODE_ENV.trim() === 'development' ? { origin: '*', - optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 + optionsSuccessStatus: 200 } : { origin: 'https://playwerewolf.uk.r.appspot.com', @@ -27,7 +27,7 @@ const corsOptions = process.env.NODE_ENV.trim() === 'development' }; router.use(cors(corsOptions)); -router.options('/:code/players', cors(corsOptions)); // enable pre-flight request for DELETE +//router.options('/:code/players', cors(corsOptions)); if (process.env.NODE_ENV.trim() === 'production') { // in prod, limit clients to creating 5 games per 10 minutes. router.use('/create', apiLimiter); @@ -65,21 +65,21 @@ router.get('/:code/availability', function (req, res) { }); }); -router.patch('/:code/players', function (req, res) { - if ( - req.body === null - || req.body.cookie === null - || (typeof req.body.cookie !== 'string' && req.body.cookie !== false) - || (req.body.cookie.length !== globals.USER_SIGNATURE_LENGTH && req.body.cookie !== false) - ) { - res.status(400).send(); - } - gameManager.joinGame(req.body.cookie, req.params.code).then((data) => { - res.status(200).send(data); - }).catch((code) => { - res.status(code).send(); - }); -}); +// router.patch('/:code/players', function (req, res) { +// if ( +// req.body === null +// || req.body.cookie === null +// || (typeof req.body.cookie !== 'string' && req.body.cookie !== false) +// || (req.body.cookie.length !== globals.USER_SIGNATURE_LENGTH && req.body.cookie !== false) +// ) { +// res.status(400).send(); +// } +// gameManager.joinGame(req.body.cookie, req.params.code).then((data) => { +// res.status(200).send(data); +// }).catch((code) => { +// res.status(code).send(); +// }); +// }); router.get('/environment', function (req, res) { res.status(200).send(gameManager.environment); diff --git a/server/modules/GameManager.js b/server/modules/GameManager.js index 0e1b745..b64f0ae 100644 --- a/server/modules/GameManager.js +++ b/server/modules/GameManager.js @@ -14,9 +14,9 @@ class GameManager { } addGameSocketHandlers = (namespace, socket) => { - socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, (accessCode, personId, ackFn) => { + socket.on(globals.CLIENT_COMMANDS.FETCH_GAME_STATE, async (accessCode, personId, ackFn) => { this.logger.trace('request for game state for accessCode: ' + accessCode + ' from socket: ' + socket.id + ' with cookie: ' + personId); - this.handleRequestForGameState( + await this.handleRequestForGameState( this.namespace, this.logger, this.activeGameRunner, @@ -253,59 +253,55 @@ class GameManager { } }; - joinGame = (cookie, accessCode) => { - const game = this.activeGameRunner.activeGames[accessCode]; - if (game) { - const person = findPersonByField(game, 'cookie', cookie); - if (person) { - return Promise.resolve(person.cookie); - } else { - const unassignedPerson = game.moderator.assigned === false - ? game.moderator - : game.people.find((person) => person.assigned === false); - if (unassignedPerson) { - this.logger.trace('request from client to join game. Assigning: ' + unassignedPerson.name); - unassignedPerson.assigned = true; - game.isFull = isGameFull(game); - this.namespace.in(game.accessCode).emit( - globals.EVENTS.PLAYER_JOINED, - GameStateCurator.mapPerson(unassignedPerson), - game.isFull - ); - return Promise.resolve(unassignedPerson.cookie); - } else { // if the game is full, make them a spectator. - const spectator = new Person( - createRandomId(), - createRandomId(), - UsernameGenerator.generate(), - globals.USER_TYPES.SPECTATOR - ); - this.logger.trace('new spectator: ' + spectator.name); - game.spectators.push(spectator); - return Promise.resolve(spectator.cookie); - } - } - } else { - return Promise.reject(404); + joinGame = (game) => { + const unassignedPerson = game.moderator.assigned === false + ? game.moderator + : game.people.find((person) => person.assigned === false); + if (unassignedPerson) { + this.logger.trace('request from client to join game. Assigning: ' + unassignedPerson.name); + unassignedPerson.assigned = true; + game.isFull = isGameFull(game); + this.namespace.in(game.accessCode).emit( + globals.EVENTS.PLAYER_JOINED, + GameStateCurator.mapPerson(unassignedPerson), + game.isFull + ); + return unassignedPerson; + } else { // if the game is full, make them a spectator. + const spectator = new Person( + createRandomId(), + createRandomId(), + UsernameGenerator.generate(), + globals.USER_TYPES.SPECTATOR + ); + this.logger.trace('new spectator: ' + spectator.name); + game.spectators.push(spectator); + return spectator; } }; - handleRequestForGameState = (namespace, logger, gameRunner, accessCode, personCookie, ackFn, socket) => { + handleRequestForGameState = async (namespace, logger, gameRunner, accessCode, personCookie, ackFn, clientSocket) => { const game = gameRunner.activeGames[accessCode]; if (game) { const matchingPerson = findPersonByField(game, 'cookie', personCookie); if (matchingPerson) { - if (matchingPerson.socketId === socket.id) { + if (matchingPerson.socketId === clientSocket.id) { logger.trace('matching person found with an established connection to the room: ' + matchingPerson.name); - ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger)); + ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, clientSocket, logger)); } else { logger.trace('matching person found with a new connection to the room: ' + matchingPerson.name); - socket.join(accessCode); - matchingPerson.socketId = socket.id; - ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, socket, logger)); + clientSocket.join(accessCode); + matchingPerson.socketId = clientSocket.id; + ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, matchingPerson, gameRunner, clientSocket, logger)); } } else { - rejectClientRequestForGameState(ackFn); + const namespaceSockets = await namespace.in(accessCode).fetchSockets(); + if (!namespaceSockets.find((namespaceSocket) => namespaceSocket.id === clientSocket.id)) { + let newlyAssignedPerson = this.joinGame(game); + clientSocket.join(accessCode); + newlyAssignedPerson.socketId = clientSocket.id; + ackFn(GameStateCurator.getGameStateFromPerspectiveOfPerson(game, newlyAssignedPerson, gameRunner, clientSocket, logger)); + } } } else { rejectClientRequestForGameState(ackFn);