-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.js
418 lines (340 loc) · 13.7 KB
/
app.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
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
'use strict';
var bodyParser = require("body-parser"),
aws = require("./awsClient.js"),
validator = require("./validator.js"),
getIP = require('external-ip')();
// Sets up express server to accept HTTP
var app = require("express")();
var http = require("http").Server(app);
var io = require('socket.io')(http);
var clientDir = "/public"
http.listen(3000, function() {
console.log("HTTP server listening on port 3000");
});
// Serve up the index.html file as the home page
app.get("/", function (req, res) {
res.sendFile(__dirname + clientDir + '/index.html');
});
// Serve up all other files through the public directory
app.get("/:fileName", function (req, res) {
res.sendFile(__dirname + clientDir + "/" + req.params.fileName);
});
app.get("/:directory/:fileName", function(req, res) {
res.sendFile(__dirname + clientDir + "/" + req.params.directory + "/" + req.params.fileName);
});
// Get the public IP address of the Node server
var externalIP = "";
getIP(function (err, ip) {
if (err) {
throw err;
}
externalIP = ip;
console.log("The server's IP address is " + externalIP);
});
// An array of active room IDs
var activeRooms = [];
// This function is called by the array of rooms - returns -1 if the room ID is not found - returns the index of the room in the array if the room ID is found
activeRooms.roomIndexByID = function(targetID) {
var resIndex = -1;
for (var i = 0; i < this.length; i++) {
if (targetID === this[i].id)
resIndex = i;
}
return resIndex;
}
// This function is called by the array of rooms - the user list of each room is checked for the user with the socket ID string specified by the paramater, and this user is deleted from the room's user list
activeRooms.deleteUserBySocketID = function(socketID) {
for (var roomInd = 0; roomInd < this.length; roomInd++) {
for (var userInd = 0; userInd < this[roomInd].userList.length; userInd++) {
if (this[roomInd].userList[userInd].socketID === socketID) {
this[roomInd].userList.splice(userInd, 1);
// Return the room ID if the user was found
return this[roomInd].id;
}
}
}
// If the user was not found, return undefined
return undefined;
}
// Toggles the handRaised value of the user in room roomID and with a socketID of socketID
activeRooms.toggleHand = function(roomID, socketID) {
for (var roomInd = 0; roomInd < this.length; roomInd++) {
if (roomID === this[roomInd].id) {
for (var userInd = 0; userInd < this[roomInd].userList.length; userInd++) {
var user = this[roomInd].userList[userInd];
if (user.socketID === socketID) {
user.handRaised ? user.handRaised = false : user.handRaised = true;
// Returns the room's index in the array if the user was found and the handRaise value was toggled
return roomInd;
}
}
}
}
// Returns undefined if the user could not be found and the hand was not toggled
return undefined;
}
// This route takes the user to the room
app.get("/room/:userType/:roomID", function (req, res) {
// The user type is p if the user is a presenter or a if the user is an attendee
if (activeRooms.roomIndexByID(req.params.roomID) > -1 && req.params.userType === "p") {
// A presenter has logged in
res.sendFile(__dirname + clientDir + "/presenterroom.html");
}
else if (activeRooms.roomIndexByID(req.params.roomID) > -1 && req.params.userType === "a") {
// An attendee has logged in
res.sendFile(__dirname + clientDir + "/attendeeroom.html");
}
else {
res.send("<h1>Room Not Found</h1>");
}
});
io.on("connection", function(socket) {
function awsFeedback(err, data, socketEvent) {
// Emits a socket event and passes the error and data objects that will come from the AWS service call
socket.emit(socketEvent, {err: err, data: data});
}
// Listen for create-room event, which is called when the user clicks the submit button in the "Create A Room" section on the landing page
socket.on("create-room", function(data) {
// These console.log statements are for debugging purposes - they may be deleted later
console.log("Creating room with instructor name " + data.instructorName + " room name " + data.roomName);
var roomName = data.roomName;
// Create random ID
var roomID = aws.randID();
var instructor = data.instructorName;
var emails = data.emails;
// Populate email addresses from form data and send message to recipients
var emailExists = true;
emails = data.emails;
// Validate the upload file.
var file = validator.validateFile(data.file);
if (file == false) {
console.log("No file uploaded.");
}
// Countdown for number of bucket creation fails - after this many fails, the server will give up trying to create a room
var bucketFails = 5;
// Countdown for number of DynamoDB add item fails
var dynamoFails = 5;
// Countdown for number of Queue creation fails.
var queueFails = 5;
// This function will be provided a boolean of whether or not a unique ID has been generated
function testIDCallback(result) {
if (result === false) {
// The ID is not unique - generate another random ID then check if unique
roomID = aws.randID();
aws.testRoomID(roomID, testIDCallback);
}
else {
// The ID is unique, create the room's bucket and entry in database
console.log("Creating room with ID " + roomID);
aws.createBucket(roomID, createBucketCallback);
}
}
function createBucketCallback(err, data) {
if (err && bucketFails > 0) {
// If the bucket could not be created, it is assumed that the bucket name is already taken
// Generate a new room ID, test it, then try creating a bucket again
console.log("Could not create room with ID " + roomID);
bucketFails--;
roomID = aws.randID();
aws.testRoomID(roomID, testIDCallback);
}
else if (bucketFails > 0) {
// Bucket was created successfully, emit event to socket to signal success
socket.emit("complete-bucket", {err: err, data: data, roomID: roomID});
if (file != false) {
// Upload the file in the bucket.
aws.uploadFileToS3Bucket(roomID, file, true);
}
// Continue with creating the room
aws.addRoomToDB(roomName, roomID, addToDBCallback);
// Continue with creating the chat queue
aws.createQueueSQS(roomID, createQueueCallback);
aws.sendEmail(emails, instructor, roomID, awsFeedback, externalIP);
// Add the newly created room's ID to the list of active rooms
//activeRooms.push(roomID);
var newRoom = new Room(roomID, roomName, instructor);
activeRooms.push(newRoom);
//Sends messages to Admins acknowledging creation of room.
aws.publish(activeRooms.length);
}
else
socket.emit("complete-bucket", {err: err, data: data});
}
function addToDBCallback(err, data) {
if (err && dynamoFails > 0) {
dynamoFails--;
console.log("Retrying " + roomName + " and " + roomID);
// Retry the database add
aws.addRoomToDB(roomName, roomID, addToDBCallback);
}
else {
socket.emit("complete-db-add", {err: err, data: data});
}
}
function createQueueCallback(err, data) {
if (err && queueFails > 0) {
queueFails--;
console.log("Retrying create a chat queue for " + roomName + " and " + roomID);
// Retry the database add
aws.createQueueSQS(roomID, createQueueCallback);
}
else {
socket.emit("complete-queue-creation", {err: err, data: data});
}
}
// Calls the test and will fire the testIDCallback (along with the rest of the callbacks) when finished, resulting in bucket, DB entry creation, and sending of emails
aws.testRoomID(roomID, testIDCallback);
});
socket.on("resend-email", function(data) {
aws.sendEmail(data.emails, data.instructorName, data.roomID, awsFeedback, externalIP);
});
socket.on("add-to-room", function(data) {
var user = new User(data.username, data.userIsPresenter, socket.id);
console.log("A new " + ((user.isPresenter) ? "presenter" : "attendee") + " named " + user.name + " entered the room " + data.roomID);
socket.join(data.roomID);
var currentRoom = activeRooms[activeRooms.roomIndexByID(data.roomID)];
currentRoom.userList.push(user);
console.log("pushing update event to room " + data.roomID + " and user list " + currentRoom.userList);
// Emits event to all in the new user's room including the new user
io.in(data.roomID).emit("update", currentRoom.userList);
var roomID = data.roomID;
var mainFileURL = null;
aws.listObjects(roomID, listObjectsCallback);
// Emit a event to recover all chat history for the user.
aws.recoverChatHistorySQS(data.roomID, recoverChatHistorySQSCallback);
function recoverChatHistorySQSCallback(err, data) {
socket.emit("chat-history", {messages: currentRoom.chatHistory});
}
});
// Listen for delete-room event, which is called when the instructor leaves the room
socket.on("delete-room", function(data) {
var roomName = data.roomName;
var roomID = data.roomID;
// Countdown for number of bucket creation fails - after this many fails, the server will give up trying to create a room
var bucketFails = 5;
// Countdown for number of DynamoDB add item fails
var dynamoFails = 5;
function deleteBucketCallback(err, data) {
if (err && bucketFails > 0) {
// If the bucket could not be deleted, try again
bucketFails--;
aws.deleteBucket(roomID, deleteBucketCallback);
}
else socket.emit("delete-bucket", {err: err, data: data});
}
function deleteFromDBCallback(err, data) {
if (err && dynamoFails > 0) {
dynamoFails--;
console.log("Retrying " + roomName + " and " + roomID);
// Retry the database delete
aws.deleteRoomFromDB(roomName, roomID, deleteFromDBCallback);
}
else {
socket.emit("delete-db-add", {err: err, data: data});
}
}
aws.deleteBucket(roomID, deleteBucketCallback);
aws.deleteRoomFromDB(roomName, roomID, deleteFromDBCallback);
});
// Listen for a chat message and broadcast for all users in the room.
socket.on("chat-send-message", function(data) {
var roomID = data.roomID;
var username = data.username;
var userIsPresenter = data.userIsPresenter;
// adding the user object to the data object that will be send to client.
console.log('User named ' + username + ' in the room' + roomID + ' sent a message on chat.');
var sendData = {
roomID: roomID,
username: username,
userIsPresenter: userIsPresenter,
message: data.message,
sentTime: (new Date).getTime()
};
// broadcasting the message.
io.in(roomID).emit("chat-receive-message", sendData);
// send the message to queue to store a chat history.
aws.logChatHistory(roomID, sendData);
if (!activeRooms[activeRooms.roomIndexByID(roomID)].chatHistory)
activeRooms[activeRooms.roomIndexByID(roomID)].chatHistory = new Array();
activeRooms[activeRooms.roomIndexByID(roomID)].chatHistory.push(sendData);
});
socket.on("req-room-info", function(data) {
var roomData = activeRooms[activeRooms.roomIndexByID(data.roomID)];
socket.emit("res-room-info", roomData);
});
socket.on("toggle-hand", function(data) {
var roomIndex = activeRooms.toggleHand(data.roomID, socket.id);
if (typeof(roomIndex) !== "undefined")
io.in(data.roomID).emit("update", activeRooms[roomIndex].userList);
});
socket.on("upload-file", function(data) {
debugger;
function uploadFileS3BucketCallback(err, data) {
debugger;
if (!err) {
socket.emit("complete-file-upload", {err: err, data: data});
aws.listObjects(data.roomID, listObjectsCallback);
}
}
// Upload the file in the bucket.
aws.uploadFileToS3Bucket(data.roomID, data.file, false, uploadFileS3BucketCallback);
});
socket.on('disconnect', function () {
// Gets the ID of the room that the user has been deleted from, if a user has been deleted
var discRoomID = activeRooms.deleteUserBySocketID(socket.id);
// If the discRoomID is not undefined, a user has been removed from a room
if (typeof(discRoomID) !== "undefined") {
console.log("user has disconnected from room " + discRoomID);
// Update all sockets in the room with the new user list
var currentRoom = activeRooms[activeRooms.roomIndexByID(discRoomID)];
io.in(discRoomID).emit("update", currentRoom.userList);
}
});
function headObjectCallback(err, data, roomID) {
debugger;
if (err) {
console.log("Error retrieving files.");
} else {
if (data['ismain'] == 'True') {
var mainFileURL = "http://s3.amazonaws.com/tcnj-csc470-nodejs-" + roomID + "/" + data['name'];
io.in(roomID).emit("update-main-file", {err: err, data: mainFileURL});
}
}
}
function listObjectsCallback(err, data, roomID) {
debugger;
if (err) {
console.log("Error retrieving files.");
} else {
if (data.length > 1 || data[0] != null) {
var files = new Array();
console.log("Updating file list for room " + roomID);
for (var file in data) {
var name = data[file]["Key"];
var link = "http://s3.amazonaws.com/tcnj-csc470-nodejs-" + roomID + "/" + name;
if (name != null) {
files.push([name,link]);
aws.headObject(roomID, name, headObjectCallback);
}
}
// Post the data to the GUI
io.in(roomID).emit("update-file-list", {err: err, data: files});
} else {
console.log("No files found for room " + roomID);
io.in(roomID).emit("update-file-list", "No files to view");
}
}
}
});
function Room(id, name, instructorName) {
this.id = id;
this.name = name;
this.userList = [];
this.instructorName = instructorName;
}
function User(name, isPresenter, socketID) {
this.name = name;
this.isPresenter = isPresenter;
this.socketID = socketID;
this.handRaised = false;
}