I have the following structure of my project in node
project structure
in a app.js, let's say i have a app.js like this
'use strict'
const express = require('express');
const realtime = require('./controllers/realtime');
const app = express();
const server = require('http').Server(app);
var sessionMiddleware = session(sessionConfig);
realtime(server,sessionMiddleware);
in a controllers/realtime.js
"use strict";
const config = require("../config");
module.exports = (server,sessionMiddleware) => {
const io = require('socket.io')(server);
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
io.on('connection', (socket) => {
socket.on('statusConnetion',(data)=>{
console.log(data)
});
socket.on('disconnect', function () {
console.log(socket.id,"Un socket se desconecto");
});
console.log(`New socket connection: ${socket.id}`);
});
}
in controllers/cargos.js
const express = require('express');
const router = express.Router();
let cargos = {};
cargos.update = (req, res, next) =>{
//How can I use sockets here?
}
module.exports = cargos;
How can I use sockets in the file cargos.js and other controllers?
You can export not only the function that initiates the server, but a class that handles all the socket.io connection and functionality. This class will be a singleton and will have functions that uses the connection, and can be usable in the different modules.
example:
app.js:
'use strict'
const express = require('express');
const realtime = require('./controllers/realtime');
const app = express();
const server = require('http').Server(app);
var sessionMiddleware = session(sessionConfig);
realtime.connect(server,sessionMiddleware);
realtime.js:
let connection = null;
class Realtime {
constructor() {
this._socket = null;
}
connect(server,sessionMiddleware) {
const io = require('socket.io')(server);
io.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
io.on('connection', (socket) => {
this._socket = socket;
this._socket.on('statusConnetion',(data)=>{
console.log(data)
});
this._socket.on('disconnect', function () {
console.log(socket.id,"Un socket se desconecto");
});
console.log(`New socket connection: ${socket.id}`);
});
}
sendEvent(event, data) {
this._socket.emit(event, data);
}
registerEvent(event, handler) {
this._socket.on(event, handler);
}
static init(server,sessionMiddleware) {
if(!connection) {
connection = new Realtime();
connection.connect(server,sessionMiddleware);
}
}
static getConnection() {
if(!connection) {
throw new Error("no active connection");
}
return connection;
}
}
module.exports = {
connect: Realtime.init,
connection: Realtime.getConnection
}
cargos.js:
const express = require('express');
const router = express.Router();
let cargos = {};
cargos.update = (req, res, next) =>{
const connection = require('./controllers/realtime').connection();
connection.sendEvent("update", {params: req.params});
}
module.exports = cargos;
Use separate endpoints for each controller like:
var io = require('socket.io').listen(80);
var user_controller = require('./controllers/user');
var chat_controller = require('./controllers/chat');
var user= io
.of('/user')
.on('connection', function (socket) {
user_controller.userData(user,socket);
});
var chat = io
.of('/chat')
.on('connection', function (socket) {
chat_controller.chatData(chat,socket);
});
And in controller simply export the module like this:
module.exports.chatData = function(endpoint,socket){
// this function now expects an endpoint as argument
socket.on('chat',function(newsreel){
// as is proper, protocol logic like
// this belongs in a controller:
endpoint.emit(chatreel); // broadcast chat to everyone subscribing
// to our endpoint/namespace
});
}
Same way you can use socket.io in multiple controllers by separating endpoints
Related
I need access to the io module in multiple files in my project so I can emit some messages. So for that I decided to create a singleton class to handle the io object so i can import the class anywhere and use the io object.
When I comment out all the middleware, it works. But I don't want that, I want the connection to be only made when the user is logged in.
here's my socketClass.js
const session = require('express-session');
const { Server } = require("socket.io");
const passport = require('passport');
class SocketManager{
constructor() {
this.io = null;
}
start(server) {
this.io = new Server(server);
// convert a connect middleware to a Socket.IO middleware
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
const sessionMiddleware = session({
secret: 'MySecrectKey',
resave: true,
saveUninitialized: true
});
this.io.use(wrap(sessionMiddleware));
this.io.use(wrap(passport.initialize()));
this.io.use(wrap(passport.session()));
this.io.use((socket, next) => {
if (socket.request.user) {
next();
} else {
next(new Error('unauthorized'))
}
});
}
get() {
return this.io;
}
}
module.exports = new SocketManager();
Here's my app.js
...
const SocketManager = require('./socketClass');
const server = http.createServer(app);
SocketManager.start(server);
const io = SocketManager.get();
io.on('connect', async (socket) => {
console.log(`new connection ${socket.id}`);
socket.on('whoami', (cb) => {
cb(socket.request.user ? socket.request.user.username : '');
});
});
The io.on('connect') wont console.log anything on a new connection. What is the problem here?
Your problem may be because of "async (socket)". Here is how I do it as simply as possible !
I created a separate file (module):
socket.js:
let io;
module.exports = {
init: httpServer => {
io = require('socket.io')(httpServer);
return io;
},
getIO: () => {
if (!io) {
throw new Error('Socket.io not initialized!');
}
return io;
}
};
Then, in my app.js:
const app = express();
const server = app.listen(8080);
const io = require('./socket').init(server);
io.on('connection', socket => {
console.log('Client connected');
});
In my express app, generated with express-generator, I want to use the io of socket.io in some other controller files to emit data to client sockets. My approach is below, but I get the following error with that. It would be a great favor if someone can help me in this case.
(node:11376) UnhandledPromiseRejectionWarning: TypeError: io.emit is not a function
at F:\backend\controllers\LessonController.js:169:9
In the express apps, generated by express-generator, the process of creating the server happens in the /bin/www.js. I tried importing the io instance from there and use it in some other file, but it didn't work.
bin/www.js
#!/usr/bin/env node
var app = require('../app');
var debug = require('debug')('backend:server');
var http = require('http');
var port = normalizePort(process.env.PORT || '8080');
app.set('port', port);
var server = http.createServer(app);
const io = require('socket.io')(server);
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
// several other functions are omitted for brevity
module.exports = io;
LessonController.js
const Lesson = require('../models/Lesson');
const Course = require('../models/Course');
const User = require('../models/User');
const io = require('../bin/www')
var _ = require('lodash');
module.exports = {
addComment: async (lessonId, userId, content, callback) => {
const newData = {
comments: {
user: userId,
content: content,
},
};
Lesson.findOneAndUpdate({ _id: lessonId }, { $push: newData }, {new: true})
.exec()
.then(
function (data) {
if (data) {
io.emit("comment_"+lessonId,data)
callback(null, data);
} else if (err) {
callback(err, null);
}
}
)
}
};
You can try to export the socket.io instance to the global level and access that as needed.
My project was also created with express-generator, therefore, follows the same template.
In my project, I would like to count the current number of active users in home page.
Here is an example:
bin/www
#!/usr/bin/env node
const app = require('../app');
const http = require('http').Server(app);
const io = require('socket.io')(http)
http.listen(process.env.PORT);
io.on('connection', (socket) => {
const qtd = socket.client.conn.server.clientsCount;
io.emit('novaconexao', qtd);
socket.on('disconnect', () => {
io.emit('disconnecteduser', qtd - 1);
});
});
app.set('socketio', io);//here you export my socket.io to a global
console.log('Microsservice login listening at http://localhost:%s', process.env.PORT);
server/index.js
const router = require('express').Router();
router.get('/', (req, res) => {
const io = req.app.get('socketio'); //Here you use the exported socketio module
console.log(io.client.conn.server.clientsCount)
io.emit('new-user', {qtd: io.client.conn.server.clientsCount})
res.status(200).json({ msg: 'server up and running' });
})
module.exports = router;
Following this strategy, you can use socketio in any route in your application.
Here is a solution
Create a module io.js
const sio = require('socket.io');
let io = null;
module.exports = {
//Initialize the socket server
initialize: function(httpServer) {
io = sio(httpServer);
io.on('connection', function(socket) {
console.log('New client connected with id = ', socket.id);
socket.on('disconnect', function(reason) {
console.log('A client disconnected with id = ', socket.id, " reason ==> ", reason);
});
});
},
//return the io instance
getInstance: function() {
return io;
}
}
In bin/www.js
var server = http.createServer(app);
require('path_to_io_js/io').initialize(server);
In your controllers / LessonController.js
//require the io module
const socket = require('path_to_io_js/io');
module.exports = {
addComment: async (lessonId, userId, content, callback) => {
const newData = { comments: { user: userId, content: content, }, };
Lesson.findOneAndUpdate({ _id: lessonId }, { $push: newData }, { new: true })
.exec().then(function (data) {
if (data) {
//get the io instance
const io = socket.getInstance();
io.emit("comment_" + lessonId, data)
}
callback(null, data);
}).catch(err => {
callback(err);
})
}
};
Create socketInstance.js
let io = null;
// set this when you initialize the io
const setSocketInstance = (ioInstance) => {
io = ioInstance;
};
// you can call this anywhere
const getSocketInstance = () => {
return io;
};
inside socket.js where you initialize io
const setSocketInstance = require("./socketInstance");
const initializeIO = (server) => {
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
// as soon as we initialize the io, we set the instance
setSocketInstance(io);
// ....
};
Now you can call getSocketInstance anywhere in your app.
I'm pretty new to sockets and I've been struggling to implement some of the documentation i've seen online. This is my set up currently and I wanted to run socket.io against just the healthcheck api endpoint (/api/v1/healthcheck) how would I go about running socket io in the healthcheck controller? and emit changes to the response? Any help is appreciated, i'm tearing my hair out :(
Server.js
const socket = require('socket.io')
const healthcheck = require('./routes/healthcheck');
const auth = require('./routes/auth');
const users = require('./routes/users');
const server = app.listen(
PORT,
console.log(
`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.cyan.bold
)
);
let io = require('socket.io')(server);
app.set("io", io);
//Auth
app.use('/api/v1/auth', auth);
app.use('/api/v1/users', users);
//Health check
app.use('/api/v1/healthcheck', healthcheck);
/routes/healthcheck.js
const express = require('express');
const { checkHealth } = require('../controllers/healthcheck');
const router = express.Router();
router.post('/', checkHealth);
module.exports = router;
/controllers/healthcheck.js
const asyncHandler = require('../middleware/async');
exports.checkHealth = asyncHandler(async (req, res, next) => {
res.status(200).json({
success: true,
data: {
status: "Alive!"
}
});
});
You can pass in the instance of io into that healthcheck route and then simply listen to events and take action. Sample code below.
server.js
const socket = require('socket.io')
const server = app.listen(
PORT,
console.log(
`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.cyan.bold
)
);
let io = require('socket.io')(server);
app.set("io", io);
// pass in io to the relevant route
const healthcheck = require('./routes/healthcheck')(io);
const auth = require('./routes/auth');
const users = require('./routes/users');
//Auth
app.use('/api/v1/auth', auth);
app.use('/api/v1/users', users);
//Health check
app.use('/api/v1/healthcheck', healthcheck);
healthcheck route
const express = require('express');
const { checkHealth } = require('../controllers/healthcheck');
const router = express.Router();
module.exports = (io) => {
router.post('/', checkHealth);
io.on('connection', socket => {
socket.emit('hello', {message: 'helloworld'});
socket.on('reply', checkHealth.someMethod);
});
return router;
}
I would rather create endpoints in files - same as you do for express routes, and init these in your server.js as follows:
let io = require('socket.io')(server);
app.set("io", io);
io.on('connection', socket => {
require('./myendpointexample')(socket);
});
myendpointexample.js
module.exports = (socket) => {
socket.on('myevent', (message) => {
mycontroller.myFunction(message).then(result => {
socket.emit('myEvent', result);
});
});
};
I have the following code
var app = require('express')();
var http = require('http');
var server = http.createServer();
var socket = require('socket.io');
var io = socket.listen( server );
io.sockets.on('connection', function(socket) {
console.log('socket connected');
socket.broadcast.emit('newUser', 'New User Joined, Say Hi :D');
socket.on('serverEmit',function(msg) {
console.log('works');
});
socket.on('chatMessage', function(msg) {
io.emit('server_emit', msg);
console.log(msg);
});
});
server.listen(3500, function() {
console.log('listening on *:3500');
});
So my question is how to add an emit function outside of this socket connection. For example, if I have a get request like below
app.get('/link',function(req,res) {
io.sockets.emit('trigger','triggered'); // Process I want to make
});
Thanks in advance.
You need to export your io first so that it can be reusable.
socket-setup.js
const socket = require("socket.io")
let _io;
const setIO = (server) => {
_io = socket(server, {
cors : {
origin : "*",
headers : {
"Access-Control-Allow-Origin" : "*"
}
}
})
return _io
}
const getIO = () => {
return _io
}
module.exports = {
getIO,
setIO
}
Then in your app entry file (index.js), setup your io.
const app = express()
const server = http.createServer(app)
let io = setIO(server)
io.on("connection", socket => {
//Your job
})
Then wherever, e.g. message.js you want to emit event. You can use io like this.
const onMessageRecieved = () => {
try {
getIO().emit("hello", "Bye")
} catch (error) {
console.log(error);
}
}
That's it. Enjoy.
You almost had it:
it's io.emit('trigger','triggered');
And if you need to emit to a namespace you can do:
const namespace = io.of("name_of_your_namespace");
namespace.emit('trigger','triggered');
I'm writing a social web with NodeJS, Express and Angular.
Now I already handled socket between server and client but I want to send a server side status of a user to only his friends, not broadcast to the whole network.
This is my code:
In client
Main.js
angular.module('appServices', ['ngResource']).
factory('socket', function($rootScope){
var socket = io.connect();
return {
on: function (eventName, callback){
socket.on(eventName, function(){
var args = arguments;
$rootScope.$apply(function(){
callback.apply(socket, args);
});
});
},
emit: function (eventName,data, callback){
socket.emit(eventName, data, function (){
var agrs = arguments;
$rootScope.$apply(function (){
if(callback){
callback.apply(socket, agrs);
}
});
})
}
};
});
Controller.js
function activityCtrl($scope, socket){
$scope.createStatus = function(){
var sttData = $scope.stt;
socket.emit('createStt',sttData);
}
socket.on('addStt',function(data){
$scope.user.activity.unshift(data);
});
socket.on('myStt',function(data){
$scope.user.activity.unshift(data);
});
}
Main.js
angular.module('webProfileApp',['appServices'])// appServices duoc goi tu services.js
.config(['$routeProvider', function($routeProvider){
$routeProvider.
when('/activity/:userId',{ templateUrl: 'partials/me.html', controller: activityCtrl });
}]);
In Serverside:
app.js
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
io.sockets.on('connection',user.createStt);
User.js
exports.createStt = function(socket){
socket.on('createStt',function(dataSocket){
console.log('Save status---------');
var reqBody = dataSocket;
//
//Save data
//
socket.broadcast.emit('addStt',reqBody);
socket.emit('myStt',reqBody);
});
});
I use a variant of rooms with a special GUID...
SERVER
var on_connected = function(sockets, socket, user) {
socket.join(token(special_room_name));
}
//...
sockets.in(token(special_room_name)).emit('special_event_name', {...});
CLIENT
socket.on('special_event_name', function(info) {
$scope.users = info;
});
Note: client only receives this if we called join() on his socket, i.e. she is a memebr of this room...