Socket undefined, send don't send if undefined - javascript

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

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.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.

how to get socket.io number of clients in room?

my socket.io version 1.3.5
I want to get number of clients in particular room.
This is my code.
socket.on('create or join', function (numClients, room) {
socket.join(room);
});
I use this code for get clients in room :
console.log('Number of clients',io.sockets.clients(room));
This works for version 3
io.sockets.adapter.rooms.get(roomName).size
To get the number of clients in a room you can do the following:
function NumClientsInRoom(namespace, room) {
var clients = io.nsps[namespace].adapter.rooms[room];
return Object.keys(clients).length;
}
This variable clients will hold a object where each client is a key.
Then you just get the number of clients (keys) in that object.
If you haven't defined a namespace the default one is "/".
Have a counter variable to keep count, increase when someone joins and decrease when people disconnect.
io.on('connection', function (socket) {
var numClients = {};
socket.on('join', function (room) {
socket.join(room);
socket.room = room;
if (numClients[room] == undefined) {
numClients[room] = 1;
} else {
numClients[room]++;
}
});
socket.on('disconnect', function () {
numClients[socket.room]--;
});
This work for me.
// check if your room isn't undefined.
if (io.sockets.adapter.rooms['your room name'])
{
// result
console.log(io.sockets.adapter.rooms['your room name'].length);
}
this work for me
io.in('yourRoom').allSockets().then(result=>{
console.log(res.size) })
Only one that worked for me in 2022 using socket.io 4.5.1:
io._nsps.get('/').adapter.rooms.get('your-room-id').size;
Thanks to #Abhishek Kumar, just making a top level comment for visibility.
In socket.io ^1.4.6
function numClientsInRoom(namespace, room) {
var clients = io.nsps[namespace].adapter.rooms[room].sockets;
return Object.keys(clients).length;
}
You need to add .sockets to rooms[room], because variable clients holds all connection inside sockets variable.
i'm a bit late to the party, but I found a valid solution:
io.in('YOUR_ROOM').engine.clientsCount;
socket.io's rooms is a Map whose elements map to Sets.
You use get(roomId) to get the value of that roomId in a Map and you use .size to the number of elements in a set unlike .length for number of elements in an array.
socket.on("join-room",roomId => {
const room = io.of("/yourNameSpace").adapter.rooms.get(roomId)
if(room === undefined || room.size < 2) {
socket.join(roomId)
}else {
io.of("/yourNameSpace").emit("can't join link")
}
})
io.nsps['/'].adapter.rooms['your room name'].sockets;
If your namespace is not /, change it.
Without Adapter
If you only have one server instance, the easiest way is:
// rooms is a Map object
const rooms = io.of("/").adapter.rooms;
const roomSockets = rooms.get('roomid')
You can see from the official document here
Using An Adapter (Redis)
But if you have multiple server instance and are using Adapters like Redis, this will not work as it will only returns the socket in that instance memory, so based on which server instance you request hit, the result will vary.
To get the actual rooms and sockets, you will need to (for RedisAdapter):
// this is a Set of room ids
const rooms = await io.of("/").adapter.allRooms();
// or get the sockets directly if you know the room id
const roomSockets = await io.of("/").adapter.sockets(new Set(['room id']);
You can see more methods here
Latest Documentation (v4)
// return all Socket instances in the "room1" room of the main namespace
const sockets = await io.in("room1").fetchSockets();
try sockets.size or sockets.length to get the actual count

Cannot read property `xxx` of undefined

I'm working with NodeJS in an attempt to make a basic Socket.IO server for the fun of it and I've ran into an issue that's confusing me to no ends.
Here's my Server code. Fairly short, only one event.
// Create the server and start listening for connections.
var s_ = require('socket.io')(5055);
var Session = require('./Session');
var connections = [];
var dummyID = 0;
// Whenever a connection is received.
s_.on('connection', function(channel) {
connections[channel] = new Session(channel, ++dummyID);;
console.log("Client connected with the ID of " + dummyID);
// Register the disconnect event to the server.
channel.on('disconnect', function() {
delete connections[channel];
console.log("A Client has disconnected.");
});
channel.on('login', function(data) {
if(data.username !== undefined && data.password !== undefined) {
var session = connections[channel];
if(session !== undefined) {
session.prototype.authenticate(data.username, data.password);
}
}
});
});
The error is thrown on this line:
session.prototype.authenticate(data.username, data.password);
Saying that "authenticate" cannot be called on undefined, meaning the prototype of session is undefined. Session itself is not undefined, as per the check above it. Here is Session.js
var Session = function(channel, dummyID) {
this.channel = channel;
this.dummyID = dummyID;
};
Session.prototype = {
authenticate: function(username, password) {
if(username == "admin" && password == "admin") {
this.channel.emit('login', {valid: true});
} else {
this.channel.emit('login', {valid: false});
}
}
};
module.exports = Session;
As you can see the prototype is clearly there, I'm exporting the Session object, and I'm really confused as to what the issue is. Any help would be greatly appreciated.
just call the function you added to the objects prototype
session.authenticate(data.username, data.password);
This article explains the prototype inheritance chain very clearly, especially with the graph.
And one more tip of myself: everything object in javascript has a __proto__ property in the inheritance chain, keep that in mind helps a lot.

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