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 |
|
142 |
|
143 | socket.send(JSON.stringify(['UserAuthenticated', userid]));
|
144 |
|
145 |
|
146 | gameTable.join(data, ipaddress, channel, gamemode);
|
147 |
|
148 | }, true);
|
149 |
|
150 |
|
151 |
|
152 | socket.on('message', (msg) => {
|
153 |
|
154 |
|
155 | if (!userid || !gameTable || !msg) return;
|
156 |
|
157 |
|
158 |
|
159 | try {
|
160 | if (typeof msg == 'string')
|
161 | msg = JSON.parse(msg);
|
162 | }
|
163 | catch (err) { }
|
164 |
|
165 |
|
166 |
|
167 | if (Object.prototype.toString.call(msg) !== '[object Array]') {
|
168 | return;
|
169 | }
|
170 |
|
171 | if (!msg.length) return;
|
172 |
|
173 | var command = msg.shift();
|
174 | var params = msg;
|
175 |
|
176 | if (!command) {
|
177 | console.log('Every message must have "command" and optionaly "params" properties');
|
178 | return;
|
179 | }
|
180 |
|
181 | var reservedWords = ['Join', 'Leave'];
|
182 | if (command in reservedWords) {
|
183 | console.log('Reserved words cant be used as command:', reservedWords);
|
184 | return;
|
185 | }
|
186 |
|
187 | command = 'on' + command;
|
188 |
|
189 |
|
190 |
|
191 | if (typeof gameTable[command] != 'function') {
|
192 | console.log('GameTable method not found with name:', command);
|
193 | return;
|
194 | }
|
195 |
|
196 | gameTable[command].apply(gameTable, params);
|
197 | });
|
198 |
|
199 |
|
200 |
|
201 | socket.on('close', () => {
|
202 |
|
203 |
|
204 | disconnected = true;
|
205 |
|
206 |
|
207 | if (!userid) return;
|
208 |
|
209 |
|
210 | this.UsersCount--;
|
211 |
|
212 |
|
213 | gameTable && gameTable.leave(userid);
|
214 |
|
215 | if (!gameTable.Players.length) {
|
216 | var index = this.GameTables.indexOf(gameTable);
|
217 |
|
218 | if (index > -1)
|
219 | this.GameTables.splice(index, 1);
|
220 | }
|
221 | });
|
222 | }
|
223 |
|
224 |
|
225 |
|
226 | findTable(user, channel: string, mode: number): TGameTable {
|
227 |
|
228 |
|
229 |
|
230 | var table = this.GameTables.filter(t =>
|
231 | (t.Players.filter(p => p.UserID == user.UserID)[0] != undefined) &&
|
232 | (t.Status == TableStatus.Started) &&
|
233 | (t.Status != TableStatus.Finished)
|
234 | )[0]
|
235 | if (table) return table;
|
236 |
|
237 |
|
238 |
|
239 | table = this.GameTables.filter(t =>
|
240 | t.Channel == channel &&
|
241 | t.Mode == mode &&
|
242 | t.Players.length < t.MaxPlayersCount &&
|
243 | (t.Status != TableStatus.Started) &&
|
244 | (t.Status != TableStatus.Finished) &&
|
245 | this.isTournamentValid(channel, t, user)
|
246 | )[0]
|
247 | if (table) return table;
|
248 |
|
249 |
|
250 |
|
251 | if (!this.createTable) return;
|
252 |
|
253 | table = this.createTable(user, channel, mode);
|
254 | if (!table) return;
|
255 |
|
256 | this.GameTables.push(table);
|
257 |
|
258 | return table;
|
259 | }
|
260 |
|
261 |
|
262 | createTable(user, channel, mode): TGameTable {
|
263 | return new this.GameTableClass(this.GamePlayerClass, channel, mode, 2, this.isTournamentChannel(channel) ? user.IsVIP : false);
|
264 | }
|
265 |
|
266 | isTournamentValid(channel: string, table: TGameTable, user): boolean {
|
267 |
|
268 | if (!this.isTournamentChannel(channel))
|
269 | return true;
|
270 |
|
271 | return (user.IsVIP == table.IsVIPTable);
|
272 | }
|
273 |
|
274 | isTournamentChannel(channel: string) {
|
275 | return (channel == 'tournament');
|
276 | }
|
277 |
|
278 |
|
279 |
|
280 | public static Start<TGamePlayer extends GamePlayerBase, TGameTable extends GameTableBase<TGamePlayer>>(port?, TGameTable?, TGamePlayerClass?) {
|
281 | return new Server<TGamePlayer, TGameTable>(port, TGameTable, TGamePlayerClass);
|
282 | }
|
283 | }
|
284 |
|
285 |
|
286 | exports.Server = Server;
|
287 | exports.Helper = Helper;
|
288 | exports.GameTableBase = GameTableBase;
|
289 | exports.GamePlayerBase = GamePlayerBase;
|
290 | exports.TableStatus = TableStatus;
|