Experimental multi-tabs support (TODO: prevent multi-connect)
[vchess.git] / server / sockets.js
CommitLineData
a29d9d6b 1const url = require('url');
1d184b4c 2
2807f530 3// Node version in Ubuntu 16.04 does not know about URL class
a0c41e7e 4// NOTE: url is already transformed, without ?xxx=yyy... parts
98db2082
BA
5function getJsonFromUrl(url)
6{
80ee5d5a
BA
7 const query = url.substr(2); //starts with "/?"
8 let result = {};
9 query.split("&").forEach((part) => {
5bd05dba
BA
10 const item = part.split("=");
11 result[item[0]] = decodeURIComponent(item[1]);
12 });
13 return result;
2807f530
BA
14}
15
1d184b4c 16module.exports = function(wss) {
71468011 17 // Associative array page --> sid --> tmpId --> socket
8418f0d7
BA
18 // "page" is either "/" for hall or "/game/some_gid" for Game,
19 // tmpId is required if a same user (browser) has different tabs
20 let clients = {};
5bd05dba
BA
21 wss.on("connection", (socket, req) => {
22 const query = getJsonFromUrl(req.url);
23 const sid = query["sid"];
8418f0d7 24 const tmpId = query["tmpId"];
71468011
BA
25 const page = query["page"];
26 const notifyRoom = (page,code,obj={}) => {
27 Object.keys(clients[page]).forEach(k => {
28 Object.keys(clients[page][k]).forEach(x => {
29 if (k == sid && x == tmpId)
30 return;
31 clients[page][k][x].send(JSON.stringify(Object.assign(
32 {code:code, from:sid}, obj)));
33 });
92a523d1
BA
34 });
35 };
71468011
BA
36 const deleteConnexion = () => {
37 if (!clients[page] || !clients[page][sid] || !clients[page][sid][tmpId])
38 return; //job already done
39 delete clients[page][sid][tmpId];
40 if (Object.keys(clients[page][sid]).length == 0)
41 {
42 delete clients[page][sid];
43 if (Object.keys(clients[page]) == 0)
44 delete clients[page];
45 }
46 };
a3ac374b 47 const messageListener = (objtxt) => {
5bd05dba 48 let obj = JSON.parse(objtxt);
71468011
BA
49 if (!!obj.target)
50 {
51 // Check if receiver is connected, because there may be some lag
52 // between a client disconnects and another notice.
53 if (Array.isArray(obj.target))
54 {
55 if (!clients[page][obj.target[0]] ||
56 !clients[page][obj.target[0]][obj.target[1]])
57 {
58 return;
59 }
60 }
61 else if (!clients[page][obj.target])
62 return;
63 }
5bd05dba
BA
64 switch (obj.code)
65 {
a3ac374b
BA
66 // Wait for "connect" message to notify connection to the room,
67 // because if game loading is slow the message listener might
68 // not be ready too early.
41c80bb6 69 case "connect":
120fe373 70 {
71468011
BA
71 notifyRoom(page, "connect");
72 if (page.indexOf("/game/") >= 0)
73 notifyRoom("/", "gconnect", {page:page});
41c80bb6 74 break;
120fe373 75 }
8418f0d7 76 case "disconnect":
71468011
BA
77 // When page changes:
78 deleteConnexion();
79 if (!clients[page][sid])
80 {
81 // I effectively disconnected from this page:
82 notifyRoom(page, "disconnect");
83 if (page.indexOf("/game/") >= 0)
84 notifyRoom("/", "gdisconnect", {page:page});
85 }
8418f0d7 86 break;
71468011 87 case "pollclients": //from Hall or Game
ac8f441c 88 {
71468011
BA
89 let sockIds = [];
90 Object.keys(clients[page]).forEach(k => {
91 // Poll myself if I'm on at least another tab (same page)
92 if (k != sid || Object.keys(clients["/"][k]).length >= 2)
93 sockIds.push(k);
8418f0d7
BA
94 });
95 socket.send(JSON.stringify({code:"pollclients", sockIds:sockIds}));
92a523d1 96 break;
ac8f441c 97 }
71468011 98 case "pollclientsandgamers": //from Hall
8418f0d7 99 {
71468011
BA
100 let sockIds = [];
101 Object.keys(clients["/"]).forEach(k => {
102 // Poll myself if I'm on at least another tab (same page)
103 if (k != sid || Object.keys(clients["/"][k]).length >= 2)
104 sockIds.push({sid:k});
105 });
106 // NOTE: a "gamer" could also just be an observer
107 Object.keys(clients).forEach(p => {
108 if (p != "/")
109 {
110 Object.keys(clients[p]).forEach(k => {
111 if (k != sid)
112 sockIds.push({sid:k, page:p}); //page needed for gamers
113 });
114 }
8418f0d7 115 });
71468011 116 socket.send(JSON.stringify({code:"pollclientsandgamers", sockIds:sockIds}));
5a3da968 117 break;
8418f0d7 118 }
71468011
BA
119
120 // Asking something: from is fully identified,
121 // but the requested resource can be from any tmpId (except current!)
5a3da968 122 case "askidentity":
71468011
BA
123 case "asklastate":
124 case "askchallenge":
125 case "askgame":
126 case "askfullgame":
8418f0d7 127 {
71468011
BA
128 const tmpIds = Object.keys(clients[page][obj.target]);
129 if (obj.target == sid) //targetting myself
130 {
131 const idx_myTmpid = tmpIds.findIndex(x => x == tmpId);
132 if (idx_myTmpid >= 0)
133 tmpIds.splice(idx_myTmpid, 1);
134 }
8418f0d7 135 const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
71468011
BA
136 clients[page][obj.target][tmpIds[tmpId_idx]].send(
137 JSON.stringify({code:obj.code, from:[sid,tmpId]}));
81d9ce72 138 break;
8418f0d7 139 }
71468011
BA
140
141 // Some Hall events: target all tmpId's (except mine),
142 case "refusechallenge":
143 case "startgame":
144 Object.keys(clients[page][obj.target]).forEach(x => {
145 if (obj.target != sid || x != tmpId)
c6788ecf 146 {
71468011
BA
147 clients[page][obj.target][x].send(JSON.stringify(
148 {code:obj.code, data:obj.data}));
c6788ecf
BA
149 }
150 });
4d64881e 151 break;
71468011
BA
152
153 // Notify all room: mostly game events
5bd05dba 154 case "newchat":
71468011
BA
155 case "newchallenge":
156 case "newgame":
157 case "deletechallenge":
5bd05dba 158 case "newmove":
5bd05dba 159 case "resign":
b988c726 160 case "abort":
2cc10cdb 161 case "drawoffer":
2cc10cdb 162 case "draw":
71468011
BA
163 notifyRoom(page, obj.code, {data:obj.data});
164 break;
165
166 // Passing, relaying something: from isn't needed,
167 // but target is fully identified (sid + tmpId)
168 case "challenge":
169 case "fullgame":
170 case "game":
171 case "identity":
172 case "lastate":
173 clients[page][obj.target[0]][obj.target[1]].send(JSON.stringify(
174 {code:obj.code, data:obj.data}));
2cc10cdb 175 break;
5bd05dba 176 }
a3ac374b
BA
177 };
178 const closeListener = () => {
71468011
BA
179 // For tab or browser closing:
180 deleteConnexion();
a3ac374b 181 };
71468011
BA
182 // Update clients object: add new connexion
183 if (!clients[page])
184 clients[page] = {[sid]: {[tmpId]: socket}};
185 else if (!clients[page][sid])
186 clients[page][sid] = {[tmpId]: socket};
187 else
188 clients[page][sid][tmpId] = socket;
a3ac374b
BA
189 socket.on("message", messageListener);
190 socket.on("close", closeListener);
5bd05dba 191 });
1d184b4c 192}