javascript - Emitiendo mensaje de websocket desde las rutas
Estoy tratando de configurar mi servidor con websockets para que cuando actualice algo a través de mis rutas también pueda emitir un mensaje de websocket cuando algo en esa ruta se actualice.
La idea es guardar algo en mi Mongo db cuando alguien accede a la ruta /add-team-member, por ejemplo, y luego emitir un mensaje a todos los que estén conectados vía websocket y sean parte de cualquier sala de websocket que corresponda con ese equipo.
He seguido la documentación de socket.io para configurar mi aplicación de la siguiente manera:
App.js
// there's a lot of code in here which sets what to use on my app but here's the important lines
const app = express();
const routes = require('./routes/index');
const sessionObj = {
secret: process.env.SECRET,
key: process.env.KEY,
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection }),
secret : 'test',
cookie:{_expires : Number(process.env.COOKIETIME)}, // time im ms
}
app.use(session(sessionObj));
app.use(passport.initialize());
app.use(passport.session());
module.exports = {app,sessionObj};
start.js
const mongoose = require('mongoose');
const passportSocketIo = require("passport.socketio");
const cookieParser = require('cookie-parser');
// import environmental variables from our variables.env file
require('dotenv').config({ path: 'variables.env' });
// Connect to our Database and handle an bad connections
mongoose.connect(process.env.DATABASE);
// import mongo db models
require('./models/user');
require('./models/team');
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const server = app.app.listen(app.app.get('port'), () => {
console.log(`Express running → PORT ${server.address().port}`);
});
const io = require('socket.io')(server);
io.set('authorization', passportSocketIo.authorize({
cookieParser: cookieParser,
key: app.sessionObj.key, // the name of the cookie where express/connect stores its session_id
secret: app.sessionObj.secret, // the session_secret to parse the cookie
store: app.sessionObj.store, // we NEED to use a sessionstore. no memorystore please
success: onAuthorizeSuccess, // *optional* callback on success - read more below
fail: onAuthorizeFail, // *optional* callback on fail/error - read more below
}));
function onAuthorizeSuccess(data, accept){}
function onAuthorizeFail(data, message, error, accept){}
io.on('connection', function(client) {
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
Mi problema es que tengo muchas acciones de guardado en Mongo DB que están ocurriendo en mi archivo ./routes/index y me gustaría poder emitir mensajes desde mis rutas en lugar de desde el final de start.js donde está conectado socket.io.
¿Hay alguna manera de que pueda emitir un mensaje de websocket desde mi archivo ./routes/index aunque IO se configure más adelante en start.js?
por ejemplo algo así:
router.get('/add-team-member', (req, res) => {
// some io.emit action here
});
Quizás necesito mover dónde estoy inicializando las cosas de socket.io, pero no he podido encontrar ninguna documentación sobre esto o tal vez ya puedo acceder a socket.io desde las rutas de alguna manera.
¡Gracias y aprecio la ayuda, avísame si algo no está claro!
Respuesta #1
Como se mencionó arriba, io está en tu ámbito global. Si haces
router.get('/add-team-member', (req, res) => {
io.sockets.emit('AddTeamMember');
});
Entonces, cada cliente conectado, si está escuchando ese evento AddTeamMember, ejecutará su función .on asociada en sus respectivos clientes. Esta probablemente sea la solución más fácil, y a menos que esperes una gran oleada de usuarios sin ningún plan de balanceo de carga, esto debería ser adecuado por el momento.
Otra alternativa que puedes considerar: la biblioteca socket.io tiene una funcionalidad de salas donde puedes unirte y emitir usando el propio objeto io https://socket.io/docs/rooms-and-namespaces/ si tienes habilidad para esto, se vería algo así:
io.sockets.in('yourroom').broadcast('AddTeamMember');
Esto esencialmente haría lo mismo que lo de arriba, solo que en lugar de transmitir a todos los clientes, solo se transmitiría a aquellos que son exclusivos de esa sala. Básicamente tendrías que encontrar una manera de que el socket de ese usuario esté en la sala //antes// de que hagan la solicitud get, o en otras palabras, hacerlos exclusivos. De esa manera puedes reducir la cantidad de carga que tu servidor tiene que manejar cada vez que se realiza esa solicitud de ruta.
Por último, si ninguna de las opciones anteriores funciona para ti, y simplemente tienes que enviar a ese cliente en particular cuando él lo inicie, entonces se va a complicar, porque tienes que tener algún tipo de identificación de esa persona, y dado que no tienes referencia, tendrías que almacenar todos tus sockets al conectarse, y luego hacer una comparación. No recomiendo completamente algo así, porque bueno, nunca lo he probado, y no sé qué tipo de repercusiones podrían ocurrir, pero aquí está una idea general que tuve:
app.set('trust proxy', true)
var SOCKETS = []
io.on('connection', function(client) {
SOCKETS.push(client);
client.on('join', function(data) {
client.emit('messages',"server socket response!!");
});
client.on('getmessage', function(data) {
client.emit('messages',data);
});
});
router.get('/add-team-member', (req, res) => {
for (let i=0; i< SOCKETS.length; i++){
if(SOCKETS[i].request.connection.remoteAddress == req.ip)
SOCKETS[i].emit('AddTeamMember');
}
});
Ten en cuenta que, si tomas esta ruta, vas a necesitar mantener ese arreglo cuando los usuarios se desconecten, y si estás manejando sesiones, eso se va a poner complicado muy, muy rápido.
Buena suerte, háganos saber sus resultados.
Respuesta #2
Sí, es posible, solo tienes que adjuntar la instancia de socket.io en cuanto recibas una solicitud en tu servidor. Mirando tu archivo start.js, solo tienes que reemplazar tus funciones por:
// Start our app!
const app = require('./app');
app.app.set('port', process.env.PORT || 7777);
const io = require('socket.io')(app.app);
const server = app.app.listen(app.app.get('port'), () => {
server.on('request', function(request, response){
request.io = io;
}
console.log(`Express running → PORT ${server.address().port}`);
});
ahora, cuando recibas un evento por el que quieras emitir algún mensaje a los clientes, puedes usar tu instancia de io desde el objeto de la solicitud.
router.get('/add-team-member', (req, res) => {
req.io.sockets.emit('addteammember', {member: 6});
//as you are doing a broadcast you just need broadcast msg
....
res.status(200)
res.end()
});
Haciendo eso también pude integrarme con un marco de pruebas como Mocha, y también probar los eventos emitidos...
Hice algunas integraciones así, y en mi experiencia lo último que había que hacer era emitir el mensaje a las instancias en el socket.
Como buena práctica, al comienzo de las funciones middleware que tenía estaba realizando la validación de datos, la desinfección de datos y la limpieza de datos. Aquí está mi ejemplo de trabajo:
var app = require('../app');
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(client) {
client.emit('connected');
client.on('disconnect', function() {
console.log('disconnected', client.id);
});
});
server.on('request', function(request, response) {
request.io = io;
});
pg.initialize(app.config.DATABASEURL, function(err){
if(err){
throw err;
}
app.set('port', process.env.PORT || 3000);
var server1 = server.listen(app.get('port'), function(){
var host = 'localhost';
var port = server1.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
});
