-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.js
423 lines (377 loc) · 15.1 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
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
419
420
421
422
423
/* eslint-disable indent */
/*******************************************/
/* /////////// Server creation \\\\\\\\\\\ */
/*******************************************/
const express = require('express')
const path = require('path')
// app represents the logic of the server.
const app = express()
// http is the server
const http = require('http').createServer(app)
const io = require('socket.io')(http)
// mark 'public' as the public dir
app.use(express.static(path.join(__dirname, 'public')))
// all routing logic is handled by routes/router.js
app.use('/', require('./routes/router'))
/*******************************************/
/* ///////////// pseudo-DB \\\\\\\\\\\\\\\ */
/*******************************************/
/* Fixed array of available chat rooms.
/* A socket should be part of less than one
* of these at any given time. */
let chat_rooms = ['1', '2']
let nb_chat_rooms = chat_rooms.length // fixed for now
/* Array of "people" object.
* To each socket(.id) corresponds a unique human-chosen username.
* --- Model ---
* user = {
* id: socket.id, (<- string - unique hash given by socket.io)
* username: usrnm, (<- string - chosen by user)
* room: i (<- integer - managed by server)
* }
* --- 'pseudo-DB' ---
* users = [user1, user2, ...] */
let users = []
/*******************************************/
/* /////////////// Utils \\\\\\\\\\\\\\\\\ */
/*******************************************/
// function searchForChatRoomAvoidUser(socket, user, room_index = 0) {
// /* Recursively search for an available chat room, avoiding 'user'.
// * If chat room of index 'room_index' in array 'chat_rooms'
// * full, or 'user' in it, try next, if existing. */
// let room = chat_rooms[room_index]
// io.in(room).clients((error, clients) => {
// if (error) console.log(error)
// //try less than 1 member in room and 'user' not member
// if (clients.length < 2 && !(user in clients)) {
// changeRoom(socket, room)
// } else if (room_index + 1 < nb_chat_rooms) {
// //if not suitable, try next chat room, if existing
// searchForChatRoomAvoidUser(socket, user, room_index + 1)
// } else {
// //else retry whole search process later
// console.log(
// 'no room found for socket ' +
// socket.id +
// ', searching again in 3s',
// )
// setTimeout(searchForChatRoom, 3000, socket)
// }
// })
// }
function searchForChatRoom(socket, room_index = 0, first_connection = false) {
/* Recursively search for an available chat room.
* If chat room of index 'room_index' in array 'chat_rooms'
* is full, try next, if existing.
* If none found, retry from beginning in 3s. */
let room = chat_rooms[room_index]
io.in(room).clients((error, clients) => {
if (error) console.log(error)
if (clients.length < 2) {
changeRoom(socket, room, first_connection, () => { })
} else if (room_index + 1 < nb_chat_rooms) {
searchForChatRoom(socket, room_index + 1, first_connection)
} else {
console.log(
'no available room found for socket ' +
socket.id +
', searching again in 3s',
)
setTimeout(searchForChatRoom, 3000, socket, 0, first_connection)
}
})
}
function changeRoom(socket, room, first_connection = false, callback) {
/* Make socket leave its current chat room (if existing,
* and if not first connection), and join 'room'
* (to ensure uniqueness). */
// Get user's current chat room
findEmittingRoom(socket, (socket_room) => {
// If found, and if not first connection...
if (socket_room != undefined && !first_connection) {
// ...leave it...
socket.leave(socket_room, () => {
console.log('socket ' + socket.id + ' left room ' + room)
io.to(socket.id).emit('left room', socket_room)
// ...then join the new one
joinRoom(socket, room, first_connection, callback)
})
} else joinRoom(socket, room, first_connection, callback)
}, first_connection)
}
function joinRoom(socket, room, first_connection, callback) {
/* Simply join the given room, without any checks.
* Then, get/emit details from/to that new room, if it isn't 'general',
* and update the pseudo-DB.
* The callback does not wait for details. */
socket.join(room, () => {
console.log('socket ' + socket.id + ' joined room ' + room)
/* Update DB */
changeRoomOfUserInDB(socket, room, () => {
/* Get/Emit details, and/or callback */
if (room != 'general') {
// Get room details
emitNewRoomDetailsToSocket(socket, first_connection, room)
// Broadcast details of socket to new room
broadcastSocketDetailsToNewRoom(socket, room)
}
})
callback()
})
}
function changeRoomOfUserInDB(socket, room, callback) {
/* Update the pseudo-DB changing 'room' attribute
* to socket. */
getUserFromSocket(socket, user => {
if (user === undefined) {
console.log('Error: user is undefined in changeRoomToUserObjInDB!')
} else {
user.room = room
}
callback()
})
}
function getUserFromSocket(socket, callback) {
/* Return to callback the (first) 'user' element
* of list 'users' which id corresponds to that of socket. */
let user = users.find(usr => usr.id === socket.id)
callback(user)
}
function broadcastSocketDetailsToNewRoom(socket, new_room) {
/* Broadcast sockets' det... ok just read the title. */
let usrnm = findUsername(socket.id)
socket.broadcast.to(new_room).emit('hello', {
id: socket.id,
username: usrnm
})
}
function emitNewRoomDetailsToSocket(socket, first_connection, new_room) {
/* Emit new room details to joining socket. */
getInterloc(socket, first_connection, (interloc) => {
getUsernamefromgetInterlocReturn(interloc, (usrnm) => {
io.to(socket.id).emit('joined room', { room: new_room, interlocutor: usrnm })
})
})
}
function getInterloc(socket, first_connection, callback) {
/* Get the interlocutor' socket id and username of a given user (socket).
* Assumes there are no more than 2 people by chat room. */
findEmittingRoom(socket, (emitting_room) => {
if (emitting_room != undefined) {
let interloc_id
io.in(emitting_room).clients((error, clients) => {
if (error) console.log(error)
for (let client of clients) {
if (client != socket.id) {
interloc_id = client
let usrnm
if (interloc_id != undefined) usrnm = findUsername(interloc_id)
else usrnm = undefined
var interloc = { id: interloc_id, username: usrnm }
return callback(interloc)
} // COMMENTS/SEPARATE CODE PLZ
}
return callback(undefined)
})
} else {
if (!first_connection) console.log('Error: couldn\'t find emitting room for socket ' + socket.id + ' in getInterloc!')
return callback(undefined)
}
}, first_connection)
}
function getUsernamefromgetInterlocReturn(interloc, callback) {
/* Callback on getInterloc return, becauz async's hard bruh */
let usrnm = undefined
if (interloc !== undefined) usrnm = interloc.username
callback(usrnm)
}
function findEmittingRoom(socket, callback, first_connection = false) {
/* Return the emitting room the socket is in.
* Assumes that the socket is only part of 'general',
* one chat room or less, and its own ('socket.id').
* Will log to console if not found, unless
* first connection specified */
let rooms = Object.keys(socket.rooms)
let emitting_room = undefined
for (let room of rooms) {
if (room != socket.id && room != 'general') {
emitting_room = room
break
} // .filter ?
}
if (emitting_room === undefined && !first_connection) {
console.log(
'Error: couldn\'t find emitting room for socket ' + socket.id + ' in findEmittingRoom!',
)
} else emitting_room
callback(emitting_room)
}
function findUsername(socket_id) {
/* Get the username of a user's id.
* Assumes the socket has a valid (ie existing and unique)
* username in "users" list.
* Returns undefined if socket_id is undefined. */
if (socket_id === undefined) {
console.log('Error: socket is undefined in findUsername!')
return undefined
} else for (let i = 0; i < users.length; i++) {
if (users[i].id == socket_id) {
return users[i].username
}
}
}
function disconnectionHandler(socket) {
/* Handle the whole(some) response to a 'disconnet' event */
createLeavingMessageInfo(socket, (room, usrnm) => {
sendLeavingMessage(socket, room)
updatePeopleCounters(usrnm)
removeUserOfDB(socket)
})
}
function createLeavingMessageInfo(socket, callback) {
/* Build the message sent to the former room of the leaver */
getUserFromSocket(socket, user => {
if (user === undefined) {
callback(undefined, undefined)
console.log('Error: could not find user while creating leaving message for socket ' + socket.id + '!')
}
else callback(user.room, user.username)
})
}
function sendLeavingMessage(socket, room) {
/* Send a message to the former room of the leaver */
if (room === undefined) {
console.log('Error: no leaving message sent to (unknown) former room of socket ' + socket.id + '!')
} else {
socket.broadcast.to(room).emit('leaving', {
id: socket.id,
username: undefined // beacause he/she just left!
})
}
}
function removeUserOfDB(socket) {
/* Remove the (first) user corresponding to socket's id
* from "users" list (assuming only one user corresponding to id) */
for (let i = 0; i < users.length; i++) {
if (users[i].id == socket.id) {
users.splice(i, 1); break
}
}
}
function updatePeopleCounters(usrnm) {
/* Tell everybody somebody just left, and update the peoplecounters */
io.in('general').clients((error, clients) => {
if (error) console.log(error)
io.to('general').emit('byebye', {
leaver: usrnm,
peoplecount: clients.length,
})
})
}
/*******************************************/
/* ///////////// Entry point \\\\\\\\\\\\\ */
/*******************************************/
/* On connection of socket... */
io.on('connect', socket => {
/* Add user to pseudo-DB */
users.push({ id: socket.id, username: undefined, room: undefined })
console.log('-> socket ' + socket.id + ' just connected ->')
/* Send connection confirmation */
io.to(socket.id).emit('connection success')
/* Everybody joins 'general' on first connection */
changeRoom(socket, 'general', true, () => {
// Then, tell everybody else somebody just connected,
// and upgrade the peoplecounter
io.in('general').clients((error, clients) => {
if (error) console.log(error)
socket.broadcast.to('general').emit('greeting', {
newcommer: socket.id,
peoplecount: clients.length,
})
})
})
/* Check username proposals */
socket.on('username proposal', (username) => {
if (users.some(user => (user.username == username))) {
// reject
io.to(socket.id).emit('used username')
} else {
// accept
getUserFromSocket(socket, user => {
if (user === undefined) {
console.log('Error: couldn\'t find user with id ' + socket.id + ' in DB!')
io.to(socket.id).emit('error finding user in DB')
} else {
// write to DB, confirm username to socket
user.username = username
io.to(socket.id).emit('accepted username', username)
/***********************/
/* Initiate chat logic */
/***********************/
/* Check for an available chat room, loop until found */
searchForChatRoom(socket, 0, true)
}
})
}
})
/* Respond to "people count" requests */
socket.on('how many?', () => {
io.in('general').clients((error, clients) => {
if (error) console.log(error)
io.to(socket.id).emit('how many?', clients.length)
})
})
/* Transmit chat messages to adequate rooms */
socket.on('chat message', msg => {
// Find the room it originates from (NOT undefined-proof)
findEmittingRoom(socket, (emitting_room) => {
// get the username of the sender (undefined-proof)
let username = findUsername(socket)
if (emitting_room != undefined) {
// Send the message back to the room, but not to the sender
socket.broadcast.to(emitting_room).emit('chat message', {
message: msg,
sender: username,
})
} else {
console.log('Error: could not find emitting room while chat message received!')
// Send back an error message
io.to(socket.id).emit('message sending error')
}
})
})
/* Send typing indicator message */
socket.on('user typing', () => {
//find the room it originates from
findEmittingRoom(socket, (emitting_room) => {
if (emitting_room != undefined) {
//send the message back to the room, but not to the sender
socket.broadcast.to(emitting_room).emit('user typing')
} else {
console.log('Error: could not find emitting room while user typing message received!')
}
})
})
/* Change of interlocutor ON HOLD */
/* socket.on('change interloc', () => {
console.log('User ' + socket.id + ' asked to change interlocutor')
let interloc = (socket)
searchForChatRoomAvoidUser(socket, interloc.id)
}) */
/* Handle disconnection */
socket.on('disconnect', reason => {
console.log('<- socket ' + socket.id + ' just left ; cause: ' + reason + ' <-')
disconnectionHandler(socket)
})
})
/*******************************************/
/* //////////// server start \\\\\\\\\\\\\ */
/*******************************************/
// Heroku configuration
let port = process.env.PORT
if (port == null || port == "") port = 8080
http.listen(port, () => {
const DASHESNL = '-----------------------\n'
console.log(DASHESNL + 'Listening on port 8080\n' + DASHESNL)
})
module.exports = app