-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSocketServer.ts
213 lines (183 loc) · 6.87 KB
/
SocketServer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import { SocketClient } from './SocketClient';
import { Server, Socket } from 'socket.io';
import { GameManager } from '../games/GameManager';
import { v4 as uuidv4 } from 'uuid';
import mongoose from 'mongoose';
import { authenticateUser, validateAuthenticationToken } from '../auth/auth';
import { logger } from '../../utils/logger';
import { sanitizeUserData } from '../db/schemas/User.model';
// How often to call the GameManager.purge() function, checking to see if disconnected games are still active and purging them if so
const PURGE_INTERVAL = 1 * 60 * 1000; // ms
export class SocketServer {
// The Socket.IO server instance the class manages
io: Server;
// The clients the server represents
clients: Map<string, SocketClient>;
gameManager: GameManager;
/**
* Creates a new SocketServer instance from a given Socket.IO server instance
* @param io The Socket.IO server instance the class manages
*/
constructor(io: Server) {
this.io = io;
this.clients = new Map();
this.createEventHandlers();
this.gameManager = new GameManager();
// Periodically call the game manager's purge function to remove old games
setInterval(() => {
this.gameManager.purge();
}, PURGE_INTERVAL);
}
/**
* Creates the handlers for the global server
*/
createEventHandlers() {
this.io.on('connection', (socket) => this.handleNewConnection(socket));
}
/**
* Handles a new connection to the server
* @param socket The socket making the connection
*/
async handleNewConnection(socket: Socket) {
// If the new connection doesn't contain string username and string password
if (!(typeof socket.handshake.auth.token === 'string')) {
// Respond and break
return socket.send('connEstablishRes', {
success: false,
user: null,
message: 'No authentication token provided on connection.'
});
}
// Attempt to authenticate the user
const authResult = await validateAuthenticationToken(socket.handshake.auth.token);
// If the authentication was successful
if (authResult.success && authResult.user) {
// Create a client for the connection
const client = new SocketClient(this.io, socket, authResult.user);
// Add the client to the client list
this.clients.set(authResult.user._id.toString(), client);
// Create the client event handlers
this.createClientEventHandlers(client);
// Update the user's last login
authResult.user.lastLogin = new Date();
authResult.user.save();
// Confirm the connection to the client
socket.send('connEstablishRes', {
success: true,
user: sanitizeUserData(authResult.user),
});
// If the client is in a game
this.gameManager.attemptRejoin(client);
// Break out here to avoid sending failure
return;
}
// The authentication was not successful, respond to the client
return socket.send('connEstablishRes', {
success: false,
user: null,
message: authResult.message,
});
}
async createClientEventHandlers(client: SocketClient) {
// When the socket is to disconnect
client.socket.once('disconnecting', (reason) => {
// Mark the client as having logged out
client.user.lastLogout = new Date();
client.user.save();
});
// When the client tries to create a game
client.socket.on('createGame', async payload => {
// Call the respective handler function
const result = await this.onCreateGame(client, payload);
// If it was successful, log a success message
if (result) {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) created a game of type ${payload.gameId}.`);
}
// Otherwise, debug log an error message
else {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) failed to create a game with payload ${payload}.`);
}
});
// When the client tries to join a game
client.socket.on('joinGame', async payload => {
const result = await this.onJoinGame(client, payload);
if (result) {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) joined a game with code ${payload.joinCode}.`);
}
else {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) failed to join a game with payload ${payload}.`);
}
});
// When the client tries to begin a game
client.socket.on('beginGame', async () => {
const result = await this.onBeginGame(client);
if (result) {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) began a game.`);
}
else {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) failed to begin a game.`);
}
});
// When the client tries to end a game
client.socket.on('terminateGame', async () => {
const result = await this.onTerminateGame(client);
if (result) {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) terminated a game.`);
}
else {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) failed to terminate a game.`);
}
});
// When the client tries to leave a game
client.socket.on('leaveGame', async () => {
const result = await this.onLeaveGame(client);
if (result) {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) left a game.`);
}
else {
logger.debug(`User ${client.user._id.toString()} (${client.user.username}) failed to leave a game.`);
}
});
}
/**
* Handles a request to create a game
* @returns
*/
async onCreateGame(client: SocketClient, payload: any): Promise<boolean> {
if (typeof payload.gameId !== 'string') {
return false;
}
return await this.gameManager.createGame(payload.gameId, client, this);
}
/**
* Handles a request to join a game
* @returns Whether the game joined successfully
*/
async onJoinGame(client: SocketClient, payload: any): Promise<boolean> {
if (typeof payload.joinCode !== 'string') {
return false;
}
return await this.gameManager.joinGame(payload.joinCode, client);
}
/**
* Handles a request to begin a game
* @returns Whether the game began successfully
*/
async onBeginGame(client: SocketClient): Promise<boolean> {
return await this.gameManager.beginGame(client);
}
/**
* Handles a request to terminate a game
* @returns Whether the game terminated successfully
*/
async onTerminateGame(client: SocketClient): Promise<boolean> {
return await this.gameManager.terminateGame(client);
}
/**
* Handles a request to leave a game
* @returns Whether the user left the game
*/
async onLeaveGame(client: SocketClient): Promise<boolean> {
return await this.gameManager.leaveGame(client);
}
}