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 module
.exports = function(wss
) {
17 // Associative array sid --> tmpId --> {socket, page},
18 // "page" is either "/" for hall or "/game/some_gid" for Game,
19 // tmpId is required if a same user (browser) has different tabs
21 wss
.on("connection", (socket
, req
) => {
22 const query
= getJsonFromUrl(req
.url
);
23 const sid
= query
["sid"];
24 const tmpId
= query
["tmpId"];
25 const notifyRoom
= (page
,code
,obj
={},excluded
=[]) => {
26 Object
.keys(clients
).forEach(k
=> {
29 if (k
!= sid
&& clients
[k
].page
== page
)
31 clients
[k
].sock
.send(JSON
.stringify(Object
.assign(
32 {code:code
, from:[sid
,tmpId
]}, obj
)));
36 const messageListener
= (objtxt
) => {
37 let obj
= JSON
.parse(objtxt
);
38 if (!!obj
.target
&& !clients
[obj
.target
])
39 return; //receiver not connected, nothing we can do
42 // Wait for "connect" message to notify connection to the room,
43 // because if game loading is slow the message listener might
44 // not be ready too early.
47 const curPage
= clients
[sid
][tmpId
].page
;
48 notifyRoom(curPage
, "connect"); //Hall or Game
49 if (curPage
.indexOf("/game/") >= 0)
50 notifyRoom("/", "gconnect"); //notify main hall
55 const oldPage
= obj
.page
;
56 notifyRoom(oldPage
, "disconnect"); //Hall or Game
57 if (oldPage
.indexOf("/game/") >= 0)
58 notifyRoom("/", "gdisconnect"); //notify main hall
63 const curPage
= clients
[sid
][tmpId
].page
;
64 let sockIds
= {}; //result, object sid ==> [tmpIds]
65 Object
.keys(clients
).forEach(k
=> {
66 Object
.keys(clients
[k
]).forEach(x
=> {
67 if ((k
!= sid
|| x
!= tmpId
)
68 && clients
[k
][x
].page
== curPage
)
77 socket
.send(JSON
.stringify({code:"pollclients", sockIds:sockIds
}));
83 Object
.keys(clients
).forEach(k
=> {
84 Object
.keys(clients
[k
]).forEach(x
=> {
85 if ((k
!= sid
|| x
!= tmpId
)
86 && clients
[k
][x
].page
.indexOf("/game/") >= 0)
95 socket
.send(JSON
.stringify({code:"pollgamers", sockIds:sockIds
}));
100 // Identity only depends on sid, so select a tmpId at random
101 const tmpIds
= Object
.keys(clients
[obj
.target
]);
102 const tmpId_idx
= Math
.floor(Math
.random() * tmpIds
.length
);
103 clients
[obj
.target
][tmpIds
[tmpId_idx
]].sock
.send(JSON
.stringify(
104 {code:"askidentity",from:[sid
,tmpId
]}));
108 clients
[obj
.target
[0]][obj
.target
[1]].sock
.send(JSON
.stringify(
109 {code:"asklastate",from:[sid
,tmpId
]}));
112 clients
[obj
.target
[0]][obj
.target
[1]].sock
.send(JSON
.stringify(
113 {code:"askchallenge",from:[sid
,tmpId
]}));
117 // Check all clients playing, and send them a "askgame" message
118 // game ID --> [ sid1 --> array of tmpIds, sid2 --> array of tmpIds]
120 const regexpGid
= /\/[a-zA-Z0-9]+$/;
121 Object
.keys(clients
).forEach(k
=> {
122 Object
.keys(clients
[k
]).forEach(x
=> {
123 if ((k
!= sid
|| x
!= tmpId
)
124 && clients
[k
][x
].page
.indexOf("/game/") >= 0)
126 const gid
= clients
[k
][x
].page
.match(regexpGid
)[0];
128 gameSids
[gid
] = [{k: [x
]}];
129 else if (k
== Object
.keys(gameSids
[gid
][0])[0])
130 gameSids
[gid
][0][k
].push(x
);
131 else if (gameSids
[gid
].length
== 1)
132 gameSids
[gid
].push({k: [x
]});
134 Object
.values(gameSids
[gid
][1]).push(x
);
137 // Request only one client out of 2 (TODO: this is a bit heavy)
138 // Alt: ask game to all, and filter later?
139 Object
.keys(gameSids
).forEach(gid
=> {
140 const L
= gameSids
[gid
].length
;
142 ? Math
.floor(Math
.random() * Math
.floor(L
))
144 const tmpIdx
= Object
.values(gameSids
[gid
][sidIdx
]
145 const rid
= gameSids
[gid
][sidIdx
][tmpIdx
];
146 clients
[sidIdx
][tmpIdx
].sock
.send(JSON
.stringify(
147 {code:"askgame", from: [sid
,tmpId
]}));
152 clients
[obj
.target
].sock
.send(JSON
.stringify(
153 {code:"askgame", from:sid
}));
156 clients
[obj
.target
].sock
.send(JSON
.stringify(
157 {code:"askfullgame", from:sid
}));
160 clients
[obj
.target
].sock
.send(JSON
.stringify(
161 {code:"fullgame", game:obj
.game
}));
164 clients
[obj
.target
].sock
.send(JSON
.stringify(
165 {code:"identity",user:obj
.user
}));
167 case "refusechallenge":
168 clients
[obj
.target
].sock
.send(JSON
.stringify(
169 {code:"refusechallenge", cid:obj
.cid
, from:sid
}));
171 case "deletechallenge":
172 clients
[obj
.target
].sock
.send(JSON
.stringify(
173 {code:"deletechallenge", cid:obj
.cid
, from:sid
}));
176 clients
[obj
.target
].sock
.send(JSON
.stringify(
177 {code:"newgame", gameInfo:obj
.gameInfo
, cid:obj
.cid
}));
180 clients
[obj
.target
].sock
.send(JSON
.stringify(
181 {code:"challenge", chall:obj
.chall
, from:sid
}));
186 clients
[obj
.target
].sock
.send(JSON
.stringify(
187 {code:"game", game:obj
.game
, from:sid
}));
191 // Notify all room except opponent and me:
192 notifyRoom("/", "game", {game:obj
.game
}, [obj
.oppsid
]);
196 notifyRoom(clients
[sid
].page
, "newchat", {chat:obj
.chat
});
198 // TODO: WebRTC instead in this case (most demanding?)
199 // --> Or else: at least do a "notifyRoom" (also for draw, resign...)
201 clients
[obj
.target
].sock
.send(JSON
.stringify(
202 {code:"newmove", move:obj
.move}));
205 clients
[obj
.target
].sock
.send(JSON
.stringify(
206 {code:"lastate", state:obj
.state
}));
209 clients
[obj
.target
].sock
.send(JSON
.stringify(
210 {code:"resign", side:obj
.side
}));
213 clients
[obj
.target
].sock
.send(JSON
.stringify(
217 clients
[obj
.target
].sock
.send(JSON
.stringify(
218 {code:"drawoffer"}));
221 clients
[obj
.target
].sock
.send(JSON
.stringify(
222 {code:"draw", message:obj
.message
}));
226 const closeListener
= () => {
231 // Turn off old sock through current client:
232 clients
[sid
].sock
.send(JSON
.stringify({code:"duplicate"}));
234 // Potentially replace current connection:
235 clients
[sid
] = {sock: socket
, page: query
["page"]};
236 socket
.on("message", messageListener
);
237 socket
.on("close", closeListener
);