1 const url
= require('url');
3 // Node version in Ubuntu 16.04 does not know about URL class
4 // NOTE: url is already transformed, without ?xxx=yyy... parts
5 function getJsonFromUrl(url
) {
6 const query
= url
.substr(2); //starts with "/?"
8 query
.split("&").forEach((part
) => {
9 const item
= part
.split("=");
10 result
[item
[0]] = decodeURIComponent(item
[1]);
15 // Helper to safe-send some message through a (web-)socket:
16 function send(socket
, message
) {
17 if (!!socket
&& socket
.readyState
== 1)
18 socket
.send(JSON
.stringify(message
));
21 module
.exports = function(wss
) {
22 // Associative array page --> sid --> tmpId --> socket
23 // "page" is either "/" for hall or "/game/some_gid" for Game,
24 // tmpId is required if a same user (browser) has different tabs
26 wss
.on("connection", (socket
, req
) => {
27 const query
= getJsonFromUrl(req
.url
);
28 const sid
= query
["sid"];
29 const tmpId
= query
["tmpId"];
30 const page
= query
["page"];
31 const notifyRoom
= (page
,code
,obj
={}) => {
32 if (!clients
[page
]) return;
33 Object
.keys(clients
[page
]).forEach(k
=> {
34 Object
.keys(clients
[page
][k
]).forEach(x
=> {
35 if (k
== sid
&& x
== tmpId
) return;
38 Object
.assign({ code: code
, from: sid
}, obj
)
43 const deleteConnexion
= () => {
44 if (!clients
[page
] || !clients
[page
][sid
] || !clients
[page
][sid
][tmpId
])
45 return; //job already done
46 delete clients
[page
][sid
][tmpId
];
47 if (Object
.keys(clients
[page
][sid
]).length
== 0) {
48 delete clients
[page
][sid
];
49 if (Object
.keys(clients
[page
]).length
== 0)
54 const doDisconnect
= () => {
56 if (!clients
[page
] || !clients
[page
][sid
]) {
57 // I effectively disconnected from this page:
58 notifyRoom(page
, "disconnect");
59 if (page
.indexOf("/game/") >= 0)
60 notifyRoom("/", "gdisconnect", { page:page
});
63 const messageListener
= (objtxt
) => {
64 let obj
= JSON
.parse(objtxt
);
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.
70 notifyRoom(page
, "connect");
71 if (page
.indexOf("/game/") >= 0)
72 notifyRoom("/", "gconnect", { page:page
});
80 // Self multi-connect: manual removal + disconnect
81 const doKill
= (pg
) => {
82 Object
.keys(clients
[pg
][obj
.sid
]).forEach(x
=> {
83 send(clients
[pg
][obj
.sid
][x
], {code: "killed"});
85 delete clients
[pg
][obj
.sid
];
87 const disconnectFromOtherConnexion
= (pg
,code
,o
={}) => {
88 Object
.keys(clients
[pg
]).forEach(k
=> {
90 Object
.keys(clients
[pg
][k
]).forEach(x
=> {
93 Object
.assign({ code: code
, from: obj
.sid
}, o
)
99 Object
.keys(clients
).forEach(pg
=> {
100 if (clients
[pg
][obj
.sid
]) {
102 disconnectFromOtherConnexion(pg
, "disconnect");
103 if (pg
.indexOf("/game/") >= 0 && clients
["/"])
104 disconnectFromOtherConnexion("/", "gdisconnect", { page: pg
});
109 case "pollclients": {
112 Object
.keys(clients
[page
]).forEach(k
=> {
113 // Avoid polling myself: no new information to get
114 if (k
!= sid
) sockIds
.push(k
);
116 send(socket
, { code: "pollclients", sockIds: sockIds
});
119 case "pollclientsandgamers": {
122 Object
.keys(clients
["/"]).forEach(k
=> {
123 // Avoid polling myself: no new information to get
124 if (k
!= sid
) sockIds
.push({sid:k
});
126 // NOTE: a "gamer" could also just be an observer
127 Object
.keys(clients
).forEach(p
=> {
129 Object
.keys(clients
[p
]).forEach(k
=> {
130 // 'page' indicator is needed for gamers
131 if (k
!= sid
) sockIds
.push({ sid:k
, page:p
});
135 send(socket
, { code: "pollclientsandgamers", sockIds: sockIds
});
139 // Asking something: from is fully identified,
140 // but the requested resource can be from any tmpId (except current!)
145 case "askfullgame": {
146 const pg
= obj
.page
|| page
; //required for askidentity and askgame
147 // In cas askfullgame to wrong SID for example, would crash:
148 if (!!clients
[pg
] && !!clients
[pg
][obj
.target
]) {
149 const tmpIds
= Object
.keys(clients
[pg
][obj
.target
]);
150 if (obj
.target
== sid
) {
152 const idx_myTmpid
= tmpIds
.findIndex(x
=> x
== tmpId
);
153 if (idx_myTmpid
>= 0) tmpIds
.splice(idx_myTmpid
, 1);
155 const tmpId_idx
= Math
.floor(Math
.random() * tmpIds
.length
);
157 clients
[pg
][obj
.target
][tmpIds
[tmpId_idx
]],
158 { code: obj
.code
, from: [sid
,tmpId
,page
] }
164 // Some Hall events: target all tmpId's (except mine),
165 case "refusechallenge":
167 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
168 if (obj
.target
!= sid
|| x
!= tmpId
)
170 clients
[page
][obj
.target
][x
],
171 { code: obj
.code
, data: obj
.data
}
176 // Notify all room: mostly game events
180 case "deletechallenge":
185 notifyRoom(page
, obj
.code
, {data: obj
.data
});
189 const dataWithFrom
= { from: [sid
,tmpId
], data: obj
.data
};
190 // Special case re-send newmove only to opponent:
191 if (!!obj
.target
&& !!clients
[page
][obj
.target
]) {
192 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
194 clients
[page
][obj
.target
][x
],
195 Object
.assign({ code: "newmove" }, dataWithFrom
)
199 // NOTE: data.from is useful only to opponent
200 notifyRoom(page
, "newmove", dataWithFrom
);
206 !!clients
[page
][obj
.target
[0]] &&
207 !!clients
[page
][obj
.target
[0]][obj
.target
[1]]
210 clients
[page
][obj
.target
[0]][obj
.target
[1]],
211 { code: "gotmove", data: obj
.data
}
217 // Special case: notify all, 'transroom': Game --> Hall
218 notifyRoom("/", "result", { gid: obj
.gid
, score: obj
.score
});
222 // Special case: notify some game rooms that
223 // I'm watching game state from MyGames
224 // TODO: this code is ignored for now
225 obj
.gids
.forEach(gid
=> {
226 const pg
= "/game/" + gid
;
227 Object
.keys(clients
[pg
]).forEach(s
=> {
228 Object
.keys(clients
[pg
][s
]).forEach(x
=> {
231 { code: "mconnect", data: obj
.data
}
239 // Also TODO: pass newgame to MyGames, and gameover (result)
242 const gamePg
= "/game/" + obj
.gid
;
243 if (!!clients
[gamePg
] && !!clients
[gamePg
][obj
.target
]) {
244 Object
.keys(clients
[gamePg
][target
]).forEach(x
=> {
246 clients
[gamePg
][obj
.target
][x
],
254 // Passing, relaying something: from isn't needed,
255 // but target is fully identified (sid + tmpId)
262 const pg
= obj
.target
[2] || page
; //required for identity and game
263 // NOTE: if in game we ask identity to opponent still in Hall,
264 // but leaving Hall, clients[pg] or clients[pg][target] could be undefined
265 if (!!clients
[pg
] && !!clients
[pg
][obj
.target
[0]]) {
267 clients
[pg
][obj
.target
[0]][obj
.target
[1]],
268 { code:obj
.code
, data:obj
.data
}
275 const closeListener
= () => {
276 // For browser or tab closing (including page reload):
279 // Update clients object: add new connexion
281 clients
[page
] = { [sid
]: {[tmpId
]: socket
} };
282 else if (!clients
[page
][sid
])
283 clients
[page
][sid
] = { [tmpId
]: socket
};
285 clients
[page
][sid
][tmpId
] = socket
;
286 socket
.on("message", messageListener
);
287 socket
.on("close", closeListener
);