moved latency calculation server-side

made host per channel exclusive
added auto-reconnection
This commit is contained in:
Steffen 2020-09-21 11:19:46 +02:00
parent 9e03d16160
commit 0b5d9c5865
4 changed files with 74 additions and 14 deletions

View File

@ -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);

View File

@ -3,7 +3,10 @@
<Screenshot ref="screenshot"></Screenshot>
<span
class="session"
:class="{ spectator: session.isSpectator }"
:class="{
spectator: session.isSpectator,
reconnecting: session.isReconnecting
}"
v-if="session.sessionId"
@click="leaveSession"
:title="
@ -342,6 +345,16 @@ export default {
&.spectator {
color: $townsfolk;
}
&.reconnecting {
animation: blink 1s infinite;
}
}
}
@keyframes blink {
50% {
opacity: 0.5;
color: gray;
}
}

View File

@ -20,6 +20,7 @@ const handleVote = (state, [index, vote]) => {
const state = () => ({
sessionId: "",
isSpectator: false,
isReconnecting: false,
playerCount: 0,
ping: 0,
playerId: "",
@ -38,6 +39,7 @@ const mutations = {
setSessionId: set("sessionId"),
setPlayerId: set("playerId"),
setSpectator: set("isSpectator"),
setReconnecting: set("isReconnecting"),
setPlayerCount: set("playerCount"),
setPing: set("ping"),
setVotingSpeed: set("votingSpeed"),

View File

@ -2,7 +2,7 @@ import rolesJSON from "../roles.json";
class LiveSession {
constructor(store) {
// this._wss = "ws://localhost:8081/";
//this._wss = "ws://localhost:8081/";
this._wss = "wss://baumgart.biz:8080/";
this._socket = null;
this._isSpectator = true;
@ -10,6 +10,7 @@ class LiveSession {
this._store = store;
this._pingInterval = 30 * 1000; // 30 seconds between pings
this._pingTimer = null;
this._reconnectTimer = null;
this._players = {}; // map of players connected to a session
this._pings = {}; // map of player IDs to ping
// reconnect to previous session
@ -25,14 +26,26 @@ class LiveSession {
*/
_open(channel) {
this.disconnect();
this._socket = new WebSocket(this._wss + channel);
this._socket = new WebSocket(
this._wss + channel + (this._isSpectator ? "" : "-host")
);
this._socket.addEventListener("message", this._handleMessage.bind(this));
this._socket.onopen = this._onOpen.bind(this);
this._socket.onclose = () => {
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",