I am writing a simple chat on socket.io with node.js server. So I need to know in which room socket is. For example, socket has connected and joined a room. So, when this socket sends a message I want to broadcast it only to the sockets that are in that room (sck.broadcast.to(it_room).emit).
io.on("connection", function(sck) {
...
sck.join(availableRoom);
sck.on("message", function(msg) {
sck.broadcast.to(**its room**).emit(msg);
sck.emit(msg);
});
...
});
I do not need an array of sockets and information about them (in which room etc).
You first enter your username and chat room that you join. Once you submit the form, username and room info will be appended URL as query string. in browser: location.search :this is how we reach to query strings. We have to parse it.
add this CDN to your main.html
<script src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>
this cdn for npm querystringify package. since it will be headache to setup npm package in clients side, cdn is easier to handle it. make sure cdn script files should be loaded first before other js files in hour html. once this cdn is loaded we can use:
const { username, room } = Qs.parse(location.search, { ignoreQueryPrefix: true })
{ ignoreQueryPrefix: true } this option will omit the "?" from query string.
once you reached username and room name, we can emit our event from client side.
client.js
socket.emit('join', { username, room }, (error) => {
if (error) {
alert(error)
location.href = '/' //will keep the client in the same url
}
})
now we gotta listen to this event from server side. but we have to keep track of users.
index.js
io.on("connection", socket => {
socket.on("join", ({ username, room }, callback) => {
//we have to define addUser function
let users = [];
const addUser = ({ id, username, room }) => {
// Clean the data
username = username.trim().toLowerCase();
room = room.trim().toLowerCase();
// Validate the data
if (!username || !room) {
return {
error: "Username and room are required!"
};
}
// Check for existing user
const existingUser = users.find(user => {
return user.room === room && user.username === username;
});
// Validate username
if (existingUser) {
return {
error: "Username is in use!"
};
}
// Store user
const user = { id, username, room };
users.push(user);
// users=users.push(user) this will cause error
return { user };
};
//as you see this function will return either error or user object
//Socket generates an id upon connection.
//result is either error or user
const { error, user } = addUser({ id: socket.id, username, room });
if (error) {
return callback(error);
}
socket.join(user.room);//socket joined to this room
//now time to emit a new event
socket.emit("message", ( "Welcome!"));
socket.broadcast
.to(user.room)
.emit(
"message",
(`${user.username} has joined!`)
);
//broadcast will send messages to everyone in the chat room except the connected socket
callback(); //success scenario. let the client knows that it is allowed to connect
})
})
Try with the following, but I am not sure if it works in every situation.
Object.keys(sck.manager.roomClients[sck.id])[1]
I found this by logging socket, then I looked up for rooms information and extract it the way you can see in my code.
I am using 0.9.6 socket.io version, maybe there is a different solution for latest version.
Related
I have to write a firebase function that receives a JSON with a list of users and has to manage them with the following rules. For each user in the received list:
If the user is already registered (email/password) in firebase, I update it.
If the user is not registered yet, I create it
If a user is registered in firebase but it's not present in the received list, I disable it.
Now, I came up with the following solution: I iterate for each user in the received list. I call admin.auth().createUser() method so that if the user is not registered it will be created, otherwise the method throws an error and in the catch() block I call admin.auth().updateUser().
For the second part, I retrieve all the users registered with admin.auth().listUsers() and for each of them I check whether it's present in the received list: if don't so, I disable it.
For some reason, the correctness of this solution is uncertain: sometimes it doesn't work at all, other times when I call the function once it doesn't work but the second time a call the function it works, idk why is that.
This only happens when I send to the function a lot of users (about 400). If I send only few users it works fine.
Could anyone suggest to me maybe a better solution? Thanks a lot for your answer.
This is the function:
exports.addClients = functions.https.onRequest(async (req, res) => {
// fetch recevied list from payload
var receivedClients = req.body.clients;
// create or update user
receivedClients.forEach(client => {
admin.auth().createUser({
uid: client.id,
email: client.email,
emailVerified: true,
password: client.password,
})
.catch(err => {
// update user
admin.auth().updateUser(client.id, {
email: client.email
}).catch(err => {
// error updating user
log("Error updating user: " + err);
});
})
});
// disabling users not present in the received list
listUsers = await admin.auth().listUsers();
userRecords = listUsers.users;
userRecords.forEach(record => {
if (!receivedClients.some(client => client.id === record.uid)) {
// disable user
admin.auth().updateUser(record.uid, {
disabled: true
})
.catch(err => {
// error disabling user
log("Error disaling user: " + err);
});
}
});
// send response
res.sendStatus(200);
});
I was looking at answers here How to send a message to a particular client with socket.io, specifically the 2nd one, which recommends using rooms for 1 to 1 chat. I did the following on my server:
io.on('connection', (socket) => {
socket.on('message', (data) => {
socket.join(data.to);
io.sockets.in(data.to).emit('send_message', { message: data.message, to: data.to });
});
});
And on my client I have this:
const sendMessage = (e) => {
e.preventDefault();
io.emit('message', { to: friend._id, from: currentUserID, message }); // here friend._id
// will be currentUserID in the 2nd user's browser. Which makes it so they join different rooms
setMessage('');
}; // this function runs on form submission.
io.on('send_message', (message) => {
// do stuff with the message
});
But my users won't join the same room. Each one joins a room identified by the other user's id (friend._id). So they're unable to see each other's messages. In the answer from above they do a similar thing, just with each user's emails instead, which should result in the same issue I have as far as I understad.
I am developing a chat app and so, I need to send notifications that new messages have been received.
For that, I am using Firebase Functions.
I'm using the sendToDevice function, that needs a token to send a notification. The problem is, I can't seem to retrieve token of the user that sent the message.
This is my .js code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref("/chats/{id}/messages/{messageId}/content")
.onWrite((change,context) => {
var content = change.after.val();
var payload = {
data:{
title: "Stranger has sent you a message",
text: content
}
};
// Here I need to the ID of the person who sent the message
// And then compare this Id with the two Ids of the to users that are in the conversation
// If the two Ids are different, then save the other Id as the token
// So that I can send a notification to the other user.
const senderId = database.ref("/chats/{id}/messages/{id}/sender/{senderId}");
admin.messaging().sendToDevice(senderId, payload)
.then(function(response){
console.log("Successfully sent message: ", response);
return null;
})
.catch(function(error){
console.log("Error sending message: ", error);
})
});
As you can see, I am checking for any changes in the messages/content child.
That as the content of my notification.
Then, I am trying to retrieve the message sender ID so I can know who sent the message and retrieve the other user Id to notify him.
This might be a little confusing so here is my Firebase Realtime Database:
What am I doing wrong so this piece of code works as it should? This is the activity I have in android to receive the message:
class MyFirebaseInstanceId : FirebaseMessagingService() {
override fun onMessageReceived(p0: RemoteMessage) {
if(p0.data.size > 0){
val payload :Map<String, String> = p0.data
sendNotification(payload)
}
}
private fun sendNotification(payload: Map<String, String>) {
val builder = NotificationCompat.Builder(this)
builder.setSmallIcon(R.drawable.common_google_signin_btn_icon_disabled)
builder.setContentTitle(payload.get("username"))
builder.setContentText(payload.get("email"))
val intent = Intent(this, MainActivity::class.java)
val stackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntent(intent)
val resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(resultPendingIntent)
val notificationManager = (getSystemService(Context.NOTIFICATION_SERVICE)) as NotificationManager
notificationManager.notify(0, builder.build())
}
}
Following our comments above, here is how to use the once() and val() methods in your Cloud Function:
//.....
const refSenderId = database.ref("/chats/{id}/messages/{id}/sender/{senderId}");
return refSenderId.once('value')
.then(dataSnapshot => {
const senderId = dataSnapshot.val();
return admin.messaging().sendToDevice(senderId, payload)
})
.then(function(response){
console.log("Successfully sent message: ", response);
return null;
})
.catch(function(error){
console.log("Error sending message: ", error);
return null; // <- Note the return here.
})
//.....
I use RabbitMQ, SocketIO and MongoDB to make private messages for my app.
The plan is when new user register, app make new unique RabbitMQ Queue for that user, with that user is able to get messages when it is offline. So user send message through SocketIO and it is passed to RabbitMQ Publisher and then when consumer is online he get that message.
My questions now is how to set Validate User ID from sendToQueue (Publisher) function to be able later to read sender ID from consume function?
amqp.connect(CONN_URL, function(err, conn) {
conn.createChannel(function(err, channel) {
ch = channel;
console.log("RabbitMQ channel created...");
});
});
const publishToQueue = async (queueName, data) => {
ch.sendToQueue(queueName, Buffer.from(data.message));
};
const consumeToQueue = async queueName => {
ch.consume(
queueName,
function(msg) {
return msg.content.toString();
},
{ noAck: true }
);
};
I worked out...
const publishToQueue = async (queueName, data) => {
let properties = { headers: {userId: data.to }, timestamp: Date.now() };
ch.sendToQueue(queueName, Buffer.from(data.message), properties);
};
This will send headers with userId information and also timestamp of message
First a little background:
I am working on an seperate mobile application that is connected with the main app. The connection is succesfully initiated and I can retrieve all collections, through subscriptions:
Remote = DDP.connect('http://localhost:3000/');
Meteor.users = new Meteor.Collection('users', {
connection: Remote
});
Remote.subscribe('users', true);
Now I want to make sure users can log in through the interface of the second app. After installing the accounts-password and the meteor-ddp-login package, I should be able to authenticate with the main app by using the next piece of code in the client side.
var Remote = DDP.connect('http://localhost:3000/');
DDP.loginWithPassword(Remote, {
username: username
}, password, function(error) {
if (!error) {
console.log(username + " is logged in!");
} else {
console.log(error);
}
});
Well, so far so good. No errors appear and the console logs a success message. Now the question comes:
How can I retrieve the user object of the user who just logged in.
I've set up several publish functions in the main app, but the user data does not become available to the client in the second app (other collections work fine, but Meteor.user() is undefined).
And also: How can I authenticate users who login with Facebook/Google/Twitter
Came across this, I had a similar need recently. Following code works in Meteor version 1.2.0.2
if (Meteor.isClient) {
Meteor.startup(function(){
//Seems that without this, on page refresh, it doesn't work.
//COMMENT: Ideally this should not be needed if the core takes care of this use case of a different connection for Accounts
//hack block 1***********
var token = Accounts._storedLoginToken();
if(token) {
Meteor.loginWithToken(token, function(err){
// this is going to throw error if we logged out
if(err)
console.log(err);
else
console.log('loginWithToken');
});//loginWithToken
}
//hack block 1***********
});//startup function
var connection = DDP.connect("http://localhost:3060");
Accounts.connection= connection;
//COMMENT: Ideally this should not be needed if the core takes care of this use case of a different connection for Accounts
//hack block 2***********
Accounts.users = new Meteor.Collection('users', {
connection: connection
});
//hack block 2***********
Tracker.autorun(function () {
//No code which directly affects the functionality. Just for testing
console.log(Meteor.user());
Accounts.connection.call('user',function(err,result){
if(err)
console.log(err);
if(result){
console.log(result);
if(result._id === Meteor.user()._id){
console.log("Server and client shows that the same user has logged in");
} else {console.log("Server and client shows different users");}
}
})
});
Template.register.events({
'submit #register-form' : function(e, t) {
e.preventDefault();
var email = t.find('#account-email').value
, password = t.find('#account-password').value;
Accounts.createUser({email:email,password:password}, function(err,result){
if (err) {
// Inform the user that account creation failed
console.log(err);
} else {
// Success. Account has been created and the user
// has logged in successfully.
console.log("registered user");
console.log('response is '+ result);
console.log(Meteor.user());
}
});//createUser
return false;
}
});//register
Template.login.events({
'submit #login-form': function(e,t){
e.preventDefault();
var email = t.find('#login-email').value
, password = t.find('#login-password').value;
Meteor.loginWithPassword(email, password, function(err){
if (err)
console.log(err);
else
// The user has been logged in.
console.log('logged in successfully');
});
return false;
}
});//login
Template.statusloggedin.events({
'click #logout': function(e,t){
e.preventDefault();
Meteor.logout();
return false;
}
});//logout
}