I am trying to build a live chat, I am using mongodb and socket.io to store the messages and users.
When a new user is created that user is stored in the mongodb and in the socket object.
If a user refreshes the page the user is removed from the socket object, meaning now in order for that person to get back in they have to create a new username and that generates a new socket.
Here is what my server side code looks like
/*
|--------------------------------------------------------------------------
| Live socket communication with front end:
|--------------------------------------------------------------------------
|
|
*/
var users = {};
io.sockets.on('connection', function (socket) {
// Listen to a new user then emit the user to all clients
socket.on('new user', function (data) {
// Check mongodb to see if the user exists and emit proper message
models.Message.findOne({username:data},function(err,user){
if(err){
console.log('something went wrong')
}
else if(user){
socket.emit('username taken', 'something');
}
else{
socket.emit('create user', data);
socket.userName = data;
socket.connected = true;
users[socket.userName] = socket;
io.sockets.emit('user name', Object.keys(users));
}
});
});
socket.on('facebook id', function(data) {
models.User.findOne({username:data.name}, function(err, user) {
if (user) {
console.log('User already exists');
socket.userName = data.name;
socket.facebook_id = data.id;
socket.connected = true;
users[socket.userName] = socket;
io.sockets.emit('user name', Object.keys(users));
}
else {
var newUser = new models.User({
username: data.name,
facebook_id: data.id
});
newUser.save(function(err, user) {
console.log('successfully inserted user/user: ' + user._id);
});
}
});
});
// Listen to a new message then emit the message to all clients
socket.on('send message', function (data, callback) {
io.sockets.emit('new message', {message: data, username: socket.userName, facebook_id: socket.facebook_id});
});
// Logic when client disconnects
socket.on('disconnect', function (data) {
if(!socket.userName) return;
seeder.disconnect(socket.userName);
delete users[socket.userName]
io.sockets.emit('user disconnected', Object.keys(users));
});
});
You see in my disconnect I remove the socket from the users object.
My question would be is there a way to save the socket info on disconnect then if the same socket tries to connect have it recognize the user and continue?
Additonal: I am thinking maybe I need to focus on creating a user login with mongodb first, then using that log in session data and pass that to the socket, creating a socket object with current database details? Does that sound like something that makes more sense, is that possible?
You can use cookies to identify users. Generate a random hash, put it in cookies, and thus this data will be transferred when establishing connection between client and server.
The client code may look like:
function generateHash(len) {
var symbols = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
var hash = '';
for (var i = 0; i < len; i++) {
var symIndex = Math.floor(Math.random() * symbols.length);
hash += symbols.charAt(symIndex);
}
return hash;
}
if (!/\buser_id=/.test(document.cookie)) { //if no 'user_id' in cookies
document.cookie = 'user_id=' + generateHash(32); //add cookie 'user_id'
}
//here goes establishing connection to server via `io.connect`
On the server-side you can write:
io.sockets.on('connection', function (socket) {
var cookie = socket.handshake.headers.cookie;
var match = cookie.match(/\buser_id=([a-zA-Z0-9]{32})/); //parse cookie header
var userId = match ? match[1] : null;
//...
Thus you have userId variable which is unique for each user. Then you can comment this line:
delete users[socket.userName]
because you should keep the user data.
You may now store your users object with userId (not username) as a key, and on each connection check whether users[userId] != null. And if such user exists, use their socket info
Related
I am trying to build an chat apps using socket.io and node.js for backend and flutter for frontend... so far I have been trying to send message to all connected user, is there a way to send message to specific user? here is part of my backend code
io.on('connection', (socket) => {
console.log(`id: ${socket.id}`)
socket.on('send_message', (msg) => {
var detail = JSON.parse(msg)
socket.in(detail["receiver"]).emit('to_user', msg)
})
});
in flutter I am using socket_io_client package (https://pub.flutter-io.cn/packages/socket_io_client) but I don't know how to emit message for specific user
here is part of code for frontend
StreamController<String> _data = StreamController<String>();
socket.on('send_message', (x) {
_data.sink.add(x);
});
sendChat(String msg, String sender, String receiver) {
Map map = {"msg": msg, "snd": sender, "rcv": receiver};
var mapbody = json.encode(map);
socket.emit('send_message', mapbody);
}
Stream<String> get sendChat => _data.stream;
you have to have the socket.id and use io.sockets.socket(SocketId).emit(msg) to send message
var express = require("express");
var redis = require("redis");
var sio = require("socket.io");
var client = redis.createClient()
var app = express.createServer();
var io = sio.listen(app);
io.set("store", new sio.RedisStore);
// In this example we have one master client socket
// that receives messages from others.
io.sockets.on('connection', function(socket) {
// Promote this socket as master
socket.on("I'm the master", function() {
// Save the socket id to Redis so that all processes can access it.
client.set("mastersocket", socket.id, function(err) {
if (err) throw err;
console.log("Master socket is now" + socket.id);
});
});
socket.on("message to master", function(msg) {
// Fetch the socket id from Redis
client.get("mastersocket", function(err, socketId) {
if (err) throw err;
io.sockets.socket(socketId).emit(msg);
});
});
});
some options you have here.
first :
you can store your connected clients ids to redis .
use io-redis :
and store your client id in it:
for storing ids in redis:
await redisClient.lpush(`socket_members`, socket.id);
for getting the specefic id:
let client = await redisClient.lrange(
`socket_members`,
0,
socket.id
);
the second option is you can create an authentication middleware
const jwt = async(socket, next) => {
let token = socket.handshake.query.token
let verify = jwt.verify(socket.handshake.query.token, jwt_encryption, async(err, decoded) => {
// find user with decoded token
if (!user) {
return next(new Error(JSON.stringify({ status: 401, message: 'unuthorized' })));
}
socket.user = user._doc
return next()
})
};
and use it in socket io instance :
io.use(jwt);
the authenticated user is in socket.user.
hope this would help
I'm trying to learn Socket.io by building a set of dynamically created chatrooms that emit 'connected' and 'disconnected' messages when users enter and leave. After looking at a couple of questions I've put together something functional but most of the response linked are from people who admit they've hacked together answers and I've noticed there's a more general - and recent - discussion about the right way to do this on the Socket.io repo (notably here and here)
As I'm such a novice I don't know if the work below is an acceptable way to do things or it just happens to incidentally function but will cause performance issues or result in too many listeners. If there's an ideal - and official - way to join and leave rooms that feels less clunky than this I'd love to learn about it.
Client
var roomId = ChatRoomData._id // comes from a factory
function init() {
// Make sure the Socket is connected
if (!Socket.socket) {
Socket.connect();
}
// Sends roomId to server
Socket.on('connect', function() {
Socket.emit('room', roomId);
});
// Remove the event listener when the controller instance is destroyed
$scope.$on('$destroy', function () {
Socket.removeListener('connect');
});
}
init();
Server
io.sockets.once('connection', function(socket){
socket.on('room', function(room){ // take room variable from client side
socket.join(room) // and join it
io.sockets.in(room).emit('message', { // Emits a status message to the connect room when a socket client is connected
type: 'status',
text: 'Is now connected',
created: Date.now(),
username: socket.request.user.username
});
socket.on('disconnect', function () { // Emits a status message to the connected room when a socket client is disconnected
io.sockets.in(room).emit({
type: 'status',
text: 'disconnected',
created: Date.now(),
username: socket.request.user.username
});
})
});
Socket.IO : recently released v2.0.3
Regarding joining / leaving rooms [read the docs.]
To join a room is as simple as socket.join('roomName')
//:JOIN:Client Supplied Room
socket.on('subscribe',function(room){
try{
console.log('[socket]','join room :',room)
socket.join(room);
socket.to(room).emit('user joined', socket.id);
}catch(e){
console.log('[error]','join room :',e);
socket.emit('error','couldnt perform requested action');
}
})
and to leave a room, simple as socket.leave('roomName'); :
//:LEAVE:Client Supplied Room
socket.on('unsubscribe',function(room){
try{
console.log('[socket]','leave room :', room);
socket.leave(room);
socket.to(room).emit('user left', socket.id);
}catch(e){
console.log('[error]','leave room :', e);
socket.emit('error','couldnt perform requested action');
}
})
Informing the room that a room user is disconnecting
Not able to get the list of rooms the client is currently in on disconnect event
Has been fixed (Add a 'disconnecting' event to access to socket.rooms upon disconnection)
socket.on('disconnect', function(){(
/*
socket.rooms is empty here
leaveAll() has already been called
*/
});
socket.on('disconnecting', function(){
// socket.rooms should isn't empty here
var rooms = socket.rooms.slice();
/*
here you can iterate over the rooms and emit to each
of those rooms where the disconnecting user was.
*/
});
Now to send to a specific room :
// sending to all clients in 'roomName' room except sender
socket.to('roomName').emit('event', 'content');
Socket.IO Emit Cheatsheet
This is how I inform users of a "disconnecting user"
socket.on('disconnecting', function(){
console.log("disconnecting.. ", socket.id)
notifyFriendOfDisconnect(socket)
});
function notifyFriendOfDisconnect(socket){
var rooms = Object.keys(socket.rooms);
rooms.forEach(function(room){
socket.to(room).emit('connection left', socket.id + ' has left');
});
}
Here is a working method.
I am using socketio "socket.io": "^2.3.0" on server side. On Client side is android
// SocketIO
implementation('io.socket:socket.io-client:1.0.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
Following is the code that is working for me.
// Join Chat Room
socket.on('ic_join', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Joined Room========== ')
console.log(room1)
socket.join(room1, function(err) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
})
})
// Leave Chat Room
socket.on('ic_leave', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Left Room========== ')
console.log(room1)
socket.leave(room1, function(err) {
if (typeof io.sockets.adapter.rooms[room1] !== 'undefined' && io.sockets.adapter.rooms[room1] != null) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
} else{
console.log("room is deleted")
}
})
})
For anyone reading this beyond 2/1/2021, using socket.io 3.1.0 and hopefully later, you can reference my example. I have found that the example on how to do this in socket.io's documentation is incorrect. They claim that in the disconnect event that socket.rooms is an object. While is uses the block container and is comma separated, there are no key pairs, meaning their demonstration of const rooms = Object.keys(socket.rooms) returns an empty value. It's creating an array out of an object that is really just an array. To my knowledge {} can only be used for block statements and objects. By some quirk, NodeJS is treating it like a normal array. I assign custom, 4 digit rooms on each connect event. So I have on the disconnect event I have the server skim through all the rooms, and if it encounters a room name with a length of 4, it tells everyone in the room that the size of the room decreased by one. On the client side, I have a socket event listener monitoring for this and when it's detected, updates a innerHTML property so that the clients can display the number of connected users.
//client side code:
socket.on('roomSize', (roomSize) => {
document.getElementById('clientCount').innerHTML = roomSize + ' clients connected';
});
//Server side code:
io.on("connection", (socket) => {
socket.on('createRoom', () => {
let ID = makeID(4)
while (io.sockets.adapter.rooms.has(ID)) {
ID = makeID(4)
}
socket.join(ID);
socket.emit('setID', ID);
});
socket.on("joinRoom", (room) => {
socket.join(room);
let roomSize = io.sockets.adapter.rooms.get(room).size
io.in(room).emit('roomSize', roomSize);
socket.emit('roomJoined', room, roomSize);
});
socket.on('disconnecting', function() {
let rooms = socket.rooms;
rooms.forEach(function(room) {
if (room.length === 4) {
let roomSize = io.sockets.adapter.rooms.get(room).size - 1
io.in(room).emit('roomSize', roomSize);
}
});
});
function makeID(length) {
var result = '';
var characters = '0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
})
I'm using Heroku + RedisToGo + Express 4.0 + socket.io 1.0.6.
I just recently upgraded to 1.0 from 0.9, and it's half working now. I've hacked together a app from tutorials but my lack of understanding socket.io is showing, so I'm taking a step back. The first question I have is that now socket.on('connect') is happening repeatedly, without stop, even when a connection is successful. My client-side console.log just keeps going and going. Here's client-side:
// Connect the user
socket.on('connect', function(){
var currentUserId = '<%= currentUser.id %>';
// Add user to redis
socket.emit('login', { userID: currentUserId});
// Retrieve presence info
socket.emit('presence');
});
// Show Presence
socket.on('presence', function(data) {
var userID = data.user;
var presence = data.presence;
if (presence) {
$('#red-dot-' + userID).css("display", "none");
$('#green-dot-' + userID).css("display", "inline");
// Show the hangout button
$('#hangout-' + userID).show();
$('#hangout-unavail-' + userID).hide();
}
else {
$('#red-dot-' + userID).css("display", "inline");
$('#green-dot-' + userID).css("display", "none");
}
});
And server-side:
io.sockets.on('connection', function (socket) {
var savedUserID;
socket.on('login', function(data){
var userID = data.userID;
savedUserID = userID;
// add first user
redis.sadd("users", userID);
redis.hmset("users:"+userID, "socketID", socket.id, "userID", userID);
});
socket.on('presence', function(){
// Get the list of online users and show Presence
redis.smembers("users", function(err,results) {
var onlineUsers = results;
for (var i in onlineUsers) {
var userID = redis.hget("users:"+onlineUsers[i],"userID", function(err,reply) {
var userID = reply;
// Emit presence
io.sockets.emit('presence', {
user: userID,
presence: "true"
});
});
}
});
});
As you can see I'm manually adding users to redis.
I found the answer in this Google Group thread.
The problem occurred because I had socket.emit('presence'); in my socket.on('connect', function(){}); client-side.
This in turn called socket.on('presence', function(){}); server-side, which was the problem.
I'm using NodeJs v0.10.28 and everytime i try to login to the chat i get a error
TypeError: Object #<Socket> has no method 'set' at Socket.<anonymous>
And if i'm deleting the lines it works but not working right.
What is the wrong thing in this code
// get the name of the sender
socket.get('nickname', function (err, name) {
console.log('Chat message by ', name);
console.log('error ', err);
sender = name;
});
AND
socket.set('nickname', name, function () {
// this kind of emit will send to all! :D
io.sockets.emit('chat', {
msg : "Welcome, " + name + '!',
msgr : "Nickname"
});
});
FULL CODE
http://pastebin.com/vJx7MYfE
You have to set the nickname property directly in the socket!
From socket.io website:
The old io.set() and io.get() methods are deprecated and only supported for backwards compatibility. Here is a translation of an old authorization example into middleware-style.
Also, from an example of the socket.io website :
// usernames which are currently connected to the chat
var usernames = {};
var numUsers = 0;
// when the client emits 'add user', this listens and executes
socket.on('add user', function (username) {
// we store the username in the socket session for this client
socket.username = username;
// add the client's username to the global list
usernames[username] = username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
});
Check the example here : https://github.com/Automattic/socket.io/blob/master/examples/chat/index.js
Like what Ludo said, set the nickname property directly in the socket:
// get the name of the sender
var name = socket.nickname;
console.log('Chat message by ', name);
console.log('error ', err);
sender = name;
and
// this kind of emit will send to all! :D
socket.nickname = name;
io.sockets.emit('chat', {
msg : "Welcome, " + name + '!',
msgr : "Nickname"
});
I'm writing a nodejs server that combine a login function and real-time chat. The problem came when I try to get all the users that logged in and connected with my real-time chat server then return to the client (so they can choose a specific user to chat with).
This is my code for log in function. I declared a variable so when client request the /doLogin, the username will be stored in it
var userLogged = {name: null, socketid: null};
app.post('/doLogin', function(req,res){
db.users.findOne({username: req.body.username}, function(err, user) {
if( err ) {
console.log("Login fail");
}
else if (user != null) {
if (req.body.password == user.password) {
req.session.user_role = "user";
userLogged.name = req.body.username;
} else {
req.session.user_role = "null";
}
}
res.send({redirect: "/"});
});
});
And in real-time chat function (using Socket.io), when user connect (after logged in), I will store the socket.id into the variable before, and save that variable in mongodb.
io.sockets.on('connection', function(socket){
var address = socket.handshake.address;
userLogged.socketid = socket.id;
db.clientList.save({name: userLogged.name, socketid: userLogged.socketid}, function(err, saved){
});
console.log("Connection " + address.address + " accepted.");
//
//
socket.on('disconnect', function(){
db.clientList.remove({socketid: userLogged.socketid});
});
});
And the problem is, when other users log in, the variable change so I cannot save the right information in to database.
Please help me!
socket.io allow you to read cookies:
// app.js
io = io.listen(server);
io.set('authorization', function (handshakeData, accept) {
handshakeData.cookie = cookie.parse(handshakeData.headers.cookie);
});
the right way to do is:
cookies --> sessionID --> user --> username
document: http://howtonode.org/socket-io-auth
More simple:
if security is not important to you, you can set username to cookie, then you read username in easily in server: cookies->username
// login handler
res.cookie('username', username)
res.send({redirect: "/"});
// io connection handler
io = io.listen(server);
io.set('authorization', function (handshakeData, accept) {
console.log(handshakeData.headers.cookie)
}
note that, any web user can change their own cookie to fake your name, so this is for test, you should NOT use this for production.