UNPKG

webrtc-signaling-helpers

Version:

This is a helper package which is needed to be installed if webrtc-signaling-server is installed

1,021 lines (859 loc) 39.4 kB
// TechnicalHeist - www.technicalheist.com // MIT License - https://github.com/technicalheist/webrtc-signaling-helpers/blob/master/LICENSE // Documentation - https://github.com/technicalheist/webrtc-signaling-helpers var listOfUsers = {}; var listOfRooms = {}; var adminSocket; // for scalable-broadcast demos var ScalableBroadcast; // pushLogs is used to write error logs into logs.json var pushLogs = require('./pushLogs.js'); // const strings var CONST_STRINGS = require('./CONST_STRINGS.js'); var isAdminAuthorized = require('./verify-admin.js'); module.exports = exports = function(socket, config) { config = config || {}; onConnection(socket); // to secure your socket.io usage: (via: docs/tips-tricks.md) // io.set('origins', 'https://domain.com'); function appendUser(socket, params) { try { var extra = params.extra; var params = socket.handshake.query; if (params.extra) { try { if (typeof params.extra === 'string') { params.extra = JSON.parse(params.extra); } extra = params.extra; } catch (e) { extra = params.extra; } } listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, extra: extra || {}, admininfo: {}, socketMessageEvent: params.socketMessageEvent || '', socketCustomEvent: params.socketCustomEvent || '' }; } catch (e) { pushLogs(config, 'appendUser', e); } sendToAdmin(); } function sendToAdmin(all) { if(config.enableAdmin !== true) { return; } try { if (adminSocket) { var users = []; // temporarily disabled config.enableAdmin === true && Object.keys(listOfUsers).forEach(function(userid) { try { var item = listOfUsers[userid]; if (!item) return; // maybe user just left? if (!item.connectedWith) { item.connectedWith = {}; } if (!item.socket) { item.socket = {}; } users.push({ userid: userid, admininfo: item.socket.admininfo || '', connectedWith: Object.keys(item.connectedWith) }); } catch (e) { pushLogs(config, 'admin.user-looper', e); } }); var scalableBroadcastUsers = 0; if(ScalableBroadcast && ScalableBroadcast._) { scalableBroadcastUsers = ScalableBroadcast._.getUsers(); } adminSocket.emit('admin', { newUpdates: !all, listOfRooms: !!all ? listOfRooms : [], listOfUsers: Object.keys(listOfUsers).length, // users scalableBroadcastUsers: scalableBroadcastUsers.length }); } } catch (e) { pushLogs(config, 'admin', e); } } function handleAdminSocket(socket, params) { if(config.enableAdmin !== true || !params.adminUserName || !params.adminPassword) { socket.emit('admin', { error: 'Please pass "adminUserName" and "adminPassword" via socket.io parameters.' }); pushLogs(config, 'invalid-admin', { message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword }); socket.disconnect(); //disabled admin return; } if (!isAdminAuthorized(params, config)) { socket.emit('admin', { error: 'Invalid admin username or password.' }); pushLogs(config, 'invalid-admin', { message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword }); socket.disconnect(); return; } socket.emit('admin', { connected: true }); adminSocket = socket; socket.on('admin', function(message, callback) { if (!isAdminAuthorized(params, config)) { socket.emit('admin', { error: 'Invalid admin username or password.' }); pushLogs(config, 'invalid-admin', { message: CONST_STRINGS.INVALID_ADMIN_CREDENTIAL, stack: 'name: ' + params.adminUserName + '\n' + 'password: ' + params.adminPassword }); socket.disconnect(); return; } callback = callback || function() {}; if (message.all === true) { sendToAdmin(true); } if (message.userinfo === true && message.userid) { try { var user = listOfUsers[message.userid]; if (user) { callback(user.socket.admininfo || {}); } else { callback({ error: CONST_STRINGS.USERID_NOT_AVAILABLE }); } } catch (e) { pushLogs(config, 'userinfo', e); } } if (message.clearLogs === true) { // last callback parameter will force to clear logs pushLogs(config, '', '', callback); } if (message.deleteUser === true) { try { var user = listOfUsers[message.userid]; if (user) { if (user.socket.owner) { // delete listOfRooms[user.socket.owner]; } user.socket.disconnect(); } // delete listOfUsers[message.userid]; callback(true); } catch (e) { pushLogs(config, 'deleteUser', e); callback(false); } } if (message.deleteRoom === true) { try { var room = listOfRooms[message.roomid]; if (room) { var participants = room.participants; delete listOfRooms[message.roomid]; participants.forEach(function(userid) { var user = listOfUsers[userid]; if (user) { user.socket.disconnect(); } }); } callback(true); } catch (e) { pushLogs(config, 'deleteRoom', e); callback(false); } } }); } function onConnection(socket) { var params = socket.handshake.query; if(!params.userid) { params.userid = (Math.random() * 100).toString().replace('.', ''); } if(!params.sessionid) { params.sessionid = (Math.random() * 100).toString().replace('.', ''); } if (params.extra) { try { params.extra = JSON.parse(params.extra); } catch (e) { params.extra = {}; } } else { params.extra = {}; } var socketMessageEvent = params.msgEvent || 'SignalingServer-Message'; // for admin's record params.socketMessageEvent = socketMessageEvent; var autoCloseEntireSession = params.autoCloseEntireSession === true || params.autoCloseEntireSession === 'true'; var sessionid = params.sessionid; var maxParticipantsAllowed = parseInt(params.maxParticipantsAllowed || 1000) || 1000; var enableScalableBroadcast = params.enableScalableBroadcast === true || params.enableScalableBroadcast === 'true'; if (params.userid === 'admin') { handleAdminSocket(socket, params); return; } if (enableScalableBroadcast === true) { try { if (!ScalableBroadcast) { // path to scalable broadcast script must be accurate ScalableBroadcast = require('./Scalable-Broadcast.js'); } ScalableBroadcast._ = ScalableBroadcast(config, socket, params.maxRelayLimitPerUser); } catch (e) { pushLogs(config, 'ScalableBroadcast', e); } } // do not allow to override userid if (!!listOfUsers[params.userid]) { var useridAlreadyTaken = params.userid; params.userid = (Math.random() * 1000).toString().replace('.', ''); socket.emit('userid-already-taken', useridAlreadyTaken, params.userid); return; } socket.userid = params.userid; appendUser(socket, params); socket.on('extra-data-updated', function(extra) { try { if (!listOfUsers[socket.userid]) return; if (listOfUsers[socket.userid].socket.admininfo) { listOfUsers[socket.userid].socket.admininfo.extra = extra; } // todo: use "admininfo.extra" instead of below one listOfUsers[socket.userid].extra = extra; try { for (var user in listOfUsers[socket.userid].connectedWith) { try { listOfUsers[user].socket.emit('extra-data-updated', socket.userid, extra); } catch (e) { pushLogs(config, 'extra-data-updated.connectedWith', e); } } } catch (e) { pushLogs(config, 'extra-data-updated.connectedWith', e); } // sent alert to all room participants if (!socket.admininfo) { sendToAdmin(); return; } var roomid = socket.admininfo.sessionid; if (roomid && listOfRooms[roomid]) { if (socket.userid == listOfRooms[roomid].owner) { // room's extra must match owner's extra listOfRooms[roomid].extra = extra; } listOfRooms[roomid].participants.forEach(function(pid) { try { var user = listOfUsers[pid]; if (!user) { // todo: remove this user from participants list return; } user.socket.emit('extra-data-updated', socket.userid, extra); } catch (e) { pushLogs(config, 'extra-data-updated.participants', e); } }); } sendToAdmin(); } catch (e) { pushLogs(config, 'extra-data-updated', e); } }); socket.on('get-remote-user-extra-data', function(remoteUserId, callback) { callback = callback || function() {}; if (!remoteUserId || !listOfUsers[remoteUserId]) { callback(CONST_STRINGS.USERID_NOT_AVAILABLE); return; } callback(listOfUsers[remoteUserId].extra); }); var dontDuplicateListeners = {}; socket.on('set-custom-socket-event-listener', function(customEvent) { if (dontDuplicateListeners[customEvent]) return; dontDuplicateListeners[customEvent] = customEvent; socket.on(customEvent, function(message) { try { socket.broadcast.emit(customEvent, message); } catch (e) {} }); }); socket.on('changed-uuid', function(newUserId, callback) { callback = callback || function() {}; try { if (listOfUsers[socket.userid] && listOfUsers[socket.userid].socket.userid == socket.userid) { if (newUserId === socket.userid) return; var oldUserId = socket.userid; listOfUsers[newUserId] = listOfUsers[oldUserId]; listOfUsers[newUserId].socket.userid = socket.userid = newUserId; delete listOfUsers[oldUserId]; callback(); return; } socket.userid = newUserId; appendUser(socket, params); callback(); } catch (e) { pushLogs(config, 'changed-uuid', e); } }); socket.on('set-password', function(password, callback) { try { callback = callback || function() {}; if (!socket.admininfo) { callback(null, null, CONST_STRINGS.DID_NOT_JOIN_ANY_ROOM); return; } var roomid = socket.admininfo.sessionid; if (listOfRooms[roomid] && listOfRooms[roomid].owner == socket.userid) { listOfRooms[roomid].password = password; callback(true, roomid, null); } else { callback(false, roomid, CONST_STRINGS.ROOM_PERMISSION_DENIED); } } catch (e) { pushLogs(config, 'set-password', e); } }); socket.on('disconnect-with', function(remoteUserId, callback) { try { if (listOfUsers[socket.userid] && listOfUsers[socket.userid].connectedWith[remoteUserId]) { delete listOfUsers[socket.userid].connectedWith[remoteUserId]; socket.emit('user-disconnected', remoteUserId); sendToAdmin(); } if (!listOfUsers[remoteUserId]) return callback(); if (listOfUsers[remoteUserId].connectedWith[socket.userid]) { delete listOfUsers[remoteUserId].connectedWith[socket.userid]; listOfUsers[remoteUserId].socket.emit('user-disconnected', socket.userid); sendToAdmin(); } callback(); } catch (e) { pushLogs(config, 'disconnect-with', e); } }); socket.on('close-entire-session', function(callback) { try { if(!callback || typeof callback !== 'function') { callback = function() {}; } var user = listOfUsers[socket.userid]; if(!user) return callback(false, CONST_STRINGS.USERID_NOT_AVAILABLE); if(!user.roomid) return callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); if(!socket.admininfo) return callback(false, CONST_STRINGS.INVALID_SOCKET); var room = listOfRooms[user.roomid]; if(!room) return callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); if(room.owner !== user.userid) return callback(false, CONST_STRINGS.ROOM_PERMISSION_DENIED); autoCloseEntireSession = true; closeOrShiftRoom(); callback(true); } catch (e) { pushLogs(config, 'close-entire-session', e); } }); socket.on('check-presence', function(roomid, callback) { try { if (!listOfRooms[roomid] || !listOfRooms[roomid].participants.length) { callback(false, roomid, { _room: { isFull: false, isPasswordProtected: false } }); } else { var extra = listOfRooms[roomid].extra; if(typeof extra !== 'object' || !extra) { extra = { value: extra }; } extra._room = { isFull: listOfRooms[roomid].participants.length >= listOfRooms[roomid].maxParticipantsAllowed, isPasswordProtected: listOfRooms[roomid].password && listOfRooms[roomid].password.toString().replace(/ /g, '').length }; callback(true, roomid, extra); } } catch (e) { pushLogs(config, 'check-presence', e); } }); function onMessageCallback(message) { try { if (!listOfUsers[message.sender]) { socket.emit('user-not-found', message.sender); return; } // we don't need "connectedWith" anymore // todo: remove all these redundant codes // fire "onUserStatusChanged" for room-participants instead of individual users // rename "user-connected" to "user-status-changed" if (!message.message.userLeft && !listOfUsers[message.sender].connectedWith[message.remoteUserId] && !!listOfUsers[message.remoteUserId]) { listOfUsers[message.sender].connectedWith[message.remoteUserId] = listOfUsers[message.remoteUserId].socket; listOfUsers[message.sender].socket.emit('user-connected', message.remoteUserId); if (!listOfUsers[message.remoteUserId]) { listOfUsers[message.remoteUserId] = { socket: null, connectedWith: {}, extra: {}, admininfo: {} }; } listOfUsers[message.remoteUserId].connectedWith[message.sender] = socket; if (listOfUsers[message.remoteUserId].socket) { listOfUsers[message.remoteUserId].socket.emit('user-connected', message.sender); } sendToAdmin(); } if (listOfUsers[message.sender] && listOfUsers[message.sender].connectedWith[message.remoteUserId] && listOfUsers[socket.userid]) { message.extra = listOfUsers[socket.userid].extra; listOfUsers[message.sender].connectedWith[message.remoteUserId].emit(socketMessageEvent, message); sendToAdmin(); } } catch (e) { pushLogs(config, 'onMessageCallback', e); } } function joinARoom(message) { try { if (!socket.admininfo || !socket.admininfo.sessionid) return; // var roomid = message.remoteUserId; var roomid = socket.admininfo.sessionid; if (!listOfRooms[roomid]) return; // find a solution? if (listOfRooms[roomid].participants.length >= listOfRooms[roomid].maxParticipantsAllowed && listOfRooms[roomid].participants.indexOf(socket.userid) === -1) { // room is full // todo: how to tell user that room is full? // do not fire "room-full" event // find something else return; } if (listOfRooms[roomid].session && (listOfRooms[roomid].session.oneway === true || listOfRooms[roomid].session.broadcast === true)) { var owner = listOfRooms[roomid].owner; if (listOfUsers[owner]) { message.remoteUserId = owner; if (enableScalableBroadcast === false) { // only send to owner i.e. only connect with room owner listOfUsers[owner].socket.emit(socketMessageEvent, message); } } return; } // redundant? // appendToRoom(roomid, socket.userid); if (enableScalableBroadcast === false) { // connect with all participants listOfRooms[roomid].participants.forEach(function(pid) { if (pid === socket.userid || !listOfUsers[pid]) return; var user = listOfUsers[pid]; message.remoteUserId = pid; user.socket.emit(socketMessageEvent, message); }); } } catch (e) { pushLogs(config, 'joinARoom', e); } sendToAdmin(); } function appendToRoom(roomid, userid) { try { if (!listOfRooms[roomid]) { listOfRooms[roomid] = { maxParticipantsAllowed: parseInt(params.maxParticipantsAllowed || 1000) || 1000, owner: userid, // this can change if owner leaves and if control shifts participants: [userid], extra: {}, // usually owner's extra-data socketMessageEvent: '', socketCustomEvent: '', identifier: '', session: { audio: true, video: true } }; } if (listOfRooms[roomid].participants.indexOf(userid) !== -1) return; listOfRooms[roomid].participants.push(userid); } catch (e) { pushLogs(config, 'appendToRoom', e); } } function closeOrShiftRoom() { try { if (!socket.admininfo) { return; } var roomid = socket.admininfo.sessionid; if (roomid && listOfRooms[roomid]) { if (socket.userid === listOfRooms[roomid].owner) { if (autoCloseEntireSession === false && listOfRooms[roomid].participants.length > 1) { var firstParticipant; listOfRooms[roomid].participants.forEach(function(pid) { if (firstParticipant || pid === socket.userid) return; if (!listOfUsers[pid]) return; firstParticipant = listOfUsers[pid]; }); if (firstParticipant) { // reset owner priviliges listOfRooms[roomid].owner = firstParticipant.socket.userid; // redundant? firstParticipant.socket.emit('set-isInitiator-true', roomid); // remove from room's participants list var newParticipantsList = []; listOfRooms[roomid].participants.forEach(function(pid) { if (pid != socket.userid) { newParticipantsList.push(pid); } }); listOfRooms[roomid].participants = newParticipantsList; } else { delete listOfRooms[roomid]; } } else { delete listOfRooms[roomid]; } } else { var newParticipantsList = []; listOfRooms[roomid].participants.forEach(function(pid) { if (pid && pid != socket.userid && listOfUsers[pid]) { newParticipantsList.push(pid); } }); listOfRooms[roomid].participants = newParticipantsList; } } } catch (e) { pushLogs(config, 'closeOrShiftRoom', e); } } socket.on(socketMessageEvent, function(message, callback) { if (message.remoteUserId && message.remoteUserId === socket.userid) { // remoteUserId MUST be unique return; } try { if (message.remoteUserId && message.remoteUserId != 'system' && message.message.newParticipationRequest) { if (enableScalableBroadcast === true) { var user = listOfUsers[message.remoteUserId]; if (user) { user.socket.emit(socketMessageEvent, message); } if (listOfUsers[socket.userid] && listOfUsers[socket.userid].extra.broadcastId) { // for /admin/ page appendToRoom(listOfUsers[socket.userid].extra.broadcastId, socket.userid); } } else if (listOfRooms[message.remoteUserId]) { joinARoom(message); return; } } // for v3 backward compatibility; >v3.3.3 no more uses below block if (message.remoteUserId == 'system') { if (message.message.detectPresence) { if (message.message.userid === socket.userid) { callback(false, socket.userid); return; } callback(!!listOfUsers[message.message.userid], message.message.userid); return; } } if (!listOfUsers[message.sender]) { listOfUsers[message.sender] = { socket: socket, connectedWith: {}, extra: {}, admininfo: {} }; } // if someone tries to join a person who is absent // -------------------------------------- DISABLED if (false && message.message.newParticipationRequest) { var waitFor = 60 * 10; // 10 minutes var invokedTimes = 0; (function repeater() { if (typeof socket == 'undefined' || !listOfUsers[socket.userid]) { return; } invokedTimes++; if (invokedTimes > waitFor) { socket.emit('user-not-found', message.remoteUserId); return; } // if user just come online if (listOfUsers[message.remoteUserId] && listOfUsers[message.remoteUserId].socket) { joinARoom(message); return; } setTimeout(repeater, 1000); })(); return; } onMessageCallback(message); } catch (e) { pushLogs(config, 'on-socketMessageEvent', e); } }); socket.on('is-valid-password', function(password, roomid, callback) { try { callback = callback || function() {}; if(!password || !password.toString().replace(/ /g, '').length) { callback(false, roomid, 'You did not enter the password.'); return; } if(!roomid || !roomid.toString().replace(/ /g, '').length) { callback(false, roomid, 'You did not enter the room-id.'); return; } if(!listOfRooms[roomid]) { callback(false, roomid, CONST_STRINGS.ROOM_NOT_AVAILABLE); return; } if(!listOfRooms[roomid].password) { callback(false, roomid, 'This room do not have any password.'); return; } if(listOfRooms[roomid].password === password) { callback(true, roomid, false); } else { callback(false, roomid, CONST_STRINGS.INVALID_PASSWORD); } } catch(e) { pushLogs('is-valid-password', e); } }); socket.on('get-public-rooms', function(identifier, callback) { try { if(!identifier || !identifier.toString().length || !identifier.toString().replace(/ /g, '').length) { callback(null, CONST_STRINGS.PUBLIC_IDENTIFIER_MISSING); return; } var rooms = []; Object.keys(listOfRooms).forEach(function(key) { var room = listOfRooms[key]; if(!room || !room.identifier || !room.identifier.toString().length || room.identifier !== identifier) return; rooms.push({ maxParticipantsAllowed: room.maxParticipantsAllowed, owner: room.owner, participants: room.participants, extra: room.extra, session: room.session, sessionid: key, isRoomFull: room.participants.length >= room.maxParticipantsAllowed, isPasswordProtected: !!room.password && room.password.replace(/ /g, '').length > 0 }); }); callback(rooms); } catch(e) { pushLogs('get-public-rooms', e); } }); socket.on('open-room', function(arg, callback) { callback = callback || function() {}; try { // if already joined a room, either leave or close it closeOrShiftRoom(); if (listOfRooms[arg.sessionid] && listOfRooms[arg.sessionid].participants.length) { callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); return; } if (enableScalableBroadcast === true) { arg.session.scalable = true; arg.sessionid = arg.extra.broadcastId; } // maybe redundant? if (!listOfUsers[socket.userid]) { listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, extra: arg.extra, admininfo: {}, socketMessageEvent: params.socketMessageEvent || '', socketCustomEvent: params.socketCustomEvent || '' }; } listOfUsers[socket.userid].extra = arg.extra; if (arg.session && (arg.session.oneway === true || arg.session.broadcast === true)) { autoCloseEntireSession = true; } } catch (e) { pushLogs(config, 'open-room', e); } // append this user into participants list appendToRoom(arg.sessionid, socket.userid); try { // override owner & session if (enableScalableBroadcast === true) { if (Object.keys(listOfRooms[arg.sessionid]).length == 1) { listOfRooms[arg.sessionid].owner = socket.userid; listOfRooms[arg.sessionid].session = arg.session; } } else { // for non-scalable-broadcast demos listOfRooms[arg.sessionid].owner = socket.userid; listOfRooms[arg.sessionid].session = arg.session; listOfRooms[arg.sessionid].extra = arg.extra || {}; listOfRooms[arg.sessionid].socketMessageEvent = listOfUsers[socket.userid].socketMessageEvent; listOfRooms[arg.sessionid].socketCustomEvent = listOfUsers[socket.userid].socketCustomEvent; listOfRooms[arg.sessionid].maxParticipantsAllowed = parseInt(params.maxParticipantsAllowed || 1000) || 1000; if(arg.identifier && arg.identifier.toString().length) { listOfRooms[arg.sessionid].identifier = arg.identifier; } try { if (typeof arg.password !== 'undefined' && arg.password.toString().length) { // password protected room? listOfRooms[arg.sessionid].password = arg.password; } } catch (e) { pushLogs(config, 'open-room.password', e); } } // admin info are shared only with /admin/ listOfUsers[socket.userid].socket.admininfo = { sessionid: arg.sessionid, session: arg.session, mediaConstraints: arg.mediaConstraints, sdpConstraints: arg.sdpConstraints, streams: arg.streams, extra: arg.extra }; } catch (e) { pushLogs(config, 'open-room', e); } sendToAdmin(); try { callback(true); } catch (e) { pushLogs(config, 'open-room', e); } }); socket.on('join-room', function(arg, callback) { callback = callback || function() {}; try { // if already joined a room, either leave or close it closeOrShiftRoom(); if (enableScalableBroadcast === true) { arg.session.scalable = true; arg.sessionid = arg.extra.broadcastId; } // maybe redundant? if (!listOfUsers[socket.userid]) { listOfUsers[socket.userid] = { socket: socket, connectedWith: {}, extra: arg.extra, admininfo: {}, socketMessageEvent: params.socketMessageEvent || '', socketCustomEvent: params.socketCustomEvent || '' }; } listOfUsers[socket.userid].extra = arg.extra; } catch (e) { pushLogs(config, 'join-room', e); } try { if (!listOfRooms[arg.sessionid]) { callback(false, CONST_STRINGS.ROOM_NOT_AVAILABLE); return; } } catch (e) { pushLogs(config, 'join-room', e); } try { if (listOfRooms[arg.sessionid].password && listOfRooms[arg.sessionid].password != arg.password) { callback(false, CONST_STRINGS.INVALID_PASSWORD); return; } } catch (e) { pushLogs(config, 'join-room.password', e); } try { if (listOfRooms[arg.sessionid].participants.length >= listOfRooms[arg.sessionid].maxParticipantsAllowed) { callback(false, CONST_STRINGS.ROOM_FULL); return; } } catch (e) { pushLogs(config, 'join-room.ROOM_FULL', e); } // append this user into participants list appendToRoom(arg.sessionid, socket.userid); try { // admin info are shared only with /admin/ listOfUsers[socket.userid].socket.admininfo = { sessionid: arg.sessionid, session: arg.session, mediaConstraints: arg.mediaConstraints, sdpConstraints: arg.sdpConstraints, streams: arg.streams, extra: arg.extra }; } catch (e) { pushLogs(config, 'join-room', e); } sendToAdmin(); try { callback(true); } catch (e) { pushLogs(config, 'join-room', e); } }); socket.on('disconnect', function() { try { if (socket && socket.namespace && socket.namespace.sockets) { delete socket.namespace.sockets[this.id]; } } catch (e) { pushLogs(config, 'disconnect', e); } try { // inform all connected users if (listOfUsers[socket.userid]) { for (var s in listOfUsers[socket.userid].connectedWith) { listOfUsers[socket.userid].connectedWith[s].emit('user-disconnected', socket.userid); // sending duplicate message to same socket? if (listOfUsers[s] && listOfUsers[s].connectedWith[socket.userid]) { delete listOfUsers[s].connectedWith[socket.userid]; listOfUsers[s].socket.emit('user-disconnected', socket.userid); } } } } catch (e) { pushLogs(config, 'disconnect', e); } closeOrShiftRoom(); delete listOfUsers[socket.userid]; if (socket.ondisconnect) { try { // scalable-broadcast.js socket.ondisconnect(); } catch(e) { pushLogs('socket.ondisconnect', e); } } sendToAdmin(); }); } };