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)
});
Related
I want to page update when new socket.io data appear.
I have a server -> client comunication:
Server code (the n var is the message)
io2.on('connection', function(socket2) {
socket2.on('live', function(data2) {
console.log('status from live client:', data2);
socket2.emit('live', n);
});
});
Live page code:
var socket = io.connect('http://localhost:3002');
socket.emit('live', 'live client is connected');
socket.on('live', function(data) {
//alert(data)
document.getElementById("demo").innerHTML = data;
});
The socket works fine because when I send the data and I refresh the page it show what I want. The problem is that I want to see the updated message without manual refresh.
Thanks!
The scope for n is undefined in the first function, so the receiving function doesn't have anything to show.
You'll need to define n in the original emitting function.
You need to emit the event from server side to the client side and then catch data there
var server = app.listen(3000);
var io = require('socket.io')(server);
io.on('connection', function(socket) {
socket.on('event', function(data) {
var n = data.message;
io.sockets.emit('event', message);
});
And then on frontend in live page you write this
var n = <%-JSON.stringify(v1)%>;
var socket = io.connect('http://localhost:3000');
socket.on('event', (data)=>{
console.log(data)
});
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.
I am trying to create an application based on socket. Looking at the console it can be said that the client to trying to socket server twice; which should not be allowed.
Is there any way to stop that?
If have modular javascript file where multiple script want to access same socket connection how is that possible?
<!DOCTYPE html>
<html>
<head>
<title>socket</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
</head>
<body>
<script type="text/javascript">
$(function(){
var socket = io();
console.log(typeof socket); // object
var socket1 = io();
console.log(socket === socket1); // false
})
</script>
</body>
</html>
Don't connect the client to socket twice....
A global variable will allow you to access the socket from all your script files
Eg)
//you can access this variable from all your scripts
var socket;
$(function(){
socket = io();
console.log(typeof socket);
})
Note that in javascript polluting the global scope with variables and functions is considered bad practice, but that's for another post.
The obvious suggestion is to stop having your client connect twice.
But, if you're trying to prevent multiple connections on the server and you really don't want any end user to be able to use more than one window at a time to your web site, then you can prevent another connection once there is already a connection.
The key would be to identify each browser with some sort of unique cookie (user ID or uniquely generated ID number). You can then implement socket.io authentication that keeps track of which user IDs already have a connection and if a connection is already live, then subsequent attempts to connect will fail the authentication step and will be disconnected.
Supposed you have a cookie set for each browser that's called userId. Then, here's a sample app that makes sure the userId cookie is present and then uses it to deny more than one socket.io connection:
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
const cookie = require('cookie');
app.use(cookieParser());
let userIdCntr = 1;
const tenDays = 1000 * 3600 * 24 * 10;
app.use(function(req, res, next) {
// make sure there's always a userId cookie
if (!req.cookies || !req.cookies.userId) {
// coin unique user ID which consists of increasing counter and current time
let userId = userIdCntr++ + "_" + Date.now();
console.log("coining userId: " + userId);
res.cookie('userId', userId, { maxAge: Date.now() + tenDays , httpOnly: true });
}
next();
});
app.get('/test.html', function (req, res) {
console.log('Cookies: ', req.cookies)
res.sendFile( __dirname + "/" + "test.html" );
});
const server = app.listen(80);
const io = require('socket.io')(server);
// which users are currently connected
var users = new Set();
// make sure cookie is parsed
io.use(function(socket, next) {
if (!socket.handshake.headers.cookieParsed) {
socket.handshake.headers.cookieParsed = cookie.parse(socket.handshake.headers.cookie || "");
}
next();
});
// now do auth to fail duplicate connections
io.use(function(socket, next) {
console.log("io.use() - auth");
// if no userId present, fail the auth
let userId = socket.handshake.headers.cookieParsed.userId;
if (!userId) {
next(new Error('Authentication error - no userId'));
return;
}
// if already logged in
if (users.has(userId)) {
console.log("Failing user " + userId);
next(new Error('Authentication error - duplicate connection for this userId'));
return;
} else {
console.log("adding user " + userId);
users.add(userId);
}
next();
});
io.on('connection', function(socket) {
console.log("socket.io connection cookies: ", socket.handshake.headers.cookieParsed);
socket.on('disconnect', function() {
console.log("socket.io disconnect");
let userId = socket.handshake.headers.cookieParsed.userId;
users.delete(userId);
});
// test message just to see if we're connected
socket.on("buttonSend", function(data) {
console.log("buttonSend: ", data);
});
});
P.S. You really have to think through the situation where a user with more than one computer (like home/work) leaves one computer on and open to your page and thus connected via socket.io and then attempts to user your site from the other computer. If you refused the second connection, this will refuse to allow them access from their second computer if the first computer happened to be left open and on.
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);
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