From 0bc96d5121a27e0f7a9ebedc3f580b696061822c Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Mon, 28 Feb 2022 13:11:27 -0800 Subject: [PATCH] Added WebSocket for responses Signed-off-by: James Ketrenos --- client/src/Table.js | 91 ++++++++++------------------ client/src/assets/raptor-robber.png | Bin 27116 -> 29757 bytes client/src/setupProxy.js | 2 +- server/app.js | 42 +++---------- server/package.json | 1 + server/pass | 21 +++++++ server/reset | 8 +++ server/routes/games.js | 85 ++++++++++++++++++-------- 8 files changed, 130 insertions(+), 120 deletions(-) create mode 100755 server/pass create mode 100755 server/reset diff --git a/client/src/Table.js b/client/src/Table.js index 4e7c5fb..1137e60 100755 --- a/client/src/Table.js +++ b/client/src/Table.js @@ -530,8 +530,6 @@ class Table extends React.Component { }; this.componentDidMount = this.componentDidMount.bind(this); this.throwDice = this.throwDice.bind(this); - this.resetGameLoad = this.resetGameLoad.bind(this); - this.loadGame = this.loadGame.bind(this); this.rollDice = this.rollDice.bind(this); this.setGameState = this.setGameState.bind(this); this.shuffleTable = this.shuffleTable.bind(this); @@ -612,7 +610,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.setState({ loading: this.state.loading - 1 }); - this.resetGameLoad(); }); } @@ -679,53 +676,6 @@ class Table extends React.Component { return this.sendAction('roll'); } - loadGame() { - if (this.loadTimer) { - window.clearTimeout(this.loadTimer); - this.loadTimer = null; - } - - if (!this.state.game) { - console.error('Attempting to loadGame with no game set'); - return; - } - - this.setState({ loading: this.state.loading + 1 }); - return window.fetch(`${base}/api/v1/games/${this.state.game.id}`, { - method: "GET", - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - } - }).then((res) => { - if (res.status >= 400) { - console.log(res); - throw new Error(`Server temporarily unreachable.`); - } - return res.json(); - }).then((game) => { - const error = (game.status !== 'success') ? game.status : undefined; - this.updateGame(game); - this.updateMessage(); - this.setState({ error: error }); - }).catch((error) => { - console.error(error); - this.setState({error: error.message}); - }).then(() => { - this.setState({ loading: this.state.loading - 1 }); - this.resetGameLoad(); - }); - } - - resetGameLoad() { - if (this.loadTimer) { - window.clearTimeout(this.loadTimer); - this.loadTimer = 0; - } - this.loadTimer = window.setTimeout(this.loadGame, 1000); - } - setGameState(state) { if (this.loadTimer) { window.clearTimeout(this.loadTimer); @@ -755,7 +705,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.setState({ loading: this.state.loading + 1 }); - this.resetGameLoad(); return this.game.state; }); } @@ -808,7 +757,7 @@ class Table extends React.Component { this.setState( { signature: game.signature }); } // console.log("Update Game", game); - this.setState( { game: game }); + this.setState( { game }); this.game = game; } @@ -904,21 +853,44 @@ class Table extends React.Component { } else { new_uri = "ws"; } - new_uri = `${new_uri}://${loc.host}${base}/ws`; + new_uri = `${new_uri}://${loc.host}${base}/api/v1/games/ws/${this.id}`; this.ws = new WebSocket(new_uri); this.ws.onopen = (event) => { - console.log(event); - //ws.send(JSON.stringify(apiCall)); + console.log(`WebSocket open:`, event); }; this.ws.onmessage = (event) => { - const json = JSON.parse(event.data); - console.log(json); - }; + let data; + try { + data = JSON.parse(event.data); + } catch (error) { + this.setState({ error }); + return; + } + let update; + switch (data.type) { + case 'game-update': + update = data.update; + const error = (update.status !== 'success') ? update.status : undefined; + this.updateGame(update); + this.updateMessage(); + this.setState({ error }); + break; + default: + console.log(`Unknown event type: ${data.type}`); + break; + } + } this.ws.onerror = (event) => { - console.error(event); + this.setState({ error: event.message }); + console.error(`WebSocket error:`, event); + }; + + this.ws.onclose = (event) => { + this.setState({ error: event.message }); + console.error(`WebSocket close:`, event); }; const params = {}; @@ -982,7 +954,6 @@ class Table extends React.Component { this.setState({error: error.message}); }).then(() => { this.setState({ loading: this.state.loading - 1 }); - this.resetGameLoad(); }); } diff --git a/client/src/assets/raptor-robber.png b/client/src/assets/raptor-robber.png index 4e5aade6cf3e08249dae3b47bac30248e0c32ee1..8537abe4fe06ac72aadf5aa5697918e47a6d0e67 100755 GIT binary patch delta 25770 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 HChz|b!G)T~ delta 23109 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 diff --git a/client/src/setupProxy.js b/client/src/setupProxy.js index c17d3e5..c9f9061 100644 --- a/client/src/setupProxy.js +++ b/client/src/setupProxy.js @@ -4,7 +4,7 @@ module.exports = function(app) { const base = process.env.PUBLIC_URL; console.log('http-proxy-middleware'); app.use(createProxyMiddleware( - `${base}/ws`, { + `${base}/api/v1/games/ws`, { ws: true, target: 'http://localhost:8930', changeOrigin: true, diff --git a/server/app.js b/server/app.js index afce3dd..b3a1f96 100755 --- a/server/app.js +++ b/server/app.js @@ -10,7 +10,10 @@ const express = require("express"), session = require('express-session'), hb = require("handlebars"), SQLiteStore = require('connect-sqlite3')(session), - basePath = require("./basepath"); + basePath = require("./basepath"), + app = express(), + server = require("http").createServer(app), + ws = require('express-ws')(app, server); require("./console-line.js"); /* Monkey-patch console.log with line numbers */ @@ -21,8 +24,6 @@ console.log("Hosting server from: " + basePath); let userDB, gameDB; -const app = express(); - app.use(bodyParser.json()); @@ -81,6 +82,7 @@ app.use(basePath, index); /* /games loads the default index */ app.use(basePath + "games", index); + /* Allow access to the 'users' API w/out being logged in */ /* const users = require("./routes/users"); @@ -113,7 +115,8 @@ app.use(basePath, function(req, res, next) { /* Everything below here requires a successful authentication */ app.use(basePath, express.static(frontendPath, { index: false })); -app.use(basePath + "api/v1/games", require("./routes/games")); +app.set('ws', ws); +app.use(`${basePath}api/v1/games`, require("./routes/games")); /* Declare the "catch all" index route last; the final route is a 404 dynamic router */ app.use(basePath, index); @@ -123,8 +126,6 @@ app.use(basePath, index); */ app.set("port", serverConfig.port); -const server = require("http").createServer(app); - process.on('SIGINT', () => { server.close(() => { console.log("Gracefully shutting down from SIGINT (Ctrl-C)"); @@ -132,8 +133,6 @@ process.on('SIGINT', () => { }); }); -const WebSocket = require('ws'); - require("./db/games").then(function(db) { gameDB = db; }).then(function() { @@ -142,33 +141,6 @@ require("./db/games").then(function(db) { }); }).then(function() { console.log("DB connected. Opening server."); - - /* Create web socket server on top of a regular http server */ - const ws = new WebSocket.Server({ - server - }); - - /* Mount the Express app here */ - - //server.on('request', app); - - app.set('ws', ws); - - ws.on('connection', (req) => {/* - sessionParser(req.upgradeReq, {}, () => { - console.log("New websocket connection:"); - var sess = req.upgradeReq.session; - console.log("working = " + sess.working); - });*/ - }); - - ws.on('message', (message) => { - console.log(`received: ${message}`); - ws.send(JSON.stringify({ - answer: 42 - })); - }); - server.listen(serverConfig.port, () => { console.log(`http/ws server listening on ${serverConfig.port}`); }); diff --git a/server/package.json b/server/package.json index d583eac..1c79f44 100644 --- a/server/package.json +++ b/server/package.json @@ -17,6 +17,7 @@ "core-js": "^3.2.1", "express": "^4.17.1", "express-session": "^1.17.1", + "express-ws": "^5.0.2", "handlebars": "^4.7.6", "moment": "^2.24.0", "morgan": "^1.9.1", diff --git a/server/pass b/server/pass new file mode 100755 index 0000000..d5e765a --- /dev/null +++ b/server/pass @@ -0,0 +1,21 @@ +#!/bin/bash +ADMIN=$(jq -r .admin config/local.json) +if [[ "${ADMIN}" == "" ]]; then + echo "You need to set your { 'admin': 'secret' } in config/local.json" + exit 1 +fi + +id=$1 + +if [[ "${id}" == "" ]]; then + echo "Usage: pass GAME-ID" + exit 1 +fi + +curl --noproxy '*' -s -L \ + --request PUT \ + --header "PRIVATE-TOKEN: ${ADMIN}" \ + --header "Content-Type: application/json" \ + http://localhost:8930/ketr.ketran/api/v1/games/${id}/pass | + jq -r .status + diff --git a/server/reset b/server/reset new file mode 100755 index 0000000..4fd0f09 --- /dev/null +++ b/server/reset @@ -0,0 +1,8 @@ +#!/bin/bash +cp games/held_riding_farm_hat.67 games/held_riding_farm_hat +set -m +npm start & +sleep 3 +./pass held_riding_farm_hat +./roll held_riding_farm_hat 3-3 +fg %1 diff --git a/server/routes/games.js b/server/routes/games.js index 5b11ea6..7bd8dc6 100755 --- a/server/routes/games.js +++ b/server/routes/games.js @@ -1,6 +1,7 @@ "use strict"; const express = require("express"), + router = express.Router(), crypto = require("crypto"), { readFile, writeFile } = require("fs").promises, fs = require("fs"), @@ -19,8 +20,6 @@ require("../db/games").then(function(db) { gameDB = db; }); -const router = express.Router(); - function shuffle(array) { var currentIndex = array.length, temporaryValue, randomIndex; @@ -2490,6 +2489,22 @@ router.put("/:id/:action/:value?", async (req, res) => { return sendGame(req, res, game, error); }) +router.ws("/ws/:id", (ws, req) => { + const { id } = req.params; + ws.on('message', (msg) => { + console.log(msg); + }); + if (id in games) { + const game = games[id]; + + const session = getSession(game, req.session); + if (session) { + console.log(`WebSocket connected for ${session.name}`); + session.ws = ws; + } + } +}); + router.get("/:id", async (req, res/*, next*/) => { const { id } = req.params; // console.log("GET games/" + id); @@ -2640,6 +2655,9 @@ const sendGame = async (req, res, game, error) => { if (reduced.player) { delete reduced.player; } + if (reduced.ws) { + delete reduced.ws; + } reducedGame.sessions[id] = reduced; /* Do not send session-id as those are secrets */ @@ -2658,29 +2676,48 @@ const sendGame = async (req, res, game, error) => { console.error(error); }); - const player = session.player ? session.player : undefined; - if (player) { - player.haveResources = player.wheat > 0 || - player.brick > 0 || - player.sheep > 0 || - player.stone > 0 || - player.wood > 0; - } + for (let id in game.sessions) { + const target = game.sessions[id], + useWS = target !== session, + player = target.player ? target.player : undefined; - /* Strip out data that should not be shared with players */ - delete reducedGame.developmentCards; - - const playerGame = Object.assign({}, reducedGame, { - timestamp: Date.now(), - status: error ? error : "success", - name: session.name, - color: session.color, - order: (session.color in game.players) ? game.players[session.color].order : 0, - player: player, - sessions: reducedSessions, - layout: layout - }); - return res.status(200).send(playerGame); + if (player) { + player.haveResources = player.wheat > 0 || + player.brick > 0 || + player.sheep > 0 || + player.stone > 0 || + player.wood > 0; + } + + /* Strip out data that should not be shared with players */ + delete reducedGame.developmentCards; + + const playerGame = Object.assign({}, reducedGame, { + timestamp: Date.now(), + status: error ? error : "success", + name: target.name, + color: target.color, + order: (target.color in game.players) ? game.players[target.color].order : 0, + player: player, + sessions: reducedSessions, + layout: layout + }); + + if (useWS) { + if (!target.ws) { + console.error(`No WebSocket connection to ${target.name}`); + } else { + console.log(`Sending update to ${target.name}`); + target.ws.send(JSON.stringify({ + type: 'game-update', + update: playerGame + })); + } + } else { + console.log(`Returning update to ${target.name}`); + res.status(200).send(playerGame); + } + } } const resetGame = (game) => {