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?
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 am new to socket.io, and trying to figure out how to send multiple messages. Here is the scenario I am working on,
function setupServer(server) {
var socketIO = require("socket.io").listen(server);
socketIO.sockets.on('connection', function (socket) {
console.log("client is connected");
socket.emit('update', { progress: "starting..." })
});
}
I have to call setupServer(server) from another method, and I am receiving "starting..." on client side.
But the problem is, i want to send more/multiple messages LATER ON. Can not send array of messages as my application is building messages strings in say every 10 milliseconds (in a callback function) and i want to send as soon as they are created.
Any solution? Is it possible to get the socket object reference to reuse outside this function?
From socket.io docs: http://socket.io/docs/#broadcasting-messages
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.
function setupServer(server) {
var socketIO = require("socket.io").listen(server);
socketIO.on('connection', function (socket) {
console.log("client is connected");
socket.emit('update', { progress: "starting..." });
startBroadCastFromSocket(socket, 3);
socket.on('disconnect', function(){
stopBroadcastFromSocket(socket);
});
});
// broadcast message to all sockets
setInteval(function() {
broadcastMessageToEveryone(socketIO, {body: 'Hello everyone (FROM SERVER)', timestamp: new Date());
}, 1000);
}
function broadcastMessageToEveryone(io, body) {
io.emit('message', {body: body, timestamp: new Date()});
}
function broadcastMessageFromSocket(socket, body) {
socket.broadcast.emit('message', {body: body, timestamp: new Date()});
}
var socketIntervals = {};
function stopBroadcastFromSocket(socket) {
if(socketIntervals[socket.id]) {
clearInterval(socketIntervals[socket.id]);
}
}
function startBroadcastFromSocket(socket, seconds) {
socketIntervals[socket.id] = setInterval(function(){
broadcastMessageFromSocket(socket, 'Hello!');
}, seconds*1000);
}
Not sure if the issue is how I have my sockets setup - or if I am incorrectly trying to render the data with React.
I can successfully pull in data with my socket - yet it doesn't live update state when new data is posted to the server. My intention is for the state in React to automatically render new data, which is always live because of the socket connection.
Here is my client app that gets messages from the server and renders them:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
<MessageForm submitMessage={this.submitMessage}/>
</div>
);
}
});
Just in case here is my server code that emits data:
io.on('connection', function (socket) {
socket.on('getMessages', function (data) {
Message.find(function(err, messages){
socket.emit('serverMessages', messages);
})
});
});
As of right now, you're "just" grabbing data from the server once the component has been loaded. To get something a bit more "real time" you'll want to either ping the server with the same emit statement you specified regularly (which defeats the point of using websockets, really, you could use long-polling) or have the server regularly send new data to all clients.
You can do EITHER:
A) Client side: "Polling" for information [Not Ideal]
Note: I initially put this in my answer because I saw the OP was "polling" when the controller was loaded. I didn't click on that this might be because the controller may not be loaded with the websocket so sending data on connect might not work here. My bad.
Replace socket.emit('getMessages') with something that will "poll" the websocket regularly for data:
setInterval(function () {
socket.emit('getMessages')
}, 10000); /* Request data from the socket every 10 seconds */
OR
B) Server side: Send new data as it becomes available. [Best Way]
Track all clients via a clients array and delete them from it when their session ends.
var clients = [];
io.on('connection', function (socket) {
clients.push(socket);
socket.on('end', function () {
// Could also splice the array below, but it still works.
delete clients[clients.indexOf(socket)];
});
/* Previous logic for server goes here */
});
Run this code when you need to push new messages from the database/data storage:
for (var i in clients) {
clients[i].emit('serverMessages', /* messages object */);
}
Your server code is only firing upon initial socket connection.
Server:
socket.on('getMessages', function (data) {
Message.find(function(err, messages){
socket.emit('serverMessages', messages);
})
});
Client:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
</div>
);
}
});
Based on naming convention, it also appears that your Message.find() is pulling a single message. I would recommend clarifying the labeling to match cardinality.
Try this:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
<MessageForm submitMessage={this.submitMessage}/>
</div>
);
}
});
Could it be possible its due to the componentWillMount lifecycle method? Could you try the componentDidMount instead.
It looks like render will see the state update but only gets executed once despite the state change according to facebook.
this is my code ----
var nsq = require('nsqjs');
var r = require('rethinkdb');
var nsqvar = (process.env.NSQD_RETH || "localhost:4161").split(",");
var p = r.connect({host:'localhost', port:8080, db:'test', authKey:''});{
p.then(function(conn) {
console.log("Succesfull connection")
}).error(function(error) {
console.log("Error at connection")
})
// Event Reader functionality inside connect callback
var eventreader;
eventreader = new nsq.Reader('hello_topic', 'hello_channel', {
lookupdHTTPAddresses: nsqvar
});
eventreader.connect();
eventreader.on('message', function (msg) {
// Now we have access to the connection
r.table('sprinkle_nsq_test').insert(msg.json()).run(conn);
console.log('Received message [%s]: %s', msg.id, msg.body.toString());
msg.finish();
console.log(msg);
});
}
And from the terminal I am trying to insert
curl -d '{"id": "712", "name": "Douglas Adams""type "casdasdasomedy"}' 'http://127.0.0.1:4151/put?topic= hello_topic'
At nsq at receives the message but at nodejs program at says throw new Error("Invalid JSON in Message");
^
Error: Invalid JSON in Message
and also at same time the message is not storing at rethinkdb.
Looks like the error message is correct - your JSON is invalid.
Try copying and pasting it through an online JSON validator / viewer and it will be invalid.
I've cleaned it up below. Hope it all works now.
{"id": "712", "name": "Douglas Adams", "type": "casdasdasomedy"}
You have some mistakes in your code (in regards to RethinkDB at least). Here is a fixed solution with comments (this may or may not fix your problem):
var nsq = require('nsqjs');
var r = require('rethinkdb');
var nsqvar = (process.env.NSQD_RETH || "localhost:4161").split(",");
// Connect to the client driver port 28015
var p = r.connect({ host:'localhost', port:28015, db:'test' });
p.then(function(conn) {
// Wait until the database is connected
console.log("Succesfull connection");
// Event Reader functionality inside connect callback
var eventreader;
eventreader = new nsq.Reader('hello_topic', 'hello_channel', {
lookupdHTTPAddresses: nsqvar
});
eventreader.connect();
eventreader.on('message', function (msg) {
// Make sure msg.json() is actually valid json
if (typeof msg === 'object' && msg !== null) {
throw new TypeError('`msg` is already and object. It doesnt need to be convert to json');
}
var json = msg.json();
if (typeof json !== 'object' && msg !== null) {
throw new TypeError('`msg.json()` is not an object and cant be inserted into the database.');
}
// Now we have access to the connection
r.table('sprinkle_nsq_test').insert(msg.json()).run(conn)
.then(function () {
// Wait until RethinkDB is done inserted the message to call `.finish`
console.log('Received message [%s]: %s', msg.id, msg.body.toString());
msg.finish();
console.log(msg);
});
});
}).error(function(error) {
console.log("Error at connection to the database");
});
I'm using sockjs with standard configuration.
var ws = sockjs.createServer();
ws.on('connection', function(conn) {
conn.on('data', function(message) {
wsParser.parse(conn, message)
});
conn.on('close', function() {
});
});
var server = http.createServer(app);
ws.installHandlers(server, {prefix:'/ws'});
server.listen(config.server.port, config.server.host);
wsParser.parse function works like this:
function(conn, message) {
(...)
switch(message.action) {
case "titleAutocomplete":
titleAutocomplete(conn, message.data);
break;
(...) // a lot more of these
}
Each method called in switch sends back a message to client.
var titleAutocomplete = function(conn, data) {
redis.hgetall("titles:"+data.query, function(err, titles){
if(err) ERR(err);
if(titles) {
var response = JSON.stringify({"action": "titleAutocomplete", "data": {"titles": titles}});
conn.write(response);
}
})
};
Now my problem is that I'd like to make tests for my code (better late than never I guess) and I have no idea how to do it. I started writing normal http tests in with mocha + supertest but I just don't know how to handle websockets.
I'd like to have only one websocket connection to reuse through all tests, I'm binding the websocket connection with user session after first message and I want to test that persistence as well.
How do I make use of ws client's onmessage event and utilize it in my tests? How the tests can tell apart received messages and know which one they are supposed to wait for?
Collegue at work asked if it really needs to be a client connection or would it be possible to just mock it up. It turned out it was the way to go. I wrote a little helper class wsMockjs
var wsParser = require("../wsParser.js");
exports.createConnectionMock = function(id) {
return {
id: id,
cb: null,
write: function(message) {
this.cb(message);
},
send: function(action, data, cb) {
this.cb = cb;
var obj = {
action: action,
data: data
}
var message = JSON.stringify(obj);
wsParser.parse(this, message);
},
sendRaw: function(message, cb) {
this.cb = cb;
wsParser.parse(this, message);
}
}
}
Now in my mocha test I just do
var wsMock = require("./wsMock.js");
ws = wsMock.createConnectionMock("12345-67890-abcde-fghi-jklmn-opqrs-tuvwxyz");
(...)
describe('Websocket server', function () {
it('should set sessionId variable after handshake', function (done) {
ws.send('handshake', {token: data.token}, function (res) {
var msg = JSON.parse(res);
msg.action.should.equal('handshake');
msg.data.should.be.empty;
ws.should.have.property('sessionId');
ws.should.not.have.property('session');
done();
})
})
it('should not return error when making request after handshake', function (done) {
ws.send('titleAutocomplete', {query: "ter"}, function (res) {
var msg = JSON.parse(res);
msg.action.should.equal('titleAutocomplete');
msg.data.should.be.an.Object;
ws.should.have.property('session');
done();
})
})
})
It works like a charm and persist connection state and variables between requests.