Fixes
[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={}) => {
51d87b52
BA
27 if (!clients[page])
28 return;
71468011
BA
29 Object.keys(clients[page]).forEach(k => {
30 Object.keys(clients[page][k]).forEach(x => {
31 if (k == sid && x == tmpId)
32 return;
33 clients[page][k][x].send(JSON.stringify(Object.assign(
34 {code:code, from:sid}, obj)));
35 });
92a523d1
BA
36 });
37 };
71468011
BA
38 const deleteConnexion = () => {
39 if (!clients[page] || !clients[page][sid] || !clients[page][sid][tmpId])
40 return; //job already done
41 delete clients[page][sid][tmpId];
42 if (Object.keys(clients[page][sid]).length == 0)
43 {
44 delete clients[page][sid];
45 if (Object.keys(clients[page]) == 0)
46 delete clients[page];
47 }
48 };
a3ac374b 49 const messageListener = (objtxt) => {
5bd05dba 50 let obj = JSON.parse(objtxt);
71468011
BA
51 if (!!obj.target)
52 {
53 // Check if receiver is connected, because there may be some lag
54 // between a client disconnects and another notice.
55 if (Array.isArray(obj.target))
56 {
57 if (!clients[page][obj.target[0]] ||
58 !clients[page][obj.target[0]][obj.target[1]])
59 {
60 return;
61 }
62 }
63 else if (!clients[page][obj.target])
64 return;
65 }
5bd05dba
BA
66 switch (obj.code)
67 {
a3ac374b
BA
68 // Wait for "connect" message to notify connection to the room,
69 // because if game loading is slow the message listener might
70 // not be ready too early.
41c80bb6 71 case "connect":
120fe373 72 {
71468011
BA
73 notifyRoom(page, "connect");
74 if (page.indexOf("/game/") >= 0)
75 notifyRoom("/", "gconnect", {page:page});
41c80bb6 76 break;
120fe373 77 }
8418f0d7 78 case "disconnect":
71468011
BA
79 // When page changes:
80 deleteConnexion();
51d87b52 81 if (!clients[page] || !clients[page][sid])
71468011
BA
82 {
83 // I effectively disconnected from this page:
84 notifyRoom(page, "disconnect");
85 if (page.indexOf("/game/") >= 0)
86 notifyRoom("/", "gdisconnect", {page:page});
87 }
8418f0d7 88 break;
51d87b52
BA
89 case "killme":
90 {
91 // Self multi-connect: manual removal + disconnect
92 const doKill = (pg) => {
93 Object.keys(clients[pg][obj.sid]).forEach(x => {
94 clients[pg][obj.sid][x].send(JSON.stringify({code: "killed"}));
95 });
96 delete clients[pg][obj.sid];
97 };
98 const disconnectFromOtherConnexion = (pg,code,o={}) => {
99 Object.keys(clients[pg]).forEach(k => {
100 if (k != obj.sid)
101 {
102 Object.keys(clients[pg][k]).forEach(x => {
103 clients[pg][k][x].send(JSON.stringify(Object.assign(
104 {code:code, from:obj.sid}, o)));
105 });
106 }
107 });
108 };
109 Object.keys(clients).forEach(pg => {
110 if (!!clients[pg][obj.sid])
111 {
112 doKill(pg);
113 disconnectFromOtherConnexion(pg, "disconnect");
f854c94f 114 if (pg.indexOf("/game/") >= 0 && !!clients["/"])
51d87b52
BA
115 disconnectFromOtherConnexion("/", "gdisconnect", {page:pg});
116 }
117 });
118 break;
119 }
71468011 120 case "pollclients": //from Hall or Game
ac8f441c 121 {
71468011
BA
122 let sockIds = [];
123 Object.keys(clients[page]).forEach(k => {
51d87b52
BA
124 // Avoid polling myself: no new information to get
125 if (k != sid)
71468011 126 sockIds.push(k);
8418f0d7
BA
127 });
128 socket.send(JSON.stringify({code:"pollclients", sockIds:sockIds}));
92a523d1 129 break;
ac8f441c 130 }
71468011 131 case "pollclientsandgamers": //from Hall
8418f0d7 132 {
71468011
BA
133 let sockIds = [];
134 Object.keys(clients["/"]).forEach(k => {
51d87b52
BA
135 // Avoid polling myself: no new information to get
136 if (k != sid)
71468011
BA
137 sockIds.push({sid:k});
138 });
139 // NOTE: a "gamer" could also just be an observer
140 Object.keys(clients).forEach(p => {
141 if (p != "/")
142 {
143 Object.keys(clients[p]).forEach(k => {
144 if (k != sid)
145 sockIds.push({sid:k, page:p}); //page needed for gamers
146 });
147 }
8418f0d7 148 });
71468011 149 socket.send(JSON.stringify({code:"pollclientsandgamers", sockIds:sockIds}));
5a3da968 150 break;
8418f0d7 151 }
71468011
BA
152
153 // Asking something: from is fully identified,
154 // but the requested resource can be from any tmpId (except current!)
5a3da968 155 case "askidentity":
71468011
BA
156 case "asklastate":
157 case "askchallenge":
158 case "askgame":
159 case "askfullgame":
8418f0d7 160 {
71468011
BA
161 const tmpIds = Object.keys(clients[page][obj.target]);
162 if (obj.target == sid) //targetting myself
163 {
164 const idx_myTmpid = tmpIds.findIndex(x => x == tmpId);
165 if (idx_myTmpid >= 0)
166 tmpIds.splice(idx_myTmpid, 1);
167 }
8418f0d7 168 const tmpId_idx = Math.floor(Math.random() * tmpIds.length);
71468011
BA
169 clients[page][obj.target][tmpIds[tmpId_idx]].send(
170 JSON.stringify({code:obj.code, from:[sid,tmpId]}));
81d9ce72 171 break;
8418f0d7 172 }
71468011
BA
173
174 // Some Hall events: target all tmpId's (except mine),
175 case "refusechallenge":
176 case "startgame":
177 Object.keys(clients[page][obj.target]).forEach(x => {
178 if (obj.target != sid || x != tmpId)
c6788ecf 179 {
71468011
BA
180 clients[page][obj.target][x].send(JSON.stringify(
181 {code:obj.code, data:obj.data}));
c6788ecf
BA
182 }
183 });
4d64881e 184 break;
71468011
BA
185
186 // Notify all room: mostly game events
5bd05dba 187 case "newchat":
71468011
BA
188 case "newchallenge":
189 case "newgame":
190 case "deletechallenge":
5bd05dba 191 case "newmove":
5bd05dba 192 case "resign":
b988c726 193 case "abort":
2cc10cdb 194 case "drawoffer":
2cc10cdb 195 case "draw":
71468011
BA
196 notifyRoom(page, obj.code, {data:obj.data});
197 break;
198
199 // Passing, relaying something: from isn't needed,
200 // but target is fully identified (sid + tmpId)
201 case "challenge":
202 case "fullgame":
203 case "game":
204 case "identity":
205 case "lastate":
206 clients[page][obj.target[0]][obj.target[1]].send(JSON.stringify(
207 {code:obj.code, data:obj.data}));
2cc10cdb 208 break;
5bd05dba 209 }
a3ac374b
BA
210 };
211 const closeListener = () => {
71468011
BA
212 // For tab or browser closing:
213 deleteConnexion();
a3ac374b 214 };
71468011
BA
215 // Update clients object: add new connexion
216 if (!clients[page])
217 clients[page] = {[sid]: {[tmpId]: socket}};
218 else if (!clients[page][sid])
219 clients[page][sid] = {[tmpId]: socket};
220 else
221 clients[page][sid][tmpId] = socket;
a3ac374b
BA
222 socket.on("message", messageListener);
223 socket.on("close", closeListener);
5bd05dba 224 });
1d184b4c 225}