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 // or "/mygames" for Mygames page (simpler: no 'people' array).
25 // tmpId is required if a same user (browser) has different tabs
29 wss
.on("connection", (socket
, req
) => {
30 const query
= getJsonFromUrl(req
.url
);
31 const sid
= query
["sid"];
32 const id
= query
["id"];
33 const tmpId
= query
["tmpId"];
34 const page
= query
["page"];
35 const notifyRoom
= (page
,code
,obj
={}) => {
36 if (!clients
[page
]) return;
37 Object
.keys(clients
[page
]).forEach(k
=> {
38 Object
.keys(clients
[page
][k
]).forEach(x
=> {
39 if (k
== sid
&& x
== tmpId
) return;
41 clients
[page
][k
][x
].socket
,
42 Object
.assign({ code: code
, from: sid
}, obj
)
47 // For focus events: no need to target self
48 const notifyAllBut
= (page
,code
,obj
={},except
) => {
49 if (!clients
[page
]) return;
50 Object
.keys(clients
[page
]).forEach(k
=> {
51 if (except
.includes(k
)) return;
52 Object
.keys(clients
[page
][k
]).forEach(x
=> {
54 clients
[page
][k
][x
].socket
,
55 Object
.assign({ code: code
, from: sid
}, obj
)
60 const deleteConnexion
= () => {
61 if (!clients
[page
] || !clients
[page
][sid
] || !clients
[page
][sid
][tmpId
])
62 return; //job already done
63 delete clients
[page
][sid
][tmpId
];
64 if (Object
.keys(clients
[page
][sid
]).length
== 0) {
65 delete clients
[page
][sid
];
66 const pgIndex
= sidToPages
[sid
].findIndex(pg
=> pg
== page
);
67 sidToPages
[sid
].splice(pgIndex
, 1);
68 if (Object
.keys(clients
[page
]).length
== 0)
70 // Am I totally offline?
71 if (sidToPages
[sid
].length
== 0) {
72 delete sidToPages
[sid
];
78 const doDisconnect
= () => {
80 // Nothing to notify when disconnecting from MyGames page:
81 if (page
!= "/mygames" && (!clients
[page
] || !clients
[page
][sid
])) {
82 // I effectively disconnected from this page:
83 notifyRoom(page
, "disconnect");
84 if (page
.indexOf("/game/") >= 0)
85 notifyRoom("/", "gdisconnect", { page:page
});
88 const messageListener
= (objtxt
) => {
89 let obj
= JSON
.parse(objtxt
);
91 // Wait for "connect" message to notify connection to the room,
92 // because if game loading is slow the message listener might
93 // not be ready too early.
95 notifyRoom(page
, "connect");
96 if (page
.indexOf("/game/") >= 0)
97 notifyRoom("/", "gconnect", { page:page
});
101 // When page changes:
105 // Self multi-connect: manual removal + disconnect
106 const doKill
= (pg
) => {
107 Object
.keys(clients
[pg
][obj
.sid
]).forEach(x
=> {
108 send(clients
[pg
][obj
.sid
][x
].socket
, { code: "killed" });
110 delete clients
[pg
][obj
.sid
];
112 const disconnectFromOtherConnexion
= (pg
,code
,o
={}) => {
113 Object
.keys(clients
[pg
]).forEach(k
=> {
115 Object
.keys(clients
[pg
][k
]).forEach(x
=> {
117 clients
[pg
][k
][x
].socket
,
118 Object
.assign({ code: code
, from: obj
.sid
}, o
)
124 Object
.keys(clients
).forEach(pg
=> {
125 if (clients
[pg
][obj
.sid
]) {
127 disconnectFromOtherConnexion(pg
, "disconnect");
128 if (pg
.indexOf("/game/") >= 0 && clients
["/"])
129 disconnectFromOtherConnexion("/", "gdisconnect", { page: pg
});
134 case "pollclients": {
137 Object
.keys(clients
[page
]).forEach(k
=> {
138 // Avoid polling myself: no new information to get
139 if (k
!= sid
) sockIds
.push(k
);
141 send(socket
, { code: "pollclients", sockIds: sockIds
});
144 case "pollclientsandgamers": {
147 Object
.keys(clients
["/"]).forEach(k
=> {
148 // Avoid polling myself: no new information to get
149 if (k
!= sid
) sockIds
.push({sid:k
});
151 // NOTE: a "gamer" could also just be an observer
152 Object
.keys(clients
).forEach(p
=> {
153 if (p
.indexOf("/game/") >= 0) {
154 Object
.keys(clients
[p
]).forEach(k
=> {
155 // 'page' indicator is needed for gamers
156 if (k
!= sid
) sockIds
.push({ sid:k
, page:p
});
160 send(socket
, { code: "pollclientsandgamers", sockIds: sockIds
});
164 // Asking something: from is fully identified,
165 // but the requested resource can be from any tmpId (except current!)
168 case "askchallenges":
170 case "askfullgame": {
171 const pg
= obj
.page
|| page
; //required for askidentity and askgame
172 // In cas askfullgame to wrong SID for example, would crash:
173 if (!!clients
[pg
] && !!clients
[pg
][obj
.target
]) {
174 const tmpIds
= Object
.keys(clients
[pg
][obj
.target
]);
175 if (obj
.target
== sid
) {
177 const idx_myTmpid
= tmpIds
.findIndex(x
=> x
== tmpId
);
178 if (idx_myTmpid
>= 0) tmpIds
.splice(idx_myTmpid
, 1);
180 const tmpId_idx
= Math
.floor(Math
.random() * tmpIds
.length
);
182 clients
[pg
][obj
.target
][tmpIds
[tmpId_idx
]].socket
,
183 { code: obj
.code
, from: [sid
,tmpId
,page
] }
189 // Some Hall events: target all tmpId's (except mine),
190 case "refusechallenge":
192 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
193 if (obj
.target
!= sid
|| x
!= tmpId
)
195 clients
[page
][obj
.target
][x
].socket
,
196 { code: obj
.code
, data: obj
.data
}
201 // Notify all room: mostly game events
204 case "deletechallenge_s":
211 notifyRoom(page
, obj
.code
, {data: obj
.data
});
215 // A rematch game started:
216 // NOTE: no need to explicitely notify Hall: the game will be sent
217 notifyAllBut(page
, "newgame", {data: obj
.data
}, [sid
]);
218 notifyRoom("/mygames", "newgame", {data: obj
.data
});
222 const dataWithFrom
= { from: [sid
,tmpId
], data: obj
.data
};
223 // Special case re-send newmove only to opponent:
224 if (!!obj
.target
&& !!clients
[page
][obj
.target
]) {
225 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
227 clients
[page
][obj
.target
][x
].socket
,
228 Object
.assign({ code: "newmove" }, dataWithFrom
)
232 // NOTE: data.from is useful only to opponent
233 notifyRoom(page
, "newmove", dataWithFrom
);
239 !!clients
[page
][obj
.target
[0]] &&
240 !!clients
[page
][obj
.target
[0]][obj
.target
[1]]
243 clients
[page
][obj
.target
[0]][obj
.target
[1]].socket
,
250 // Special case: notify all, 'transroom': Game --> Hall
251 notifyRoom("/", "result", { gid: obj
.gid
, score: obj
.score
});
255 const gamePg
= "/game/" + obj
.gid
;
256 if (!!clients
[gamePg
] && !!clients
[gamePg
][obj
.target
]) {
257 Object
.keys(clients
[gamePg
][obj
.target
]).forEach(x
=> {
259 clients
[gamePg
][obj
.target
][x
].socket
,
269 case "notifynewgame":
270 if (!!clients
["/mygames"]) {
271 obj
.targets
.forEach(t
=> {
272 const k
= t
.sid
|| idToSid
[t
.uid
];
273 if (!!clients
["/mygames"][k
]) {
274 Object
.keys(clients
["/mygames"][k
]).forEach(x
=> {
276 clients
["/mygames"][k
][x
].socket
,
277 { code: obj
.code
, data: obj
.data
}
287 if (page
== "/") notifyAllBut("/", obj
.code
, { page: "/" }, [sid
]);
289 // Notify game room + Hall:
290 notifyAllBut(page
, obj
.code
, {}, [sid
]);
291 notifyAllBut("/", obj
.code
, { page: page
}, [sid
]);
295 // Passing, relaying something: from isn't needed,
296 // but target is fully identified (sid + tmpId)
303 const pg
= obj
.target
[2] || page
; //required for identity and game
304 // NOTE: if in game we ask identity to opponent still in Hall,
305 // but leaving Hall, clients[pg] or clients[pg][target] could be undefined
306 if (!!clients
[pg
] && !!clients
[pg
][obj
.target
[0]]) {
308 clients
[pg
][obj
.target
[0]][obj
.target
[1]].socket
,
309 { code:obj
.code
, data:obj
.data
}
316 const closeListener
= () => {
317 // For browser or tab closing (including page reload):
320 // Update clients object: add new connexion
321 const newElt
= { socket: socket
, focus: true };
323 clients
[page
] = { [sid
]: {[tmpId
]: newElt
} };
324 else if (!clients
[page
][sid
])
325 clients
[page
][sid
] = { [tmpId
]: newElt
};
327 clients
[page
][sid
][tmpId
] = newElt
;
328 // Also update helper correspondances
329 if (!idToSid
[id
]) idToSid
[id
] = sid
;
330 if (!sidToPages
[sid
]) sidToPages
[sid
] = [];
331 const pgIndex
= sidToPages
[sid
].findIndex(pg
=> pg
== page
);
332 if (pgIndex
=== -1) sidToPages
[sid
].push(page
);
333 socket
.on("message", messageListener
);
334 socket
.on("close", closeListener
);