const url = require('url');
// Node version in Ubuntu 16.04 does not know about URL class
+// NOTE: url is already transformed, without ?xxx=yyy... parts
function getJsonFromUrl(url)
{
const query = url.substr(2); //starts with "/?"
}
module.exports = function(wss) {
- let clients = {}; //associative array sid --> socket
+ // Associative array sid --> tmpId --> {socket, page},
+ // "page" is either "/" for hall or "/game/some_gid" for Game,
+ // tmpId is required if a same user (browser) has different tabs
+ let clients = {};
wss.on("connection", (socket, req) => {
const query = getJsonFromUrl(req.url);
const sid = query["sid"];
- if (!!clients[sid])
- return socket.send(JSON.stringify({code:"duplicate"}));
- clients[sid] = {sock: socket, page: query["page"]};
+ const tmpId = query["tmpId"];
const notifyRoom = (page,code,obj={},excluded=[]) => {
Object.keys(clients).forEach(k => {
if (k in excluded)
if (k != sid && clients[k].page == page)
{
clients[k].sock.send(JSON.stringify(Object.assign(
- {code:code, from:sid}, obj)));
+ {code:code, from:[sid,tmpId]}, obj)));
}
});
};
- notifyRoom(query["page"], "connect"); //Hall or Game
- if (query["page"].indexOf("/game/") >= 0)
- notifyRoom("/", "connect"); //notify main hall
- socket.on("message", objtxt => {
+ const messageListener = (objtxt) => {
let obj = JSON.parse(objtxt);
if (!!obj.target && !clients[obj.target])
return; //receiver not connected, nothing we can do
switch (obj.code)
{
+ // Wait for "connect" message to notify connection to the room,
+ // because if game loading is slow the message listener might
+ // not be ready too early.
+ case "connect":
+ {
+ const curPage = clients[sid][tmpId].page;
+ notifyRoom(curPage, "connect"); //Hall or Game
+ if (curPage.indexOf("/game/") >= 0)
+ notifyRoom("/", "gconnect"); //notify main hall
+ break;
+ }
+ case "disconnect":
+ {
+ const oldPage = obj.page;
+ notifyRoom(oldPage, "disconnect"); //Hall or Game
+ if (oldPage.indexOf("/game/") >= 0)
+ notifyRoom("/", "gdisconnect"); //notify main hall
+ break;
+ }
case "pollclients":
- const curPage = clients[sid].page;
- socket.send(JSON.stringify({code:"pollclients",
- sockIds: Object.keys(clients).filter(k => k != sid &&
- (clients[k].page == curPage ||
- // Consider that people playing are in Hall too:
- (curPage == "/" && clients[k].page.indexOf("/game/") >= 0))
- )}));
- break;
- case "pagechange":
- notifyRoom(clients[sid].page, "disconnect");
- if (clients[sid].page.indexOf("/game/") >= 0)
- notifyRoom("/", "disconnect");
- clients[sid].page = obj.page;
- notifyRoom(obj.page, "connect");
- if (obj.page.indexOf("/game/") >= 0)
- notifyRoom("/", "connect");
+ {
+ const curPage = clients[sid][tmpId].page;
+ let sockIds = {}; //result, object sid ==> [tmpIds]
+ Object.keys(clients).forEach(k => {
+ Object.keys(clients[k]).forEach(x => {
+ if ((k != sid || x != tmpId)
+ && clients[k][x].page == curPage)
+ {
+ if (!sockIds[k])
+ sockIds[k] = [x];
+ else
+ sockIds[k].push(x);
+ }
+ });
+ });
+ socket.send(JSON.stringify({code:"pollclients", sockIds:sockIds}));
+ break;
+ }
+ case "pollgamers":
+ {
+ let sockIds = {};
+ Object.keys(clients).forEach(k => {
+ Object.keys(clients[k]).forEach(x => {
+ if ((k != sid || x != tmpId)
+ && clients[k][x].page.indexOf("/game/") >= 0)
+ {
+ if (!sockIds[k])
+ sockIds[k] = [x];
+ else
+ sockIds[k].push(x);
+ }
+ });
+ });
+ socket.send(JSON.stringify({code:"pollgamers", sockIds:sockIds}));
break;
+ }
case "askidentity":
- clients[obj.target].sock.send(JSON.stringify(
- {code:"askidentity",from:sid}));
+ {
+ // Identity only depends on sid, so select a tmpId at random
+ const tmpIds = Object.keys(clients[obj.target]);
+ const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
+ clients[obj.target][tmpIds[tmpId_idx]].sock.send(JSON.stringify(
+ {code:"askidentity",from:[sid,tmpId]}));
+ break;
+ }
+ case "asklastate":
+ clients[obj.target[0]][obj.target[1]].sock.send(JSON.stringify(
+ {code:"asklastate",from:[sid,tmpId]}));
break;
case "askchallenge":
- clients[obj.target].sock.send(JSON.stringify(
- {code:"askchallenge",from:sid}));
+ clients[obj.target[0]][obj.target[1]].sock.send(JSON.stringify(
+ {code:"askchallenge",from:[sid,tmpId]}));
break;
case "askgames":
{
// Check all clients playing, and send them a "askgame" message
- let gameSids = {}; //game ID --> [sid1, sid2]
+ // game ID --> [ sid1 --> array of tmpIds, sid2 --> array of tmpIds]
+ let gameSids = {};
const regexpGid = /\/[a-zA-Z0-9]+$/;
Object.keys(clients).forEach(k => {
- if (k != sid && clients[k].page.indexOf("/game/") >= 0)
+ Object.keys(clients[k]).forEach(x => {
+ if ((k != sid || x != tmpId)
+ && clients[k][x].page.indexOf("/game/") >= 0)
{
- const gid = clients[k].page.match(regexpGid)[0];
+ const gid = clients[k][x].page.match(regexpGid)[0];
if (!gameSids[gid])
- gameSids[gid] = [k];
+ gameSids[gid] = [{k: [x]}];
+ else if (k == Object.keys(gameSids[gid][0])[0])
+ gameSids[gid][0][k].push(x);
+ else if (gameSids[gid].length == 1)
+ gameSids[gid].push({k: [x]});
else
- gameSids[gid].push(k);
+ Object.values(gameSids[gid][1]).push(x);
}
});
// Request only one client out of 2 (TODO: this is a bit heavy)
// Alt: ask game to all, and filter later?
Object.keys(gameSids).forEach(gid => {
const L = gameSids[gid].length;
- const idx = L > 1
+ const sidIdx = L > 1
? Math.floor(Math.random() * Math.floor(L))
: 0;
- const rid = gameSids[gid][idx];
- clients[rid].sock.send(JSON.stringify(
- {code:"askgame", from: sid}));
+ const tmpIdx = Object.values(gameSids[gid][sidIdx]
+ const rid = gameSids[gid][sidIdx][tmpIdx];
+ clients[sidIdx][tmpIdx].sock.send(JSON.stringify(
+ {code:"askgame", from: [sid,tmpId]}));
});
break;
}
+ case "askgame":
+ clients[obj.target].sock.send(JSON.stringify(
+ {code:"askgame", from:sid}));
+ break;
case "askfullgame":
clients[obj.target].sock.send(JSON.stringify(
{code:"askfullgame", from:sid}));
}
break;
case "newchat":
- // WARNING: do not use query["page"], because the page may change
- notifyRoom(clients[sid].page, "newchat",
- {msg: obj.msg, name: obj.name});
+ notifyRoom(clients[sid].page, "newchat", {chat:obj.chat});
break;
// TODO: WebRTC instead in this case (most demanding?)
+ // --> Or else: at least do a "notifyRoom" (also for draw, resign...)
case "newmove":
clients[obj.target].sock.send(JSON.stringify(
{code:"newmove", move:obj.move}));
{code:"draw", message:obj.message}));
break;
}
- });
- socket.on("close", () => {
- const page = clients[sid].page;
+ };
+ const closeListener = () => {
delete clients[sid];
- notifyRoom(page, "disconnect");
- if (page.indexOf("/game/") >= 0)
- notifyRoom("/", "disconnect"); //notify main hall
- });
+ };
+ if (!!clients[sid])
+ {
+ // Turn off old sock through current client:
+ clients[sid].sock.send(JSON.stringify({code:"duplicate"}));
+ }
+ // Potentially replace current connection:
+ clients[sid] = {sock: socket, page: query["page"]};
+ socket.on("message", messageListener);
+ socket.on("close", closeListener);
});
}