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 const tmpIds
= Object
.keys(clients
[pg
][obj
.target
]);
156 if (obj
.target
== sid
) //targetting myself
158 const idx_myTmpid
= tmpIds
.findIndex(x
=> x
== tmpId
);
159 if (idx_myTmpid
>= 0)
160 tmpIds
.splice(idx_myTmpid
, 1);
162 const tmpId_idx
= Math
.floor(Math
.random() * tmpIds
.length
);
163 send(clients
[pg
][obj
.target
][tmpIds
[tmpId_idx
]], {code:obj
.code
, from:[sid
,tmpId
,page
]});
167 // Some Hall events: target all tmpId's (except mine),
168 case "refusechallenge":
170 Object
.keys(clients
[page
][obj
.target
]).forEach(x
=> {
171 if (obj
.target
!= sid
|| x
!= tmpId
)
172 send(clients
[page
][obj
.target
][x
], {code:obj
.code
, data:obj
.data
});
176 // Notify all room: mostly game events
180 case "deletechallenge":
186 notifyRoom(page
, obj
.code
, {data:obj
.data
});
189 // Passing, relaying something: from isn't needed,
190 // but target is fully identified (sid + tmpId)
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
});
203 const closeListener
= () => {
204 // For tab or browser closing:
207 // Update clients object: add new connexion
209 clients
[page
] = {[sid
]: {[tmpId
]: socket
}};
210 else if (!clients
[page
][sid
])
211 clients
[page
][sid
] = {[tmpId
]: socket
};
213 clients
[page
][sid
][tmpId
] = socket
;
214 socket
.on("message", messageListener
);
215 socket
.on("close", closeListener
);