mirror of https://github.com/bra1n/townsquare.git
moved latency calculation server-side
made host per channel exclusive added auto-reconnection
This commit is contained in:
parent
9e03d16160
commit
0b5d9c5865
|
@ -17,6 +17,7 @@ const wss = new WebSocket.Server({
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
function heartbeat() {
|
function heartbeat() {
|
||||||
|
this.latency = Math.round((new Date().getTime() - this.pingStart) / 2);
|
||||||
this.isAlive = true;
|
this.isAlive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +26,32 @@ wss.on("connection", function connection(ws, req) {
|
||||||
.split("/")
|
.split("/")
|
||||||
.pop()
|
.pop()
|
||||||
.toLocaleLowerCase();
|
.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.isAlive = true;
|
||||||
|
ws.pingStart = new Date().getTime();
|
||||||
|
ws.ping(noop);
|
||||||
ws.on("pong", heartbeat);
|
ws.on("pong", heartbeat);
|
||||||
ws.on("message", function incoming(data) {
|
ws.on("message", function incoming(data) {
|
||||||
if (!data.match(/^\["ping/i)) {
|
const isPing = data.match(/^\["ping/i);
|
||||||
console.log(ws.channel, wss.clients.size, data);
|
if (!isPing) {
|
||||||
|
console.log(new Date(), wss.clients.size, ws.channel, data);
|
||||||
}
|
}
|
||||||
wss.clients.forEach(function each(client) {
|
wss.clients.forEach(function each(client) {
|
||||||
if (
|
if (
|
||||||
|
@ -37,7 +59,12 @@ wss.on("connection", function connection(ws, req) {
|
||||||
client.readyState === WebSocket.OPEN &&
|
client.readyState === WebSocket.OPEN &&
|
||||||
client.channel === ws.channel
|
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) {
|
wss.clients.forEach(function each(ws) {
|
||||||
if (ws.isAlive === false) return ws.terminate();
|
if (ws.isAlive === false) return ws.terminate();
|
||||||
ws.isAlive = false;
|
ws.isAlive = false;
|
||||||
|
ws.pingStart = new Date().getTime();
|
||||||
ws.ping(noop);
|
ws.ping(noop);
|
||||||
});
|
});
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
<Screenshot ref="screenshot"></Screenshot>
|
<Screenshot ref="screenshot"></Screenshot>
|
||||||
<span
|
<span
|
||||||
class="session"
|
class="session"
|
||||||
:class="{ spectator: session.isSpectator }"
|
:class="{
|
||||||
|
spectator: session.isSpectator,
|
||||||
|
reconnecting: session.isReconnecting
|
||||||
|
}"
|
||||||
v-if="session.sessionId"
|
v-if="session.sessionId"
|
||||||
@click="leaveSession"
|
@click="leaveSession"
|
||||||
:title="
|
:title="
|
||||||
|
@ -342,6 +345,16 @@ export default {
|
||||||
&.spectator {
|
&.spectator {
|
||||||
color: $townsfolk;
|
color: $townsfolk;
|
||||||
}
|
}
|
||||||
|
&.reconnecting {
|
||||||
|
animation: blink 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
color: gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ const handleVote = (state, [index, vote]) => {
|
||||||
const state = () => ({
|
const state = () => ({
|
||||||
sessionId: "",
|
sessionId: "",
|
||||||
isSpectator: false,
|
isSpectator: false,
|
||||||
|
isReconnecting: false,
|
||||||
playerCount: 0,
|
playerCount: 0,
|
||||||
ping: 0,
|
ping: 0,
|
||||||
playerId: "",
|
playerId: "",
|
||||||
|
@ -38,6 +39,7 @@ const mutations = {
|
||||||
setSessionId: set("sessionId"),
|
setSessionId: set("sessionId"),
|
||||||
setPlayerId: set("playerId"),
|
setPlayerId: set("playerId"),
|
||||||
setSpectator: set("isSpectator"),
|
setSpectator: set("isSpectator"),
|
||||||
|
setReconnecting: set("isReconnecting"),
|
||||||
setPlayerCount: set("playerCount"),
|
setPlayerCount: set("playerCount"),
|
||||||
setPing: set("ping"),
|
setPing: set("ping"),
|
||||||
setVotingSpeed: set("votingSpeed"),
|
setVotingSpeed: set("votingSpeed"),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import rolesJSON from "../roles.json";
|
||||||
|
|
||||||
class LiveSession {
|
class LiveSession {
|
||||||
constructor(store) {
|
constructor(store) {
|
||||||
// this._wss = "ws://localhost:8081/";
|
//this._wss = "ws://localhost:8081/";
|
||||||
this._wss = "wss://baumgart.biz:8080/";
|
this._wss = "wss://baumgart.biz:8080/";
|
||||||
this._socket = null;
|
this._socket = null;
|
||||||
this._isSpectator = true;
|
this._isSpectator = true;
|
||||||
|
@ -10,6 +10,7 @@ class LiveSession {
|
||||||
this._store = store;
|
this._store = store;
|
||||||
this._pingInterval = 30 * 1000; // 30 seconds between pings
|
this._pingInterval = 30 * 1000; // 30 seconds between pings
|
||||||
this._pingTimer = null;
|
this._pingTimer = null;
|
||||||
|
this._reconnectTimer = null;
|
||||||
this._players = {}; // map of players connected to a session
|
this._players = {}; // map of players connected to a session
|
||||||
this._pings = {}; // map of player IDs to ping
|
this._pings = {}; // map of player IDs to ping
|
||||||
// reconnect to previous session
|
// reconnect to previous session
|
||||||
|
@ -25,14 +26,26 @@ class LiveSession {
|
||||||
*/
|
*/
|
||||||
_open(channel) {
|
_open(channel) {
|
||||||
this.disconnect();
|
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.addEventListener("message", this._handleMessage.bind(this));
|
||||||
this._socket.onopen = this._onOpen.bind(this);
|
this._socket.onopen = this._onOpen.bind(this);
|
||||||
this._socket.onclose = () => {
|
this._socket.onclose = err => {
|
||||||
this._socket = null;
|
this._socket = null;
|
||||||
this._store.commit("session/setSessionId", "");
|
|
||||||
clearInterval(this._pingTimer);
|
clearInterval(this._pingTimer);
|
||||||
this._pingTimer = null;
|
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._send("ping", [
|
||||||
this._isSpectator,
|
this._isSpectator,
|
||||||
this._store.state.session.playerId,
|
this._store.state.session.playerId,
|
||||||
new Date().getTime()
|
"latency"
|
||||||
]);
|
]);
|
||||||
this._handlePing();
|
this._handlePing();
|
||||||
clearTimeout(this._pingTimer);
|
clearTimeout(this._pingTimer);
|
||||||
|
@ -168,9 +181,11 @@ class LiveSession {
|
||||||
this._pings = {};
|
this._pings = {};
|
||||||
this._store.commit("session/setPlayerCount", 0);
|
this._store.commit("session/setPlayerCount", 0);
|
||||||
this._store.commit("session/setPing", 0);
|
this._store.commit("session/setPing", 0);
|
||||||
|
this._store.commit("session/setReconnecting", false);
|
||||||
|
clearTimeout(this._reconnectTimer);
|
||||||
if (this._socket) {
|
if (this._socket) {
|
||||||
this._send("bye", this._store.state.session.playerId);
|
this._send("bye", this._store.state.session.playerId);
|
||||||
this._socket.close();
|
this._socket.close(1000);
|
||||||
this._socket = null;
|
this._socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,7 +393,7 @@ class LiveSession {
|
||||||
* @param timestamp
|
* @param timestamp
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_handlePing([isSpectator, playerId, timestamp] = []) {
|
_handlePing([isSpectator, playerId, latency] = []) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
// remove players that haven't sent a ping in twice the timespan
|
// remove players that haven't sent a ping in twice the timespan
|
||||||
for (let player in this._players) {
|
for (let player in this._players) {
|
||||||
|
@ -404,10 +419,12 @@ class LiveSession {
|
||||||
alert("Another storyteller joined the session!");
|
alert("Another storyteller joined the session!");
|
||||||
} else if (this._isSpectator && !isSpectator) {
|
} else if (this._isSpectator && !isSpectator) {
|
||||||
// ping to ST
|
// ping to ST
|
||||||
this._store.commit("session/setPing", now - timestamp);
|
if (parseInt(latency, 10)) {
|
||||||
} else {
|
this._store.commit("session/setPing", parseInt(latency, 10));
|
||||||
|
}
|
||||||
|
} else if(parseInt(latency, 10)) {
|
||||||
// ping to Players
|
// ping to Players
|
||||||
this._pings[playerId] = now - timestamp;
|
this._pings[playerId] = parseInt(latency, 10);
|
||||||
const pings = Object.values(this._pings);
|
const pings = Object.values(this._pings);
|
||||||
this._store.commit(
|
this._store.commit(
|
||||||
"session/setPing",
|
"session/setPing",
|
||||||
|
|
Loading…
Reference in New Issue