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 4e5aade..8537abe 100755 Binary files a/client/src/assets/raptor-robber.png and b/client/src/assets/raptor-robber.png differ 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) => {