encapsulate a socket.io instance in nodejs - node.js

I am trying encapsule a socket.io instance in a module. I did this but it looks a bit messy because i have to inject some dependencies for authentication of the socket transport from express / passport.
My problem is i want to access the socket instance outside of the module like socket.on("newDevice", function (data) {});
The socket instance which i get by the connection event is inside the function and it even may not exist on creation because no connection is established. This simply looks kind of wrong to me. I dont want to inject more and more depencies just because i need them inside of the function scope.
I thought about doing the sio.on('connection', function(socket) {}); outside of the module. And maybe i could do it twice, first inside of the module and then outside but i would create two listeners i guess.
Is there any good practice or pattern how to do this properly?
var io = require('socket.io');
var socket = function (server, sessionStore, cookieParser, authentication) {
var sio = io.listen(server);
// Configure socket.io
sio.configure(function () {
// Authorize the socket.io request
sio.set('authorization', function (data, accept) {
// Authorization is done here
});
});
sio.on('connection', function(socket) {
var lastActionTime = new Date();
// Get the userId from the session
var session = socket.handshake.session;
var userId = session.passport.user;
var sessionID = socket.handshake.sessionID;
var userdata = null;
// Deserialize user by the userId
authentication.deserializeUser(userId, function(err, user) {
// get the userdata
});
socket.on('disconnect', function() {
});
socket.on('brightnessChange', function(data) {
// TODO Do something here device control
// Broadcast to other devices
this.broadcast.emit('brightnessChange', data);
});
});
return sio;
};
module.exports = socket;

I would suggest, the below, flexibility and scales well. I've tried both ways and resulted in using multiple connection events.
I thought about doing the sio.on('connection', function(socket) {}); outside of the module. And maybe i could do it twice, first inside of the module and then outside but i would create
two listeners i guess.

Related

Is it possible to open two sockets on server side

I am building an app using nodeJS along with socket io .
I have a server.js file in which i did the following :
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.on('connection', function(socket){
console.log("a user is connected");
});
Also i have another file "api.js" in which i have the following function :
filter.watch(function(error, result){
if (!error){
var newblock = new Block(web3.eth.getBlock(result));
newblock.save(function (err) {
if (err) return handleError(err);
// saved!
})
}
});
Im trying to send the object "newblock" (which is changing constantly) from the file api.js to server.js with socket io . I tried doing " socket.emit('fromApi_toserver',newblock)" in api.js but it's not working... Does anyone have any ideas ?
There appear to be several issues here.
First, if you want to send to all connected clients, then you would use io.emit(), not socket.emit().
Second, since io is defined in your app.js file, if you want access to it in your api.js file, then you need to specifically make it available to that file. There are a number of ways to do that, but the most common is to pass it to that file with a module constructor when the api.js module is loaded. Here's a way to do that:
app.js
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.on('connection', function(socket){
console.log("a user is connected");
});
// pass io variable to app.js when loaded
require('api.js')(io);
api.js
// define module constructor
let io;
modules.exports = function(socketio) {
// save io instance for later use
io = socketio;
}
filter.watch(function(error, result){
if (!error){
var newblock = new Block(web3.eth.getBlock(result));
newblock.save(function (err) {
if (err) return handleError(err);
// send JSON stringified version of newblock to all connected clients
io.emit(newblock);
});
}
});
Third, you can't send live objects to clients. Data for socket.io is serialized into JSON strings before sending to clients and the client-side socket.io will then deserialize the JSON back into an object. But, it will just be a plain object on the client end, not any specific object type. If you need the data to be a specific object type (such as a client-side Block object), then you must take the data you receive and use it to create your own new client-side Block object.
Fourth, your questions asked about a second socket, but you don't need another socket. You can use the io instance to send to all currently connected clients with io.emit().

How to use socket.io with the latest mean.io?

I have fetched a copy of the latest Mean.io and noted quite a number of changes compared to the previous version I have been working with before. Now, what I am doing is creating a very basic chat application that uses socket.io with rooms. Following the basic setup in the Socket documentation I have to implement the following:
var app = require('express')()
, server = require('http').createServer(app)
, io = require('socket.io').listen(server);
server.listen(80);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
Where would I define the basic socket room setup?
socket.set("log level", 1);
var people = {};
var rooms = {};
var clients = [];
You can set the socket.io to listen on your server on
/server/config/system/bootstrap.js
Require the socket.io module
var express = require('express'),
appPath = process.cwd(),
io = require('socket.io');
Now set the socket.io to listen on your app
// Express settings
var app = express();
require(appPath + '/server/config/express')(app, passport, db);
io = io(app.listen(3000));
return io;
Then you need to inject the socket.io object into your app on bootstrapDependencies() function.
function bootstrapDependencies() {
...
// Register socket.io dependency
mean.register('io', function() {
return io;
});
}
Mean.uses this project for its dependency injection
https://www.npmjs.org/package/dependable
Finally you need to configure your app to listen on every socket connections
probably you want to do these on your main app's router at
/server/routes/index.js
Sample connection handler
var io = require('meanio').io;
io.on('connection', function (socket) {
// emit data to the clients
socket.emit('news', { hello: 'world' });
// event listeners
socket.on('my other event', function (data) {
// call your controller function here
Controller.action(data);
});
});
And more importantly, don't forget to setup socket.io on the client side.
// on '/server/views/includes/foot.html'
<script src='/socket.io/socket.io.js'></script>
<script>
var socket = io();
</script>
I've just responded to another SO post (Mean.io framwork with socket.io).
Note: I'm using mean.io v0.5.26 and socket.io v1.1.0.
Pasting my answer again, here.
I also faced the same issue and took me about a week to finally get it right. I'll try to explain what I did:
app.js
In this file, I just invoke the code that creates and sets up a socket.io object for me, which is then passed to the routes module.
'use strict';
/*
* Defining the Package
*/
var Module = require('meanio').Module;
var MeanSocket = new Module('chat');
/*
* All MEAN packages require registration
* Dependency injection is used to define required modules
*/
MeanSocket.register(function(app, http) {
var io = require('./server/config/socketio')(http);
//We enable routing. By default the Package Object is passed to the routes
MeanSocket.routes(io);
return MeanSocket;
});
server/config/socketio.js
This file simply configures the socket.io object. Please note that I had to upgrade meanio module to version 0.5.26 for this work, as http object (express server) is not available in older meanio versions. Moreover, in case you want to use ssl, you can inject https instead of http.
'use strict';
var config = require('meanio').loadConfig(),
cookie = require('cookie'),
cookieParser = require('cookie-parser'),
socketio = require('socket.io');
module.exports = function(http) {
var io = socketio.listen(http);
io.use(function(socket, next) {
var data = socket.request;
if (!data.headers.cookie) {
return next(new Error('No cookie transmitted.'));
}
var parsedCookie = cookie.parse(data.headers.cookie);
var sessionID = parsedCookie[config.sessionName];
var parsedSessionID = cookieParser.signedCookie(parsedCookie[config.sessionName], config.sessionSecret);
if (sessionID === parsedSessionID) {
return next(new Error('Cookie is invalid.'));
}
next();
});
return io;
};
routes/chat.js
Finally, use the routes file to define the socket events, etc.
'use strict';
// The Package is passed automatically as first parameter
module.exports = function(MeanSocket, io) {
io.on('connection', function(socket) {
console.log('Client Connected');
socket.on('authenticate', function(data, callback) {
});
});
};
Hope this helps!
The latest update v0.4.0 requires another strategy to get socket.io setup. I'm currently in discussion with one of the project contributors to validate my solution. I'll make sure to update my response once I'm 100% sure.
The meanio package is now where the bootstrap functionality is located, as well, where express setup is being called from.
Looks like the mean.io guys have recently released an official Socket.io implementation that integrates directly with their stack. Check it out on Github.

Using sockets outside their dedicated 'connection' scope

I'm making a node.js app with sockets.io.
The logic of my app needs to communicate to client through their respective sockets, but the problem with sockets is they're in their own "domain" of code.
var io = require('socket.io').listen(80);
io.sockets.on('connection', function (socket) {
// socket object is only available here
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
See?
I tried to export this socket object out
var Sockets = []
io.sockets.on('connection', function (socket) {
Sockets[socket.handshake.user.id] = socket;
});
Now it's available outside
function myLogic(userid) {
Sockets[userid].emit('free!')
}
But I'm facing weird bugs because it's probably not supposed to be used this way... like new connections make new socket objects but previous ones still exist somewhere in memory and they still react when their .on('..' gets fired...
What is the correct way to use sockets outside of their respective io.sockets.on('connection', function(socket){} scope?
Answering your question: if you want to use sockets outside of their respective "io.sockets.on('connection', function(socket){} scope", you have to access them through the io object --> io.sockets.socket(socketId), where socketId is stored somewhere.
Make a setup function for your module and pass it the initialized socket thing. Ie. something like this from main.js:
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var myModule = require('./myModule').setup(io);
Then in your myModule.js save a reference to the io object:
var localIo;
exports.setup = function(io) {
localIo = io;
};
// Then after in your other code....
function myLocalFunction(myData) {
localIo.sockets.volatile.emit('myevent', {data: myData});
};

Scoping of socket.io server side and client side objects in express routes

In my app.js I have
var app = express();
var serv = http.createServer(app);
var io = require('socket.io').listen(serv);
io.sockets.on('connection', function(socket) {
//some code here
}
var SessionSockets = require('session.socket.io'),
sessionSockets = new SessionSockets(io, express_store, cookieParser);
sessionSockets.on('connection', function (err, socket, session) {
//set up some socket handlers here for the specific client that are
//only called when a client does a socket.emit.
//These handlers have access to io, sessionSockets, socket, session objects.
}
How can the express routes access a particular client's socket reference after processing a post/get which is not triggered by a client socket.emit but triggered by a client post/get. What is the best way to scope the socket.io server(io/sessionSockets)/client(socket) objects in routes so that I can get this client's socket reference easily?
These three steps helped me to the solve the problem. This identifies tabs also uniquely as that was one of my requirements.
On connection, join using socket.id and then send the socket.id back to the client using
io.sockets.on('connection', function(socket) {
socket.join(socket.id);
socket.emit('server_socket_id', {socket_id : socket.id});
}
Client receives the emit event using
socket.on('server_socket_id', function(data){
//assign some global here which can be sent back to the server whenever required.
server_socket_id = data.socket_id;
});
In app.js I fetch the corresponding socket like this and pass it on to the routes.
app.post('/update', function(req, res){
var socket_id = req.body.socket_id;
route.update(req, res, io.sockets.in(socket_id).sockets[socket_id]);
});
The best way to do this is to use the socket.io authorization setting, although the module session.socket.io was created specifically for that purpose. Each time a socket establishes a connection, there is handshake data that is stored (although I've heard that flashsockets won't pass the browser cookies). This is what it looks like (and is similarly written in the module you're using):
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
//error object, then boolean that allows/denies the auth
callback(null, true);
});
});
What you could do from here is parse the cookie, then store a reference to that socket by the cookie name. So you would add this to the authorization setting:
var data = handshakeData;
if (data.headers.cookie) {
//note that this is done differently if using signed cookies
data.cookie = parseCookie(data.headers.cookie);
data.sessionID = data.cookie['express.sid'];
}
Then, when you listen on connections, store the client by session identifier:
var clients = {};
io.sockets.on('connection', function(socket) {
//store the reference based on session ID
clients[socket.handshake.sessionID] = socket;
});
And when you receive an HTTP request in Express, you can fetch it like this:
app.get('/', function(req, res) {
//I've currently forgotten how to get session ID from request,
//will go find after returning from school
var socket = clients[sessionID];
});

Working with Routes in express js and socket.io and maybe node in general

I am trying to write a multi channel application in socket.io. The channel you are in should be defined by the url you are on. If I do the joining part in the app.js with permanent values everything works. As soon as I change it so that the route for route.page does the joining I get the error, that sockets is not available in the context. What would be the correct way so that I can dynamically join the channel?
/app.js
var io = socketio.listen(app);
require('./io')(io);
io.sockets.on('connection', function (socket) {
socket.on('debug', function (message) {
socket.get('channel', function (err, name) {
socket.in(name).broadcast.emit('debug', message);
});
});
});
/io.js
var socketio = function (io) {
if (!io) return socketio._io;
socketio._io = io;
}
module.exports = socketio;
/routes/index.js
var io = require('../io')();
exports.page = function(req, res){
var channel = req.params.id;
res.render('page', { title: 'PAGE', channel: channel });
io.sockets.on('connection', function (socket) {
socket.join(channel);
socket.set('channel', channel );
});
};
The easiest way I've found to do multiple channels is off of different URLs.
For example I have the client do the following:
io.connect('/game/1')
io.connect('/system')
and on the server I have
io.of('/game/1').on('connect' function(socket) {...})
io.of('/system').on('connect' function(socket) {...})
It looks like I'm connecting twice here, but socket.io is smart enough to use a single websocket for this connection (at least it says so in the how-to-use).

Resources