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 4aada68..7087832 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",