Let's say I have angular page with a table with 10 rows, that each row has an id, and redirects me to a page with unique id.
I want to subscribe to this id in socket.io, and when I will leave this page / change to another, I want to replace the previous subscription to the new one.
How can I do that? Do I have to save all my current subscriptions in a database?
(I work with sails.js framework on node, and angular on client).
When you disconnect a socket (i.e. change page to another), socket.io unsuscribe your socket automatically.
To subscribe to a room, you could simply send a message from angular when you connect with the id and then in your sails controller you subscribe the user to the id
EDIT
Like this page says, you need to implement your system to track sockets.
First, create a service in /api/services/ and put that :
module.exports = {
switchRoom: function (socket, to_id) {
var tmp = _.find(sails.mysubscribers, function (sub) {
return sub.socket === socket;
});
if (tmp) { // If user came from another page unsuscribe him
sails.sockets.leave(socket, 'Unique prefix' + tmp.roomId);
} else {
tmp = {
socket: socket
};
sails.mysubscribers.push(tmp);
}
if (!to_id) { // If the user leave remove him from the array
var index = sails.mysubscribers.indexOf(tmp);
if (index > -1) sails.mysubscribers.splice(index, 1);
} else {
tmp.roomId = to_id;
sails.sockets.join(socket, 'Unique prefix' + to_id);
}
}
};
Then, in /config/sockets.js edit the afterDisconnect function :
module.exports.sockets = {
afterDisconnect: function (session, socket, cb) {
YourService.switchRoom(socket);
return cb();
}
};
Now, when you need to switchRoom, you just have to call YourService.switchRoom(req.socket, roomId) where roomId is the id send by angular...
That's it !
Related
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).
Is it possible to make socket.io broadcast to all users of a namespace who are in both room A and room B but not those who are just in room A or room B?
If not, how would I go about implementing this myself? Is there a way to retrieve all users in a namespace who are in a given room?
I am working with socket.io 1.0 in node
Edit:
If there is no native method, how would I go about to create my own syntax such as:
socket.broadcast.in('room1').in('room2').emit(...)?
You can look up all the users of a room using (ref How to update socket object for all clients in room? (socket.io) )
var clients = io.sockets.adapter.room["Room Name"]
So given two arrays for your 2 rooms' roster list, you can compute the intersection using something like the answer here (ref: Simplest code for array intersection in javascript)
And finally you can take that list of users in both rooms and emit events using (ref: How to update socket object for all clients in room? (socket.io) )
//this is the socket of each client in the room.
var clientSocket = io.sockets.connected[clientId];
//you can do whatever you need with this
clientSocket.emit('new event', "Updates");
The alternate ofcourse is to have hidden rooms, where you maintain a combination of all rooms, and add users to those rooms behind the scenes, and then you are able to just simply emit to those hidden rooms. But that suffers from an exponential growth problem.
There is no in-built way to do this. So first let's look up how the broadcast works:
https://github.com/Automattic/socket.io/blob/master/lib/namespace.js 206...221-224...230
this.adapter.broadcast(packet, {
rooms: this.rooms,
flags: this.flags
});
Now we know every broadcast creates a bunch of temp objects, indexOf lookups, arguments slices... And then calls the broadcast method of the adapter. Lets take a look at that one:
https://github.com/Automattic/socket.io-adapter/blob/master/index.js 111-151
Now we are creating even more temp objects and loop through all clients in the rooms or all clients if no room was selected. The loop happens in the encode callback. That method can be found here:
https://github.com/socketio/socket.io-parser/blob/master/index.js
But what if we are not sending our packets via broadcast but to each client separately after looping through the rooms and finding clients that exist both in room A and room B?
socket.emit is defined here: https://github.com/Automattic/socket.io/blob/master/lib/socket.js
Which brings us to the packetmethod of the client.js:
https://github.com/Automattic/socket.io/blob/master/lib/client.js
Each directly emitted packet will be separately encoded, which again, is expensive. Because we are sending the exact same packet to all users.
To answer your question:
Either change the socket.io adapter class and modify the broadcast method, add your own methods to the prototype or roll your own adapter by inheriting from the adapter class). (var io = require('socket.io')(server, { adapter: yourCustomAdapter });)
Or overwrite the joinand leave methods of the socket.js. Which is rather convenient considering that those methods are not called very often and you don't have the hassle of editing through multiple files.
Socket.prototype.join = (function() {
// the original join method
var oldJoin = Socket.prototype.join;
return function(room, fn) {
// join the room as usual
oldJoin.call(this, room, fn);
// if we join A and are alreadymember of B, we can join C
if(room === "A" && ~this.rooms.indexOf("B")) {
this.join("C");
} else if(room === "B" && ~this.rooms.indexOf("A")) {
this.join("C");
}
};
})();
Socket.prototype.leave = (function() {
// the original leave method
var oldLeave = Socket.prototype.leave;
return function(room, fn) {
// leave the room as usual
oldLeave.call(this, room, fn);
if(room === "A" || room === "B") {
this.leave("C");
}
};
})();
And then broadcast to C if you want to broadcast to all users in A and B.
This is just an example code, you could further improve this by not hard coding the roomnames but using an array or object instead to loop over possible room combinations.
As custom Adapter to make socket.broadcast.in("A").in("B").emit()work:
var Adapter = require('socket.io-adapter');
module.exports = CustomAdapter;
function CustomAdapter(nsp) {
Adapter.call(this, nsp);
};
CustomAdapter.prototype = Object.create(Adapter.prototype);
CustomAdapter.prototype.constructor = CustomAdapter;
CustomAdapter.prototype.broadcast = function(packet, opts){
var rooms = opts.rooms || [];
var except = opts.except || [];
var flags = opts.flags || {};
var packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress
};
var ids = {};
var self = this;
var socket;
packet.nsp = this.nsp.name;
this.encoder.encode(packet, function(encodedPackets) {
if (rooms.length) {
for (var i = 0; i < rooms.length; i++) {
var room = self.rooms[rooms[i]];
if (!room) continue;
for (var id in room) {
if (room.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) {
ids[id] = ids[id] || 0;
if(++ids[id] === rooms.length){
socket.packet(encodedPackets, packetOpts);
}
}
}
}
}
} else {
for (var id in self.sids) {
if (self.sids.hasOwnProperty(id)) {
if (~except.indexOf(id)) continue;
socket = self.nsp.connected[id];
if (socket) socket.packet(encodedPackets, packetOpts);
}
}
}
});
};
And in your app file:
var io = require('socket.io')(server, {
adapter: require('./CustomAdapter')
});
io.sockets.adapter.room["Room A"].forEach(function(user_a){
io.sockets.adapter.room["Room B"].forEach(function(user_b){
if(user_a.id == user_b.id){
user_a.emit('your event', { your: 'data' });
}
});
});
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.
This question title was really worded incorrectly, but I have no idea how to word it. If anyone wants to edit it, please feel free.
Basically I'm trying to figure out how to get a specific instance of a player via the socket that the data came from, this is going to be used to relay movements through my server for my small 2d project. Currently I have an object that's storing all of the players by the identification number, some people say it's an object, some say it's a hashmap implementation, some say it's an array, whatever the hell it is, I'm using it.
var connectedPlayers = {};
When a connection is created I create a new player like so:
var playerId = Math.floor(Math.random()*(50000-1+1)+1);
var userId = Math.floor(Math.random()*(50000-1+1)+1);
var player = new Player(playerId, "Guest"+userId, socket);
connectedPlayers[playerId] = player;
Obviously this is just generating random names/identification numbers for now, but that's perfectly fine, this is executed on the socket.on('connection', function() call.
If you need to see the Player.js script it's just a basic prototype script which sends information to all players about the player logging in, this all works properly.
var playerId;
var playerName;
var socket;
var positionX, positionY;
function Player(playerId, playerName, socket) {
this.playerId = playerId;
this.playerName = playerName;
this.socket = socket;
this.positionX = 250;
this.positionY = 250;
socket.emit('login', { playerID: playerId, playerX: this.positionX, playerY: this.positionY, playerName: playerName});
socket.broadcast.emit('player-connected',{ playerID: playerId, playerX: this.positionX, playerY: this.positionY, playerName: playerName} )
}
Player.prototype.getId = function() {
return this.playerId;
};
Player.prototype.getName = function() {
return this.playerName;
};
Player.prototype.getSocket = function() {
return this.socket;
};
Player.prototype.getX = function() {
return this.positionX;
};
Player.prototype.getY = function() {
return this.positionY;
};
My problem is, I need to be able to find out which Player belongs to a socket when the data comes in, because I want to authoritatively send movements to the clients. To do this I need to not give the client any control when it comes to which playerId it contains, because if I let the client tell the server, we could have other players moving at random.
The only way I've thought of is something like this, but it doesn't seem very efficient.
for(var i in connectedPlayers) {
if(conntectedPlayers[i].getSocket() == socket) {
// do stuff
}
}
EDIT: Adding the entire sockets.on('connection') due to a request from comments.
socketIO.sockets.on('connection', function(socket) {
console.log("Connection maid.");
socket.on('login', function(json) {
sqlConnection.query('select * FROM Accounts', function(err, rows, fields) {
if(err) throw err;
for(var row in rows) {
if(rows[row].Username == json.username) {
var playerId = Math.floor(Math.random()*(50000-1+1)+1);
var player = new Player(playerId, "Guest"+playerId, socket);
connectedPlayers[playerId] = player;
}
}
});
});
socket.on('move-request', function(json) {
socket.emit('move-request', {x: json.x, y: json.y, valid: true});
});
});
You can just add one or more properties to the socket object upon connection:
socketIO.sockets.on('connection', function(socket) {
console.log("Connection maid.");
socket.on('login', function(json) {
sqlConnection.query('select * FROM Accounts', function(err, rows, fields) {
if(err) throw err;
for(var row in rows) {
if(rows[row].Username == json.username) {
var playerId = Math.floor(Math.random()*(50000-1+1)+1);
var player = new Player(playerId, "Guest"+playerId, socket);
connectedPlayers[playerId] = player;
// add custom properties to the socket object
socket.player = player;
socket.playerId = playerId;
}
}
});
});
socket.on('move-request', function(json) {
// you can access socket.playerId and socket.player here
socket.emit('move-request', {x: json.x, y: json.y, valid: true});
});
});
So, now anytime you get an incoming message on a socket, you then have access to the playerId and player. In your code, when a message arrives, the socket variable for this message is in your parent scope and accessible so you can then get the player and playerId.
I've run into this situation a few times when storing "objects" in a C hash. I use a 32 bit hash function specifically tailed for pointers.
I'm not sure what you could use to uniquely identify the socket instance and suggest you see what underlying fields exist for that purpose. If you do find something that uniquely identifies the socket, you can feed that into a hash algorithm to generate a key. Of course, you would need a hash implementation (most likely in JavaSCript from the looks of it). I found this: http://www.timdown.co.uk/jshashtable/.
Good luck!
Is there a way to emit a message via socket.io while excluding some socket id?
I know about the existence of rooms, but that's a no-no for me.
If what I'm trying is impossible, what should I do between those two things:
a) Iterate over all users that I want to send a message (in fact, all of them except 1) and do a emit for each socket
or
b) Just emit the message to everyone and do something hacky on the client side to "ignore" the message.
EDIT: I can't do a broadcast because the message is generated from the server side (so there is no client interaction).
Rooms - are not the way to accomplish of what you are trying to do, as they are meant to be used only to limit groups of people, but not specific.
What you are looking for is simple collection of users and ability to set some sort of filter on them.
Iteration through list of sockets - is not that critical, as when you broadcast - it does iteration anyway.
So here is small class that keeps sockets, removes them when needed (disconnect) and sends message to all with optional exception of single socket.
function Sockets() {
this.list = [ ];
}
Sockets.prototype.add = function(socket) {
this.list.push(socket);
var self = this;
socket.on('disconnect', function() {
self.remove(socket);
});
}
Sockets.prototype.remove = function(socket) {
var i = this.list.indexOf(socket);
if (i != -1) {
this.list.splice(i, 1);
}
}
Sockets.prototype.emit = function(name, data, except) {
var i = this.list.length;
while(i--) {
if (this.list[i] != except) {
this.list[i].emit(name, data)
}
}
}
Here is example of usage, user sends 'post' message with some data, and server just sends it to all in collection except of original sender:
var collection = new Sockets();
io.on('connection', function(socket) {
collection.add(socket);
socket.on('post', function(data) {
collection.emit('post', data, socket);
});
});
In fact it can be used as rooms as well.