I am using Node.js to create a media upload microservice. This service works by taking in the binary data of the upload to a buffer, and then using the S3 npm package to upload to an S3 bucket. I am trying to use the eventEmitter present in that package which shows the amount of data uploaded to S3, and send that back to the client which is doing the uploading (so that they can see upload progress). I am using socket.io for this sending of progress data back to the client.
The problem I am having is that the .emit event in socket.io will send the upload progress data to all connected clients, not just the client which initiated the upload. As I understand it, a socket connects to a default room on 'connection', which is mirrored by the 'id' on the client side. According to the official docs, using socket.to(id).emit() should send the data scoped only to that client, but this is not working for me.
UPDATED Example code:
server.js:
var http = require('http'),
users = require('./data'),
app = require('./app')(users);
var server = http.createServer(app);
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
var io = require('./socket.js').listen(server);
socket.js:
var socketio = require('socket.io');
var socketConnection = exports = module.exports = {};
socketConnection.listen = function listen(app) {
io = socketio.listen(app);
exports.sockets = io.sockets;
io.sockets.on('connection', function (socket) {
socket.join(socket.id);
socket.on('disconnect', function(){
console.log("device "+socket.id+" disconnected");
});
socketConnection.upload = function upload (data) {
socket.to(socket.id).emit('progress', {progress:(data.progressAmount/data.progressTotal)*100});
};
});
return io;
};
s3upload.js:
var config = require('../config/aws.json');
var s3 = require('s3');
var path = require('path');
var fs = require('fs');
var Busboy = require('busboy');
var inspect = require('util').inspect;
var io = require('../socket.js');
...
var S3Upload = exports = module.exports = {};
....
S3Upload.upload = function upload(params) {
// start uploading to uploader
var uploader = client.uploadFile(params);
uploader.on('error', function(err) {
console.error("There was a problem uploading the file to bucket, either the params are incorrect or there is an issue with the connection: ", err.stack);
res.json({responseHTML: "<span>There was a problem uploading the file to bucket, either the params are incorrect or there is an issue with the connection. Please refresh and try again.</span>"});
throw new Error(err);
}),
uploader.on('progress', function() {
io.upload(uploader);
}),
uploader.on('end', function(){
S3Upload.deleteFile(params.localFile);
});
};
When using DEBUG=* node myapp.js, I see the socket.io-parser taking in this information, but it isn't emitting it to the client:
socket.io-parser encoding packet {"type":2,"data":["progress",{"progress":95.79422221709825}],"nsp":"/"} +0ms
socket.io-parser encoded {"type":2,"data":["progress",{"progress":95.79422221709825}],"nsp":"/"} as 2["progress",{"progress":95.79422221709825}] +0ms
However, if I remove the .to portion of this code, it sends the data to the client (albeit to all clients, which will not help at all):
io.sockets.on('connection', function(socket) {
socket.join(socket.id);
socket.emit('progress', {progress: (data.progressAmount/data.progressTotal)*100});
});
DEBUG=* node myapp.js:
socket.io:client writing packet {"type":2,"data":["progress",{"progress":99.93823786632886}],"nsp":"/"} +1ms
socket.io-parser encoding packet {"type":2,"data":["progress",{"progress":99.93823786632886}],"nsp":"/"} +1ms
socket.io-parser encoded {"type":2,"data":["progress",{"progress":99.93823786632886}],"nsp":"/"} as 2["progress",{"progress":99.93823786632886}] +0ms
engine:socket sending packet "message" (2["progress",{"progress":99.93823786632886}]) +0ms
engine:socket flushing buffer to transport +0ms
engine:ws writing "42["progress",{"progress":99.84186540937002}]" +0ms
engine:ws writing "42["progress",{"progress":99.93823786632886}]" +0ms
What am I doing wrong here? Is there a different way to emit events from the server to only specific clients that I am missing?
socket.io rooms are a lightweight data structure. They are simply an array of connections that are associated with that room. You can have as many as you want (within normal memory usage limits).
socket. emit - This method is responsible for sending messages. socket. on - This method is responsible for listening for incoming messages.
The second example of code you posted should work and if it does not, you should post more code.
As I understand it, a socket connects to a default room on 'connection', which is mirrored by the 'id' on the client side. According to the official docs, using socket.to(id).emit() should send the data scoped only to that client, but this is not working for me.
Socket.io is pretty much easier than that. The code below will send a 'hello' message to each client when they connect:
io.sockets.on('connection', function (socket) {
socket.emit('hello');
});
Everytime a new client connects to the socket.io server, it will run the specified callback using that particular socket as a parameter. socket.id
is just an unique code to identify that socket but you don't really need that variable for anything, the code above shows you how to send a message through a particular socket
.
Socket.io also provides you some functions to create namespaces/rooms so you can group connections under some identifier (room name) and be able to broadcast messages to all of them:
io.sockets.on('connection', function (socket) {
// This will be triggered after the client does socket.emit('join','myRoom')
socket.on('join', function (room) {
socket.join(room); // Now this socket will receive all the messages broadcast to 'myRoom'
});
...
Now you should understand socket.join(socket.id)
just does not make sense because no socket will be sharing socket id.
Edit to answer the question with the new code:
You have two problems here, first:
socketConnection.upload = function upload (data) {
socket.to(socket.id).emit('progress', {progress:(data.progressAmount/data.progressTotal)*100});
};
Note in the code above that everything inside io.sockets.on('connection',function (socket) {
will be run each time a clients connect to the server. You are overwriting the function to point it to the socket of the latest user.
The other problem is that you are not linking sockets and s3 operations. Here is a solution merging socket.js
and s3upload.js
in the same file. If you really need to keep them separated you will need to find a different way to link the socket connection to the s3 operation:
var config = require('../config/aws.json');
var s3 = require('s3');
var path = require('path');
var fs = require('fs');
var Busboy = require('busboy');
var inspect = require('util').inspect;
var io = require('socket.io');
var socketConnection = exports = module.exports = {};
var S3Upload = exports = module.exports = {};
io = socketio.listen(app);
exports.sockets = io.sockets;
io.sockets.on('connection', function (socket) {
socket.on('disconnect', function(){
console.log("device "+socket.id+" disconnected");
});
socket.on('upload', function (data) { //The client will trigger the upload sending the data
/*
some code creating the bucket params using data
*/
S3Upload.upload(params,this);
});
});
S3Upload.upload = function upload(params,socket) { // Here we pass the socket so we can answer him back
// start uploading to uploader
var uploader = client.uploadFile(params);
uploader.on('error', function(err) {
console.error("There was a problem uploading the file to bucket, either the params are incorrect or there is an issue with the connection: ", err.stack);
res.json({responseHTML: "<span>There was a problem uploading the file to bucket, either the params are incorrect or there is an issue with the connection. Please refresh and try again.</span>"});
throw new Error(err);
}),
uploader.on('progress', function() {
socket.emit('progress', {progress:(uploader.progressAmount/uploader.progressTotal)*100});
}),
uploader.on('end', function(){
S3Upload.deleteFile(params.localFile);
});
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With