snakeia-server 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.drone.yml +2 -1
- package/GameEngineMultithreading.js +97 -44
- package/GameEngineMultithreadingController.js +136 -105
- package/Player.js +132 -0
- package/README.md +54 -6
- package/assets/css/dark-theme.css +103 -0
- package/config/default.json +4 -1
- package/package.json +10 -10
- package/server.js +174 -197
- package/views/admin.html +7 -0
- package/views/authentication.html +7 -0
- package/views/banned.html +7 -0
- package/views/index.html +7 -0
package/server.js
CHANGED
|
@@ -40,12 +40,8 @@ const { doubleCsrf } = require("csrf-csrf");
|
|
|
40
40
|
const bodyParser = require("body-parser");
|
|
41
41
|
const node_config = require("config");
|
|
42
42
|
|
|
43
|
-
const games = {}; // Contains all the games processed by the server
|
|
44
43
|
process.env["ALLOW_CONFIG_MUTATIONS"] = true;
|
|
45
44
|
let config = node_config.get("ServerConfig"); // Server configuration (see default config file config.json)
|
|
46
|
-
const tokens = []; // User tokens
|
|
47
|
-
const invalidatedUserTokens = []; // Invalidated user tokens
|
|
48
|
-
const invalidatedAdminTokens = []; // Invalidated admin tokens
|
|
49
45
|
|
|
50
46
|
// Load config file
|
|
51
47
|
const configSources = node_config.util.getConfigSources();
|
|
@@ -96,114 +92,17 @@ i18n.configure({
|
|
|
96
92
|
|
|
97
93
|
// Game modules
|
|
98
94
|
const snakeia = require("snakeia");
|
|
95
|
+
const { randomUUID } = require("crypto");
|
|
99
96
|
const Snake = snakeia.Snake;
|
|
100
97
|
const Grid = snakeia.Grid;
|
|
101
98
|
const GameConstants = snakeia.GameConstants;
|
|
102
99
|
const GameEngine = config.enableMultithreading ? require("./GameEngineMultithreadingController")(logger) : snakeia.GameEngine;
|
|
100
|
+
const Player = require("./Player");
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.snake = snake;
|
|
109
|
-
this.ready = ready;
|
|
110
|
-
this.version = version;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
get username() {
|
|
114
|
-
return Player.getUsername(this);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
static getPlayer(array, id) {
|
|
118
|
-
for(let i = 0; i < array.length; i++) {
|
|
119
|
-
if(array[i] != null && array[i].id == id) {
|
|
120
|
-
return array[i];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
static getPlayerAllGames(id) {
|
|
128
|
-
const keys = Object.keys(games);
|
|
129
|
-
|
|
130
|
-
for(let i = 0; i < keys.length; i++) {
|
|
131
|
-
const game = games[keys[i]];
|
|
132
|
-
|
|
133
|
-
if(game) {
|
|
134
|
-
const p = this.getPlayer(game.players, id);
|
|
135
|
-
const p2 = this.getPlayer(game.spectators, id);
|
|
136
|
-
if(p) return p;
|
|
137
|
-
if(p2) return p2;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
static getPlayerToken(array, token) {
|
|
145
|
-
if(!token) return null;
|
|
146
|
-
for(let i = 0; i < array.length; i++) {
|
|
147
|
-
if(array[i] != null && array[i].token == token) {
|
|
148
|
-
return array[i];
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
static getPlayerAllGamesToken(token) {
|
|
156
|
-
if(!token) return null;
|
|
157
|
-
const keys = Object.keys(games);
|
|
158
|
-
|
|
159
|
-
for(let i = 0; i < keys.length; i++) {
|
|
160
|
-
const game = games[keys[i]];
|
|
161
|
-
|
|
162
|
-
if(game) {
|
|
163
|
-
const p = this.getPlayerToken(game.players, token);
|
|
164
|
-
const p2 = this.getPlayerToken(game.spectators, token);
|
|
165
|
-
if(p) return p;
|
|
166
|
-
if(p2) return p2;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
static containsId(array, id) {
|
|
174
|
-
return Player.getPlayer(array, id) != null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
static containsToken(array, token) {
|
|
178
|
-
return Player.getPlayerToken(array, token) != null;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
static containsIdAllGames(id) {
|
|
182
|
-
return Player.getPlayerAllGames(id) != null;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
static containsTokenAllGames(token) {
|
|
186
|
-
return Player.getPlayerAllGamesToken(token) != null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
static getUsername(player) {
|
|
190
|
-
try {
|
|
191
|
-
const decoded_token = jwt.verify(player.token, jsonWebTokenSecretKey);
|
|
192
|
-
return decoded_token && decoded_token.username ? decoded_token.username : null;
|
|
193
|
-
} catch(e) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
static getUsernameSocket(socket) {
|
|
199
|
-
try {
|
|
200
|
-
const decoded_token = jwt.verify(socket.handshake.auth.token || socket.handshake.query.token || socket.request.cookies.token, jsonWebTokenSecretKey);
|
|
201
|
-
return decoded_token && decoded_token.username ? decoded_token.username : null;
|
|
202
|
-
} catch(e) {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
102
|
+
const games = {}; // Contains all the games processed by the server
|
|
103
|
+
const tokens = []; // User tokens
|
|
104
|
+
const invalidatedUserTokens = []; // Invalidated user tokens
|
|
105
|
+
const invalidatedAdminTokens = []; // Invalidated admin tokens
|
|
207
106
|
|
|
208
107
|
function getRoomsData() {
|
|
209
108
|
const rooms = [];
|
|
@@ -218,20 +117,20 @@ function getRoomsData() {
|
|
|
218
117
|
rooms[i]["players"] = Object.keys(game["players"]).length + game.numberAIToAdd;
|
|
219
118
|
rooms[i]["width"] = "???";
|
|
220
119
|
rooms[i]["height"] = "???";
|
|
221
|
-
rooms[i]["speed"] = game["
|
|
120
|
+
rooms[i]["speed"] = game["gameEngine"].speed;
|
|
222
121
|
rooms[i]["code"] = keysRooms[i];
|
|
223
122
|
rooms[i]["maxPlayers"] = getMaxPlayers(keysRooms[i]);
|
|
224
123
|
rooms[i]["state"] = (game["started"] ? GameConstants.GameState.STARTED : game["timeoutPlay"] != null ? GameConstants.GameState.STARTING : game["searchingPlayers"] ? GameConstants.GameState.SEARCHING_PLAYERS : "");
|
|
225
124
|
|
|
226
|
-
if(game["
|
|
227
|
-
rooms[i]["width"] = game["
|
|
228
|
-
rooms[i]["height"] = game["
|
|
229
|
-
rooms[i]["borderWalls"] = game["
|
|
230
|
-
rooms[i]["generateWalls"] = game["
|
|
125
|
+
if(game["gameEngine"].grid != null) {
|
|
126
|
+
rooms[i]["width"] = game["gameEngine"].grid.width;
|
|
127
|
+
rooms[i]["height"] = game["gameEngine"].grid.height;
|
|
128
|
+
rooms[i]["borderWalls"] = game["gameEngine"].grid.borderWalls;
|
|
129
|
+
rooms[i]["generateWalls"] = game["gameEngine"].grid.generateWalls;
|
|
231
130
|
}
|
|
232
131
|
|
|
233
|
-
if(game["
|
|
234
|
-
rooms[i]["players"] = game["
|
|
132
|
+
if(game["gameEngine"].snake != null) {
|
|
133
|
+
rooms[i]["players"] = game["gameEngine"].snake.length;
|
|
235
134
|
}
|
|
236
135
|
|
|
237
136
|
if(game["spectators"] != null) {
|
|
@@ -263,7 +162,7 @@ function generateRandomJsonWebTokenSecretKey(precValue) {
|
|
|
263
162
|
}
|
|
264
163
|
|
|
265
164
|
function getMaxPlayers(code) {
|
|
266
|
-
const game = games[code].
|
|
165
|
+
const game = games[code].gameEngine;
|
|
267
166
|
|
|
268
167
|
const heightGrid = parseInt(game.grid.height);
|
|
269
168
|
const widthGrid = parseInt(game.grid.width);
|
|
@@ -286,7 +185,7 @@ function getMaxPlayers(code) {
|
|
|
286
185
|
}
|
|
287
186
|
|
|
288
187
|
function createRoom(data, socket) {
|
|
289
|
-
if(Object.keys(games).filter(key => games[key] != null).length < config.maxRooms && !Player.containsTokenAllGames(socket
|
|
188
|
+
if(Object.keys(games).filter(key => games[key] != null).length < config.maxRooms && !Player.containsTokenAllGames(socket?.handshake?.auth?.token || socket?.handshake?.query?.token || socket?.request?.cookies?.token, games) && !Player.containsIdAllGames(socket.id, games)) {
|
|
290
189
|
let heightGrid = 20;
|
|
291
190
|
let widthGrid = 20;
|
|
292
191
|
let borderWalls = false;
|
|
@@ -356,10 +255,14 @@ function createRoom(data, socket) {
|
|
|
356
255
|
const grid = new Grid(widthGrid, heightGrid, generateWalls, borderWalls, false, null, false);
|
|
357
256
|
grid.reset();
|
|
358
257
|
grid.init();
|
|
359
|
-
const game = new GameEngine(grid, [], speed
|
|
258
|
+
const game = new GameEngine(grid, [], speed, null, null, null, null, null, {
|
|
259
|
+
modelListAPIURL: config.aiUltraAPIURL,
|
|
260
|
+
modelID: config.aiUltraCustomModelURL ? "custom" : config.aiUltraModelID,
|
|
261
|
+
customURL: config.aiUltraCustomModelURL
|
|
262
|
+
});
|
|
360
263
|
|
|
361
264
|
games[code] = {
|
|
362
|
-
|
|
265
|
+
gameEngine: game,
|
|
363
266
|
private: privateGame,
|
|
364
267
|
players: [],
|
|
365
268
|
spectators: [],
|
|
@@ -401,7 +304,7 @@ function createRoom(data, socket) {
|
|
|
401
304
|
});
|
|
402
305
|
}
|
|
403
306
|
} else if(socket != null) {
|
|
404
|
-
if(Player.containsTokenAllGames(socket
|
|
307
|
+
if(Player.containsTokenAllGames(socket?.handshake?.auth?.token || socket?.handshake?.query?.token || socket?.request?.cookies?.token, games) || Player.containsIdAllGames(socket.id, games)) {
|
|
405
308
|
socket.emit("process", {
|
|
406
309
|
success: false,
|
|
407
310
|
code: null,
|
|
@@ -418,20 +321,57 @@ function createRoom(data, socket) {
|
|
|
418
321
|
}
|
|
419
322
|
|
|
420
323
|
function copySnakes(snakes) {
|
|
421
|
-
const
|
|
324
|
+
const snakesCopy = [];
|
|
422
325
|
|
|
423
|
-
if(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
326
|
+
if(snakes) {
|
|
327
|
+
snakes.forEach(snake => {
|
|
328
|
+
if(snake) {
|
|
329
|
+
const snakeCopy = new Snake();
|
|
330
|
+
|
|
331
|
+
snakeCopy.color = snake.color;
|
|
332
|
+
snakeCopy.direction = snake.direction;
|
|
333
|
+
snakeCopy.errorInit = snake.errorInit;
|
|
334
|
+
snakeCopy.gameOver = snake.gameOver;
|
|
335
|
+
snakeCopy.autoRetry = snake.autoRetry;
|
|
336
|
+
snakeCopy.aiLevel = snake.aiLevel;
|
|
337
|
+
|
|
338
|
+
if(snake.lastTail) {
|
|
339
|
+
snakeCopy.lastTail = JSON.parse(JSON.stringify(snake.lastTail));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if(snake.lastHead) {
|
|
343
|
+
snakeCopy.lastHead = JSON.parse(JSON.stringify(snake.lastHead));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
snakeCopy.lastTailMoved = snake.lastTailMoved;
|
|
347
|
+
snakeCopy.lastHeadMoved = snake.lastHeadMoved;
|
|
348
|
+
snakeCopy.name = snake.name;
|
|
349
|
+
snakeCopy.player = snake.player;
|
|
350
|
+
|
|
351
|
+
if(snake.queue) {
|
|
352
|
+
snakeCopy.queue = JSON.parse(JSON.stringify(snake.queue));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
snakeCopy.score = snake.score;
|
|
356
|
+
snakeCopy.scoreMax = snake.scoreMax;
|
|
357
|
+
snakeCopy.ticksDead = snake.ticksDead;
|
|
358
|
+
snakeCopy.ticksWithoutAction = snake.ticksWithoutAction;
|
|
359
|
+
snakeCopy.grid = null;
|
|
360
|
+
|
|
361
|
+
if(snake.snakeAI && snake.snakeAI.aiLevelText) {
|
|
362
|
+
snakeCopy.snakeAI.aiLevelText = snake.snakeAI.aiLevelText;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
snakesCopy.push(snakeCopy);
|
|
366
|
+
}
|
|
427
367
|
});
|
|
428
368
|
}
|
|
429
369
|
|
|
430
|
-
return
|
|
370
|
+
return snakesCopy;
|
|
431
371
|
}
|
|
432
372
|
|
|
433
373
|
function setupRoom(code) {
|
|
434
|
-
const game = games[code].
|
|
374
|
+
const game = games[code].gameEngine;
|
|
435
375
|
|
|
436
376
|
game.onReset(() => {
|
|
437
377
|
io.to("room-" + code).emit("reset", {
|
|
@@ -621,8 +561,8 @@ function cleanRooms() {
|
|
|
621
561
|
if(nb <= 0) {
|
|
622
562
|
toRemove.push(keys[i]);
|
|
623
563
|
|
|
624
|
-
if(game.
|
|
625
|
-
game.
|
|
564
|
+
if(game.gameEngine && game.gameEngine.kill) {
|
|
565
|
+
game.gameEngine.kill();
|
|
626
566
|
}
|
|
627
567
|
}
|
|
628
568
|
}
|
|
@@ -671,13 +611,14 @@ function gameMatchmaking(game, code) {
|
|
|
671
611
|
"playerNumber": numberPlayers,
|
|
672
612
|
"maxPlayers": getMaxPlayers(code),
|
|
673
613
|
"spectatorMode": false,
|
|
674
|
-
"errorOccurred": game.
|
|
614
|
+
"errorOccurred": game.gameEngine.errorOccurred,
|
|
675
615
|
"onlineMaster": false,
|
|
676
616
|
"onlineMode": true,
|
|
677
617
|
"enableRetryPauseMenu": false,
|
|
678
618
|
"countBeforePlay": game.countBeforePlay,
|
|
679
|
-
"initialSpeed": game.
|
|
680
|
-
"speed": game.
|
|
619
|
+
"initialSpeed": game.gameEngine.initialSpeed,
|
|
620
|
+
"speed": game.gameEngine.speed,
|
|
621
|
+
"errorOccured": game.gameEngine.errorOccured
|
|
681
622
|
});
|
|
682
623
|
|
|
683
624
|
io.to(game.players[0].id).emit("init", {
|
|
@@ -689,7 +630,7 @@ function gameMatchmaking(game, code) {
|
|
|
689
630
|
setupSpectators(code);
|
|
690
631
|
}
|
|
691
632
|
|
|
692
|
-
function startGame(code) {
|
|
633
|
+
async function startGame(code) {
|
|
693
634
|
const game = games[code];
|
|
694
635
|
|
|
695
636
|
if(game != null) {
|
|
@@ -700,15 +641,15 @@ function startGame(code) {
|
|
|
700
641
|
|
|
701
642
|
game.searchingPlayers = false;
|
|
702
643
|
game.started = true;
|
|
703
|
-
game.
|
|
704
|
-
game.
|
|
705
|
-
game.
|
|
644
|
+
game.gameEngine.snakes = [];
|
|
645
|
+
game.gameEngine.grid.reset();
|
|
646
|
+
game.gameEngine.grid.init();
|
|
706
647
|
|
|
707
648
|
for(let i = 0; i < game.players.length; i++) {
|
|
708
649
|
const username = game.players[i].username;
|
|
709
650
|
|
|
710
|
-
game.players[i].snake = new Snake(null, null, game.
|
|
711
|
-
game.
|
|
651
|
+
game.players[i].snake = new Snake(null, null, game.gameEngine.grid, null, null, null, username);
|
|
652
|
+
game.gameEngine.snakes.push(game.players[i].snake);
|
|
712
653
|
|
|
713
654
|
io.to(game.players[i].id).emit("init", {
|
|
714
655
|
"currentPlayer": (i + 1),
|
|
@@ -718,29 +659,38 @@ function startGame(code) {
|
|
|
718
659
|
|
|
719
660
|
if(game.enableAI) {
|
|
720
661
|
for(let i = 0; i < game.numberAIToAdd; i++) {
|
|
721
|
-
const snakeAI = new Snake(null, null, game.
|
|
722
|
-
game.
|
|
662
|
+
const snakeAI = new Snake(null, null, game.gameEngine.grid, GameConstants.PlayerType.AI, game.levelAI);
|
|
663
|
+
game.gameEngine.snakes.push(snakeAI);
|
|
723
664
|
}
|
|
724
665
|
}
|
|
725
666
|
|
|
726
667
|
if(config.enableMaxTimeGame) {
|
|
727
668
|
clearTimeout(game.timeoutMaxTimePlay);
|
|
728
|
-
game.
|
|
669
|
+
game.gameEngine.timeStart = Date.now() + 5000;
|
|
729
670
|
game.timeoutMaxTimePlay = setTimeout(() => {
|
|
730
|
-
game.
|
|
671
|
+
game.gameEngine.stop(true);
|
|
731
672
|
}, config.maxTimeGame + 5000);
|
|
732
673
|
}
|
|
674
|
+
|
|
675
|
+
io.to("room-" + code).emit("init", {
|
|
676
|
+
"engineLoading": true
|
|
677
|
+
});
|
|
733
678
|
|
|
734
679
|
if(!game.alreadyInit) {
|
|
735
|
-
game.
|
|
736
|
-
game.
|
|
680
|
+
await game.gameEngine.init();
|
|
681
|
+
game.gameEngine.start();
|
|
737
682
|
game.alreadyInit = true;
|
|
738
683
|
} else {
|
|
739
|
-
game.
|
|
740
|
-
game.
|
|
741
|
-
game.
|
|
684
|
+
game.gameEngine.countBeforePlay = 3;
|
|
685
|
+
await game.gameEngine.init();
|
|
686
|
+
game.gameEngine.reset();
|
|
742
687
|
}
|
|
743
688
|
|
|
689
|
+
io.to("room-" + code).emit("init", {
|
|
690
|
+
"errorOccurred": game.gameEngine.errorOccurred,
|
|
691
|
+
"engineLoading": false
|
|
692
|
+
});
|
|
693
|
+
|
|
744
694
|
setupSpectators(code);
|
|
745
695
|
}
|
|
746
696
|
}
|
|
@@ -753,7 +703,8 @@ function setupSpectators(code) {
|
|
|
753
703
|
io.to(game.spectators[i].id).emit("init", {
|
|
754
704
|
"spectatorMode": true,
|
|
755
705
|
"onlineMode": true,
|
|
756
|
-
"enableRetryPauseMenu": false
|
|
706
|
+
"enableRetryPauseMenu": false,
|
|
707
|
+
"engineLoading": false
|
|
757
708
|
});
|
|
758
709
|
}
|
|
759
710
|
}
|
|
@@ -765,8 +716,8 @@ function sendStatus(code) {
|
|
|
765
716
|
|
|
766
717
|
if(game != null) {
|
|
767
718
|
for(let i = 0; i < game.players.length; i++) {
|
|
768
|
-
game.
|
|
769
|
-
if(game.players[i].snake.gameOver) game.
|
|
719
|
+
game.gameEngine.key(game.players[i].snake.lastKey, i + 1);
|
|
720
|
+
if(game.players[i].snake.gameOver) game.gameEngine.setGameOver(i + 1);
|
|
770
721
|
}
|
|
771
722
|
}
|
|
772
723
|
}
|
|
@@ -908,14 +859,16 @@ app.use(i18n.init);
|
|
|
908
859
|
// Rate limiter
|
|
909
860
|
app.use("/authentication", rateLimit({
|
|
910
861
|
windowMs: config.authentWindowMs,
|
|
911
|
-
max: config.authentMaxRequest
|
|
862
|
+
max: config.authentMaxRequest,
|
|
863
|
+
validate: { trustProxy: false }
|
|
912
864
|
}));
|
|
913
865
|
|
|
914
866
|
// IP ban
|
|
915
867
|
app.use(function(req, res, next) {
|
|
916
868
|
ipBanned(req.ip).then(() => {
|
|
917
869
|
res.render(__dirname + "/views/banned.html", {
|
|
918
|
-
contact: config.contactBan
|
|
870
|
+
contact: config.contactBan,
|
|
871
|
+
theme: req.query.theme
|
|
919
872
|
});
|
|
920
873
|
res.end();
|
|
921
874
|
}, () => {
|
|
@@ -926,7 +879,8 @@ app.use(function(req, res, next) {
|
|
|
926
879
|
app.get("/", function(req, res) {
|
|
927
880
|
res.render(__dirname + "/views/index.html", {
|
|
928
881
|
version: config.version,
|
|
929
|
-
engineVersion: GameConstants.Setting.APP_VERSION
|
|
882
|
+
engineVersion: GameConstants.Setting.APP_VERSION,
|
|
883
|
+
theme: req.query.theme
|
|
930
884
|
});
|
|
931
885
|
});
|
|
932
886
|
|
|
@@ -948,7 +902,8 @@ app.get("/authentication", function(req, res) {
|
|
|
948
902
|
min: config.minCharactersUsername,
|
|
949
903
|
max: config.maxCharactersUsername,
|
|
950
904
|
enableMaxTimeGame: config.enableMaxTimeGame,
|
|
951
|
-
maxTimeGame: config.maxTimeGame
|
|
905
|
+
maxTimeGame: config.maxTimeGame,
|
|
906
|
+
theme: req.query.theme
|
|
952
907
|
});
|
|
953
908
|
});
|
|
954
909
|
} else {
|
|
@@ -985,7 +940,8 @@ app.post("/authentication", function(req, res) {
|
|
|
985
940
|
min: config.minCharactersUsername,
|
|
986
941
|
max: config.maxCharactersUsername,
|
|
987
942
|
enableMaxTimeGame: config.enableMaxTimeGame,
|
|
988
|
-
maxTimeGame: config.maxTimeGame
|
|
943
|
+
maxTimeGame: config.maxTimeGame,
|
|
944
|
+
theme: req.query.theme
|
|
989
945
|
});
|
|
990
946
|
|
|
991
947
|
logger.info("authentication - username: " + username + " - ip: " + req.ip);
|
|
@@ -1007,7 +963,8 @@ app.post("/authentication", function(req, res) {
|
|
|
1007
963
|
min: config.minCharactersUsername,
|
|
1008
964
|
max: config.maxCharactersUsername,
|
|
1009
965
|
enableMaxTimeGame: config.enableMaxTimeGame,
|
|
1010
|
-
maxTimeGame: config.maxTimeGame
|
|
966
|
+
maxTimeGame: config.maxTimeGame,
|
|
967
|
+
theme: req.query.theme
|
|
1011
968
|
});
|
|
1012
969
|
});
|
|
1013
970
|
}
|
|
@@ -1030,9 +987,11 @@ app.get("/rooms", function(req, res) {
|
|
|
1030
987
|
|
|
1031
988
|
// Admin panel
|
|
1032
989
|
function kickUser(socketId, token) {
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
990
|
+
const sockets = io.of("/").sockets;
|
|
991
|
+
|
|
992
|
+
if(sockets.get(socketId)) {
|
|
993
|
+
logger.info("user kicked (socket: " + socketId + ") - ip: " + getIPSocketIO(sockets.get(socketId).handshake));
|
|
994
|
+
sockets.get(socketId).disconnect(true);
|
|
1036
995
|
invalidateUserToken(token);
|
|
1037
996
|
}
|
|
1038
997
|
}
|
|
@@ -1058,8 +1017,10 @@ function invalidateUserToken(token) {
|
|
|
1058
1017
|
}
|
|
1059
1018
|
|
|
1060
1019
|
function banUserIP(socketId) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1020
|
+
const sockets = io.of("/").sockets;
|
|
1021
|
+
|
|
1022
|
+
if(sockets.get(socketId)) {
|
|
1023
|
+
let ip = getIPSocketIO(sockets.get(socketId).handshake);
|
|
1063
1024
|
|
|
1064
1025
|
if(ip && ip.substr(0, 7) == "::ffff:") {
|
|
1065
1026
|
ip = ip.substr(7, ip.length);
|
|
@@ -1168,8 +1129,16 @@ function verifyFormAuthenticationAdmin(body) {
|
|
|
1168
1129
|
}
|
|
1169
1130
|
|
|
1170
1131
|
const csrfSecret = generateRandomJsonWebTokenSecretKey(jsonWebTokenSecretKeyAdmin);
|
|
1171
|
-
const { doubleCsrfProtection,
|
|
1132
|
+
const { doubleCsrfProtection, generateCsrfToken } = doubleCsrf({
|
|
1172
1133
|
getSecret: () => csrfSecret,
|
|
1134
|
+
getSessionIdentifier: (req) => req.cookies.tokenAdmin || randomUUID(),
|
|
1135
|
+
getCsrfTokenFromRequest: (req) => {
|
|
1136
|
+
return (
|
|
1137
|
+
req.headers["x-csrf-token"] ||
|
|
1138
|
+
req.body?._csrf ||
|
|
1139
|
+
req.query?._csrf
|
|
1140
|
+
);
|
|
1141
|
+
},
|
|
1173
1142
|
cookieName: productionMode ? "__Host-snakeia-server.x-csrf-token" : "snakeia-server.x-csrf-token",
|
|
1174
1143
|
cookieOptions: {
|
|
1175
1144
|
sameSite: productionMode ? "strict" : "lax",
|
|
@@ -1206,10 +1175,11 @@ app.get("/admin", doubleCsrfProtection, function(req, res) {
|
|
|
1206
1175
|
games: games,
|
|
1207
1176
|
io: io,
|
|
1208
1177
|
config: config,
|
|
1209
|
-
csrfToken:
|
|
1178
|
+
csrfToken: generateCsrfToken(req, res, { overwrite: true, validateOnReuse: true }),
|
|
1210
1179
|
serverLog: logFile,
|
|
1211
1180
|
errorLog: errorLogFile,
|
|
1212
|
-
getIPSocketIO: getIPSocketIO
|
|
1181
|
+
getIPSocketIO: getIPSocketIO,
|
|
1182
|
+
theme: req.query.theme
|
|
1213
1183
|
});
|
|
1214
1184
|
});
|
|
1215
1185
|
});
|
|
@@ -1311,7 +1281,8 @@ app.use(function (err, req, res, next) {
|
|
|
1311
1281
|
|
|
1312
1282
|
const adminRateLimiter = rateLimit({
|
|
1313
1283
|
windowMs: config.authentWindowMs,
|
|
1314
|
-
max: config.authentMaxRequest
|
|
1284
|
+
max: config.authentMaxRequest,
|
|
1285
|
+
validate: { trustProxy: false }
|
|
1315
1286
|
});
|
|
1316
1287
|
|
|
1317
1288
|
app.post("/admin", adminRateLimiter, function(req, res) {
|
|
@@ -1345,7 +1316,8 @@ app.post("/admin", adminRateLimiter, function(req, res) {
|
|
|
1345
1316
|
csrfToken: null,
|
|
1346
1317
|
serverLog: null,
|
|
1347
1318
|
errorLog: null,
|
|
1348
|
-
getIPSocketIO: getIPSocketIO
|
|
1319
|
+
getIPSocketIO: getIPSocketIO,
|
|
1320
|
+
theme: req.query.theme
|
|
1349
1321
|
});
|
|
1350
1322
|
});
|
|
1351
1323
|
}
|
|
@@ -1395,22 +1367,24 @@ function checkAuthentication(token) {
|
|
|
1395
1367
|
}
|
|
1396
1368
|
|
|
1397
1369
|
function checkAuthenticationSocket(socket) {
|
|
1398
|
-
return checkAuthentication(socket
|
|
1370
|
+
return checkAuthentication(socket?.handshake?.auth?.token || socket?.handshake?.query?.token || socket?.request?.cookies?.token);
|
|
1399
1371
|
}
|
|
1400
1372
|
|
|
1401
1373
|
function checkAuthenticationExpress(req) {
|
|
1402
1374
|
return checkAuthentication(req.cookies.token);
|
|
1403
1375
|
}
|
|
1404
1376
|
|
|
1405
|
-
|
|
1377
|
+
const checkBanned = function(socket, next) {
|
|
1406
1378
|
ipBanned(getIPSocketIO(socket.handshake)).then(() => {
|
|
1407
1379
|
next(new Error(GameConstants.Error.BANNED));
|
|
1408
1380
|
}, () => {
|
|
1409
1381
|
next();
|
|
1410
1382
|
});
|
|
1411
|
-
}
|
|
1383
|
+
};
|
|
1384
|
+
|
|
1385
|
+
io.use(checkBanned);
|
|
1412
1386
|
|
|
1413
|
-
io.of("/rooms").on("connection", function(socket) {
|
|
1387
|
+
io.of("/rooms").use(ioCookieParser()).use(checkBanned).on("connection", function(socket) {
|
|
1414
1388
|
checkAuthenticationSocket(socket).then(() => {
|
|
1415
1389
|
socket.emit("rooms", {
|
|
1416
1390
|
rooms: getRoomsData(),
|
|
@@ -1430,7 +1404,7 @@ io.of("/rooms").on("connection", function(socket) {
|
|
|
1430
1404
|
});
|
|
1431
1405
|
});
|
|
1432
1406
|
|
|
1433
|
-
io.of("/createRoom").on("connection", function(socket) {
|
|
1407
|
+
io.of("/createRoom").use(ioCookieParser()).use(checkBanned).on("connection", function(socket) {
|
|
1434
1408
|
socket.on("create", function(data) {
|
|
1435
1409
|
checkAuthenticationSocket(socket).then(() => {
|
|
1436
1410
|
createRoom(data, socket);
|
|
@@ -1450,7 +1424,7 @@ io.on("connection", function(socket) {
|
|
|
1450
1424
|
const version = data.version;
|
|
1451
1425
|
const game = games[code];
|
|
1452
1426
|
|
|
1453
|
-
if(game != null && !Player.containsId(game.players, socket.id) && !Player.containsId(game.spectators, socket.id) && !Player.containsToken(game.players, token) && !Player.containsToken(game.spectators, token) && !Player.containsTokenAllGames(token) && !Player.containsIdAllGames(socket.id)) {
|
|
1427
|
+
if(game != null && !Player.containsId(game.players, socket.id) && !Player.containsId(game.spectators, socket.id) && !Player.containsToken(game.players, token) && !Player.containsToken(game.spectators, token) && !Player.containsTokenAllGames(token, games) && !Player.containsIdAllGames(socket.id, games)) {
|
|
1454
1428
|
socket.join("room-" + code);
|
|
1455
1429
|
|
|
1456
1430
|
if(game.players.length + game.numberAIToAdd >= getMaxPlayers(code) || game.started) {
|
|
@@ -1470,37 +1444,40 @@ io.on("connection", function(socket) {
|
|
|
1470
1444
|
|
|
1471
1445
|
if(game.started) {
|
|
1472
1446
|
socket.emit("init", {
|
|
1473
|
-
"paused": game.
|
|
1474
|
-
"isReseted": game.
|
|
1475
|
-
"exited": game.
|
|
1476
|
-
"snakes": copySnakes(game.
|
|
1477
|
-
"grid": game.
|
|
1478
|
-
"numFruit": game.
|
|
1479
|
-
"ticks": game.
|
|
1480
|
-
"scoreMax": game.
|
|
1481
|
-
"gameOver": game.
|
|
1482
|
-
"gameFinished": game.
|
|
1483
|
-
"gameMazeWin": game.
|
|
1484
|
-
"starting": game.
|
|
1485
|
-
"initialSpeed": game.
|
|
1486
|
-
"speed": game.
|
|
1487
|
-
"offsetFrame": game.
|
|
1447
|
+
"paused": game.gameEngine.paused,
|
|
1448
|
+
"isReseted": game.gameEngine.isReseted,
|
|
1449
|
+
"exited": game.gameEngine.exited,
|
|
1450
|
+
"snakes": copySnakes(game.gameEngine.snakes),
|
|
1451
|
+
"grid": game.gameEngine.grid,
|
|
1452
|
+
"numFruit": game.gameEngine.numFruit,
|
|
1453
|
+
"ticks": game.gameEngine.ticks,
|
|
1454
|
+
"scoreMax": game.gameEngine.scoreMax,
|
|
1455
|
+
"gameOver": game.gameEngine.gameOver,
|
|
1456
|
+
"gameFinished": game.gameEngine.gameFinished,
|
|
1457
|
+
"gameMazeWin": game.gameEngine.gameMazeWin,
|
|
1458
|
+
"starting": game.gameEngine.starting,
|
|
1459
|
+
"initialSpeed": game.gameEngine.initialSpeed,
|
|
1460
|
+
"speed": game.gameEngine.speed,
|
|
1461
|
+
"offsetFrame": game.gameEngine.speed * GameConstants.Setting.TIME_MULTIPLIER,
|
|
1488
1462
|
"confirmReset": false,
|
|
1489
1463
|
"confirmExit": false,
|
|
1490
1464
|
"getInfos": false,
|
|
1491
1465
|
"getInfosGame": false,
|
|
1492
|
-
"errorOccurred": game.
|
|
1466
|
+
"errorOccurred": game.gameEngine.errorOccurred,
|
|
1493
1467
|
"timerToDisplay": config.enableMaxTimeGame ? (config.maxTimeGame - (Date.now() - game.timeStart)) / 1000 : -1,
|
|
1494
|
-
"countBeforePlay": game.
|
|
1495
|
-
"aiStuck": game.
|
|
1496
|
-
"precAiStuck": false
|
|
1468
|
+
"countBeforePlay": game.gameEngine.countBeforePlay,
|
|
1469
|
+
"aiStuck": game.gameEngine.aiStuck,
|
|
1470
|
+
"precAiStuck": false,
|
|
1471
|
+
"enablePause": game.gameEngine.enablePause,
|
|
1472
|
+
"enableRetry": game.gameEngine.enableRetry,
|
|
1473
|
+
"progressiveSpeed": game.gameEngine.progressiveSpeed
|
|
1497
1474
|
});
|
|
1498
1475
|
} else {
|
|
1499
1476
|
socket.emit("init", {
|
|
1500
|
-
"enablePause": game.
|
|
1501
|
-
"enableRetry": game.
|
|
1502
|
-
"progressiveSpeed": game.
|
|
1503
|
-
"offsetFrame": game.
|
|
1477
|
+
"enablePause": game.gameEngine.enablePause,
|
|
1478
|
+
"enableRetry": game.gameEngine.enableRetry,
|
|
1479
|
+
"progressiveSpeed": game.gameEngine.progressiveSpeed,
|
|
1480
|
+
"offsetFrame": game.gameEngine.speed * GameConstants.Setting.TIME_MULTIPLIER
|
|
1504
1481
|
});
|
|
1505
1482
|
}
|
|
1506
1483
|
|
|
@@ -1573,7 +1550,7 @@ io.on("connection", function(socket) {
|
|
|
1573
1550
|
success: false,
|
|
1574
1551
|
errorCode: GameConstants.Error.ROOM_ALREADY_JOINED
|
|
1575
1552
|
});
|
|
1576
|
-
} else if(Player.containsTokenAllGames(token) || Player.containsIdAllGames(socket.id)) {
|
|
1553
|
+
} else if(Player.containsTokenAllGames(token, games) || Player.containsIdAllGames(socket.id, games)) {
|
|
1577
1554
|
socket.emit("join-room", {
|
|
1578
1555
|
success: false,
|
|
1579
1556
|
errorCode: GameConstants.Error.ALREADY_CREATED_ROOM
|
|
@@ -1586,6 +1563,6 @@ io.on("connection", function(socket) {
|
|
|
1586
1563
|
});
|
|
1587
1564
|
});
|
|
1588
1565
|
|
|
1589
|
-
http.listen(config.port,
|
|
1566
|
+
http.listen(config.port, () => {
|
|
1590
1567
|
console.log("listening on *:" + config.port);
|
|
1591
1568
|
});
|
package/views/admin.html
CHANGED
|
@@ -24,12 +24,19 @@
|
|
|
24
24
|
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
|
25
25
|
<link href="/css/flat-ui.min.css" rel="stylesheet">
|
|
26
26
|
<link href="/css/main.css" rel="stylesheet">
|
|
27
|
+
<% if(theme === "dark") { %>
|
|
28
|
+
<link href="/css/dark-theme.css" rel="stylesheet">
|
|
29
|
+
<% } %>
|
|
27
30
|
<script src="/js/jquery.min.js"></script>
|
|
28
31
|
<script src="/js/bootstrap.bundle.min.js"></script>
|
|
29
32
|
<% if(enableRecaptcha && publicKey) { %><script src="https://www.google.com/recaptcha/api.js?hl=<%= locale %>" async defer></script><% } %>
|
|
30
33
|
<title>SnakeIA Server</title>
|
|
31
34
|
</head>
|
|
35
|
+
<% if(theme === "dark") { %>
|
|
36
|
+
<body class="text-center dark">
|
|
37
|
+
<% } else { %>
|
|
32
38
|
<body class="text-center">
|
|
39
|
+
<% } %>
|
|
33
40
|
<div class="container">
|
|
34
41
|
<div class="row">
|
|
35
42
|
<div class="col">
|