Fix trans-pages events askidentity and askgame
[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 };
a3ac374b 55 const messageListener = (objtxt) => {
5bd05dba 56 let obj = JSON.parse(objtxt);
5bd05dba
BA
57 switch (obj.code)
58 {
a3ac374b
BA
59 // Wait for "connect" message to notify connection to the room,
60 // because if game loading is slow the message listener might
61 // not be ready too early.
41c80bb6 62 case "connect":
120fe373 63 {
71468011
BA
64 notifyRoom(page, "connect");
65 if (page.indexOf("/game/") >= 0)
66 notifyRoom("/", "gconnect", {page:page});
41c80bb6 67 break;
120fe373 68 }
8418f0d7 69 case "disconnect":
71468011
BA
70 // When page changes:
71 deleteConnexion();
51d87b52 72 if (!clients[page] || !clients[page][sid])
71468011
BA
73 {
74 // I effectively disconnected from this page:
75 notifyRoom(page, "disconnect");
76 if (page.indexOf("/game/") >= 0)
77 notifyRoom("/", "gdisconnect", {page:page});
78 }
8418f0d7 79 break;
51d87b52
BA
80 case "killme":
81 {
82 // Self multi-connect: manual removal + disconnect
83 const doKill = (pg) => {
84 Object.keys(clients[pg][obj.sid]).forEach(x => {
8b152ada 85 send(clients[pg][obj.sid][x], {code: "killed"});
51d87b52
BA
86 });
87 delete clients[pg][obj.sid];
88 };
89 const disconnectFromOtherConnexion = (pg,code,o={}) => {
90 Object.keys(clients[pg]).forEach(k => {
91 if (k != obj.sid)
92 {
93 Object.keys(clients[pg][k]).forEach(x => {
8b152ada 94 send(clients[pg][k][x], Object.assign({code:code, from:obj.sid}, o));
51d87b52
BA
95 });
96 }
97 });
98 };
99 Object.keys(clients).forEach(pg => {
100 if (!!clients[pg][obj.sid])
101 {
102 doKill(pg);
103 disconnectFromOtherConnexion(pg, "disconnect");
f854c94f 104 if (pg.indexOf("/game/") >= 0 && !!clients["/"])
51d87b52
BA
105 disconnectFromOtherConnexion("/", "gdisconnect", {page:pg});
106 }
107 });
108 break;
109 }
71468011 110 case "pollclients": //from Hall or Game
ac8f441c 111 {
71468011
BA
112 let sockIds = [];
113 Object.keys(clients[page]).forEach(k => {
51d87b52
BA
114 // Avoid polling myself: no new information to get
115 if (k != sid)
71468011 116 sockIds.push(k);
8418f0d7 117 });
8b152ada 118 send(socket, {code:"pollclients", sockIds:sockIds});
92a523d1 119 break;
ac8f441c 120 }
71468011 121 case "pollclientsandgamers": //from Hall
8418f0d7 122 {
71468011
BA
123 let sockIds = [];
124 Object.keys(clients["/"]).forEach(k => {
51d87b52
BA
125 // Avoid polling myself: no new information to get
126 if (k != sid)
71468011
BA
127 sockIds.push({sid:k});
128 });
129 // NOTE: a "gamer" could also just be an observer
130 Object.keys(clients).forEach(p => {
131 if (p != "/")
132 {
133 Object.keys(clients[p]).forEach(k => {
134 if (k != sid)
135 sockIds.push({sid:k, page:p}); //page needed for gamers
136 });
137 }
8418f0d7 138 });
8b152ada 139 send(socket, {code:"pollclientsandgamers", sockIds:sockIds});
5a3da968 140 break;
8418f0d7 141 }
71468011
BA
142
143 // Asking something: from is fully identified,
144 // but the requested resource can be from any tmpId (except current!)
5a3da968 145 case "askidentity":
71468011
BA
146 case "asklastate":
147 case "askchallenge":
148 case "askgame":
149 case "askfullgame":
8418f0d7 150 {
8b152ada
BA
151 // DEBUG:
152 //console.log(sid + " " + page + " " + obj.code + " " + obj.target + " " + obj.page);
153 //console.log(clients);
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
189 // Passing, relaying something: from isn't needed,
190 // but target is fully identified (sid + tmpId)
191 case "challenge":
192 case "fullgame":
193 case "game":
194 case "identity":
195 case "lastate":
8b152ada
BA
196 {
197 const pg = obj.target[2] || page; //required for identity and game
198 send(clients[pg][obj.target[0]][obj.target[1]], {code:obj.code, data:obj.data});
2cc10cdb 199 break;
8b152ada 200 }
5bd05dba 201 }
a3ac374b
BA
202 };
203 const closeListener = () => {
71468011
BA
204 // For tab or browser closing:
205 deleteConnexion();
a3ac374b 206 };
71468011
BA
207 // Update clients object: add new connexion
208 if (!clients[page])
209 clients[page] = {[sid]: {[tmpId]: socket}};
210 else if (!clients[page][sid])
211 clients[page][sid] = {[tmpId]: socket};
212 else
213 clients[page][sid][tmpId] = socket;
a3ac374b
BA
214 socket.on("message", messageListener);
215 socket.on("close", closeListener);
5bd05dba 216 });
1d184b4c 217}