// Node Server Script // Version 2.0.04 (9:37 AM Wed March 21, 2018) // Written by: James D. Miller var app = require('express')(); var http = require('http').Server(app); var io = require('socket.io')(http); const port = process.env.PORT || 3000; app.get('/', function(req, res) { // In a browser, if you set the URL to localhost:3000, you'll get this page: res.sendfile('links.html'); }); // Put various client data and maps in a global. var cD = {}; cD.userIndex = 0; // Map: userName[ socket.id] cD.userName = {}; cD.nickName = {}; // Map: id[ userName] cD.id = {}; // Map: room[ socket.id] cD.room = {}; // Map: hostID[ roomName] cD.hostID = {}; // Miscellaneous support functions... function setDefault( theValue, theDefault) { // Return the default if the value is undefined. return (typeof theValue !== "undefined") ? theValue : theDefault; } function removeUserFromMaps( clientID) { // Do this first, before removing this user from the maps. // Check to see if this is the host. var hostID = cD.hostID[ cD.room[ clientID]]; if (hostID == clientID) { delete cD.hostID[ cD.room[ clientID]]; } // In a similar way, make use of the userName map before removing the user from userName. delete cD.id[ cD.userName[ clientID]]; delete cD.userName[ clientID]; // Not every user will have a nick name. if (cD.nickName[ clientID]) delete cD.nickName[ clientID]; // The room map was used above. Now it's ok to remove the user from the room map. delete cD.room[ clientID]; } function setDisplayName( clientID, mode) { var displayNameString; if (cD.nickName[clientID]) { if (mode == 'comma') { displayNameString = cD.nickName[clientID] + ', ' + cD.userName[clientID]; } else if (mode == 'prens') { displayNameString = cD.nickName[clientID] + ' (' + cD.userName[clientID] + ')'; } } else { displayNameString = cD.userName[clientID]; } return displayNameString; } // Socket.io stuff... io.on('connection', function(socket) { // Example of how to parse out the query string if it is sent in the connection attempt from the client. console.log(""); console.log("Connection starting..."); console.log("mode=" + socket.handshake.query['mode'] + ", current name=" + socket.handshake.query['currentName'] + ", nickName=" + socket.handshake.query['nickName']); // Normal initial connection if (socket.handshake.query['mode'] == 'normal') { cD.userIndex += 1; var user_name = 'u' + cD.userIndex; // If re-connecting, re-use the current user name that comes in via the query string. } else if (socket.handshake.query['mode'] == 're-connect') { var user_name = socket.handshake.query['currentName']; } var nick_name = socket.handshake.query['nickName']; // Two maps cD.userName[ socket.id] = user_name; if (nick_name) cD.nickName[ socket.id] = nick_name; cD.id[ user_name] = socket.id; console.log(''); console.log('Their count=' + io.engine.clientsCount + ', my index=' + cD.userIndex + ', names=' + Object.keys(cD.userName).length + ', nick names=' + Object.keys(cD.nickName).length); console.log('New client: '+ cD.userName[socket.id] +', '+ socket.id + '.'); // Tell the new user their network name. io.to(socket.id).emit('your name is', JSON.stringify({'name':cD.userName[socket.id], 'nickName':nick_name})); // Now set up the various listeners. I know this seems a little odd, but these listeners // need to be defined each time this connection event fires, i.e. for each socket. // Echo test... socket.on('echo-from-Client-to-Server', function(msg) { if (msg == 'server') { // This bounces off the SERVER and goes right back to the client. io.to(socket.id).emit('echo-from-Server-to-Client', 'server'); } else if (msg == 'host') { // Send this first to the host (the scenic route). Include the id of the client so that we know where to send it // when it bounces off the host. io.to( cD.hostID[ cD.room[ socket.id]]).emit('echo-from-Server-to-Host', socket.id); } }); socket.on('echo-from-Host-to-Server', function(msg) { var socket_id = msg; // Now that this has come back from the HOST, complete the trip and send this to the originating client. io.to(socket_id).emit('echo-from-Server-to-Client', 'host'); }); // Broadcast the incoming chat messages that come in. socket.on('chat message', function(msg) { // General emit to the room. io.to( cD.room[ socket.id]).emit('chat message', msg + " (" + setDisplayName(socket.id, 'comma') + ")"); }); // Signaling in support of WebRTC. socket.on('signaling message', function(msg) { var signal_message = JSON.parse(msg); if (signal_message.to == 'host') { var target = cD.hostID[ cD.room[ socket.id]]; } else { var target = cD.id[ signal_message.to]; } // Relay the message (emit) to the target user. io.to( target).emit('signaling message', msg); }); // General control message (note: same structure as the above handler for signaling messages) socket.on('control message', function(msg) { var control_message = JSON.parse(msg); if (control_message.to == 'host') { var target = cD.hostID[ cD.room[ socket.id]]; } else { var target = cD.id[ control_message.to]; } // Relay the message (emit) to the target user. io.to( target).emit('control message', msg); }); // Send mouse and keyboard states to the host client. socket.on('client-mK-event', function(msg) { // Determine the id of the room-host for this client. Then send data to the host for that room. // socket.id --> room --> room host. var hostID = cD.hostID[ cD.room[ socket.id]]; // StH: Server to Host io.to( hostID).emit('client-mK-StH-event', msg); }); socket.on('roomJoin', function(msg) { var msgParsed = JSON.parse( msg); var roomName = setDefault( msgParsed.roomName, null); var requestStream = setDefault( msgParsed.requestStream, false); var player = setDefault( msgParsed.player, null); var hostOrClient = setDefault( msgParsed.hostOrClient, 'client'); nickName = cD.nickName[ socket.id]; var displayName = setDisplayName( socket.id, 'prens'); if (hostOrClient == 'client') { // Check to make sure the room has a host. if (cD.hostID[ roomName]) { socket.join(roomName); cD.room[ socket.id] = roomName; console.log('Room ' + roomName + ' joined by ' + cD.userName[ socket.id] + '.'); // Send message to the individual client that is joining the room. io.to(socket.id).emit('room-joining-message', 'You have joined room ' + cD.room[socket.id] + ' and your client name is '+ displayName +'.'); // Message to the room host. // Give the host the name of the new user so a new game client can be created. This is where "player" and "nickName" info gets // sent to the host. Notice this is not done, or needed, in the host block below. io.to( cD.hostID[ roomName]).emit('new-game-client', JSON.stringify({'clientName':cD.userName[socket.id], 'requestStream':requestStream, 'player':player, 'nickName':nickName})); // Chat message to the host. io.to( cD.hostID[ roomName]).emit('chat message', displayName + ' is a new client in room '+roomName+'.'); } else { io.to(socket.id).emit('room-joining-message', 'Sorry, there is no host yet for room ' + roomName + '.'); } } else if (hostOrClient == 'host') { // Should check if the room already has a host. if (cD.hostID[ roomName]) { // Send warning to the client that is attempting to host. io.to(socket.id).emit('room-joining-message', 'Sorry, there is already a host for room ' + roomName + '.'); } else { socket.join(roomName); cD.room[ socket.id] = roomName; console.log('Room ' + roomName + ' joined by ' + cD.userName[ socket.id] + '.'); // General you-have-joined-the-room message. io.to(socket.id).emit('room-joining-message', 'You have joined room ' + cD.room[socket.id] + ' and your client name is '+ displayName +'.'); // Set this user as the host for this room. cD.hostID[ cD.room[ socket.id]] = socket.id; console.log('User '+ displayName +' identified as host for room '+ cD.room[ socket.id] + '.'); // An oh-by-the-way "you are the host" message. io.to(socket.id).emit('room-joining-message', 'You are the host of room ' + cD.room[ socket.id] + '.'); } } }); // This "disconnect" event is fired by the server. socket.on('disconnect', function() { if (cD.userName[ socket.id]) { var displayName = setDisplayName( socket.id, 'prens'); // Report at the server. console.log(' '); var message = displayName + ' has disconnected.'; console.log( message + ' (by self, ' + socket.id + ').'); // Report to the room host. var hostID = cD.hostID[ cD.room[ socket.id]]; io.to( hostID).emit('chat message', message+'.'); io.to( hostID).emit('client-disconnected', cD.userName[ socket.id]); // Remove this user from the maps. removeUserFromMaps( socket.id); } }); socket.on('clientDisconnectByHost', function(msg) { var clientName = msg; var clientID = cD.id[ clientName]; // Send disconnect message to the client. io.to( clientID).emit('disconnectByServer', clientName); // Don't do the following. It will disconnect the host socket. Not what we want here! //socket.disconnect(); }); socket.on('okDisconnectMe', function(msg) { // This event indicated the non-host client got the clientDisconnectByHost message (see above) and // agrees to go peacefully. var clientName = msg; var clientID = cD.id[ clientName]; // Report this at the server. console.log(' '); var message = clientName + ' has disconnected'; console.log( message + ' (by host, '+clientID+').'); // Report to the room host. var hostID = cD.hostID[ cD.room[ clientID]]; io.to( hostID).emit('chat message', message); io.to( hostID).emit('client-disconnected', clientName); // Remove this user from the maps. removeUserFromMaps( socket.id); //Finally, go ahead and disconnect this client's socket. socket.disconnect(); }); socket.on('shutDown-p2p-deleteClient', function( msg) { var clientName = msg; var clientID = cD.id[ clientName]; var hostID = cD.hostID[ cD.room[ clientID]]; io.to( hostID).emit('shutDown-p2p-deleteClient', clientName); }); socket.on('command-from-host-to-all-clients', function( msg) { // General emit to the room. io.to( cD.room[ socket.id]).emit('command-from-host-to-all-clients', msg); }); }); http.listen(port, function() { console.log('listening on *:' + port); });