1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var engine = require('engine.io');
|
7 | var engineRooms = require('engine.io-rooms');
|
8 | var http = require('http');
|
9 | var urlParser = require('url');
|
10 |
|
11 |
|
12 | class Server<TGamePlayer extends GamePlayerBase, TGameTable extends GameTableBase<TGamePlayer>> {
|
13 |
|
14 | public static API_ROOT_URL = 'http://api.jok.io/';
|
15 |
|
16 | public StartTime;
|
17 | public GameTables: TGameTable[] = [];
|
18 | public UsersCount = 0;
|
19 |
|
20 | private io;
|
21 |
|
22 | constructor(private port = process.env.PORT || 9003, private GameTableClass?, private GamePlayerClass?) {
|
23 |
|
24 | var server = http.createServer(this.httpHandler.bind(this));
|
25 | this.io = engine.attach(server);
|
26 |
|
27 |
|
28 | this.io = engineRooms(this.io);
|
29 |
|
30 | Helper.IO = this.io;
|
31 |
|
32 |
|
33 | this.StartTime = Date.now();
|
34 |
|
35 | this.io.on('connection', this.onConnectionOpen.bind(this));
|
36 |
|
37 | server.listen(this.port, () => {
|
38 | console.log('server listening at port:', this.port);
|
39 | });
|
40 |
|
41 |
|
42 | Helper.SendMail('status-update@jok.io', 'jok-realtime-server started', 'StartTime: ' + new Date());
|
43 | }
|
44 |
|
45 |
|
46 |
|
47 | httpHandler(req, res) {
|
48 |
|
49 | var urlInfo = urlParser.parse(req.url, true);
|
50 |
|
51 | switch (urlInfo.pathname) {
|
52 | case '/stats':
|
53 | {
|
54 | res.end(JSON.stringify({
|
55 | ConnectionsCount: this.io.clientsCount,
|
56 | UsersCount: this.UsersCount,
|
57 | TablesCount: this.GameTables.length,
|
58 | Uptime: (Date.now() - this.StartTime) / (1000 * 60) + ' min.'
|
59 | }));
|
60 | }
|
61 | break;
|
62 |
|
63 | default:
|
64 | {
|
65 | res.end('Hi, Bye');
|
66 | }
|
67 | break;
|
68 | }
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 | onConnectionOpen(socket) {
|
74 |
|
75 | var sid = socket.request.query.token;
|
76 | var gameid = socket.request.query.gameid;
|
77 | var gamemode = socket.request.query.gamemode;
|
78 | var channel = socket.request.query.channel;
|
79 | var ipaddress = socket.request.headers["x-forwarded-for"];
|
80 |
|
81 |
|
82 |
|
83 | if (!channel)
|
84 | channel = '';
|
85 |
|
86 | channel = channel.toLowerCase();
|
87 |
|
88 |
|
89 |
|
90 | if (ipaddress) {
|
91 | var list = ipaddress.split(",");
|
92 | ipaddress = list[list.length - 1];
|
93 | } else {
|
94 | ipaddress = socket.request.connection.remoteAddress;
|
95 | }
|
96 |
|
97 |
|
98 |
|
99 | if (!sid || !ipaddress || !gameid) return;
|
100 |
|
101 |
|
102 | var userid;
|
103 | var disconnected;
|
104 | var gameTable: TGameTable;
|
105 |
|
106 | var url = Server.API_ROOT_URL + 'User/InfoBySID?sid=' + sid + '&ipaddress=' + ipaddress + '&gameid=' + gameid;
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | Helper.HttpGet(url, (isSuccess, data) => {
|
112 |
|
113 | if (!isSuccess || !data.UserID || disconnected) return;
|
114 |
|
115 |
|
116 |
|
117 | userid = data.UserID;
|
118 |
|
119 |
|
120 |
|
121 | this.UsersCount++;
|
122 |
|
123 |
|
124 |
|
125 | var userChannel = 'User' + userid;
|
126 | var oldConnections = Helper.ChannelSockets(userChannel);
|
127 | socket.join(userChannel);
|
128 |
|
129 |
|
130 |
|
131 | if (oldConnections)
|
132 | oldConnections.forEach(c => c.close());
|
133 |
|
134 |
|
135 |
|
136 | gameTable = this.findTable(data, channel, gamemode);
|
137 | if (!gameTable) {
|
138 | console.log('GameTable not found, it must not happen. Passed parameters:', channel, gamemode);
|
139 | return;
|
140 | }
|
141 | gameTable.join(data, ipaddress, channel, gamemode);
|
142 |
|
143 |
|
144 |
|
145 | socket.send(Helper.BuildCommand('UserAuthenticated', userid));
|
146 |
|
147 | }, true);
|
148 |
|
149 |
|
150 |
|
151 | socket.on('message', (msg) => {
|
152 |
|
153 |
|
154 | if (!userid || !gameTable || !msg) return;
|
155 |
|
156 |
|
157 |
|
158 | try {
|
159 | if (typeof msg == 'string')
|
160 | msg = JSON.parse(msg);
|
161 | }
|
162 | catch (err) { }
|
163 |
|
164 |
|
165 |
|
166 | if (Object.prototype.toString.call(msg) === '[object Array]') {
|
167 | return;
|
168 | }
|
169 |
|
170 | if (!msg.length) return;
|
171 |
|
172 | var command = msg.shift();
|
173 | var params = msg;
|
174 |
|
175 | var command = msg.command;
|
176 | var params = msg.params;
|
177 |
|
178 | if (!command) {
|
179 | console.log('Every message must have "command" and optionaly "params" properties');
|
180 | return;
|
181 | }
|
182 |
|
183 | var reservedWords = ['Join', 'Leave'];
|
184 | if (command in reservedWords) {
|
185 | console.log('Reserved words cant be used as command:', reservedWords);
|
186 | return;
|
187 | }
|
188 |
|
189 | command = 'on' + command;
|
190 |
|
191 |
|
192 |
|
193 | if (typeof gameTable[command] != 'function') {
|
194 | console.log('GameTable method not found with name:', command);
|
195 | return;
|
196 | }
|
197 |
|
198 | gameTable[command].apply(gameTable, params);
|
199 | });
|
200 |
|
201 |
|
202 |
|
203 | socket.on('close', () => {
|
204 |
|
205 |
|
206 | disconnected = true;
|
207 |
|
208 |
|
209 | if (!userid) return;
|
210 |
|
211 |
|
212 | this.UsersCount--;
|
213 |
|
214 |
|
215 | gameTable && gameTable.leave(userid);
|
216 |
|
217 | if (!gameTable.Players.length) {
|
218 | var index = this.GameTables.indexOf(gameTable);
|
219 |
|
220 | if (index > -1)
|
221 | this.GameTables.splice(index, 1);
|
222 | }
|
223 | });
|
224 | }
|
225 |
|
226 |
|
227 |
|
228 | findTable(user, channel: string, mode: number): TGameTable {
|
229 |
|
230 |
|
231 |
|
232 | var table = this.GameTables.filter(t =>
|
233 | (t.Players.filter(p => p.UserID == user.UserID)[0] != undefined) &&
|
234 | (t.Status == TableStatus.Started) &&
|
235 | (t.Status != TableStatus.Finished)
|
236 | )[0]
|
237 | if (table) return table;
|
238 |
|
239 |
|
240 |
|
241 | table = this.GameTables.filter(t =>
|
242 | t.Channel == channel &&
|
243 | t.Mode == mode &&
|
244 | t.Players.length < t.MaxPlayersCount &&
|
245 | (t.Status != TableStatus.Started) &&
|
246 | (t.Status != TableStatus.Finished) &&
|
247 | this.isTournamentValid(channel, t, user)
|
248 | )[0]
|
249 | if (table) return table;
|
250 |
|
251 |
|
252 |
|
253 | if (!this.createTable) return;
|
254 |
|
255 | table = this.createTable(user, channel, mode);
|
256 | if (!table) return;
|
257 |
|
258 | this.GameTables.push(table);
|
259 |
|
260 | return table;
|
261 | }
|
262 |
|
263 |
|
264 | createTable(user, channel, mode): TGameTable {
|
265 | return new this.GameTableClass(this.GamePlayerClass, channel, mode, 2, this.isTournamentChannel(channel) ? user.IsVIP : false);
|
266 | }
|
267 |
|
268 | isTournamentValid(channel: string, table: TGameTable, user): boolean {
|
269 |
|
270 | if (!this.isTournamentChannel(channel))
|
271 | return true;
|
272 |
|
273 | return (user.IsVIP == table.IsVIPTable);
|
274 | }
|
275 |
|
276 | isTournamentChannel(channel: string) {
|
277 | return (channel == 'tournament');
|
278 | }
|
279 |
|
280 |
|
281 |
|
282 | public static Start<TGamePlayer extends GamePlayerBase, TGameTable extends GameTableBase<TGamePlayer>>(port?, TGameTable?, TGamePlayerClass?) {
|
283 | return new Server<TGamePlayer, TGameTable>(port, TGameTable, TGamePlayerClass);
|
284 | }
|
285 | }
|
286 |
|
287 |
|
288 | exports.Server = Server;
|
289 | exports.Helper = Helper;
|
290 | exports.GameTableBase = GameTableBase;
|
291 | exports.GamePlayerBase = GamePlayerBase;
|