Sending subsets of streaming data to Node clients using Sockets.io - javascript

I have a Node server which sends streaming tweets to clients as they connect, using Sockets.io and ntwitter. At the moment all the tweets (from different users) get sent to every client. But each client only requires a certain subset of tweets, and I'd like to the server to only send that subset (or category).
I think that having each category being like a room, in Sockets, would work, but I can't quite work out how to adapt my code to use them. Or, given that there's no communication between clients, maybe that's not the best solution?
The relevant, simplified, bits of current code...
Client:
var socket = io.connect(window.location.hostname);
socket.on('messages', function(messages_packet) {
$.each(messages_packet, function(idx, tweet) {
if (tweet_is_in_this_clients_category(tweet)) {
display_messages(messages_packet);
};
}
});
Server:
// [A function which, on start-up, fetches existing tweets and caches them.]
// Send cached messages when a client connects.
sockets.sockets.on('connection', function(socket) {
socket.emit('messages', cached_messages);
});
// Fetch tweets from the stream, and send new ones to clients.
twitter.stream('statuses/filter', {follow: [12345, 345678, etc]}, function(stream) {
stream.on('data', function(tweet) {
add_tweet_to_cache(tweet);
sockets.sockets.emit('messages', [tweet]);
}
});
So, the sockets.emit() part is currently sending every tweet to all the clients. And then the client decides whether to show that tweet, if it's in the client's category. It would obviously be more efficient if the server only sent tweets to the clients in the correct category. Given the server already knows which tweets are in which categories, how do I only emit them to those categories, rather than every client?

After some trial and error I seem to have it working. No idea if this is the best or correct way, but... the above code has been tweaked so it's now something like that shown below. There's one addition to the client code, and two to the server code.
Client:
var socket = io.connect(window.location.hostname);
// NEW CLIENT PART:
socket.on('connect', function(){
// room_name was created based on the URL this page was requested at:
socket.emit('subscribe', room_name);
};
socket.on('messages', function(messages_packet) {
$.each(messages_packet, function(idx, tweet) {
if (tweet_is_in_this_clients_category(tweet)) {
display_messages(messages_packet);
};
}
});
Server:
// [A function which, on start-up, fetches existing tweets and caches them.]
// Send cached messages when a client connects.
sockets.sockets.on('connection', function(socket) {
// NEW SERVER PART 1:
socket.on('subscribe', function(room_name) {
socket.join(room_name);
socket.emit('messages', cached_messages_for_this_room);
});
});
// Fetch tweets from the stream, and send new ones to clients.
twitter.stream('statuses/filter', {follow: [12345, 345678, etc]}, function(stream) {
stream.on('data', function(tweet) {
add_tweet_to_cache(tweet);
// NEW SERVER PART 2:
// Gets the array of room_names this twitter account is associated with:
var rooms = get_rooms_for_twitter_account(tweet.user.screen_name);
rooms.forEach(
function(room_name) {
sockets.sockets.in(room_name).emit('messages', [tweet]);
};
);
};
});
So, when a client connects it sends a 'subscribe' message, and the name of the room it wants to join.
When the server receives a 'subscribe' event it sends only the existing tweets associated with that room (the logic for getting that list of tweets is done elsewhere; not relevant to this particular issue).
And whenever a new tweet is picked up by the server, it finds the room(s) this tweet is associated with, and then emits the tweet to every client in each of them.
This seems to work... do point out anything that makes no sense, or could be done much better!

Related

How to create sockets that particular to user and disposable on Socket.Io

I write a Node.Js app and I use Socket.Io as the data transfer system, so requests should be particular to per user. How can I make this?
My actual code;
node:
io.on('connection', (socket) => {
socket.on('loginP', data => {
console.log(data);
})
})
js:
var socket = io('',{forceNew : false});
$("#loginbutton").click(function() {
var sessionInfo = {
name : $("#login input[name='username']").val(),
pass : $("#login input[name='pass']").val()
}
socket.emit("loginP", sessionInfo)
})
It returns one more data for per request and this is a problem for me. Can I make this on Socket.Io or should I use another module, and If I should, which module?
If I understand your question correctly (It's possible I don't), you want to have just one connection from each user's browser to your nodejs program.
On the nodejs side, your io.on('connection'...) event fires with each new incoming user connection, and gives you the socket for that specific connection. So, keep track of your sockets; you'll have one socket per user.
On the browser side, you should build your code to ensure it only calls
var socket = io(path, ...);
once for each path (your path is ''). TheforceNew option is for situations where you have multiple paths from one program.

Socket IO and Dynamic Rooms

If anyone is experienced with Websockets / Socket IO hopefully you can point me in the right direction.
I made a Discord Clone and I'm trying to optimize it to scale better. Right now when a user sends a message I query the DB for all users part of that server, and emit a message to their specific socket. This is obviously not going to scale well as every message requires a expensive query and lookup in the client list
// Emit messages to only users part of specific server
// Will only return list of users part of server and active in last 10 minutes
sqlQuery = `SELECT userservers.user_id FROM userservers
JOIN users ON users.user_id = userservers.user_id AND users.user_last_active > (NOW() - INTERVAL 10 minute)
WHERE server_id = ${sql.escape(serverId)}`;
const users = await sql.query(sqlQuery);
action = { type: "message", payload: msg };
// Iterate over users, and find them in clients list
// Emit over socket only to that user
users.forEach((user) => {
clients.forEach((client) => {
if (client.userId === user.user_id) {
io.to(client.id).emit(user.user_id, action);
}
})
});
However using Rooms for each Sever would eliminate my need to query the DB. I understand I can do this when the socket server first starts
// Get server list from Mysql DB
servers.forEach((server) => {
socket.join(server.name);
}
However my issue becomes, when a user create a new server once the application is already running It will not update the list.
I am probably missing some concept on creating dynamic rooms.
EDIT : I am thinking the solution could be that every time a "server" is created, I send a message to the socket server so it can join that "room"
Right now when a user sends a message I query the DB for all users part of that server
I think you can submit broad cast message to all online users, so instead of forEach client => io.to(clientId) you can submit broad cast message to all connected users io.emit('some event', { for: 'everyone' });
also I'm wondering why you are creating many servers? you can divide your server into namespaces by using const namespace = io.of('/thisIsASeparateNamespace'); and also you can submit broadcast messages to all users inside this name space by namespace.emit('some event', { for: 'everyone in name space' });
So your chat structure can be like this
Server
Namespaces // for separate chat app / or like slack work spaces
Rooms // for group chatting
ClientID // for one to one

unable to broadcast a user disconnection in socket.io

i am new to socket.io & nodejs. i have been writing a tictactoe game and i have a problem in it, my purpose is if someone refresh the page or close the browser let his/her opponent know . i know i should do this with disconnect event and here is what i try, but it is not working .
server side
socket.on('disconnect', function() {
socket.emit('user:disconnect');
});
client side
socket.on('user:disconnect', function() {
var message = player.getPlayerName() + ' leaves';
socket.emit('gameEnded', {room: this.getRoomId(), message: message,id:player.getPlayerId(),gameid: this.getGameId(),winType:"leave"});
});
also, i need to know how to get the room of disconnected user .
i already saw this but i just want to send the users in a room not all users in the whole application.
server.js
socket.on('disconnect', function() {
socket.emit('user:disconnect');
});
In the above code if a user named 'xxx' is disconnected, server is emitting 'user:disconnect' to the same user. You have to find the socket connection of other player and emit the event to that client.
You can achieve your goal by joining both the clients in a room and send message to other user in the room.
socket.join('room1');
io.sockets.in('room1').emit('user:disconnect');
Otherwise you have to store all clients as mentioned below and send message to the specific client using the following code.
var users = {
'client1': [socket object],
'client2': [socket object],
'client3': [socket object]
}
users['client3'].emit('user:disconnect')

How to use the correct protocol for receiving active mq web socket stomp from java based program

I have a JavaScript based active mq listener, who is using stomp and web sockets.I was able to send test messages to active mq and receive them.
What I really want is it needs to be sent from a Java based code.
Is it Ok to have the JavaScript listening on web sockets/stomp and
the java code be using tcp ?
If it is OK, should all of the ports be the same?
I am having issues receiving data in JavaScript. However I am seeing the topic being enquued in the active mq.thanks
function subscribeEndpoint(endpoint){
var client = Stomp.client("ws://localhost:61614/stomp", "v11.stomp");
var headers = { id:'JUST.FCX', ack: 'client'};
client.connect("admin", "admin", function () {
client.subscribe(endpoint,
function (message) {
alert(message);
}, headers);
});
}
Java:
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616/stomp");
// Create a Connection
Connection connection = connectionFactory.createConnection();
connection.start();
// Create a Session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// Create the destination (Topic)
Destination destination = session.createTopic("vrwrThreat");
// Create a MessageProducer from the Session to the Topic or Queue
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
// Create a messages
TextMessage message = session.createTextMessage(text);
producer.send(message);
// Clean up
session.close();
connection.close();
Clients can communicate with each other across protocols and transport mechanisms without issue. A STOMP client on WebSocket or any other port (TCP, SSL, etc) can send to a Topic and an AMQP, MQTT or OpenWire client can receive the message so long as the protocol supports the content of the message, same goes for the reverse case of AMQP, MQTT or OpenWire sending and STOMP receiving.
The case you've mentioned is pretty standard. The code you've posted appears to be using the OpenWire client to send to a Topic "vrwrThreat". The URI used in the connection factory is slightly off in that you added the '/stomp' which is meaningless. Since you are sending to a Topic you need to ensure that the client that is to receive the message is active at the time of transmission otherwise the message will be dropped. Also you need to ensure that both are operating on the same Topic which is unclear from your code snippets.

socket.io assigning custom socket.id

I keep track the list of every users connected in the array.
So if there is a new connection, it will check whether the user is already on the list or not, if he was already on the list, then assign their socket.id with the corresponding socket.id on the list, otherwise just add them to the list.
It's for preventing same user counted as 2 user while he attempt to do multi-login.
Object.keys(client).forEach(function (key) {
if (client[key].id == data.id){
is_connected = true;
socket.id = key;
}
});
I have no problem handling the messages/chat that was sent/received by the user who attempt multi-login.
socket.on('chat', function(msg){
var data = {"name": client[socket.id].name, "message": msg};
io.emit('chat', data);
});
The io.emit for the chat message was succesfully sent to the user who attempting multi-login.
The problem I got was whenever the user decide to logout/disconnect from the server.
io.emit('user_leave', client[socket.id].id);
[Multi-Login Case] -> Multi-User and Dual-User are same user attempting Multi-Login
Whenever the Main-User disconnected from the server, the Dual-User received 'user_leave' sent by the server, because io.emit supposed to send it to all sockets.
But not otherwise, while the Sub-User disconnected from the server, the Main-user do not receive 'user_leave' emitted by the server.
*Note: Main-User is login first, then the Dual-User. So the Main-User information was saved directly in the array, while the Sub-User socket.id was assigned with the Main-User socket.id
[Update]
B2 socket.id was assigned with B1 socket.id, the io.emit for chat work perfectly while io.emit for disconnect only emitted to All except Dual-User(B2)
socket.id is used internally by socket.io for its own socket list. You cannot overwrite that or you break some of its ability to maintain its own data structures.
You have two choices:
You can use the existing socket.id value as is (without overwriting it) so you don't break existing behavior. It is already guaranteed to be unique on the server.
You can use a different property name for your own id such as socket.userId and then you won't conflict.
If you need to, you can maintain a map between your own custom id and the socket.io socket.id so you could get to one from the other.
Similar question here: Socket.io custom client ID
generateId prop of io.engine object can be used for to set the custom id.
Using this way, the all socket ids can be created on the server side without any issue.
Actually I wrote an answer for a similar question today.
An example:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.engine.generateId = function (req) {
// generate a new custom id here
return 1
}
io.on('connection', function (socket) {
// listing the default namespace rooms
console.log("rooms: ", io.nsps["/"].adapter.rooms);
})
The console output would be as the following:
rooms: { '/#1': Room { sockets: { '/#1': true }, length: 1 } }
It seems to be it has been handled.
It must be in mind that socket id must be unpredictable and unique value with considering security and the app operations!
Extra: If socket.id is returned as undefined because of your intense processes on your generateId method, async/await combination can be used to overcome this issue on node.js version 7.6.0 and later. handshake method of node_modules/engine.io/lib/server.js file should be changed as following:
former:
Server.prototype.handshake = function (transportName, req) {
var id = this.generateId(req);
...
}
new:
Server.prototype.handshake = async function (transportName, req) {
var id = await this.generateId(req);
...
}

Categories