So I am building, a chat app and need help figuring out how to send private messages. This is the code I have, to 'send a message'.
users = {}
socket.on('send message', function(data, callback){
var msg = data.trim();
console.log(users);
console.log('after trimming message is: ' + msg);
var name = req.params.posteruname;//reciever
var msg = msg;
if(name in users){
var message = new Chat({
msg : msg,
sender : req.user.username,
reciever : name
}).save(function(err, savedMessage){
if(err) {
users[name].emit('whisper', {msg: "Error, some tried sending you a message but something went wrong", nick: socket.nickname});
} else {
users[name].emit('whisper', {
reciever: name,
sender:req.user.username,
msg:msg
});
}
});
} else{
callback("Something went wrong");
}
});
This code isn't working very well. When I try to send message, it still displays to all users.
Take a look in this document. You should specify rooms. Every one will be a "room", when you send a message with event and room specified the correct user will receive it.
Your users object is just a plain object, don't reflect what I'm talking about.
Related
I followed few tutorials on how to display messages sent in the chatroom before joining in, but I don't know how to display them in React and I have few questions below in the server side.
Client side, in constructor :
this.state = {
msg: "",
messages: []
};
Client side, I have a form which clicked button will send the message to the server by this function :
sendMessage(e) {
e.preventDefault();
let msg = this.state.msg;
this.socket.emit("sendMessage", msg);
this.setState({ msg: "" });
}
Server side, I have a mongoose Schema for the message, named Message and the collection in the database is messages.
const Message = new mongoose.Schema({
sender: {
type: "string"
},
message: {
type: "string"
}
});
var messages = [];
io.on("connection", (socket, user) => {
var user = socket.request.session.user;
Message.find({}).exec((err, messages) => {
if (err) throw err;
console.log(messages);
io.emit("showingPastMessages", messages);
});
console.log(messages) shows in PowerShell all the messages (entries) saved in Mongo in an array of javascript objects ?
[{id_ : 4qxxx, sender : 'user123', message : 'hello!'}, {id_ : 5exxx, sender : 'user456', message : 'hi!'}]
I would like to know if it is possible to access only to sender and message properties to send it to the client ? Something like messages.sender + messages.message because when I console.log(messages.message) it shows undefined
Here is where the server receives the message sent then saves it in Mongo.
socket.on("sendMessage", function(msg) {
var newMsg = new Message({ message: msg, sender: user });
newMsg.save(function(err) {
if (err) {
console.log(err);
} else {
messages.push(newMsg);
console.log(newMsg);
console.log(messages);
}
});
});
console.log(newMsg) shows the latest msg sent, but the console.log(messages) doesn't show the previous messages but only the latest one, why ?
then in React, I should have something like this in constructor, in ComponentDidMount() ? If should it be with prevState
this.socket.on("showingPastMessages", function(messages){
this.setState({ ...this.state.messages, messages})
});
Could you could give me some advices ?
Here my client side code to retrieve the data:
class Chat extends Component {
constructor(props) {
super(props);
this.state = {
msg: "",
messages: []
};
var socket = io('http://localhost:3000');
this.socket.on("history", function(messages) {
console.log(messages);
});
}
That's a good start. What I would suggest doing is making this a little more event driven. To do that, you'll want to add the sockets to a room when they connect. How many rooms and how to split up the rooms will depend on your app, but I'll demonstrate the basic idea.
First, when a socket connects to your server, add that socket to a room and emit your chat history immediately.
io.on('connection', async (socket) => {
socket.user = socket.request.session.user;
socket.join('chat');
try {
const messages = await Message.find({});
socket.emit('history', messages);
} catch (err) {
// Handle this error properly.
console.error(err);
}
});
Then, later on, when you receive a message, you'll want to save that message and emit it to all of the sockets in your chat room.
socket.on("sendMessage", (msg, callback) => {
const message = new Message({ message: msg, sender: socket.user });
message.save(function(err) {
if (err) {
return callback(err);
}
io.to('chat').emit('message', message);
});
});
Finally, on the client side, you'll want to listen for the history event. When you receive it, you'll want to clear the chat history you currently have and replace it with what the server is telling you. Maybe this would look something like
socket.on('history', (messages) => {
this.setState({ messages });
});
You'll also want to listen for this message event, but with this event, you'll only append the message to your history. This might look something like
socket.on('message', (message) => {
this.setState({ messages: [ ...messages, message ] });
});
A word of warning, if when you tell the server about a new message, do not add it to your messages state array until you receive the message event. If you do so, you will notice double messages. For example, this might look something like
onSendMessage(evnt) {
evnt.preventDefault();
socket.emit("sendMessage", msg);
this.setState({ msg: "" });
}
Note: After receiving some feedback from the OP, I wanted to add a section on where to put the event handlers attached to the socket (i.e. all the socket.ons). My suggestion would be to add this code in the file that defines the Message schema at the bottom of the file in the io.on('connection') callback. For example,
const Message = new mongoose.Schema({/*...*/});
io.on('connection', (socket) => {
// Everything else I wrote above...
socket.on('sendMessage', (msg, callback) => {
// ...
});
});
On the client side, the event handlers would probably be registered when the chat component is mounted. For example,
class ChatComponent extends React.Component {
componentDidMount() {
this.socket = io('https://your-server-or-localhost/');
this.socket.on('history', (messages) => {
// See above...
});
}
}
I made a simple web app with node.js and hosted it with heroku. The main process does many operations, one of which is reading and writing a text file with ids. However, when I try to send this file from a different process to my email with nodemailer it is sent as blank. The file is initially blank but then is written by the main process; the main process read text from it too so I know the problem is not there. Can you tell me why it isn't working?
Edit: I know for sure that when I send the file is not empty (i do console.log with the content of the file)
Main process code:
function handleSubscriptions(event){
var senderID = event.sender.id;
var recipientID = event.recipient.id;
var timeOfPostback = event.timestamp;
var payload = event.postback.payload;
console.log("Received postback for user %d and page %d with payload '%s' " +
"at %d", senderID, recipientID, payload, timeOfPostback);
if(payload == "iscrizione"){
fs.appendFile("./users.txt", senderID + ",", function (err) {
if (err) throw err;
console.log('The id was appended to file!');
});
var data = fs.readFile('./users.txt', function(err, data) {
if (err) throw err;
processData(String(data));
});
function processData(data){
var arr = data.split(",");
for(i = 0; i < arr.length - 1; i++){
var val = parseInt(arr[i]);
msg = "user number " + i + " :"+ val ;
console.log(msg)
}
}
sendMessage(senderID, "Complimenti, sei iscritto!");
}
}
Sender code:
fs.readFile("./users.txt", function (err, data) {
var message = {
sender: email,
to: email,
subject: 'File user',
body: 'File in allegato',
attachments: [{'filename': 'users.txt', 'content': data}]
};
transporter.sendMail(message, function(error, info){
if (error) {
console.log('Error occurred');
console.log(error.message);
return;
}
console.log('Message 2 sent successfully!');
transporter.close();
});
});
It sounds like a problem with concurrency - you are probably reading the file before it is written to.
You need to make sure that you are not reading the values before they are written. Without seeing a single line of code it is impossible to give you any more specific advice than that.
I'm quite new to Redis Pub/sub so please bear with me. I'm trying to create an IRC where users can create their own chat rooms, kinda like Gitter. Below is what I've done so far.. I'm subscribing the users to different channels by their username only just for testing.. Thing is that when I publish to channel x, a client who's subbed to channel y still gets the same message.. I'm publishing using redis-cli and PUBLISH command.
function handleIO(socket){
function disconnect(){
console.log("Client disconnected");
socket.broadcast.emit("user d/c", socket.username+" has left!");
}
socket.on("new user", function(username){
socket.username = username;
if(socket.username == "chat"){
redisClient.subscribe("chat");
}else{
redisClient.subscribe("other");
}
socket.userColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
socket.emit("new_user", username);
emitter.lrange("messages", 0, -1, function(err, messages){
//reversing to be in correct order
messages = messages.reverse();
messages.forEach(function(message){
message = JSON.parse(message);
socket.emit("messages", message);
});
});
socket.broadcast.emit("user connection", username+" has connected to the Haven!");
});
socket.on("disconnect", disconnect);
socket.on("send", function(msg){
var msg = JSON.stringify( { name: socket.username, messageText: msg, color: socket.userColor } );
emitter.lpush("messages", msg, function(err, response){
//keep newest 10 items
emitter.ltrim("messages", 0, 9);
});
io.sockets.emit("receive", msg, socket.userColor);
});
redisClient.on("message", function (channel, message) {
console.log(channel+":"+message);
socket.emit("message", channel, message);
});
}
For the lost wanderer out there... What I did is implement another event on the client to basically check whether that client is 'entitled' to the message (i.e. whether the message's channel belongs in the client's list of subbed channels if that makes sense).
Client-side
socket.on("message", function(channel, message){
socket.emit("entitled", channel, message);
});
socket.on("entitled", function(reply, channel, message){
if(reply == 1){
$("#msgArea").append(message+"<br/>");
$("#msgArea").prop({ scrollTop: $("#msgArea").prop("scrollHeight") });
}
});
Server-side
socket.on("entitled", function(channel, message){
//check that user is subbed
emitter.sismember('channels:'+socket.username, channel, function(err, reply){
if(err) throw err;
socket.emit("entitled", reply, channel, message);
});
});
What I purposely left out is that I didn't keep using socket.username but started using sessions for persistence.. My word of advice would be to stick with redis store since it's one of the most popular on github.
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 am using Faye with Node.js (javascript) for a chat server and I am trying to implement 'notices' so that when one use subscribes, the server will send a message to the channel with a property __messageType = 'subscribe'
The server code looks like so (the ext field is present and working just fine)
var chat = new Faye.NodeAdapter()... //etc
chat.addExtension({
incoming: function(message, callback) {
if (message.channel === '/meta/subscribe') {
console.log('A new user subscribed');
chat.getClient().publish(message.subscription, {
ext: message.ext,
__messageType: 'subscription'});
}
callback(message);
}
});
On my client side I have attached an 'incoming' extension so that people can easily determine if this is a subscription notification and not an actual 'data message'
clientChat.addExtension({
incoming: function(message, callback) {
console.log('Incoming message', message);
message.getType = function() {
return message.__messageType;
};
messge.getData = function(key) {
return message.ext[key];
};
callback(message);
}
});
I am structuring my messages this way that way people can do something like this:
var sub = new Faye.Client(url).subscribe('/messages', function(message) {
if (message.getType() === 'subscribe') console.log('Someone subscribed');
if (message.getType() === 'unsubscribe') console.log('Someone left');
else console.log('Ext data: ', message.ext);
The problem is that my messages are coming through with the ext field wrapped in a 'data' field and I have no idea where it's coming from. My getType and getData methods have been successfully added, but they obviously don't work because the ext field is no longer present at the top level, they are instead embedded in the message's data field.
Expected: Actual:
channel: "/messages" channel: "/messages/"
ext: { variousData } data: {ext: variousData}
getData: function (key) { getData: function(key) {
getType: function () { getType: function() {
id: "4" id: "4"
Does anyone have any idea why I'm getting that 'data' field in my messages?