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
)
7 const query
= url
.substr(2); //starts with "/?"
9 query
.split("&").forEach((part
) => {
10 const item
= part
.split("=");
11 result
[item
[0]] = decodeURIComponent(item
[1]);
16 // Helper to safe-send some message through a (web-)socket:
17 function send(socket
, message
)
19 if (!!socket
&& socket
.readyState
== 1)
20 socket
.send(JSON
.stringify(message
));
23 module
.exports = function(wss
) {
24 // Associative array page --> sid --> tmpId --> socket
25 // "page" is either "/" for hall or "/game/some_gid" for Game,
26 // tmpId is required if a same user (browser) has different tabs
28 wss
.on("connection", (socket
, req
) => {
29 const query
= getJsonFromUrl(req
.url
);
30 const sid
= query
["sid"];
31 const tmpId
= query
["tmpId"];
32 const page
= query
["page"];
33 const notifyRoom
= (page
,code
,obj
={}) => {
36 Object
.keys(clients
[page
]).forEach(k
=> {
37 Object
.keys(clients
[page
][k
]).forEach(x
=> {
38 if (k
== sid
&& x
== tmpId
)
40 send(clients
[page
][k
][x
], Object
.assign({code:code
, from:sid
}, obj
));
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)
50 delete clients
[page
][sid
];
51 if (Object
.keys(clients
[page
]) == 0)
55 const doDisconnect
= () => {
57 if (!clients
[page
] || !clients
[page
][sid
])
59 // I effectively disconnected from this page:
60 notifyRoom(page
, "disconnect");
61 if (page
.indexOf("/game/") >= 0)
62 notifyRoom("/", "gdisconnect", {page:page
});
65 const messageListener
= (objtxt
) => {
66 let obj
= JSON
.parse(objtxt
);
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.
74 notifyRoom(page
, "connect");
75 if (page
.indexOf("/game/") >= 0)
76 notifyRoom("/", "gconnect", {page:page
});
85 // Self multi-connect: manual removal + disconnect
86 const doKill
= (pg
) => {
87 Object
.keys(clients
[pg
][obj
.sid
]).forEach(x
=> {
88 send(clients
[pg
][obj
.sid
][x
], {code: "killed"});
90 delete clients
[pg
][obj
.sid
];
92 const disconnectFromOtherConnexion
= (pg
,code
,o
={}) => {
93 Object
.keys(clients
[pg
]).forEach(k
=> {
96 Object
.keys(clients
[pg
][k
]).forEach(x
=> {
97 send(clients
[pg
][k
][x
], Object
.assign({code:code
, from:obj
.sid
}, o
));
102 Object
.keys(clients
).forEach(pg
=> {
103 if (!!clients
[pg
][obj
.sid
])
106 disconnectFromOtherConnexion(pg
, "disconnect");
107 if (pg
.indexOf("/game/") >= 0 && !!clients
["/"])
108 disconnectFromOtherConnexion("/", "gdisconnect", {page:pg
});
113 case "pollclients": //from Hall or Game
116 Object
.keys(clients
[page
]).forEach(k
=> {
117 // Avoid polling myself: no new information to get
121 send(socket
, {code:"pollclients", sockIds:sockIds
});
124 case "pollclientsandgamers": //from Hall
127 Object
.keys(clients
["/"]).forEach(k
=> {
128 // Avoid polling myself: no new information to get
130 sockIds
.push({sid:k
});
132 // NOTE: a "gamer" could also just be an observer
133 Object
.keys(clients
).forEach(p
=> {
136 Object
.keys(clients
[p
]).forEach(k
=> {
138 sockIds
.push({sid:k
, page:p
}); //page needed for gamers
142 send(socket
, {code:"pollclientsandgamers", sockIds:sockIds
});
146 // Asking something: from is fully identified,
147 // but the requested resource can be from any tmpId (except current!)
154 const pg
= obj
.page
|| page
; //required for askidentity and askgame
155 // In cas askfullgame to wrong SID for example, would crash:
156 if (clients
[pg
] && clients
[pg
][obj
.target
])
158 const tmpIds
= Object
.keys(clients
[pg
][obj
.target
]);
159 if (obj
.target
== sid
) //targetting myself
161 const idx_myTmpid
= tmpIds
.findIndex(x
=> x
== tmpId
);
162 if (idx_myTmpid
>= 0)
163 tmpIds
.splice(idx_myTmpid
, 1);
165 const tmpId_idx
= Math
.floor(Math
.random() * tmpIds
.length
);
167 clients
[pg
][obj
.target
][tmpIds
[tmpId_idx
]],
168 {code:obj
.code
, from:[sid
,tmpId
,page
]}
174 // Some Hall events: target all tmpId's (except mine),
175 case "refusechallenge":
177 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
178 if (obj
.target
!= sid
|| x
!= tmpId
)
179 send(clients
[page
][obj
.target
][x
], {code:obj
.code
, data:obj
.data
});
183 // Notify all room: mostly game events
187 case "deletechallenge":
194 notifyRoom(page
, obj
.code
, {data:obj
.data
});
195 const mygamesPg
= "/mygames";
196 if (obj
.code
== "newmove" && clients
[mygamesPg
])
198 // Relay newmove info to myGames page
199 // NOTE: the move itself is not needed (for now at least)
200 const newmoveForMygames
= {
201 gid: page
.split("/")[2] //format is "/game/gid"
203 obj
.data
.players
.forEach(pSid
=> {
204 if (clients
[mygamesPg
][pSid
])
206 Object
.keys(clients
[mygamesPg
][pSid
]).forEach(x
=> {
208 clients
[mygamesPg
][pSid
][x
],
209 {code:"newmove", data:newmoveForMygames
}
219 // Special case: notify all, 'transroom': Game --> Hall
220 notifyRoom("/", "result", {gid:obj
.gid
, score:obj
.score
});
223 // Passing, relaying something: from isn't needed,
224 // but target is fully identified (sid + tmpId)
231 const pg
= obj
.target
[2] || page
; //required for identity and game
232 // NOTE: if in game we ask identity to opponent still in Hall,
233 // but leaving Hall, clients[pg] or clients[pg][target] could be ndefined
234 if (clients
[pg
] && clients
[pg
][obj
.target
[0]])
235 send(clients
[pg
][obj
.target
[0]][obj
.target
[1]], {code:obj
.code
, data:obj
.data
});
240 const closeListener
= () => {
241 // For browser or tab closing (including page reload):
244 // Update clients object: add new connexion
246 clients
[page
] = {[sid
]: {[tmpId
]: socket
}};
247 else if (!clients
[page
][sid
])
248 clients
[page
][sid
] = {[tmpId
]: socket
};
250 clients
[page
][sid
][tmpId
] = socket
;
251 socket
.on("message", messageListener
);
252 socket
.on("close", closeListener
);