I am trying to use Socket.io and Sequelize to create a chat app. Socket.io will handle the socket to allow for instant messaging. Sequelize will handle storing the messages so when you refresh the screen you still have your messages.
What is happening is that on localhost my socket works, but it does not send the messages to the database. When I put it onto Heroku, my database worked, but it does not use the sockets.
My socket is located in app.js and my database route is located in routes/messages.js.
I have been working on this bug for a while now and I have been trying to get help with it. I think the best way to share this is with my markdown I created detailing my efforts to fix my bug that can be found at here. My repo for this can be found here.
There are a few different parts that you need to distinguish:
the HTTP server, in your code represented by the variable http
the Express app, represented by app
the Socket.IO server, represented by io
a Socket.IO (client) connection (see below)
The HTTP server directs "normal" HTTP requests to the Express app, which will handle them according to the middleware and routes that are set up. A router handler gets called with (at least) two arguments, generally called req and res, to represent the (incoming) HTTP request and the (outgoing) HTTP response.
The Socket.IO server gets to handle specific Socket.IO requests, which get sent to the server by the Socket.IO client (running in the browser). When such a client sets up a connection with the server, the connection event gets triggered on the server. Any handlers for this event will get passed an argument, generally called socket, that represents the (bidirectional) connection with that client.
That Socket.IO connection can receive messages (sent from the client running in the browser), which trigger events on the socket. You can install a handler to listen for particular messages (like "chat message"), which will receive, as argument, the data that was sent to it by the client.
The issue in your code seems to be with setting up everything to handle those chat messages. The correct setup order would be:
listen on the Socket.IO server for connection events
when such an event is received, add a listener for the chat message event on the connection
when such an event is received, write the data to the database.
In code:
// Listen for new client connections.
io.on('connection', function(socket) {
// Listen for the client to send a _"chat message"_ message.
socket.on('chat message', function(data) {
// Store the data in the database.
models.Messages.create({
message : data.message,
username : data.username
});
});
});
As you can see, req and res aren't available inside of those Socket.IO event handlers, because those are only used for normal HTTP requests.
Also, as opposed to HTTP, you don't necessarily have to send anything back to the client when you have received a message, so I left that part out. The handler above only writes the message data to the database (it also doesn't check for, or handle, errors, which eventually you should add).
Related
I have a chat app, something like Whatsapp. I'm considering two ways to store messages in db and also emit them. My approach now is that client emits new message, server is listening then store that message in database and return whole message back to server. Server after that emits new message to all clients in session/group. So bascially it server first stores messages in db and then emits them back to clients.
this is my code on server:
//this is just basic example on how it works for now
io.on("connection", socket => {
socket.on("message", async (message) =>{
db.query("call insertnewMessage(params)",(res)=>{
if(res[0]){
socket.emit("newMessage",{newMessage: res[0]});
}
})
}
}
Second approach is to first with normal http request send message to REST api (not socket server), rest api query database to insert message, returns new message to server and server returns whole new message to client and then client emits new message to socket server? In this approach socket server does not need to query db and I actually know that message is sent ( it is inserted in db).
I read from somewhere that every new connection is actually new instance of socket on server. Wouldn't it made that if I have code from my example, and if every connection is new instance, that if I send new message to server and have for example 5 clients, it would be inserted 5 times in db because every socket connection will query db if they are independent? Am i missing something here and can someone make this clearer please.
I'm going through this right now. I actually went with broadcasting to the user as for my application, availability is more important than persistence.
The user should be able to hear messages in their chat messages even if the database fails.
Thus, I emit/broadcast the message and then save to the database.
I'm using ws webSocket (can't use socket.io) and I'm a bit new to it. I know how it works, but don't fully get a couple of things.
How can I authenticate using jwt? The docs say that using the upgrade event is the correct way to go about it, but when the client connected, the code in the upgrade function didn't run.
What's the correct way to add an event type? In socket.io, one can listen using client.on('eventType', function). What's the webSocket ws equivalent?
When and why would one use paths in webSocket ws?
Update
The update event doesn't get fired on my end. Here's the code I have:
File 1
const server = require('http').createServer(app);
require('./socket/socket')(server);
File 2
module.exports = function(server) {
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
server.on('upgrade', function(request, socket, head) {
console.log('Upgraded');
wss.handleUpgrade(request, socket, head, function(ws) {
wss.emit('connection', ws, request);
});
});
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
});
};
How can I authenticate using jwt? The docs say that using the upgrade event is the correct way to go about it, but when the client connected, the code in the upgrade function didn't run.
Well, there are lots of different choices. You could pass a token as a query parameter of a custom header when first making the connection. You could require that the first data sent over the webSocket is a token and disconnect if such a token does not arrive soon and first. If the client was already authenticated and there's an http cookie that indicates that, you could examine that cookie upon websocket connection.
What's the correct way to add an event type? In socket.io, one can listen using client.on('eventType', function). What's the webSocket ws equivalent?
The socket.io message types (or event types as you call them) are something that socket.io adds on top of webSocket. The webSocket protocol itself does not have such a thing. You send a data packet and that data packet arrives at the other end. All data packets are of the same webSocket "type". If you want to identify your data packets as being a certain type of message, then you have to invent a way of doing that inside your data back. For example, if your data was JSON formatted, you could add a type: "someType" property to event message and then examine that property upon receipt (this is essentially what socket.io does although it does it outside of the socket.io payload).
When and why would one use paths in webSocket ws?
You may have two completely separate parts of code that use a webSocket for different things that have nothing in common and reside in separate modules and it's just more straightforward for each to have its own webSocket connection. And, they may be used by two completely separate types of clients.
How to access the orignal URL when a webSocket client connects to your server is shown here on your server's upgrade event used with the ws library.
You will note in that example that the upgrade event occurs on the http server, not on the websocket server instance.
ws provide auth examples.
These work when a HTTP server first handle the auth requests. Then pass a
HTTP upgrade request to ws rather than ws itself listening on a port. Note
the noServer: true option they include in the setup:
const wss = new WebSocket.Server({ clientTracking: false, noServer: true });
The jwt component will be easier using koa or express in HTTP
first then doing the upgrade in this way. Otherwise you would need to
write a ws message handler to look for some token data and verify it.
The message is the event in ws. You get to write anything more specific on top of the message event, which is a big reason to use socket.io that has done all that for you (including client callback functions, which are super helpful).
Using a URI path in the ws:// or http upgrade request would usually be to connect to a separate ws context on the server side. A simple way to namespace or separate connection into discreet groups.
I want to emit an event to the client when a long fucntion comes to an end.
This will show a hidden div with a link - on the client side.
This is the approach i tested:
//server.js
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
require('./app/routes.js')(app, io);
//routes.js
app.post('/pst', function(req, res) {
var url = req.body.convo;
res.render('processing.ejs');
myAsyncFunction(url).then(result => {
console.log('Async Function completed');
socket.emit('dlReady', { description: 'Your file is ready!'});
//do some other stuff here
}).catch(err => {
console.log(err);
res.render('error.ejs');
})
});
I get this
ERROR: ReferenceError: socket is not defined
If i change the socket.emit() line to this:
io.on('connection', function(socket) {
socket.emit('dlReady', { description: 'Your file is ready!'});
});
Then i don't receive an error, but nothing happens at the client.
This is the client code:
<script>
document.querySelector('.container2').style.display = "none";
var socket = io();
socket.on('dlReady', function(data) { //When you receive dlReady event from socket.io, show the link part
document.querySelector('.container1').style.display = "none";
document.querySelector('.container2').style.display = "block";
});
</script>
This whole concept is likely a bit flawed. Let me state some facts about this environment that you must fully understand before you can follow what needs to happen:
When the browser does a POST, there's an existing page in the browser that issues the post.
If that POST is issued from a form post (not a post from Javascript in the page), then when you send back the response with res.render(), the browser will close down the previous page and render the new page.
Any socket.io connection from the previous page will be closed. If the new page from the res.render() has Javascript in it, when that Javascript runs, it may or may not create a new socket.io connection to your server. In any case, that won't happen until some time AFTER the res.render() is called as the browser has to receive the new page, parse it, then run the Javascript in it which has to then connect socket.io to your server again.
Remember that servers handle lots of clients. They are a one-to-many environment. So, you could easily have hundreds or thousands of clients that all have a socket.io connection to your server. So, your server can never assume there is ONE socket.io connection and sending to that one connection will go to a particular page. The server must keep track of N socket.io connections.
If the server ever wants to emit to a particular page, it has to create a means of figuring out which exact socket.io connect belongs to the page that it is trying to emit to, get that particular socket and call socket.emit() only on that particular socket. The server can never do this by creating some server-wide variable named socket and using that. A multi-user server can never do that.
The usual way to "track" a given client as it returns time after time to a server is by setting a unique cookie when the client first connects to your server. From then on, every connection from that client to your server (until the cookie expires or is somehow deleted by the browser) whether the client is connection for an http request or is making a socket.io connection (which also starts with an http request) will present the cookie and you can then tell which client it is from that cookie.
So, my understanding of your problem is that you'd like to get a form POST from the client, return back to the client a rendered processing.ejs and then sometime later, you'd like to communicate with that rendered page in the client via socket.io. To do that, the following steps must occur.
Whenever the client makes the POST to your server, you must make sure there is a unique cookie sent back to that client. If the cookie already exists, you can leave it. If it does not exist, you must create a new one. This can be done manually, or you can use express-session to do it for you. I'd suggest using express-session because it will make the following steps easier and I will outline steps assuming you are using express-session.
Your processing.ejs page must have Javascript in it that makes a socket.io connection to your server and registers a message listener for your "dlready" message that your server will emit.
You will need a top-level io.on('connection', ...) on your server that puts the socket into the session object. Because the client can connect from multiple tabs, if you don't want that to cause trouble, you probably have to maintain an array of sockets in the session object.
You will need a socket.on('disconnect', ...) handler on your server that can remove a socket from the session object it's been stored in when it disconnects.
In your app.post() handler, when you are ready to send the dlready message, you will have to find the appropriate socket for that browser in the session object for that page and emit to that socket(s). If there are none because the page you rendered has not yet connected, you will have to wait for it to connect (this is tricky to do efficiently).
If the POST request comes in from Javascript in the page rather than from a form post, then things are slightly simpler because the browser won't close the current page and start a new page and thus the current socket.io connection will stay connected. You could still completely change the page visuals using client-side Javascript if you wanted. I would recommend this option.
I am very eager to integrate Socket.io to my node.js project, but I have some confusions on how to properly use socket.io. I have been looking through documentations and tutorials, but I have not been able to understand some concepts on how socket.io works.
The scenario I have in mind is the following:
There are multiple clients C1, C2, ..., Cn
Clients emit request to the server R1,...,Rn
Server receives request, does data processing
When data-processing is complete, Server emits response to clients Rs1, .., Rs2
The confusion I have in this scenario is that, when the server has finished data processing it emits the response in the following way:
// server listens for request from client
socket.on('request_from_client', function(data){
// user data and request_type is stored in the data variable
var user = data.user.id
var action = data.action
// server does data processing
do_some_action(..., function(rData){
// when the processing is completed, the response data is emitted as a response_event
// The problem is here, how to make sure that the response data goes to the right client
socket.emit('response_to_client', rData)
})
})
But here I have NOT defined which client I am sending the response to!
How does socket.io handle this ?
How does socket.io make sure that: response Rs1 is sent to C1 ?
What is making sure that: response Rs1 is not sent to C2 ?
I hope I have well explained my doubts.
The instance of the socket object corresponds to a client connection. So every message you emit from that instance is send to the client that opened that socket connection. Remember that upon the connection event you get (through the onDone callback) the socket connection object. This event triggers everytime a client connects to the socket.io server.
If you want to send a message to all clients you can use
io.sockets.emit("message-to-all-clients")
and if you want to send an event to every client apart the one that emits the event
socket.broadcast.emit("message-to-all-other-clients");
I've ran into a fairly difficult to debug error with my node web server.
Background
I'm creating a node server with socket.io to provide a restful service, connected to mongodb which use web sockets(socket.io) for server-client messages.
Issue
In my node app, I've used an npm package called node-scheduler, in which I do some processing at set times(these are very dynamic times but work fairly well to date).
So I'll set off a job, using node-scheduler and when it ends you can provide a function.
In this function I emit a web socket message, exactly how I emit messages in the rest of the application but my client side never receives the message.
Checking the logs the client disconnections then re connections after the function has completed.
I've debugged a little further, and I send two messages to the client in this function. Only one of them is processed by the client. May be a client issue not a server issue.
Any ideas for solutions or suggestions would greatly be appreciated.
Well generally socket.io is only meant to be used as a "channel". You should have the Client exist as a separate entity in memory or something, and update the socket if and when it reconnects. Otherwise you're just sending to the past (disconnected) sockets.
Using passport you can identify a client as a user.
app.get('/', function(req, res){
// req.user;
});
Using passport.socketio you can get the same user in your socket
io.on('connection', function(socket){
// socket.request.user;
socket.request.user.socket = socket;
// this will be updated with the latest socket in case of a future reconnection
// So now you can be sure that user object will always have the latest socket
nodeScheduler(function(){
carryOutJobs(function callback(){
socket.request.user.socket.emit('done');
// will always emit to the "latest" socket.
});
});
});