+
{ corners }
-
+
{ roads }
> }
diff --git a/client/src/Table.js b/client/src/Table.js
index 1137e60..100270e 100755
--- a/client/src/Table.js
+++ b/client/src/Table.js
@@ -403,7 +403,7 @@ const Action = ({ table }) => {
> }
- { game.state === 'normal' && <>
+ { !inLobby && <>
@@ -430,7 +430,8 @@ const PlayerName = ({table}) => {
if (name !== table.game.name) {
table.setPlayerName(name);
} else {
- table.setState({ pickName: false, error: "" });
+ table.setError("");
+ table.setState({ pickName: false });
}
}
@@ -604,10 +605,10 @@ class Table extends React.Component {
const error = (game.status !== 'success') ? game.status : undefined;
this.updateGame(game);
this.updateMessage();
- this.setState({ error: error });
+ this.setError(error);
}).catch((error) => {
console.error(error);
- this.setState({error: error.message});
+ this.setError(error.message);
}).then(() => {
this.setState({ loading: this.state.loading - 1 });
});
@@ -640,7 +641,7 @@ class Table extends React.Component {
shuffleTable() {
return this.sendAction('shuffle')
.then(() => {
- this.setState({ error: "Table shuffled!" });
+ this.setError("Table shuffled!");
});
}
@@ -676,6 +677,19 @@ class Table extends React.Component {
return this.sendAction('roll');
}
+ setError(error) {
+ if (!error) {
+ return;
+ }
+ if (this.errorTimeout) {
+ clearTimeout(this.errorTimeout);
+ }
+ setTimeout(() => this.setState({error: undefined}), 3000);
+ if (this.state.error !== error) {
+ this.setState({ error });
+ }
+ }
+
setGameState(state) {
if (this.loadTimer) {
window.clearTimeout(this.loadTimer);
@@ -702,9 +716,9 @@ class Table extends React.Component {
this.updateMessage();
}).catch((error) => {
console.error(error);
- this.setState({error: error.message});
+ this.setError(error.message);
}).then(() => {
- this.setState({ loading: this.state.loading + 1 });
+ this.setState({ loading: this.state.loading - 1 });
return this.game.state;
});
}
@@ -865,7 +879,7 @@ class Table extends React.Component {
try {
data = JSON.parse(event.data);
} catch (error) {
- this.setState({ error });
+ this.setError(error);
return;
}
let update;
@@ -875,7 +889,16 @@ class Table extends React.Component {
const error = (update.status !== 'success') ? update.status : undefined;
this.updateGame(update);
this.updateMessage();
- this.setState({ error });
+ this.setError(error);
+ break;
+ case 'ping':
+ if (this.keepAlive) {
+ clearTimeout(this.keepAlive);
+ }
+ this.keepAlive = setTimeout(() => {
+ console.error(`No server ping for 5 seconds!`);
+ }, 5000);
+ this.ws.send(JSON.stringify({ type: 'pong', timestamp: data.ping }));
break;
default:
console.log(`Unknown event type: ${data.type}`);
@@ -925,7 +948,7 @@ class Table extends React.Component {
error = `Unable to find game ${this.id}. Starting new game.`
console.log(error);
- this.setState({ error: error });
+ this.setError(error);
params.url = `${base}/api/v1/games/${this.id}`;
params.method = "POST";
@@ -951,7 +974,7 @@ class Table extends React.Component {
this.setState({ error: "" });
}).catch((error) => {
console.error(error);
- this.setState({error: error.message});
+ this.setError(error.message);
}).then(() => {
this.setState({ loading: this.state.loading - 1 });
});
@@ -961,10 +984,16 @@ class Table extends React.Component {
if (this.loadTimer) {
clearTimeout(this.loadTimer);
}
+ if (this.keepAlive) {
+ clearTimeout(this.keepAlive);
+ }
if (this.updateSizeTimer) {
clearTimeout(this.updateSizeTimer);
this.updateSizeTimer = 0;
}
+ if (this.errorTimeout) {
+ clearTimeout(this.errorTimeout);
+ }
}
cardClicked(card) {
@@ -1098,7 +1127,7 @@ class Table extends React.Component {
(!game.player || !game.player.mustDiscard) &&
}
- { this.state.error && {this.state.error}
}
+ { this.state.error && this.setState({ error: undefined })} className="Error">{this.state.error}
}
);
diff --git a/server/app.js b/server/app.js
index b3a1f96..bfda87b 100755
--- a/server/app.js
+++ b/server/app.js
@@ -115,7 +115,6 @@ app.use(basePath, function(req, res, next) {
/* Everything below here requires a successful authentication */
app.use(basePath, express.static(frontendPath, { index: false }));
-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 */
@@ -127,10 +126,9 @@ app.use(basePath, index);
app.set("port", serverConfig.port);
process.on('SIGINT', () => {
- server.close(() => {
- console.log("Gracefully shutting down from SIGINT (Ctrl-C)");
- process.exit(1);
- });
+ console.log("Gracefully shutting down from SIGINT (Ctrl-C) in 2 seconds");
+ setTimeout(() => process.exit(-1), 2000);
+ server.close(() => process.exit(1));
});
require("./db/games").then(function(db) {
diff --git a/server/routes/games.js b/server/routes/games.js
index 7bd8dc6..a9832b1 100755
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -8,6 +8,7 @@ const express = require("express"),
accessSync = fs.accessSync,
randomWords = require("random-words");
+const session = require("express-session");
const layout = require('./layout.js');
const MAX_SETTLEMENTS = 5;
@@ -2489,20 +2490,59 @@ router.put("/:id/:action/:value?", async (req, res) => {
return sendGame(req, res, game, error);
})
+const ping = (session) => {
+ session.ping = Date.now();
+ session.ws.send(JSON.stringify({ type: 'ping', ping: session.ping }));
+ if (session.keepAlive) {
+ clearTimeout(session.keepAlive);
+ }
+ session.keepAlive = setTimeout(() => { ping(session); }, 2500);
+}
+
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;
- }
+ console.log(`WebSocket connect from game ${id}`);
+
+ let game;
+ if (!(id in games)) {
+ game = createGame(id);
+ } else {
+ game = games[id];
}
+
+ const session = getSession(game, req.session);
+ if (session) {
+ console.log(`WebSocket connected for ${session.name ? session.name : "Unnamed"}`);
+ session.ws = ws;
+ if (session.keepAlive) {
+ clearTimeout(session.keepAlive);
+ }
+ session.keepAlive = setTimeout(() => { ping(session); }, 2500);
+ } else {
+ console.log(`No session found for WebSocket with id ${id}`);
+ }
+
+ ws.on('error', (event) => {
+ console.error(`WebSocket error: `, event.message);
+ });
+
+ ws.on('open', (event) => {
+ console.log(`WebSocket open: `, event.message);
+ });
+
+ ws.on('message', (message) => {
+ try {
+ const data = JSON.parse(message);
+ switch (data.type) {
+ case 'pong':
+ console.log(`Latency for ${session.name ? session.name : 'Unammed'} is ${Date.now() - data.timestamp}`);
+ break;
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ });
});
router.get("/:id", async (req, res/*, next*/) => {
@@ -2658,6 +2698,9 @@ const sendGame = async (req, res, game, error) => {
if (reduced.ws) {
delete reduced.ws;
}
+ if (reduced.keepAlive) {
+ delete reduced.keepAlive;
+ }
reducedGame.sessions[id] = reduced;
/* Do not send session-id as those are secrets */
@@ -2704,17 +2747,19 @@ const sendGame = async (req, res, game, error) => {
});
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
- }));
+ if (!error) {
+ 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}`);
+ console.log(`Returning update to ${target.name ? target.name : 'Unnamed'}`);
res.status(200).send(playerGame);
}
}
@@ -2726,7 +2771,8 @@ const resetGame = (game) => {
Object.assign(game, {
startTime: Date.now(),
state: 'lobby',
- turn: 0,
+ turns: 0,
+ turn: {},
sheep: 19,
ore: 19,
wool: 19,
@@ -2738,7 +2784,6 @@ const resetGame = (game) => {
},
developmentCards: [],
chat: [],
- turn: {},
pipOrder: game.pipOrder,
borderOrder: game.borderOrder,
tileOrder: game.tileOrder,