I would like to use socket.io to keep track of daily active users of an application. My socket connection looks like this:
let visitorData = {};
io.on('connection', (socket) => {
socket.on('user', (data) => {
visitorData[socket.id] = data;
})
socket.on('disconnect', () => {
delete visitorData[socket.id]
})
})
I would like to be able to incorporate existing Node/Express routes and the data received to socket connections. In particular, how would I handle a POST to a /login endpoint to associate the login username to the socket id connection.
For example:
app.get('/login', (req, res, next) => {
const someSocket = /* socket */
const someSocketId = /* socket id */;
visitorData[someSocketId] = req.body.username;
someSocket.emit('user', visitorData);
})
Is there a way I can incorporate socket.io into Node/Express routes to (a) associate the user with a socket.id and (b) emit information from a request/response body?
I set up something just like this using passport socket.io. It creates middleware that allows you to access users from sockets. Here is some of my implementation:
app.js:
import GameSocket from './lib/game-socket';
var app = express();
GameSocket(app, 3700);
lib/game-socket.js:
import Server from 'socket.io';
import passportSocketIo from 'passport.socketio';
import cookieParser from 'cookie-parser';
// Configured redisStore for authentication
import {redisStore} from '../lib/session';
// Configured passport strategy for authentication
import passport from '../lib/passport';
export default function GameSocket(app, port) {
// Setup socket
var server = new Server();
var io = server.listen(app.listen(port));
// Setup authentication
io.use(passportSocketIo.authorize({
key: 'connect.sid',
secret: 'your_secret',
store: redisStore,
passport: passport,
cookieParser: cookieParser,
success: (data, accept) => accept(null, true),
failure: (data, message, err, accept) => {
console.log(err);
if(err) throw new Error(message);
}
}));
// On connection listener
io.sockets.on('connection', function (socket) {
// This is an example of getting user info from the socket
console.log('Success: New connection with: ', socket.request.user.username);
// Add your socket events here
});
return io;
}
In order to use this you need to use a session store. I personally used a redis session store, connect-redis. This is configured in lib/session.js.
Related
I just set up SocketIO in my PHP project. I am completly new to websockets at all so bear with me.
I am defining the socketIO variable globally
let socketIO = io("http://localhost:3000");
When people are logging in to my application, they are connected to it with their ID comming from the database. The login script just gives back true which redirects the user in very simplified terms:
// get component
$.get(url, data, (data) => {
if (data.status) {
// connect with Node JS server
socketIO.emit("connected", data.user_id);
// redirect
load_new_page("/users/" + data.user_id);
}
});
My concern here now is that people could just go and change the data.user_id to anything they want and receive what ever the chosen id would receive.
My server.js:
// initialize express server
var express = require("express");
var app = express();
// create http server from express instance
var http = require("http").createServer(app);
// include socket IO
var socketIO = require("socket.io")(http, {
cors: {
origin: ["http://localhost"],
},
});
// start the HTTP server at port 3000
http.listen(process.env.PORT || 3000, function () {
console.log("Server started running...");
// an array to save all connected users IDs
var users = [];
// called when the io() is called from client
socketIO.on("connection", function (socket) {
// called manually from client to connect the user with server
socket.on("connected", function (id) {
users[id] = socket.id;
});
});
});
How can I prevent something like this?
I'd like to know how to work with connectivity to a database in MEAN stack application. In particular, when should I create a connection to a database and when should I destroy a connection to a database. Should I create and destroy a connection on every new HTTP request or should I store a once created connection and use it for any subsequent requests as long as possible. I use Mongoose as a modeling tool.
Here is an example.
This is my routes.js file with a route /index. A request to this route should fetch some date from MongoDb database. It bothers me how I connect and disconnect to a database now. Yes, I connect and disconnect to a database exactly as written in Mongoose docs, but it it the right way to do it in a serious production environment?
var express = require('express');
var router = express.Router();
var config = require('./db-config');
// I create a Mongoose instance as a module object,
// as opposite to create it in every request handler function below.
var mongoose = require('mongoose');
var productSchema = require('../db/productSchema'); // model schema is also a module-wide object
// And here is a request handler function.
// It is called on every request as a brand new.
// I create and destroy a database connection inside this request handler
router.get('/index', function(req, res, next) {
// I connect to a database on every request.
// Do I need to do it here in a request handler?
// May I do it outside of this request handler on a module-wide level?
mongoose.connect('mongodb://my_database');
// I create a new connection here in a request handler.
// So it lives only during this request handler run.
// Is this the right way? May I do it outside of this request handler
// on a module-wide level and somehow keep this connection and use it
// in every subsequent requests to this or any other route in the app?
var db = mongoose.connection;
db.on('connecting', function() {
console.log('connecting');
});
db.on('connected', function() {
console.log('connected');
});
db.on('open', function() {
console.log('open');
});
db.on('error', console.error.bind(console, 'connection error'));
db.once('open', function(cb) {
var Product = mongoose.model('Product', productSchema);
Product.find({category: "books"}, function(err, prods) {
if (err) return console.error(err);
// I close a connection here in a callback.
// As soon as successfully fetched the data.
// Do I need to close it after every request?
// What is the right place and time to do it?
db.close(disconnect);
res.json(prods);
});
});
})
Found some good answers:
https://softwareengineering.stackexchange.com/questions/142065/creating-database-connections-do-it-once-or-for-each-query
What are best practices on managing database connections in .NET?
Its best practice to have your db connection in a separate module (db.js)
var mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/dbname', function(){
console.log('mongodb connected')
})
module.exports = mongoose
Each model should have a separate module that takes in the db connection (post.js)
var db = require('../db.js')
var Post = db.model('Post', {
username: {type: String, required: true},
body: {type: String, required: true},
date: { type: Date, required: true, default: Date.now }
})
module.exports = Post
Then whenever you need to use that data set just require it and make calls
var Post = require('/models/post')
Post.save()
Post.find()
This is an opinion based question I'd say. What I use for my app is
app.get('/', function (req, res) {
res.sendfile('index.html');
});
mongoose.connect('mongodb://localhost:27017/my_db');
This way I create a connection once rather than on every HTTP request. Your way should work fine but it seems you will have to connect and disconnect the db to your app way too many times specially when the app is in development.
You want your connection to act like a singleton so as mentioned in the answer above it makes sense to do it outside of, and preferable before your routes:
var compression = require('compression');
var express = require('express');
var app = express();
var port = process.env.PORT || 8080;
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
...
app.use(compression());
// db
var mongoose = require('mongoose');
var configDB = require('./config/database.js');
mongoose.connect(configDB.url); // connect to our database
config/database.js:
module.exports = {
'url' : '#localhost:27017/dbname'
};
This is my solution :
import express from 'express';
import mongoose from 'mongoose';
import { name } from '../package.json';
import * as localconfig from './local-config';
import debug from 'debug';
debug(name);
const app = express();
const port = process.env.PORT || 3000;
const mongoUrl = localconfig.credentials.MONGO_URL;
import usersRoutes from './routes/users/user-routes';
app.use('/v1/users', usersRoutes);
mongoose.connect(mongoUrl)
.then(() => {
debug('DB connection successful');
app.listen(port, '0.0.0.0', () => {
debug(`Running on port ${port}`);
});
})
.catch((err) => {
debug(err);
});
You should first check weather the connection is successful or not and only then listen to a certain port. This is my app.js file where all the routes are loaded, so you do not have to call the db connection in all your files. You have a single config file where all the config is done. Your router file user-routes.js will look something similar to this:
import express from 'express';
import User from '../models/user'
const router = express.Router();
router.get('/', (req, res, next) => {
User.find()
.then((response) => res.json(response))
.catch((err) => next(err));
});
module.exports = router;
I've looked at several answers on here, but I think they are referring to older versions of socket.io as their solutions have not worked for me. I'm getting the data back in the browser with
io.emit('update', data)
but it's emitting to all clients so the same data is showing up in multiple windows when I go to the same URL. Do I have to store the client id somewhere upon connection or can I just get it back before emitting? Please be specific. I tried a few other solutions from SO, but I got a lot of ReferenceError 'id' is not defined or sockets instead of socket.
Server set up and connection:
var app = express();
var server = require('http').createServer(app)
var io = require('socket.io')(server)
app.get('/aPath', function (req, res, next) {
res.writeHead(200)
var data = {
"val1": req.query.val1,
"val2": req.query.val2,
"val3": req.query.val3,
"val4": req.query.val4,
"val5": req.query.val5,
"val6": req.query.val6,
}
/*console.log(io.sockets.id)*/
//io.to(io.sockets.id).emit('update', data)
//io.sockets.socket(id).emit('update', data)
io.emit('update', data)
res.end("OK")
})
io.on('connection', function (socket) {
console.log('websocket user connected')
});
Since a third-party client is sending the info via a restful interface, you will need to include reference data for the client in that request in the form of a header or query string.
I suggest using Redis to store the active socket users for quick reference. This will allow you to have multiple applications in deployment that use a singular redis instance to keep the data in sync. You can also do the same in app memory, but that just doesn't scale well.
first, you need to use middleware to authenticate user and cache the socket.id
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var redis = require('redis');
io.use(function(socket, next){
// validate user
// cache user with socket.id
var userId = validatedUser;
socket.handshake.userId = userId;
redis.set(userId, socket.id, function (err, res) {
next()
});
});
next handle all socket communication
io.on('connection', function (socket) {
console.log('websocket user connected');
//next handle all socket communication
socket.on('endpoint', function (payload) {
//do stuff
socket.emit('endpoint.response', {/*data*/});
});
//Then remove socket.id from cache
socket.on('disconnect', function (payload) {
//remove user.id from cache
redis.del(socket.handshake.userId, function (err, res) {
console.log('user with %s disconnected', socket.id);
});
});
});
Handle third party event.
app.get('/aPath', function (req, res, next) {
// get user from third party
var userId = req.query.userId
var data = {
"val1": req.query.val1,
"val2": req.query.val2,
"val3": req.query.val3,
"val4": req.query.val4,
"val5": req.query.val5,
"val6": req.query.val6,
};
// get cached socketId from userId
redis.get(userId, function (err, socketId) {
// return ok to third party;
res.status(200).send("OK");
only emit if socketid still exists
if (err || !socketId) return;
// now emit to user
io.to(socketId).emit('update', data):
});
});
In a nodejs-express app, in the server.js file I set the socket-io connection.
It works fine doing something like this
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
io.sockets.on('connection', function (socket) {
// how can I save globally this 'socket'?
});
I would like to save this 'socket' globally in the server.js file and so, be able to use it everywhere in the project. Like this:
app.get('/on', function(req, res){
socket.on('test', function (data) {
console.log(data);
});
});
app.get('/emit', function(req, res){
socket.emit('test', "ciao");
});
I red there is a way to do it saving the 'socket' connection in session. My sessions settings:
app.configure(function(){
// ....
app.use(express.cookieParser(SITE_SECRET));
app.use(express.session({
secret :
,store : new MongoStore({
mongoose_connection : mongoose.connection
,db: mongoose.connections[0].db
})
,cookie: { maxAge: new Date(Date.now() + (1000*60*60*24*30*12)) }
}));
...
});
What is a good way to do it?
And also, after have made this saving, how to use socket.on() and socket.emit() loading the first page if the connection is not opened yet?
What you might have heard is not saving a socket into a session, but referencing sockets by their session cookie, which is passed to the server during the socket authorization process. During authorization, this is an example of the type of object that is passed to the server:
{
headers: req.headers, // <Object> the headers of the request
time: (new Date) +'', // <String> date time of the connection
address: socket.address(), // <Object> remoteAddress and remotePort object
xdomain: !!headers.origin, // <Boolean> was it a cross domain request?
secure: socket.secure, // <Boolean> https connection
issued: +date, // <Number> EPOCH of when the handshake was created
url: request.url, // <String> the entrance path of the request
query: data.query // <Object> the result of url.parse().query or a empty object
}
What we're interested in is the headers property, where we can find the session cookies of a connecting socket. We then parse the cookies during authorization:
// pass same objects from Express to Socket.IO so they match
var parseCookie = express.cookieParser(SITE_SECRET);
var store = new MongoStore({
mongoose_connection: mongoose.connection,
db: mongoose.connections[0].db
});
io.configure(function() {
io.set('authorization', function(handshake, callback) {
if (handshake.headers.cookie) {
parseCookie(handshake, null, function(err) {
// we used the signedCookies property since we have a secret
// save the session ID to the socket object, we can access it later
handshake.sessionID = handshake.signedCookies['connect.sid'];
store.get(handshake.sessionID, function(err, session) {
// we have the same Express session, reference it
socket.session = session;
callback(null, true);
});
});
} else {
// they client has no session yet, don't let them connect
callback('No session.', false);
}
});
});
app.use(parseCookie);
app.use(express.session({
secret: SITE_SECRET,
store: store,
cookie: {maxAge: new Date(Date.now() + (1000*60*60*24*30*12))}
}));
Then once we have saved the session ID, we can use the typical connection events:
var server = require('http').createServer(app)
var io = require('socket.io').listen(server);
var clients = {};
io.sockets.on('connection', function (socket) {
// save to a global object
var session = socket.handshake.sessionID;
clients[session] = socket;
socket.on('disconnect', function() {
delete clients[session];
});
});
Then we have a global reference by cookie signature. We can then access the socket like this:
app.get('/path', function(req, res) {
var socket = clients[req.sessionID];
socket.emit('Socket client accessed route.');
});
Keep in mind you might have to add some logic into your global logic for clients with multiple tabs, which would result in two sockets with the same authorization cookie.
As for your question about using socket.on() and socket.emit(), you can't use that before the connection has been established because the socket itself does not exist. If you want to send a message to all connected clients, then you should just use the global io.sockets object. It would then be more like io.sockets.emit().
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];
});