Merge pull request #109 from bra1n/network-refactoring

Network refactoring
This commit is contained in:
Steffen 2021-02-02 10:32:43 +01:00 committed by GitHub
commit 142968bf8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 130 additions and 76 deletions

View File

@ -150,12 +150,31 @@ wss.on("connection", function connection(ws, req) {
.substr(1)
.split(",", 1)
.pop();
// don't log ping messages
if (messageType !== '"ping"') {
console.log(new Date(), wss.clients.size, ws.channel, ws.playerId, data);
switch (messageType) {
case '"ping"':
// ping messages will only be sent host -> all or all -> host
channels[ws.channel].forEach(function each(client) {
if (
client !== ws &&
client.readyState === WebSocket.OPEN &&
(ws.playerId === "host" || client.playerId === "host")
) {
client.send(
data.replace(/latency/, (client.latency || 0) + (ws.latency || 0))
);
metrics.messages_outgoing.inc();
}
});
break;
case '"direct"':
// handle "direct" messages differently
if (messageType === '"direct"') {
console.log(
new Date(),
wss.clients.size,
ws.channel,
ws.playerId,
data
);
try {
const dataToPlayer = JSON.parse(data)[1];
channels[ws.channel].forEach(function each(client) {
@ -171,19 +190,23 @@ wss.on("connection", function connection(ws, req) {
} catch (e) {
console.log("error parsing direct message JSON", e);
}
} else {
break;
default:
// all other messages
console.log(
new Date(),
wss.clients.size,
ws.channel,
ws.playerId,
data
);
channels[ws.channel].forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
// inject latency between both clients if ping message
if (messageType === '"ping"' && client.latency && ws.latency) {
client.send(data.replace(/latency/, client.latency + ws.latency));
} else {
client.send(data);
}
metrics.messages_outgoing.inc();
}
});
break;
}
});
});

View File

@ -62,13 +62,33 @@ class LiveSession {
}
}
/**
* Send a message directly to a single playerId, if provided.
* Otherwise broadcast it.
* @param playerId player ID or "host", optional
* @param command
* @param params
* @private
*/
_sendDirect(playerId, command, params) {
if (playerId) {
this._send("direct", { [playerId]: [command, params] });
} else {
this._send(command, params);
}
}
/**
* Open event handler for socket.
* @private
*/
_onOpen() {
if (this._isSpectator) {
this._send("req", "gs");
this._sendDirect(
"host",
"getGamestate",
this._store.state.session.playerId
);
} else {
this.sendGamestate();
}
@ -80,12 +100,13 @@ class LiveSession {
* @private
*/
_ping() {
this._handlePing();
this._send("ping", [
this._isSpectator,
this._store.state.session.playerId,
this._isSpectator
? this._store.state.session.playerId
: Object.keys(this._players).length,
"latency"
]);
this._handlePing();
clearTimeout(this._pingTimer);
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
}
@ -103,10 +124,8 @@ class LiveSession {
console.log("unsupported socket message", data);
}
switch (command) {
case "req":
if (params === "gs") {
this.sendGamestate();
}
case "getGamestate":
this.sendGamestate(params);
break;
case "edition":
this._updateEdition(params);
@ -204,7 +223,9 @@ class LiveSession {
this._store.commit("session/setReconnecting", false);
clearTimeout(this._reconnectTimer);
if (this._socket) {
this._send("bye", this._store.state.session.playerId);
if (this._isSpectator) {
this._sendDirect("host", "bye", this._store.state.session.playerId);
}
this._socket.close(1000);
this._socket = null;
}
@ -213,9 +234,10 @@ class LiveSession {
/**
* Publish the current gamestate.
* Optional param to reduce traffic. (send only player data)
* @param playerId
* @param isLightweight
*/
sendGamestate(isLightweight = false) {
sendGamestate(playerId = "", isLightweight = false) {
if (this._isSpectator) return;
this._gamestate = this._store.state.players.players.map(player => ({
name: player.name,
@ -227,12 +249,15 @@ class LiveSession {
: {})
}));
if (isLightweight) {
this._send("gs", { gamestate: this._gamestate, isLightweight });
this._sendDirect(playerId, "gs", {
gamestate: this._gamestate,
isLightweight
});
} else {
const { session, grimoire } = this._store.state;
const { fabled } = this._store.state.players;
this.sendEdition();
this._send("gs", {
this.sendEdition(playerId);
this._sendDirect(playerId, "gs", {
gamestate: this._gamestate,
isNight: grimoire.isNight,
nomination: session.nomination,
@ -322,15 +347,16 @@ class LiveSession {
/**
* Publish an edition update. ST only
* @param playerId
*/
sendEdition() {
sendEdition(playerId = "") {
if (this._isSpectator) return;
const { edition } = this._store.state;
let roles;
if (!edition.isOfficial) {
roles = Array.from(this._store.state.roles.keys());
}
this._send("edition", {
this._sendDirect(playerId, "edition", {
edition: edition.isOfficial
? { id: edition.id }
: Object.assign({}, edition, { logo: "" }),
@ -462,13 +488,13 @@ class LiveSession {
/**
* Handle a ping message by another player / storyteller
* @param isSpectator
* @param playerId
* @param timestamp
* @param playerIdOrCount
* @param latency
* @private
*/
_handlePing([isSpectator, playerId, latency] = []) {
_handlePing([playerIdOrCount = 0, latency] = []) {
const now = new Date().getTime();
if (!this._isSpectator) {
// remove players that haven't sent a ping in twice the timespan
for (let player in this._players) {
if (now - this._players[player] > this._pingInterval * 2) {
@ -478,7 +504,7 @@ class LiveSession {
}
// remove claimed seats from players that are no longer connected
this._store.state.players.players.forEach(player => {
if (!this._isSpectator && player.id && !this._players[player.id]) {
if (player.id && !this._players[player.id]) {
this._store.commit("players/update", {
player,
property: "id",
@ -487,16 +513,12 @@ class LiveSession {
}
});
// store new player data
if (playerId) {
this._players[playerId] = now;
if (playerIdOrCount) {
this._players[playerIdOrCount] = now;
const ping = parseInt(latency, 10);
if (ping && ping > 0 && ping < 30 * 1000) {
if (this._isSpectator && !isSpectator) {
// ping to ST
this._store.commit("session/setPing", ping);
} else if (!this._isSpectator) {
// ping to Players
this._pings[playerId] = ping;
this._pings[playerIdOrCount] = ping;
const pings = Object.values(this._pings);
this._store.commit(
"session/setPing",
@ -504,19 +526,26 @@ class LiveSession {
);
}
}
} else if (latency) {
// ping to ST
this._store.commit("session/setPing", parseInt(latency, 10));
}
// update player count
if (!this._isSpectator || playerIdOrCount) {
this._store.commit(
"session/setPlayerCount",
Object.keys(this._players).length
this._isSpectator ? playerIdOrCount : Object.keys(this._players).length
);
}
}
/**
* Handle a player leaving the sessions
* Handle a player leaving the sessions. ST only
* @param playerId
* @private
*/
_handleBye(playerId) {
if (this._isSpectator) return;
delete this._players[playerId];
this._store.commit(
"session/setPlayerCount",
@ -783,7 +812,7 @@ export default store => {
case "players/clear":
case "players/remove":
case "players/add":
session.sendGamestate(true);
session.sendGamestate("", true);
break;
case "players/update":
session.sendPlayer(payload);

View File

@ -1,3 +1,5 @@
module.exports = {
// if the app is supposed to run on Github Pages in a subfolder, use the following config:
// publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
publicPath: process.env.NODE_ENV === "production" ? "/" : "/"
};