From ea0e6d2d72b5532f130e521a82e43e1118806ac0 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 27 Jan 2021 22:05:32 +0100 Subject: [PATCH 01/33] ping changed to direct message --- src/store/socket.js | 75 +++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/store/socket.js b/src/store/socket.js index 925c326..bdab4c1 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -80,12 +80,14 @@ class LiveSession { * @private */ _ping() { - this._send("ping", [ - this._isSpectator, - this._store.state.session.playerId, - "latency" - ]); this._handlePing(); + if (this._isSpectator) { + this._send("direct", { + host: [this._store.state.session.playerId, "latency"] + }); + } else { + this._send("ping", [Object.keys(this._players).length, "latency"]); + } clearTimeout(this._pingTimer); this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval); } @@ -462,41 +464,37 @@ 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, latency] = []) { const now = new Date().getTime(); - // 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) { - delete this._players[player]; - delete this._pings[player]; + 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) { + delete this._players[player]; + delete this._pings[player]; + } } - } - // 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]) { - this._store.commit("players/update", { - player, - property: "id", - value: "" - }); - } - }); - // store new player data - if (playerId) { - this._players[playerId] = 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) { + // remove claimed seats from players that are no longer connected + this._store.state.players.players.forEach(player => { + if (player.id && !this._players[player.id]) { + this._store.commit("players/update", { + player, + property: "id", + value: "" + }); + } + }); + // store new player data + if (playerIdOrCount) { + this._players[playerIdOrCount] = now; + const ping = parseInt(latency, 10); + if (ping && ping > 0 && ping < 30 * 1000) { // ping to Players - this._pings[playerId] = ping; + this._pings[playerIdOrCount] = ping; const pings = Object.values(this._pings); this._store.commit( "session/setPing", @@ -504,10 +502,15 @@ class LiveSession { ); } } + } else if (latency) { + // ping to ST + this._store.commit("session/setPing", parseInt(latency, 10)); } this._store.commit( "session/setPlayerCount", - Object.keys(this._players).length + this._isSpectator + ? playerIdOrCount || 0 + : Object.keys(this._players).length ); } From fafbb3c23fa50453910d10d16625d8d4092c1fd5 Mon Sep 17 00:00:00 2001 From: Dave Date: Wed, 27 Jan 2021 23:22:56 +0000 Subject: [PATCH 02/33] adding a regex in to the join session method in Menu.vue, to check if a townsquare domain has been entered in to join dialog, if it finds one it will get the session id from the # query string instead of just taking the value directly from the dialog. --- src/components/Menu.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 79715a5..8ed2507 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -255,9 +255,16 @@ export default { }, joinSession() { if (this.session.sessionId) return this.leaveSession(); - const sessionId = prompt( + let sessionId = prompt( "Enter the channel number / name of the session you want to join" ); + if ( + sessionId.match( + /^https?:\/\/([^.]+\.github\.io|localhost|clocktower\.online|eddbra1nprivatetownsquare\.xyz)/i + ) + ) { + sessionId = sessionId.split("#")[1]; + } if (sessionId) { this.$store.commit("session/clearVoteHistory"); this.$store.commit("session/setSpectator", true); From 724a218b6f0a85cc127eb3a98117df9ed6385b64 Mon Sep 17 00:00:00 2001 From: Dave Date: Fri, 29 Jan 2021 14:55:19 +0000 Subject: [PATCH 03/33] simplified the regex on joining a session to only look for anything starting with http(s):// and parseing out the code. --- src/components/Menu.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 8ed2507..eb039d9 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -258,11 +258,7 @@ export default { let sessionId = prompt( "Enter the channel number / name of the session you want to join" ); - if ( - sessionId.match( - /^https?:\/\/([^.]+\.github\.io|localhost|clocktower\.online|eddbra1nprivatetownsquare\.xyz)/i - ) - ) { + if (sessionId.match(/^https?:\/\//i)) { sessionId = sessionId.split("#")[1]; } if (sessionId) { From 4d33e7dda99d98776ebe0211d9548b2ecfcbbb1b Mon Sep 17 00:00:00 2001 From: Steffen Date: Fri, 29 Jan 2021 21:54:27 +0100 Subject: [PATCH 04/33] optimized ping and gamestate messages --- server/index.js | 73 ++++++++++++++++++++++++++-------------- src/store/socket.js | 82 +++++++++++++++++++++++++++++---------------- vue.config.js | 2 ++ 3 files changed, 104 insertions(+), 53 deletions(-) diff --git a/server/index.js b/server/index.js index 0a82715..2a8ebc1 100644 --- a/server/index.js +++ b/server/index.js @@ -150,40 +150,63 @@ 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); - } - // handle "direct" messages differently - if (messageType === '"direct"') { - try { - const dataToPlayer = JSON.parse(data)[1]; + 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 && - dataToPlayer[client.playerId] + (ws.playerId === "host" || client.playerId === "host") ) { - client.send(JSON.stringify(dataToPlayer[client.playerId])); + client.send( + data.replace(/latency/, (client.latency || 0) + (ws.latency || 0)) + ); metrics.messages_outgoing.inc(); } }); - } catch (e) { - console.log("error parsing direct message JSON", e); - } - } else { - // all other messages - 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; + case '"direct"': + // handle "direct" messages differently + 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) { + if ( + client !== ws && + client.readyState === WebSocket.OPEN && + dataToPlayer[client.playerId] + ) { + client.send(JSON.stringify(dataToPlayer[client.playerId])); + metrics.messages_outgoing.inc(); + } + }); + } catch (e) { + console.log("error parsing direct message JSON", e); } - }); + 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) { + client.send(data); + metrics.messages_outgoing.inc(); + } + }); + break; } }); }); diff --git a/src/store/socket.js b/src/store/socket.js index bdab4c1..be5175e 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -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(); } @@ -81,13 +101,12 @@ class LiveSession { */ _ping() { this._handlePing(); - if (this._isSpectator) { - this._send("direct", { - host: [this._store.state.session.playerId, "latency"] - }); - } else { - this._send("ping", [Object.keys(this._players).length, "latency"]); - } + this._send("ping", [ + this._isSpectator + ? this._store.state.session.playerId + : Object.keys(this._players).length, + "latency" + ]); clearTimeout(this._pingTimer); this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval); } @@ -105,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); @@ -206,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; } @@ -215,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, @@ -229,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, @@ -324,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: "" }), @@ -468,7 +492,7 @@ class LiveSession { * @param latency * @private */ - _handlePing([playerIdOrCount, 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 @@ -506,20 +530,22 @@ class LiveSession { // ping to ST this._store.commit("session/setPing", parseInt(latency, 10)); } - this._store.commit( - "session/setPlayerCount", - this._isSpectator - ? playerIdOrCount || 0 - : Object.keys(this._players).length - ); + // update player count + if (!this._isSpectator || playerIdOrCount) { + this._store.commit( + "session/setPlayerCount", + 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", @@ -786,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); diff --git a/vue.config.js b/vue.config.js index d61bd25..c18495d 100644 --- a/vue.config.js +++ b/vue.config.js @@ -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" ? "/" : "/" }; From 7f0d60c272b3454b68d5866e8e249c95ffea5fff Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 30 Jan 2021 12:50:09 +0000 Subject: [PATCH 05/33] changed code to grab session id from pasted domain in join dialog to be a pop, to avoid errors if the hash string is not present --- src/components/Menu.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Menu.vue b/src/components/Menu.vue index eb039d9..5160305 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -259,7 +259,7 @@ export default { "Enter the channel number / name of the session you want to join" ); if (sessionId.match(/^https?:\/\//i)) { - sessionId = sessionId.split("#")[1]; + sessionId = sessionId.split("#").pop(); } if (sessionId) { this.$store.commit("session/clearVoteHistory"); From ec64584da5586cc2cfed774286588c1aeabf2e86 Mon Sep 17 00:00:00 2001 From: Steffen Date: Mon, 1 Feb 2021 22:33:59 +0100 Subject: [PATCH 06/33] added custom image opt in --- src/components/Menu.vue | 80 +++++++++++++++++++++--------------- src/store/index.js | 55 +++++++++++-------------- src/store/modules/session.js | 10 ++--- src/store/persistence.js | 36 ++++++++++++---- 4 files changed, 103 insertions(+), 78 deletions(-) diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 79715a5..aa8ea5a 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -73,7 +73,7 @@ Background image -
  • +
  • Mute Sounds Live Session
  • -
  • - Host (Storyteller)[H] -
  • -
  • - Join (Player)[J] -
  • -
  • - Delay to {{ session.isSpectator ? "host" : "players" }} - {{ session.ping }}ms -
  • -
  • - Copy player link - -
  • -
  • - Send Characters - -
  • -
  • - Nomination history[V] -
  • -
  • - Leave Session - {{ session.sessionId }} -
  • + +