-
Notifications
You must be signed in to change notification settings - Fork 11
/
server.js
152 lines (141 loc) · 4.93 KB
/
server.js
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
/* eslint-disable require-jsdoc */
/**
* This is the main server file, responsible for three operations:
* 1. Server the HTML/JS files to the players
* 2. Serve as an auth server to authenticate front-end clients
* with Ably and assign a unique clientId to each player
* 3. Create a new worker thread for each game room using NodeJS worker threads
*/
const { Worker, isMainThread, threadId } = require('worker_threads');
// eslint-disable-next-line no-unused-vars
const envConfig = require('dotenv').config();
const express = require('express');
const Ably = require('ably');
const app = express();
const ABLY_API_KEY = process.env.ABLY_API_KEY;
const globalGameChName = 'main-game-thread';
let globalChannel;
const activeGameRooms = {};
let totalPlayersThroughout = 0;
// instantiate the Ably library
// eslint-disable-next-line new-cap
const realtime = Ably.Realtime({
key: ABLY_API_KEY,
// echo messages client option is true by default
// making it false will prevent the server from
// receiving the messages published by it
echoMessages: false
});
app.use(express.static('public'));
/**
* the auth endpoint will allow front end clients to
* authenticate with Ably using Token auth
*/
app.get('/auth', (request, response) => {
// assign each front-end client a unique clientId
const tokenParams = { clientId: uniqueId() };
realtime.auth.createTokenRequest(tokenParams, function (err, tokenRequest) {
if (err) {
response
.status(500)
.send('Error requesting token: ' + JSON.stringify(err));
} else {
// return the token request to the front-end client
response.setHeader('Content-Type', 'application/json');
response.send(JSON.stringify(tokenRequest));
}
});
});
// create a uniqueId to assign to clients on auth
const uniqueId = function () {
return 'id-' + Math.random().toString(36).substr(2, 16);
};
// show the index.html file on the home page
app.get('/', (request, response) => {
response.sendFile(__dirname + '/public/views/index.html');
});
// show the game html page to the client
app.get('/game', (request, response) => {
const requestedRoomCode = request.query.roomCode;
const isReqHost = request.query.isHost === 'true';
/**
* check if the requested game room exists
* and if the client is a host or not,
* and serve the HTML files accordingly
* */
if (!isReqHost && activeGameRooms[requestedRoomCode]) {
if (!activeGameRooms[requestedRoomCode].isGameOn) {
response.sendFile(__dirname + '/public/views/game-not-host.html');
} else {
response.sendFile(__dirname + '/public/views/game-started.html');
}
} else if (isReqHost) {
response.sendFile(__dirname + '/public/views/game-host.html');
} else {
response.sendFile(__dirname + '/public/views/game-code-wrong.html');
}
});
// add a listener for the express server
const listener = app.listen(process.env.PORT, () => {
console.log('Your app is listening on port ' + listener.address().port);
});
// wait until connection with Ably is established
realtime.connection.once('connected', () => {
// create the channel object
globalChannel = realtime.channels.get(globalGameChName);
// subscribe to new host players entering the game
globalChannel.presence.subscribe('enter', (player) => {
// generate a new worker thread for each game room
generateNewGameThread(
player.data.isHost,
player.data.nickname,
player.data.roomCode,
player.clientId
);
});
});
// method to create and update a new worker thread
function generateNewGameThread(
isHost,
hostNickname,
hostRoomCode,
hostClientId
) {
if (isHost && isMainThread) {
const worker = new Worker('./game-server.js', {
workerData: {
hostNickname: hostNickname,
hostRoomCode: hostRoomCode,
hostClientId: hostClientId
}
});
console.log(`CREATING NEW THREAD WITH ID ${threadId}`);
worker.on('error', (error) => {
console.log(`WORKER EXITED DUE TO AN ERROR ${error}`);
});
// wait for an update from the worker thread to confirm it's created
worker.on('message', (msg) => {
if (msg.roomCode && !msg.killWorker) {
// save all live threads in an associative array
activeGameRooms[msg.roomCode] = {
roomCode: msg.roomCode,
totalPlayers: msg.totalPlayers,
isGameOn: msg.isGameOn
};
totalPlayersThroughout += totalPlayersThroughout;
} else if (msg.roomCode && msg.killWorker) {
// delete the thread entry from the associative array
// if the killWorker method was invoked in the thread
totalPlayersThroughout -= msg.totalPlayers;
delete activeGameRooms[msg.roomCode];
}
});
// check if any error occurs when a worker thread quits
worker.on('exit', (code) => {
console.log(`WORKER EXITED WITH THREAD ID ${threadId}`);
if (code !== 0) {
console.log(`WORKER EXITED DUE TO AN ERROR WITH CODE ${code}`);
}
});
}
}