Node.js Set socket ID - javascript

Starting from the official chat example of Node.js.
The user is prompted to emit his name to the server through 'register' (client.html):
while (name == '') {
name = prompt("What's your name?","");
}
socket.emit('register', name );
The server receives the name. I want it to make the name as the identifier of the socket. So when I need to send a message to that user I send it to the socket having his name (Names are stored in a database for the info).
Changes will take place here (server.js):
socket.on('register', function (name) {
socket.set('nickname', name, function () {
// this kind of emit will send to all! :D
io.sockets.emit('chat', {
msg : "naay nag apil2! si " + name + '!',
msgr : "mr. server"
});
});
});
I'm struggling making this works, because I can't go any farer if I can't identify the socket. So any help will be really appreciated.
Update: I understand that nickname is a parameter for the socket, so the question is more specifically: how to get the socket that has "Kyle" as a nickname to emit it a message?

Store your sockets in a structure like this:
var allSockets = {
// A storage object to hold the sockets
sockets: {},
// Adds a socket to the storage object so it can be located by name
addSocket: function(socket, name) {
this.sockets[name] = socket;
},
// Removes a socket from the storage object based on its name
removeSocket: function(name) {
if (this.sockets[name] !== undefined) {
this.sockets[name] = null;
delete this.sockets[name];
}
},
// Returns a socket from the storage object based on its name
// Throws an exception if the name is not valid
getSocketByName: function(name) {
if (this.sockets[name] !== undefined) {
return this.sockets[name];
} else {
throw new Error("A socket with the name '"+name+"' does not exist");
}
}
};

Related

socket.io send data to matching socket's

when a user connects to my socket
I add to a session map:
io.on('connection', function (socket) {
sessionMap.set(socket.id,socket);
}
my session Map
var SessionMap = {};
module.exports = {
set: function(key,value){
SessionMap[key] = value;
},
get: function(key){
return SessionMap[key]
},
delete: function(key){
delete SessionMap[key]
},
all: function(){
return SessionMap
}
}
And also save my user socket id in a class player:
socket.on('addPlayer-Queue', (result) => {
sessionMap.set(socket.id,socket);
queue.addPlayer(new Player({
id: result.id,
name: result.name,
mmr: result.mmr
}, socket.id));
And I have a function that selects two players that are connected (where I save in an array) and create a "battle" and then I wanted to send to the socket that was selected / matched for this battle
the battle dice
This is my role that selects both players and creates a battle:
searching() {
const firstPlayer = this.getRandomPlayer();
const secondPlayer = this.players.find(
playerTwo =>
playerTwo.mmr < this.calculateLessThanPercentage(firstPlayer) &&
playerTwo.mmr > this.calculateGreaterThanPercentage(firstPlayer) &&
playerTwo.id != firstPlayer.id
);
if (!secondPlayer) {
return null;
}
const matchedPlayers = [firstPlayer, secondPlayer];
this.removePlayers(matchedPlayers);
return new Match(matchedPlayers);
}
}
And also when connecting I use a set interval to be performing this function every 1 second
But my difficulty is how I would send the data from this battle to the corresponding socket's
my relation socket with player
When a player enters my event I create a player by going through socket id
And I also make a session map of this socket
sessionMap.set(socket.id,socket);
my class player:
class Player {
constructor(player,socketId) {
this.id = player.id
this.socketId = socketId
this.name = player.name
this.mmr = player.mmr
}
}
module.exports = Player;
const getMatchConfigurationFor = player => {
/* configure and return the payload notifying the player of the match */
}
const configurePlayersForNewMatch = () => matchedPlayers.forEach(player =>
sessionMap.get(player.socketid)
.broadcast.to(player.socketid)
.emit(messageTags.MATCH_CONFIGURATION,
getMatchConfigurationFor(player)))
regarding where to do this work .. the single responsibility principle says that a function should have a singular clear purpose. So the search method should search for matching players, not configure the match. You should do this work in another function that is called while configuring the match, which itself is called after the search returns successfully. I've provided the wrapper function for that here: it is written in a fashion to expect the relevant pieces are in scope. You could rewrite it as a proper function with parameters if you prefer.
This is a work in progress solution for Felipe, posted by request.
After a match is found, you'd probably want to emit a MatchFound object to both clients detailing information about the match (including information about their opponent). Once a client gets this, you can initiate anything the client needs for a match (load a level, display names, or a lobby).

Socket undefined, send don't send if undefined

Hello I wrote the following function to get someone his socket;
var getSocket = function(persId){
var socketid = users[persId].socket;
if(io.sockets.connected[socketid]){
return io.sockets.connected[socketid];
}
}
They are being emitted like so;
getSocket(372).emit({ ... });
Now if an user has disconnected before the socket is sent out it will result in a undefined socket error, which may cause issues.
is there any way by modifying the function (without having to check for if getSocket(372)) to make it not throw out an error? it's causing problems right now.
Thanks!
What you should do instead, is use socket.io function, that will check for that already.
io.to(room).emit()
On connection just join the user to a room named 372 (I believe that's the user id), and then emit to it:
io.on('connection', socket => {
const room = 372; // Get user id somehow
socket.join(room);
});
// Somewhere
io.to(372).emit(..)
Answering your specific question, you can return an object with an emit nop method. Also you have to check if the user exist before accessing to .socket. You can do that using destructuring.
const { socket: socketId } = users[persId] || {};
The full function should be:
const getSocket = function(persId){
const { socket: socketId } = users[persId] || {};
if(socketId && io.sockets.connected[socketId]){
return io.sockets.connected[socketId];
}
return { emit: () => {} }
}
And now you don't have to perform a check, since .emit will always exist.
getSocket(23123213).emit(); // Will no throw if 23123213 doesn't exist

socket.io - issue with the socket variable while using namespace

I'm following a book which has a code snippet for socket.io namespaces. the version specified in the book is socket 0.9
this is the below code for a simple chatroom using namespaces.
exports.initialize = function(server) {
io = io.listen(server);
var chatInfra = io.of("/chat_infra")
.on("connection", function(socket) {
socket.on("set_name", function(data) {
socket.set('nickname', data.name, function() {
socket.emit('name_set', data);
socket.send(JSON.stringify({
type: 'serverMessage',
message: 'Welcome to the most interesting ' +
'chat room on earth!'
}));
socket.broadcast.emit('user_entered', data);
});
});
});
var chatCom = io.of("/chat_com")
.on("connection", function(socket) {
socket.on('message', function(message) {
message = JSON.parse(message);
if (message.type == "userMessage") {
socket.get('nickname', function(err, nickname) {
message.username = nickname;
socket.broadcast.send(JSON.stringify(message));
message.type = "myMessage";
socket.send(JSON.stringify(message));
});
}
});
});
}
i'm trying to recreate the code using the latest socket version. but i'm having trouble getting the socket.nickname variable in the chatCom namespace. the socket.nickname variable in the chatCom namespace returns undefined because of the local scope.
here's my version
exports.initialize = function(server){
io = io(server);
var chatInfra = io.of('/chat_infra');
chatInfra.on('connection',function(socket){
socket.on('set_name',function(data){
socket.nickname = data.name;
console.log(socket.nickname);
socket.emit('name_set',data);
socket.send(JSON.stringify({
type:'serverMessage',
message:'Welcome to the most interesting chat room on earth'
}));
socket.broadcast.emit('user_entered',data);
})
})
var chatCom = io.of('/chat_com');
chatCom.on('connection',function(socket){
socket.on('message',function(message){
message = JSON.parse(message);
if(message.type === 'userMessage'){
message.nickname = socket.nickname;
socket.broadcast.send(JSON.stringify(message));
message.type = 'myMessage';
socket.send(JSON.stringify(message));
}
})
})
}
Is there a way i can access the nickname value in the second namespace?
I'm able to access the same socket instance in both namespaces. but however, the nickname property seems to be erased when i invoke it from a different namespace.
Output of console.log
socket id in /chat_infra is : Zo0kfigUFJOZIgH8AAAB and socket nickname is kj
socket id in /chat_com is : Zo0kfigUFJOZIgH8AAAB and socket nickname is undefine
Sorry that I can't verify this myself right now (and also that I can't comment yet), but hopefully I can help you figure it out.
Both chatInfra and chatCom should have a connected property that is an map of all the current sockets connected to each. If the socket is not shared/reused in each namespace, perhaps they still share the same connection ID. So, you may be able to get the nickname from chatInfra.connected[socket.id].nickname.
If that doesn't work, console.log each of their connected properties and see their differences. Also, socket.io handles JSON natively, so you don't need to JSON.parse and JSON.stringify your objects.
Instead setting nickname to socket, you can set nickname to the client object of socket, such as socket.client.nickname = data.name.

WebRTC Failed to Add a Third Peer: "Cannot set remote answer in state stable"

I am writing a multi-peer WebRTC video chat.
Two peers have no trouble connecting, no error or warning in console, and video works well, but I cannot add a third party to the chat successfully.
On the host (the first participant, Firefox), the error appear as "Cannot set remote answer in state stable" when trying to create an answer. At the second participant (Chrome), the error is "Failed to set remote answer sdp: Called in wrong state: STATE_INPROGRESS". At he third peer, the error is "the error is "Failed to set remote answer sdp: Called in wrong state: STATE_RECEIVEDINITIATE".
As it turn out, the first peer failed to have video with the third peer. Other two links appear fine.
Generally, my communication model is as below, self_id is a unique id per each peer in the session, and locate_peer_connection() will return the local peer_connection of the particular peer from which we receive message:
a new client send "peer_arrival" to the session using signalling server
all peers already in the session setlocaldescription, create offer and send to the new client
new client create answers to all other peers and setremotedescription
new client has video coming up
Signalling is done using WebSocket on a node.js server.
I have some of the core code below, some more note:
self_id is an unique id per client in a session
peer_connection stores peerConnection to other nodes, and peer_id store the respective user_id of these objects
local_stream is the local video stream from getUserMedia (already considered different browser)
Any insights in to the issue? Is there something wrong with my model?
// locate a peer connection according to its id
function locate_peer_connection(id) {
var index = peer_id.indexOf(id);
// not seen before
if (index == -1) {
add_peer_connection();
peer_id.push(id);
index = peer_id.length - 1;
}
return index;
}
// add a peer connection
function add_peer_connection() {
console.log('add peer connection');
// add another peer connection for use
peer_connection.push(new rtc_peer_connection({ "iceServers": [{ "url": "stun:"+stun_server }]}));
// generic handler that sends any ice candidate to the other peer
peer_connection[peer_connection.length - 1].onicecandidate = function (ice_event) {
if (ice_event.candidate) {
signaling_server.send(
JSON.stringify({
type: "new_ice_candidate",
candidate: ice_event.candidate,
id: self_id,
token:call_token
})
);
console.log('send new ice candidate, from ' + self_id);
}
};
// display remote video streams when they arrive using local <video> MediaElement
peer_connection[peer_connection.length - 1].onaddstream = function (event) {
video_src.push(event.stream); // store this src
video_src_id.push(peer_connection.length - 1);
if (video_src.length == 1) { // first peer
connect_stream_to_src(event.stream, document.getElementById("remote_video"));
// video rotating function
setInterval(function() {
// rorating video src
var video_now = video_rotate;
if (video_rotate == video_src.length - 1) {
video_rotate = 0;
} else {
video_rotate++;
}
var status = peer_connection[video_src_id[video_rotate]].iceConnectionState;
if (status == "disconnected" || status == "closed") { // connection lost, do not show video
console.log('connection ' + video_rotate + ' liveness check failed');
} else if (video_now != video_rotate) {
connect_stream_to_src(video_src[video_rotate], document.getElementById("remote_video"));
}
}, 8000);
// hide placeholder and show remote video
console.log('first remote video');
document.getElementById("loading_state").style.display = "none";
document.getElementById("open_call_state").style.display = "block";
}
console.log('remote video');
};
peer_connection[peer_connection.length - 1].addStream(local_stream);
}
// handle new peer
function new_peer(signal) {
// locate peer connection
var id = locate_peer_connection(signal.id);
console.log('new peer ' + id);
// create offer
peer_connection[id].createOffer(function(sdp) {
peer_connection[id].setLocalDescription(sdp,
function() { // call back
console.log('set local, send offer, connection '+ id);
signaling_server.send(
JSON.stringify({
token: call_token,
id: self_id,
type:"new_offer",
sdp: sdp
})
);
}, log_error);
}, log_error);
}
// handle offer
function new_offer_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('new offer ' + id);
// set remote description
peer_connection[id].setRemoteDescription(
new rtc_session_description(signal.sdp),
function() { // call back
peer_connection[id].createAnswer(function(sdp) {
peer_connection[id].setLocalDescription(sdp, function () {
console.log('set local, send answer, connection '+ id);
signaling_server.send(
JSON.stringify({
token: call_token,
id: self_id,
type:"new_answer",
sdp: sdp
})
);
},
log_error);
}, log_error);
}, log_error);
}
// handle answer
function new_answer_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('new answer ' + id);
peer_connection[id].setRemoteDescription(new rtc_session_description(signal.sdp),
function() {
console.log('receive offer answer, set remote, connection '+ id);
}
, log_error);
}
// handle ice candidate
function ice_candidate_handler(signal) {
var id = locate_peer_connection(signal.id);
console.log('get new_ice_candidate from ' + id);
if (typeof(RTCIceCandidate) != "undefined") {
peer_connection[id].addIceCandidate(
new RTCIceCandidate(signal.candidate)
);
} else { // firefox
peer_connection[id].addIceCandidate(
new mozRTCIceCandidate(signal.candidate)
);
}
}
function event_handler(event) {
var signal = JSON.parse(event.data);
if (signal.type === "peer_arrival") {
new_peer(signal);
} else if (signal.type === "new_ice_candidate") {
ice_candidate_handler(signal);
} else if (signal.type === "new_offer") { // get peer description offer
new_offer_handler(signal);
} else if (signal.type === "new_answer") { // get peer description answer
new_answer_handler(signal);
} else if (signal.type === "new_chat_message") { // chat message and file sharing info
add_chat_message(signal);
} else if (signal.type === "new_file_thumbnail_part") { // thumbnail
store_file_part(signal.name, "thumbnail", signal.id, signal.part, signal.length, signal.data);
if (file_store[signal.id].thumbnail.parts.length == signal.length) {
document.getElementById("file_list").innerHTML = get_file_div(signal.id, signal.name)+document.getElementById("file_list").innerHTML;
document.getElementById("file-img-"+signal.id).src = file_store[signal.id].thumbnail.parts.join("");
}
} else if (signal.type === "new_file_part") { // file
console.log('get new_file_part ' + signal.id);
store_file_part(signal.name, "file", signal.id, signal.part, signal.length, signal.data);
update_file_progress(signal.name, signal.id, file_store[signal.id].file.parts.length, signal.length);
}
}
// generic error handler
function log_error(error) {
console.log(error);
}
I might be wrong, but these two must be your major problems:
signaling_server.send(... I am not seeing any target here, so guessing that the server just broadcasts this message to everyone. When you are sending an sdp to already established peer connection, you are bound to get the error which you are getting now. My suggesting would be to add a target id in the message, either the server can forward it to that particular peer or, server can just broadcast, but event_handler of the peer can check if the target id of message is same as it's own id, if not, just ignore the message.
onicecandidate event, you are broadcasting the ICE candidates to all remote peers, again this is meant for single peer, another issue might be, addIceCandidate on PeerConnection before setting it's local and remote description would throw error, you need to some sort of mechanism to handle this( add ICE candidates only after setting the connections descriptions).
finally a suggestion. I am guessing peer_connection is an Array, if you change it Object, you can remove the redundancy of locate_peer_connection,
you can do something like.
if(peer_connection[signal.id]){
//do something...
}else{
peer_connection[signal.id] = new PeerConnection(...
}
i had the same problem when i was implementing one-to-many rtc broadcast, and what mido22 said is right. you might be sending/resetting existing established peer object with other incoming client. you have to create new RTCPeerConenction object for every new incoming client. the general work flow would be as follow.
peerconnection=[];//empty array
then you initialize your media devices with getUserMedia
and store media stream into global variable so that it can be added when creating offer.
once this is done you inform your singalling server with unique id
your signalling server may then broadcast this to all clients except from which it is received. each client will then check if that unique id does exist in there peerconnection array and like mido22 said you can do this as
if(peerconnection[signal.id])
{
//do something with existing peerconnections
}
else
{
peerconnection[signal.id]=new RTCPeerConnection({"stun_server_address"});
//register peerconnection[signal.id].onicecandidate callback
//register peerconnection[signal.id].onaddstream callback
//createoffer
//if local stream is ready then
peerconnection[signal.id].addStream(localStream);
//and rest of the stuff go as it is like in one-to-one call..
//setLocalDescriptor setRemoteDescriptor
}

SOCKET.IO - Usage of socket.id in two different browsers for same logged in user

This is more of a question regarding what to do in the scenario where you want to trigger a socket event for one user, that might be logged into another browser.
I've got a couple of functions that update a users' workstack real-time (in a queue of other workstacks that are assignable by other users); however, if the user is logged into another browser at the same time, and do an update in one browser, it doesn't update in the other (as they have a different socket.id).
I'm not sure what to do with this... I could do it based on the user ID of the person logged in, but at present my socket code does not have scope of any session variables and although there are modules such as session-socket - I'm not sure if that's the right path to go down.
Can anyone advise a way I might approach this please?
If you don't use any cluster you can follow the approach I had in my Miaou chat : I simply set the user as a property of the socket object and I iterate on sockets when necessary. This allow for a few utilitarian functions.
Here's the (simplified) related code.
io.on('connect', function(socket){
...
var userId = session.passport.user;
if (!userId) return die("no authenticated user in socket's session");
...
var shoe = new Shoe(socket, completeUser) // <=== bindind user socket here
// socket event binding here
The Shoe object :
// A shoe embeds a socket and is provided to controlers and plugins.
// It's kept in memory by the closures of the socket event handlers
function Shoe(socket, completeUser){
this.socket = socket;
this.completeUser = completeUser;
this.publicUser = {id:completeUser.id, name:completeUser.name};
this.room;
socket['publicUser'] = this.publicUser;
this.emit = socket.emit.bind(socket);
}
var Shoes = Shoe.prototype;
// emits something to all sockets of a given user. Returns the number of sockets
Shoes.emitToAllSocketsOfUser = function(key, args, onlyOtherSockets){
var currentUserId = this.publicUser.id,
nbs = 0;
for (var clientId in io.sockets.connected) {
var socket = io.sockets.connected[clientId];
if (onlyOtherSockets && socket === this.socket) continue;
if (socket && socket.publicUser && socket.publicUser.id===currentUserId) {
socket.emit(key, args);
nbs++;
}
}
return nbs;
}
// returns the socket of the passed user if he's in the same room
Shoes.userSocket = function(userIdOrName) {
var clients = io.sockets.adapter.rooms[this.room.id],
sockets = [];
for (var clientId in clients) {
var socket = io.sockets.connected[clientId];
if (socket && socket.publicUser && (socket.publicUser.id===userIdOrName||socket.publicUser.name===userIdOrName)) {
return socket;
}
}
}
// returns the ids of the rooms to which the user is currently connected
Shoes.userRooms = function(){
var rooms = [],
uid = this.publicUser.id;
iorooms = io.sockets.adapter.rooms;
for (var roomId in iorooms) {
if (+roomId!=roomId) continue;
var clients = io.sockets.adapter.rooms[roomId];
for (var clientId in clients) {
var socket = io.sockets.connected[clientId];
if (socket && socket.publicUser && socket.publicUser.id===uid) {
rooms.push(roomId);
break;
}
}
}
return rooms;
}
// returns the first found socket of the passed user (may be in another room)
function anyUserSocket(userIdOrName) {
for (var clientId in io.sockets.connected) {
var socket = io.sockets.connected[clientId];
if (socket.publicUser && (socket.publicUser.id===userIdOrName||socket.publicUser.name===userIdOrName)) {
return socket;
}
}
}
// closes all sockets from a user in a given room
exports.throwOut = function(userId, roomId, text){
var clients = io.sockets.adapter.rooms[roomId];;
for (var clientId in clients) {
var socket = io.sockets.connected[clientId];
if (socket.publicUser && socket.publicUser.id===userId) {
if (text) socket.emit('miaou.error', text);
socket.disconnect('unauthorized');
}
}
}
Real code
Now, with ES6 based node versions and WeakMap, I might implement a more direct mapping but the solution I described is robust and efficient enough.

Categories

Resources