I am trying to set up a chat interface on a site using nodejs.
I need to update the logged_in_user list after a user is authenticated.
I am using this code as a reference
https://github.com/amirrajan/nodejs-chat
io.sockets.on('connection', (socket) => {
socket.on('sendchat', (data) => {
io.sockets.emit('updatechat', socket.username, data);
});
As you can see on the 'connection' event the updatechat event is triggered.
In the client side (public/app.js),the chatlist is updated on getting the updatechat event.
I need to do the same but after a client is authenticated.
Can I somehow tweak the socket object to trigger the event on a 'login' event
This is my code
app.get('/authenticate',function(request,response)
{
var db = couch.db.use('users');
var email = request.query['email'];
var pass = request.query['password'];
db.get(email,{email:email},function(err,body){
if(err)
console.log(err);
else
{
var user_pass = body['password'];
var name = body['name'];
console.log(user_pass);
if(pass == user_pass)
{
eventEmitter.emit('login',name);
response.render('pages/dashboard',{user:body['name']});
}
}
});
// And on 'login' event
eventEmitter.on('login', function(name)
{
// ?????? Broadcast the new user to all logged in users to update the chatlist
});
I have tried to redirect the logged in users to another port and use the socket 'connection' event there.It sort of works but I want to know if there is another approach I could use.
You should use socket.io middleware for authentication.
io.use(function(socket,next){
//auth here
});
Take a look at the docs http://socket.io/docs/server-api/
You can get the req and res objects from the socket object being passed to the middleware.
Note, it is not a great idea to pass a password on the query since the url is not pass ssl.
Related
I am creating a rest api on nodejs. I have email id and user id in database. I want to share a file (present on same server) from one user to a particular user. Can anyone tell me how this can be done in a best way ?
Here is the code i have tried yet.
const server = require('./../server/server.js')
const socketIO = require('socket.io');
const io = socketIO(server);
const mongoose = require('mongoose');
const User = require('../models/user.js');
let sockets = [];
io.on('connection', socket=>{
console.log("User connected");
socket.on('online', (data)=>{
socket.name = data._id;
sockets[data._id] = socket.id;
console.log("user is online")
})
socket.on('send_file', (data)=>{
User.find({emailId: data},{emailId:0, password:0})
.exec()
.then(userid => {
if(userid.length<1){
console.log("No such user");
}
else{
console.log(userid[0].id);
socket.to(sockets[userid[0].id]).emit('hello', "HELLO");
}
})
.catch(err =>{
console.log(err);
});
});
socket.on('disconnect', ()=>{
console.log("User disconnected");
})
})
module.exports = io;
server.listen('8080', (err)=>{
if(err) throw err;
console.log("running on port 8080");
});
Assuming that you have already configured the socketio and express sever properly with he mechanism to save the file path and file name in you database.
Try something like this (with socketio)
let sockets = [];
io.on("connection", function(socket) {
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
socket.on("send_file", function(data) {
// your logic to retrieve the file path from you database in to the variable **filedata**
// let filedata = ................
socket.to(sockets[data.user]).emit("file",filedata);
});
socket.on("disconnect", reason => {
sockets.splice(sockets.findIndex(id => id === socket.id), 1);
});
});
in send_file event you will have receive the username from the sender inside the data object. The following code will be one which will help you to send file to selected user.
socket.to(sockets[data.user]).emit("file",filedata);
Replying to your 1st comment.
history.push() will not refresh the client since its a single page application.
But when you refresh(from user A side) a new socket session will be created then the other user(user B) will still be referring the old socket session(which is already being disconnected by the refresh). So to handle this use the following lines
socket.on("online", data => {
socket.name = data.username;
sockets[data.username] = socket.id;
});
where you will be keeping a pool(an array) of sockets with the usernames so when ever a user refresh their client the newly created socket will be referring to the same user. Since you will be updating the the socket.id to the same person.
For example assume that you the user who refresh the client and im the other user. so when you refresh a new socket session will be created an it will be sent to the back end along with the user name. When the data comes to the server it will get your session object from the array(sockets[data.username]) and update it with the new socketio sent from your front-end sockets[data.username] = socket.id;.
for this to happen you will have to send the user name along with the socket message. like this
socket.emit("online", {
username: "username"
});
Replying to your 2nd comment
To send data in real time the users should be online. if not you can just create an array of notifications with the following information (sender and receiver). So when the receiver logs in or clicks on the notification panel the list of shared files notification can be shown. This is just a suggestion you can come up with you own idea. Hope this helps.
In case of Non-real time data:
Server Side-:
use res.sendFile() is a way to solve this problem. In addition to file send the receiver and sender id in headers
Client Side-:
Increase the notification count if the receiver id matches the logged in user.
res.sendFile(path.join("path to file"),
{headers:{
receiverid:result[0]._id,
senderid:result[1]._id
}
});
In case of real time data:
follow the answer posted by TRomesh
Below is my Server side and client side file:
Server side file:
'use strict';
var model = require('../model/model.js');
class Socket{
constructor(socket){
this.io = socket;
this.users = [];
}
socketEvents(){
this.io.on('connection', (socket) => {
socket.on('username', (data) => {
this.users.push({
id : socket.id,
userName : data.username
});
let len = this.users.length;
len--;
model.addSocketId( data.username,this.users[len].id);
this.io.emit('userList',this.users,this.users[len].id);
});
socket.on('getMsg', (data) => {
model.insertMessages({
fromUserId: data.fromUserId,
toUserId: data.toUserId,
message: data.msg
});
console.log("socket id is:");
console.log(data.toid);
socket.broadcast.to(data.toid).emit('sendMsg',{
msg:data.msg,
name:data.name
});
});
socket.on('disconnect',()=>{
for(let i=0; i < this.users.length; i++){
if(this.users[i].id === socket.id){
this.users.splice(i,1);
}
}
this.io.emit('exit',this.users);
});
});
}
socketConfig(){
this.socketEvents();
}
}
module.exports = Socket;
Below is my Client Side File:
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect("http://192.168.1.12:3000");
socket.on('sendMsg', (data) => {
console.log("send message-list");
$('#message-list').append("<li class='friend-user'>"+data.msg+"</li>");
});
socket.on('userList', (completeUserList,userSocketId) => {
console.log('userlist function');
var userList = completeUserList;
});
$('document').ready(function(){
$(document).on('click', "#msg-btn", function (event) {
var messagePacket = {
toid: $("#friendSocketId").text(), //stored in hidden format from db
msg: $('#message').val(),
name: $("#myUserName").text(),
fromUserId: $("#loginUserId").text(),
toUserId: $("#friendUserId").text(),
};
socket.emit('getMsg',messagePacket);
});
</script>
In this, When user clicks on send button from front-end then "getMsg" event emitting successfully. getMsg function took receiver socket id as message and emit "sendMsg" event to particular socket id. But sendMsg event is not sending msg to particular socket id. please help.
It looks like you don't have a key called toid on data object. You might have mistyped it. Replace toid with toUserId and it should work.
Since you are sending a message to another socket, instead of a room, you can use socket.to().emit() instead of socket.broadcast.to().
From Socket.IO docs:
// a private message to another socket
socket.to(/* another socket id */).emit('hey');
So, replace socket.broadcast.to() with socket.to().emit(), like:
socket.to(data.toid).emit('sendMsg',{
msg:data.msg,
name:data.name
});
EDIT:
You asked the following question in the comments below this answer:
Does socket id changed automatically after
login when user send message or it remains same when client logged in?
Socket id is created for each client when io.connect() is executed.
In your code, this is the first line in your script file. So, a new socket id is created every time this script loads.
So, every time the page containing this script is loaded, a new socket id is created. If you refresh the page, this script loads again and a new socket id is created.
To answer your question, socket id remains same as long as user is on the same page. So, when a user sends a message it doesn't change, unless the user navigates to another page or refreshes this page after sending the message. So, if a user logs out and logs in, this page loads again, so socket id changes.
I think you are storing the socket id in database after user logs in, but are not updating it when user navigates to another page or logs out and logs in again. So, when you are emitting a message from server, you are sending it to user's previous socket id, not the current one.
How do I get other user's socket.id?
Since socket.id is keep changing, whenever the browser refresh or disconnect then connect again.
For example this line of code will solve my problem
socket.broadcast.to(id).emit('my message', msg);
But the question is How do i get that id?
Let say Jack wants to send a message to Jonah
Jack would use the above code to send the message, but how to get Jonah's socket id?
Just for the record, I already implemented socket.io and passport library so that I could use session in socket.io , the library is call passport-socket.io. So to get the user id in socket.io would be
socket.request.user._id
What i did for this was to maintain a database model(i was using mongoose) containing userId and socketId of connected users. You could even do this with a global array. From client side on socket connect, emit an event along with userId
socket.on('connect', function() {
socket.emit('connected', userName); //userName is unique
})
On server side,
var Connect = require('mongoose').model('connect');; /* connect is mongoose model i used to store currently connected users info*/
socket.on('connected', function(user) { // add user data on connection
var c=new Connect({
socketId : socket.id,
client : user
})
c.save(function (err, data) {
if (err) console.log(err);
});
})
socket.on('disconnect', function() { //remove user data from model when a socket disconnects
Connect.findOne({socketId : socket.id}).remove().exec();
})
This way always have connected user info(currently used socketId) stored. Whenever you need to get a users current socketId fetch it as
Connect.findOne({client : userNameOfUserToFind}).exec(function(err,res) {
if(res!=null)
io.to(res.socketId).emit('my message', msg);
})
I used mongoose here but you could instead even use an array here and use filters to fetch socketId of a user from the array.
The documentation suggests that `socket.on(SOME_MESSAGE's callback is provided an id as the fist param.
http://socket.io/docs/rooms-and-namespaces/#default-room
javascript
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
Is there a way to set up socket.io listeners for certain clients after they execute a command? For example:
socket.on('setupServer', function (data) {
console.log("setupServer called");
// Now set up listeners
socket.on('serverAction', function (data) {
console.log('Listener for clients calling setupServer');
});
});
In the above, a client has connected and has issued a 'setupServer' command to the node.js server. The server now listens for 'serverAction' from the specific client. Other clients won't be able to use 'serverAction' without calling 'setupServer' first.
You could create a room and validate some data from user and then join those users to that room, after that emit some events to that users in that room.
Something like this.
server.js
var io = require('socket.io')(server);
io.on('connection',function(socket){
socket.emit('auth_user')
socket.on('response_from_user',function(data){
if(data.name === "Blashadow"){
//join that user to the room
socket.join('/room',function(){
//emit some custom event to users in that room
io.in('/room').emit('custom_event_room', { info: 'new used connected from room' });
});
}
});
});
client.html
var socket = io('localhost');
socket.on('auth_user', function (data) {
socket.emit('response_from_user', { name : "Blashadow" });
});
socket.on('custom_event_room',function(data){
console.log(data);
});
i'm beginning with node.js, in my first example i'm creating a chat. This char have two page, the first (index.jade) the user insert your nickname and second page (chat.jade) the user can chat with other users.
i have two problems.
the first: when user insert your name, the system change the page (chat.jade) and return the your name wrote but the code ( io.sockets.emit('nicknames', nicknames); ) no execute and i think that's because when change of page no load de funcion en el client (socket.on('nicknames',function(data){ .....) . Why??
second problem:
when a user send a message, the server send a json with user and message but the client get the user null and message correct. why the user is null??
code:
index.jade
$('#form-login').submit(function(){
var nickname = $('#nickname').val();
if(nickname !== ''){
socket.emit('login',nickname, function(data){
if(!data){
return false;
}
});
}
});
chat.jade
$('#btnSendMessage').on('click',function(){
var message = $("#message").val();
if(message != ''){
socket.emit('sendMessage',message);
$('#message').val('');
$('#message').focus();
}
});
socket.on('nicknames',function(data){
var $p = $('<p>'+data+'</p>');
$('.users').append($p);
});
//app.js
var nicknames = [];
io.sockets.on('connection', function(socket) {
socket.on('login', function(data, callback){
if (nicknames.indexOf(data) != -1){
callback(false);
}else{
callback(true);
nicknames.push(data);
socket.set('nickname', data);
io.sockets.emit('nicknames', nicknames);
}
});
socket.on('sendMessage', function(data){
socket.get('nickname', function(err, name){
console.log('nickname: '+name);
});
var message = { "nickname":socket.nickname, "data":data };
io.sockets.emit('userMessage', message);
});
});
Submitting the form navigates away from this page, thus closing a socket connection to the server.
Because of the above when client arrives at your chat.jade, it establishes new socket connection, which doesn't have socket.nickname set - and also you should be using socket.get('nickname') - which is an asynchronous functions.
have a look at my implementation of chat using socket.io and agular
Use sessions to tie socket connection to a user session, link
Use AngularJs or similar frameworks to build Single Page App, to prevent refreshing, and loosing connection