How to push notification to specific user Node + AngularJS + Laravel - javascript

I need help how to push notification to specific user. I can now push notifcation but all user will get that notification. I can filter it on clinet side but I think it is unsecure...
First I send data with laravel 5:
$redis = Redis::connection();
$redis->publish('update.answer', json_encode($events));
here is my node.js i emite data:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.subscribe('update.group', function(err, count) {
});
redis.subscribe('update.question', function(err, count) {
});
redis.subscribe('update.answer', function(err, count) {
});
redis.subscribe('update.comment', function(err, count) {
});
redis.on('message', function(channel, message) {
message = JSON.parse(message);
console.log(message);
io.emit(channel, message);
});
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
and with angularjs I take data and push to the client.
socket.on('update.answer',function(data){
if($scope.remove){
$scope.remove = false;
}
$scope.feed = $("#feed").val();
if(parseInt($scope.feed) === parseInt(data.userID)){
$scope.answers.push(data);
$scope.$digest();
}
});
WIth this part:
$scope.feed = $("#feed").val();
if(parseInt($scope.feed) === parseInt(data.user_id) && data.admin_id !== null){
}
I check if client should get notification but it is unsecure...
Any way to improve this?

To push message to specific user , you must store his/her reference somewhere.
for ex
io.on('connection', function(socket) {
socket.on('add-user', function(data){
clients[data.username] = socket;
});
});
now to push message to specific user just use his username to retrive his socket
clients[data.username].emit(channel, message);
Update : Explanation
This Assume that each user who uses you web app is having some sort of authentication.
As soon as user login into your application , let him join on the nodejs backend socket.
on client side
socket.emit('add-user',userObj);
});
userObj is object that contains user details,you can send the username alone too
socket.emit('add-user',username);
in your nodejs first decalre one array that contains the socket of all the users who joins the website
var clients = [];
now in your nodejs application write this additional code
io.on('connection', function(socket) {
socket.on('add-user', function(data){
clients[data.username] = socket;
});
});
up to this moment the user who login into your website will call add-user event from client side which will in turn call add-user on nodejs and there socket will be added into the clients array
now to send message to any particular user you must know there username,so if you know the username of the user then you can simply emit message to them using
clients[data.username].emit(channel, message);

Related

Can't update users using socket.io

Im trying to show all the users connected. For first time the event show the only user connected, but if i load a new page
with a new nickname, the users are not updated in the first one, and the second page update perfectly,
but if i load a third page, the third page updates perfectly
and the others not. Only the final page loaded show all the users connected.
Image of my problem
SERVER
var app = require('express')();
var express = require('express');
var http = require('http').Server(app);
var io = require('socket.io')(http);
var nicks = new Array();
app.use(express.static('./public'));
io.sockets.on('connection', function(socket) {
socket.on('nick', function(data) {
var nick = data.nick;
nicks.push(nick);
socket.emit('users', {
nicks: nicks
});
})
});
app.get('/', function(req, res) {
res.sendFile(__dirname + '/index.html');
});
http.listen(3000, function() {
console.log('listening on *:3000');
});
CLIENT
$(document).ready(function() {
var socket = io();
var nickname;
var nicks = new Array();
nickname = prompt('Write your name:');
$("#button").on("click", function(event) {
var nick = nickname;
socket.emit('nick', {
nick: nick
});
});
socket.on('users', function(data) {
nicks = data.nicks;
for (var i = 0; i < nicks.length; i++) {
$("#users_connected").append('<li>' + nicks[i] + '</li>');
}
});
});
The problem is that your are using socket.emit()
from docs:
Emits an event to the socket identified by the string name.
Meaning that the only one that is getting the users event is the same that send a nick event.
Yoy must want to use Server.emit()
From docs:
Emits an event to all connected clients. The following two are
var io = require('socket.io')();
io.sockets.emit('an event sent to all connected clients');
io.emit('an event sent to all connected clients');
That way will allow you to inform all conections about the current users but mostly will fail in your current schema if you dont clear or ignore somehow already added users.
I would prefer to have two events a "new member" and send whole user data not all users and then a "disconected" in order to remove member reference and avoid re printing or validating things.
For more advenced usage you need to check on namespaces. To send events to certain groups of connections.
Check it out ;)
http://socket.io/docs/server-api/

Working with multiple tabs with Socket.io

I've the following code working in my server-side, it's all ok. But, I want to keep the same connection between n tabs, because when I open a new tab, looks like I've disconnected from the first tab... So, how can I keep the same connection?
client.js
socket.emit("connected", {user: inputUser.val()};
app.js
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
users[socket.user] = socket;
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
delete users[socket.user];
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
Update:
The typing event above works fine... So I tried the typing event according to the answer:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
users[userMsg].emit("typing", {user: socket.user});
}
});
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
But it is giving the following error:
users[userMsg].emit("typing", {user: socket.user});
^
TypeError: users[userMsg].emit is not a function
Update²:
To fix the typing event error, I just changed to:
socket.on("typing", function(data) {
var userMsg = data.user;
if(userMsg in users) {
for(let userSet of users[userMsg]) {
userSet.emit("typing", {user: socket.user});
}
}
});
There is no simple way to share a single socket.io connection among multiple tabs in the same browser. The usual model for multiple tabs would be that each tab just has its own socket.io connection.
The opening of a new tab and a new socket.io connection should not, on its own, cause your server to think anything was disconnected. If your code is doing that, then that is a fault in your code and it is probably easier to fix that particular fault.
In fact, if you want to explicitly support multiple tabs and be able to recognize that multiple tabs may all be used by the same user, then you may want to change your server side code so that it can keep track of multiple sockets for a single user, rather than how it is currently coded to only keep track of one socket per user.
If your server code is really just trying to keep track of which users online, then there's probably an easier way to do that by referencing counting each user. I will post a code example in a bit.
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// increment reference count for this user
if (!users[socket.user]) {
users[socket.user] = 0;
}
++users[socket.user];
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// decrement reference count for this user
// and remove user if reference count hits zero
if (users.hasOwnProperty(socket.user)) {
--users[socket.user];
if (users[socket.user] === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
If you need the users object to have the socket object in it, then you can change what is stored in the users object to be a Set of sockets like this:
var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};
io.on("connection", function(socket) {
socket.on("connected", function(data) {
socket.user = data.user;
// add this socket to the Set of sockets for this user
if (!users[socket.user]) {
users[socket.user] = new Set();
}
users[socket.user].add(socket);
updateUsers();
});
function updateUsers() {
io.emit("users", Object.keys(users));
}
socket.on("disconnect", function(data) {
if(!socket.user) {
return;
}
// remove socket for this user
// and remove user if socket count hits zero
if (users[socket.user]) {
users[socket.user].delete(socket);
if (users[socket.user].size === 0) {
delete users[socket.user];
}
}
updateUsers();
});
});
var port = Number(process.env.PORT || 8000);
http.listen(port, function() {
console.log("Server running on 8000!");
});
For anyone still having this issue. here is how i fixed it.
let me explain.
once the page refreshes or a new tab is opened, socket dosen't really care so it opens a new connection every time . this is more of a advantage than disadvantage. the best way to tackle the issue is on the server side, once a user logs in with his or her user name , you can send that name along with the query options on the client so it can be used as a unique identifier. in my case i used a token
this.socket = io.connect(`${environment.domain}` , {
query: {token: this.authservice.authToken}
});
then on the server side you can create an empty array to a key and an array of values. the username of the user will be used as a key and the corresponding array of socket as the value. in my own case like i said i used a token
const users = [ ]
socket.nickname = (decoded token username);
users[socket.nickname] = [socket];
then you can perform a simple logic to check if a user already exists in an array, if it does, push the new socket to the array of the user
if ( user.username in users) {
console.log('already exists')
users[user.username].push(socket);
}
if it dosent, just create a new key and add the socket as the key.(make sure its an array because a user can always refresh or open a new tab with the same account and you dont want the chat message to deliver in one tab and not deliver in another)
else {
socket.nickname = username;
users[socket.nickname] = [socket];
}
then to emit a message you simply loop through the array and emit the message accordingly. this way each tab gets the message
socket.on('chat', (data) => {
if (data.to in users) {
for(let i = 0; i < users[data.to].length; i++) {
users[data.to][i].emit('chat', data)
}
for(let i = 0; i < users[data.user].length; i++) {
users[data.user][i].emit('chat', data)
}
}
})
you can add a disconnect logic to remove the socket from the users array too to save memory, so only currently open tabs acre active and closed tabs are removed. i hope it solved your problem
My solution is joining socket to a room with specific user Id.
io.on('connection', async (socket) => {
socket.join('user:' + socket.handshake.headers.uid) // The right way is getting `uid` from cookie/token and verifying user
})
One advantage is sending data to specific user (sending to all tabs)
io.to('user:' + uid).emit('hello');
Hope it's helpful!
I belive the best way is create a channel for the user and unique it by their ID, so, when you need to receive or send something you use the channel and every socket connected to it will receive.
Another solution is to save the flag to localStorage and use eventListener to change localStorage.
Do not connect when another connection exists.
and save message in local storage for send with master tab.

Match websocket with http request when user connects

The issue that I'm having is http request happen independently or at different time as a web socket connection. The idea is that when a user connects I can store a session id and a web socket within the same pair in var allConnectionsMatches = [];and use this information later in a post request to find out which socket is calling the request so I can emit to that particular socket. The code below shows my attempt. What I wrote does work to an extent but It has a few issues such as when you refresh it sometimes doesnt emit messages anymore, or when you exit out the browser and connect again there is no message emitted by socket io. Any ideas?
var allConnectionsMatches = [];
var sessionID;
app.get('/', function(req, res, next) {
sessionID = req.session.id;
res.render('index.ejs')
});
function findDuplicates(data, sessionID, socket) {
var isPositive = data.lastIndexOf(sessionID);
if (isPositive === true) {
var socketLocation = allConnectionsMatches.indexOf(sessionID);
socketLocation + 1;
allConnectionsMatches.splice(socketLocation, 1, socket)
} else if(isPositive === -1) {
data.push(sessionID, socket);
} else {
}
}
io.sockets.on('connection', function (socket) {
findDuplicates(allConnectionsMatches, sessionID, socket)
});

Making node array accessible in view

I am trying to create a basic messaging system using express/nodejs. While I am able to emit messages to all users successfully. I need for users to be able to message each other in a 1-to-1 private manner.
What I am trying to do below is simple. When a user logs in, once the session has been validated, store the user object in the clients array and make this array accessible in the view -- that's it!
The intention is that this array will to grow on the server as users log in and I'll need to make it accessibly in the view, so I can generate a list of online users that are available for chat.
I have tried several different approaches, the approach below results in an empty array in the view.
My goal is to simply store online users in an array on the server as they log in and have that array accessible in the view.
I appreciate any suggestions.
index.js
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var mysql = require('mysql');
var session = require('client-sessions');
var server = require('http').createServer( app );
var io = require('socket.io').listen( server );
var clients = [];
app.locals.delimiters = '<% %>';
app.get('/', function(req, res){
if(req.session && req.session.user.username){
mysql.query("SELECT * FROM users WHERE username = ? AND pass = ? LIMIT 1", [req.session.user.username, req.session.user.pass], function(error, results, fields){
if(results.length === 0){
req.session.reset();
res.redirect('/login');
}else{
res.locals.user = results[0];
io.sockets.on('connection', function(socket){
clients.push({user : res.locals.user, socket : socket});
});
res.render('index');
}
});
}else{
res.redirect('/login');
}
});
server.listen( 3331 ); //chat port
io.sockets.on('connection', function(socket){
//load all users
socket.on('load users', function(){
io.emit('load users', {clients : clients});
});
socket.on('error', function(err){
console.error(err.stack);
});
});
module.exports = app;
index.hjs
<div class="messenger-user" ng-repeat="n in clients">
<div class="avatar-icon glyphicon glyphicon-user">
</div>
<div class="user-meta">
{{n.user.firstname}} {{n.user.lastname}}
</div>
</div>
index.hjs JavaScript
socket.emit('load users', function(clients){
$scope.clients = clients;
$scope.$apply();
});
Makes perfect sense. It sends an empty array because you send it before populating it.
Currently :
1) A MySQL query is launched to retrieve and populate the users array.
2) Meanwhile, a socket on 'connection' event is triggered (line 24), that emits the users array, which is currently empty.
3) Your MySQL query ends (line 3) and gets back with the data you want.
4) Inside the callback function, you set another listener for io.sockets.on('connection'). It's too late, because you already set one, and the connection event already happened. So you have two listeners for the same event, and the second one never triggers.
What you should do is wait for the SQL query to end, then populate your array and emit it to the client.
Edit 2 : Don't use both HTTP connection and socket for authentication. You're trying to store a user object from a HTTP request, together with a socket that is unrelated to the HTTP request. Do everything with the sockets :
var clients = [];
io.sockets.on('connection', function(socket){
socket.on('credentials', function(data){ // data == { "username":"John" , "password":"123456" }
login(data, socket); // sending credentials and socket to the login function, so both are defined there.
})
socket.on('error', function(err){
console.error(err.stack);
});
});
function login(data, socket){
mysql.query("SELECT * FROM users WHERE username='"+data.username+"' AND pass='"+data.password+"' LIMIT 1", [req.session.user.username, req.session.user.pass], function(error, results, fields){
if(!results.length){
req.session.reset();
res.redirect('/login');
}else{
clients.push({user : results[0], socket : socket}); // Both defined!
io.emit('load users', {clients : clients});
res.render('index');
}
});
}
server.listen( 3331 ); //chat port

User receive more than 1 notification from nodejs

This is my first time with nodejs and I have some issues with it. The main problem is that the user receive more than 1 signal from the server. The count is based on the refresh of the page.
Below is my code:
var http = require('http');
var server = http.createServer().listen(1332);
var io = require('socket.io').listen(server);
var redis = require('redis');
var sub = redis.createClient();
//Subscribe to the Redis chat channel
sub.subscribe('notification_count');
console.log("Server is running...\nClick on Ctrl+C to exit");
io.sockets.on('connection', function (socket) {
var user_id = socket["handshake"]["query"]["user_id"];
console.log("user_id", user_id);
socket.room = user_id;
socket.join(user_id);
socket.on('disconnect', function(){
socket.leave(socket.room);
});
//Grab message from Redis and send to client
sub.on('message', function(channel, message){
io.sockets.in(message).emit('message', message );
});
});
And here is the client side js code:
var socket = io.connect('localhost:1332', { query: "user_id={{ request.user.id }}" });
socket.on('connect', function(){
console.log("connected");
});
socket.on('message', function(message) {
//something
});
Basically on connection from the server I send to the server the user_id. After that I create a new room which name is the same as the user_id. Of course on disconnect the room should be delete. I have noticed that sub.on() is fired more than once, but I cannot figure out why. I will appreciate any help. Thank you in advance !
The problem is that you are using a handler inside the connection event, everytime a client connects it will execute everything inside the connection event including your sub.on
Place sub.on out of the connection event and it should stop the double messages

Categories