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