-
Notifications
You must be signed in to change notification settings - Fork 94
/
presence.js
472 lines (415 loc) · 11.4 KB
/
presence.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
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
// websocket and http servers
var webSocket = require('ws');
var http = require('http');
var https = require('https');
var common = require('../../dashboard/helper/common');
exports.init = function(settings, httpserver) {
/**
* Global variables
*/
// List of currently connected clients (users)
var clients = [];
// Http server
var server = httpserver;
// List of shared activities
var sharedActivities = [];
/**
* Message types
*/
/* eslint-disable no-unused-vars */
var msgInit = 0;
var msgListUsers = 1;
var msgCreateSharedActivity = 2;
var msgListSharedActivities = 3;
var msgJoinSharedActivity = 4;
var msgLeaveSharedActivity = 5;
var msgOnConnectionClosed = 6;
var msgOnSharedActivityUserChanged = 7;
var msgSendMessage = 8;
var msgListSharedActivityUsers = 9;
var closedBecauseDuplicate = 4999;
/* eslint-enable no-unused-vars */
/**
* HTTP server
*/
if (settings.presence.port != settings.web.port) {
// Not on the same port: create a new one
if (settings.security.https) {
var credentials = common.loadCredentials(settings);
if (!credentials) {
console.log("Error reading HTTPS credentials");
process.exit(-1);
}
server = https.createServer(credentials);
} else {
server = http.createServer();
}
server.listen(settings.presence.port, function() {
console.log("Presence is listening on"+(settings.security.https ? " secure":"")+" port " + settings.presence.port + "...");
}).on('error', function(err) {
console.log("Ooops! cannot launch presence on port "+ settings.presence.port + ", error code "+err.code);
process.exit(-1);
});
} else {
// Use the existing HTTP server
console.log("Presence is listening on"+(settings.security.https ? " secure":"")+" port " + settings.presence.port + "...");
}
// Log message
var level = settings.log?settings.log.level:1;
var logmessage = function(buffer) {
if (level != 0) {
console.log(buffer);
}
};
/**
* WebSocket server
*/
const wsServer = new webSocket.Server({
server: server,
maxPayload: 44040192
});
// Callback function called every time someone connect to the WebSocket server
wsServer.on('connection', function(connection) {
// Add client to array, wait for userId
var userIndex;
var userId = false;
// An user sent some message
connection.on('message', function(message) {
// First message sent is user settings
if (userId === false) {
// Get user settings
var rjson = JSON.parse(message);
// Forbid user arlready connected on another device
if ((userIndex = findClient(rjson.networkId)) != -1) {
// Disconnect user on other device
clients[userIndex].connection.close(closedBecauseDuplicate);
// Reset user
clients[userIndex].settings = rjson;
clients[userIndex].connection = connection;
userId = rjson.networkId;
logmessage('User ' + userId + ' already connected, closed previous connection and reconnect it');
} else {
// Add client
userIndex = addClient(connection);
clients[userIndex].settings = rjson;
// Get user name
userId = rjson.networkId;
logmessage('User ' + userId + ' join the network');
}
} else {
// Get message content
var rjson = JSON.parse(message);
// Process message depending of this type
switch (rjson.type) {
// MESSAGE: listUsers
case msgListUsers:
{
// Compute connected user list
var connectedUsers = [];
for (var i = 0; i < clients.length; i++) {
if (clients[i] != null) {
connectedUsers.push(clients[i].settings);
}
}
// Send the list
connection.send(JSON.stringify({
type: msgListUsers,
data: connectedUsers
}));
break;
}
// MESSAGE: createSharedActivity
case msgCreateSharedActivity:
{
// Create shared activities
var activityId = rjson.activityId;
var groupId = createSharedActivity(activityId, userId);
logmessage('Shared group ' + groupId + " (" + activityId + ") created");
// Add user into group
addUserIntoGroup(groupId, userId);
// Send the group id
connection.send(JSON.stringify({
type: msgCreateSharedActivity,
data: groupId
}));
break;
}
// MESSAGE: listSharedActivities
case msgListSharedActivities:
{
// Compute shared activities list
var listShared = [];
for (var i = 0; i < sharedActivities.length; i++) {
if (sharedActivities[i] != null) {
listShared.push(sharedActivities[i]);
}
}
// Send the list
connection.send(JSON.stringify({
type: msgListSharedActivities,
data: listShared
}));
break;
}
// MESSAGE: joinSharedActivity
case msgJoinSharedActivity:
{
// Update group
var groupId = rjson.group;
var groupProperties = addUserIntoGroup(groupId, userId);
// Send the group properties
connection.send(JSON.stringify({
type: msgJoinSharedActivity,
data: groupProperties
}));
break;
}
// MESSAGE: leaveSharedActivity
case msgLeaveSharedActivity:
{
// Update group
var groupId = rjson.group;
removeUserFromGroup(groupId, userId);
break;
}
// MESSAGE: listSharedActivityUsers
case msgListSharedActivityUsers:
{
// Get group
var groupId = rjson.group;
// Compute connected user list
var connectedUsers = listUsersFromGroup(groupId);
var usersList = [];
for (var i = 0; i < connectedUsers.length; i++) {
var j = findClient(connectedUsers[i]);
if (j != -1) {
usersList.push(clients[j].settings);
}
}
// Send the list
connection.send(JSON.stringify({
type: msgListSharedActivityUsers,
data: usersList
}));
break;
}
// MESSAGE: sendMessage
case msgSendMessage:
{
// Get arguments
var groupId = rjson.group;
var data = rjson.data;
// Send the group properties
var message = {
type: msgSendMessage,
data: data
};
broadcastToGroup(groupId, message);
break;
}
default:
console.log("Unrecognized received json type");
break;
}
}
});
// user disconnected
connection.on('close', function(reason) {
if (userId !== false) {
if (reason == closedBecauseDuplicate) {
logmessage("User " + userId + " disconnected automatically");
} else {
logmessage("User " + userId + " disconnected");
removeClient(userIndex);
}
}
});
});
/**
* Utility functions
*/
// Create a uuid
function createUUID() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4";
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
// Find client by id
function findClient(userId) {
for (var i = 0; i < clients.length; i++) {
if (clients[i] != null && clients[i].settings.networkId == userId)
return i;
}
return -1;
}
// Add a new client in the client array
function addClient(connection) {
// Create client
var client = {
connection: connection
};
// Find a free space in array
for (var i = 0; i < clients.length; i++) {
// Found, use it to store user
if (clients[i] == null) {
clients[i] = client;
return i;
}
}
// Not found, increase array to store user
return clients.push(client) - 1;
}
// Remove a client from the client array
function removeClient(index) {
// Iterate on each shared activities
if (!clients[index])
return;
var userId = clients[index].settings.networkId;
for (var i = 0; i < sharedActivities.length; i++) {
if (sharedActivities[i] == null)
continue;
// Remove user from group
removeUserFromGroup(sharedActivities[i].id, userId);
}
// Clean array
clients[index] = null;
}
// Create a new shared activity
function createSharedActivity(activityId, user) {
// Create a new group
var group = {
id: createUUID(),
activityId: activityId,
users: []
};
// Find a free space in array
for (var i = 0; i < sharedActivities.length; i++) {
// Found, use it to store group
if (sharedActivities[i] == null) {
sharedActivities[i] = group;
break;
}
}
if (i >= sharedActivities.length) {
// Not found, increase array size
sharedActivities.push(group);
}
// Fill activity color with user color
var userIndex = findClient(user);
if (userIndex != -1 && clients[userIndex]) {
group.colorvalue = clients[userIndex].settings.colorvalue;
}
return group.id;
}
// Find a group by Id
function findGroup(groupId) {
for (var i = 0; i < sharedActivities.length; i++) {
// Found, use it to store group
if (sharedActivities[i] == null)
continue;
if (sharedActivities[i].id == groupId)
return i;
}
return -1;
}
// Add user into a group id
function addUserIntoGroup(groupId, userId) {
// Find the group
var groupIndex = findGroup(groupId);
if (groupIndex == -1)
return null;
// Add the user in group if not already there
var usersInGroup = sharedActivities[groupIndex].users;
var foundUser = false;
for (var j = 0; j < usersInGroup.length; j++) {
// Check if client is in the group
if (usersInGroup[j] == userId) {
foundUser = true;
break;
}
}
if (!foundUser) {
sharedActivities[groupIndex].users.push(userId);
logmessage('User ' + userId + ' join group ' + groupId);
var userIndex = findClient(userId);
var message = {
type: msgOnSharedActivityUserChanged,
data: {
user: clients[userIndex].settings,
move: +1
}
};
broadcastToGroup(groupId, message);
}
// Return group properties
return sharedActivities[groupIndex];
}
// Remove an user from a group id
function removeUserFromGroup(groupId, userId) {
// Find the group
var groupIndex = findGroup(groupId);
if (groupIndex == -1)
return null;
// Remove the userId
var usersInGroup = sharedActivities[groupIndex].users;
var newUsersInGroup = [];
for (var j = 0; j < usersInGroup.length; j++) {
// Check if client is in the group
var currentUser = usersInGroup[j];
if (currentUser != userId)
newUsersInGroup.push(currentUser);
else {
logmessage('User ' + userId + ' leave group ' + groupId);
var userIndex = findClient(userId);
var message = {
type: msgOnSharedActivityUserChanged,
data: {
user: clients[userIndex].settings,
move: -1
}
};
broadcastToGroup(groupId, message);
}
}
// If the group is now empty, remove it
sharedActivities[groupIndex].users = newUsersInGroup;
if (newUsersInGroup.length == 0) {
logmessage('Shared group ' + groupId + " removed");
sharedActivities[groupIndex] = null;
}
}
// Broadcast a message to all group member
function broadcastToGroup(groupId, json) {
// Find the group
var groupIndex = findGroup(groupId);
if (groupIndex == -1)
return;
// For each user in the group
var usersInGroup = sharedActivities[groupIndex].users;
for (var j = 0; j < usersInGroup.length; j++) {
// Get client
var clientIndex = findClient(usersInGroup[j]);
if (clientIndex == -1)
return;
// Send message
var connection = clients[clientIndex].connection;
connection.send(JSON.stringify(json));
}
}
// List users for a group
function listUsersFromGroup(groupId) {
// Find the group
var groupIndex = findGroup(groupId);
if (groupIndex == -1)
return [];
// Return users in group
return sharedActivities[groupIndex].users;
}
};