diff --git a/server/index.js b/server/index.js index 130b894..4153c45 100644 --- a/server/index.js +++ b/server/index.js @@ -17,6 +17,7 @@ const wss = new WebSocket.Server({ function noop() {} function heartbeat() { + this.latency = Math.round((new Date().getTime() - this.pingStart) / 2); this.isAlive = true; } @@ -25,11 +26,32 @@ wss.on("connection", function connection(ws, req) { .split("/") .pop() .toLocaleLowerCase(); + if (ws.channel.match(/-host$/i)) { + ws.isHost = true; + ws.channel = ws.channel.substr(0, ws.channel.length - 5); + // check for another host on this channel + if ( + Array.from(wss.clients).some( + client => + client !== ws && + client.readyState === WebSocket.OPEN && + client.channel === ws.channel && + client.isHost + ) + ) { + console.log(ws.channel, "duplicate host"); + ws.close(1000, `The channel "${ws.channel}" already has a host`); + return; + } + } ws.isAlive = true; + ws.pingStart = new Date().getTime(); + ws.ping(noop); ws.on("pong", heartbeat); ws.on("message", function incoming(data) { - if (!data.match(/^\["ping/i)) { - console.log(ws.channel, wss.clients.size, data); + const isPing = data.match(/^\["ping/i); + if (!isPing) { + console.log(new Date(), wss.clients.size, ws.channel, data); } wss.clients.forEach(function each(client) { if ( @@ -37,7 +59,12 @@ wss.on("connection", function connection(ws, req) { client.readyState === WebSocket.OPEN && client.channel === ws.channel ) { - client.send(data); + // inject latency between both clients if ping message + if (isPing && client.latency && ws.latency) { + client.send(data.replace(/latency/, client.latency + ws.latency)); + } else { + client.send(data); + } } }); }); @@ -47,6 +74,7 @@ const interval = setInterval(function ping() { wss.clients.forEach(function each(ws) { if (ws.isAlive === false) return ws.terminate(); ws.isAlive = false; + ws.pingStart = new Date().getTime(); ws.ping(noop); }); }, 30000); diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 148ddbd..7536bd7 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -3,7 +3,10 @@ { + this._socket.onclose = err => { this._socket = null; - this._store.commit("session/setSessionId", ""); clearInterval(this._pingTimer); this._pingTimer = null; + if (err.code !== 1000) { + // connection interrupted, reconnect after 3 seconds + this._store.commit("session/setReconnecting", true); + this._reconnectTimer = setTimeout( + () => this.connect(channel), + 3 * 1000 + ); + } else { + this._store.commit("session/setSessionId", ""); + if (err.reason) alert(err.reason); + } }; } @@ -69,7 +82,7 @@ class LiveSession { this._send("ping", [ this._isSpectator, this._store.state.session.playerId, - new Date().getTime() + "latency" ]); this._handlePing(); clearTimeout(this._pingTimer); @@ -168,9 +181,11 @@ class LiveSession { this._pings = {}; this._store.commit("session/setPlayerCount", 0); this._store.commit("session/setPing", 0); + this._store.commit("session/setReconnecting", false); + clearTimeout(this._reconnectTimer); if (this._socket) { this._send("bye", this._store.state.session.playerId); - this._socket.close(); + this._socket.close(1000); this._socket = null; } } @@ -378,7 +393,7 @@ class LiveSession { * @param timestamp * @private */ - _handlePing([isSpectator, playerId, timestamp] = []) { + _handlePing([isSpectator, playerId, latency] = []) { const now = new Date().getTime(); // remove players that haven't sent a ping in twice the timespan for (let player in this._players) { @@ -404,10 +419,12 @@ class LiveSession { alert("Another storyteller joined the session!"); } else if (this._isSpectator && !isSpectator) { // ping to ST - this._store.commit("session/setPing", now - timestamp); - } else { + if (parseInt(latency, 10)) { + this._store.commit("session/setPing", parseInt(latency, 10)); + } + } else if(parseInt(latency, 10)) { // ping to Players - this._pings[playerId] = now - timestamp; + this._pings[playerId] = parseInt(latency, 10); const pings = Object.values(this._pings); this._store.commit( "session/setPing",