Express JS Controllers with Socket IO Issue - node.js

I am trying to setup Socket IO to work well in different controllers in my Express application.
Here is how I have Socket IO initialized in my server.js
//Run When Connection Received
io.on('connection', (socket) => {
socket.emit('message', 'This is a test message from the server.')
})
Here is what one of my controllers looks like:
const getComments = asyncHandler(async (request, response) => {
const { liveID, token: accessToken } = request.body
var source = new EventSource(
`https://streaming-graph.facebook.com/${liveID}/live_comments?access_token=${accessToken}&comment_rate=one_per_two_seconds&fields=from{name,id},message`
)
source.onmessage = function (event) {
// Do something with event.message for example
console.log(event)
// THIS IS WHERE I NEED TO EMIT A SOCKET IO EVENT.
// THE DATA IN 'event' NEEDS TO BE SENT BACK TO THE CLIENT VIA THE SOCKET
}
source.onerror = function (error) {
console.log('Error!')
console.log(error)
}
source.onopen = function (event) {
console.log(event)
}
})
I have been trying to follow this documentation from Socket IO but for the life in me I can not seem to figure out how to make this work with my above example I have tried so many different things and I just seem to be missing something.
https://socket.io/docs/v4/server-application-structure/
Any help here would be greatly appreciated!

What you can do to have access to io in controller is to attach it to request object like so:
app.use((req, res, next) => {
req.io = io;
next();
});
When a socket connects add an user id to the socket
io.on('connection', (socket) => {
// assuming you send user id in a header for example
socket.userId = socket.client.request.headers.userId;
})
Then in a controller:
const getComments = asyncHandler(async (request, response) => {
var io = request.io;
// https://socket.io/docs/v4/server-api/#serverfetchsockets
// return all Socket instances
const sockets = await io.fetchSockets();
// assuming there's a loggedin user object attached to request
const socket = sockets.find(s => s.userId === request.user.id);
const { liveID, token: accessToken } = request.body
var source = new EventSource(
`https://streaming-graph.facebook.com/${liveID}/live_comments?access_token=${accessToken}&comment_rate=one_per_two_seconds&fields=from{name,id},message`
)
source.onmessage = function (event) {
// Do something with event.message for example
console.log(event)
socket.emit(....);
}
source.onerror = function (error) {
console.log('Error!')
console.log(error)
}
source.onopen = function (event) {
console.log(event)
}
})

Related

nodejs websocket ws call send in controller

I'm trying to send an event if there is a request from controller.
using websocket ws:
// openWSConnection
const wsServer = new WebSocketServer({
port: '8080'
})
const publish = (data) => {
return data
}
const testSend = (socket) => {
publish({}, (err,data) => {
socket.send(data)
})
}
wsServer.on('connection', function (socket) {
testSend(socket)
})
module.exports = {
publish
}
In controller
router.post("/create", async (req, res, next) => {
openWSConnection.publish(
{
toUser_id,
type,
status,
title,
subtitle,
description,
})
})
Every time the create will triggered, Websocket are not able to send the data to the client.
I'm trying to make it work like, If there are events that is being created it will send the data to websocket and pass it to the client.
Thanks for the help!

How to transfer data from a controller to a socket.io in nodejs?

I'm building an web app to receive data from an api using nodejs as backend, and show this data on the client side using React. But it's my first time using socket.io.
Sockets.ts
function socket( io ){
io.on("connection", socket => {
var socketId = socket.id;
var clientIp = socket.request.connection.remoteAddress;
console.log('New connection ' + socketId + ' from ' + clientIp);
socket.on("disconnect", () => {
console.log("Client disconnected");
});
});
}
export default socket;
router.controller.ts
export const getData: RequestHandler = async (req, res) => {
const options= {
method: 'GET',
};
const response = await fetch(citybikeurl, options)
.then((res: any) => res.json())
.catch((e: any) => {
console.error({ error: e });
});
console.log("RESPONSE: ", response);
res.json(response);
}
routes.ts
router.get('/', dataController.getData)
At the moment, I don't know if I'm passing any data from controller.ts to Sockets.ts, and after of this be able to emit the results.
You should be using a socket on the server-side as well.
Please refer to this guide for how to set up a socket server:
https://medium.com/#raj_36650/integrate-socket-io-with-node-js-express-2292ca13d891

How to make https.get request assign data from within the request to a variable outside of the request?

I'm trying to get an https.get request to assign data from within the request to a variable outside of the request. I'm also using axios. Within the https.get request, it returns the data I want in the res.on('end'... But I can't figure out how to get that data outside of the res.on('end'... portion of the request. Here is my code:
require('dotenv').config();
const express = require('express');
const {SERVER_PORT} = process.env;
const https = require('https');
const xml2js = require('xml2js');
const parser = new xml2js.Parser({ attrkey: "ATTR" });
const app = express();
app.use(express.json());
app.post('/api/ecb/forex/stats', async(req, res) => {
const {base_currency, base_amount, target_currency} = req.body;
let currencyInfo = https.get("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", function(res) {
let data = '';
res.on('data', async function(stream) {
data += stream;
});
res.on('end', async function(){
parser.parseString(data, async function(error, result) {
if(error === null) {
return result['gesmes:Envelope'].Cube[0].Cube.forEach(element => {
console.log("at",element.Cube);
return element.Cube;
});;
}
else {
console.log(error);
}
});
});
});
console.log(currencyInfo);
})
const port = SERVER_PORT;
app.listen(port, () => console.log(`Port running on port ${port}`));
I want the value of 'element.Cube;' within the res.on('end"... portion of the https.get request to be assigned to the variable "currencyInfo". What am I doing wrong and how do I fix the code?
You can change your code to something like below, then you have Promise to return:
let currencyInfo = await new Promise((resolve, reject) => {
https.get('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml', function(res) {
let data = '';
res.on('data', async function(stream) {
data += stream;
});
return res.on('end', async function() {
return parser.parseString(data, async function(error, result) {
if(error === null) {
return result['gesmes:Envelope'].Cube[0].Cube.forEach(element => {
resolve(element.Cube);
});
}
else {
reject(error);
}
});
});
});
});

nodejs socket.io socket on is not a function

I got a middlware to validate my socket by jwt
but I have a problem making new events in other folders
Not to get everything in my app.js
I did this to authenticate my socket:
let io = require('socket.io')(server);
app.set("io", io);
io.use(verify.passportJwtAuth);
io.on('connection', function(socket) {
console.log('Authentication passed!');
// now you can access user info through socket.request.user
// socket.request.user.logged_in will be set to true if the user was authenticated
socket.emit('success', {
message: 'success logged in!'
});
});
midd:
async passportJwtAuth(socket,next) {
const secret = config.jwt.secret;
const token = socket.handshake.query.token;
jwt.verify(token, secret, function(err, decoded) {
if(err) {
return next(err);
}
console.log("sucess");
return next();
});
}
I've tried to make a new event:
const socketRouter = require('./routes/socket');
io.use(verify.passportJwtAuth);
io.on('connection', function(socket) {
console.log('Authentication passed!');
// now you can access user info through socket.request.user
// socket.request.user.logged_in will be set to true if the user was authenticated
socket.emit('success', {
message: 'success logged in!'
});
});
socketRouter.configuration();
my socketRouter.configuration:
const socketController = require ('../controllers/SocketController');
const verify = require('../auth/index');
const socket = require('socket.io');
module.exports = {
configuration: () => {
socket.on ('message', (message) => {
socket.emit ('myevent', {message: 'Ok, You are signed in'});
console.log (message);
console.log (`Socket $ {socket.id}`)
});
}
}
error:
C:\Users\SpiriT\Documents\Projetos\FtcJokenPo\back\src\routes\socket.js:6
socket.on ('message', (message) => {
^
TypeError: socket.on is not a function
at Object.configuration (C:\Users\SpiriT\Documents\Projetos\FtcJokenPo\back\src\routes\socket.js:6:14)
at Object.<anonymous> (C:\Users\SpiriT\Documents\Projetos\FtcJokenPo\back\src\app.js:36:14)
I didn't want to have to leave all my socket logic in my app.js
Also this way soon when I turn on the server I already call this module (I wanted it to call only when I use socket.on on the front end
Could someone give me a hand?
my front end:
verify = () => {
let socket = null;
var token = 312312312;
socket = io('http://localhost:8080', {
query: {token: token}
});
socket.on('error', function(err) {
console.error(err);
});
// Connection succeeded
socket.on('success', function(data) {
console.log(data.message);
})
}
In your socketRouter.configuration file, with this code below, your socket variable is not what you need it to be. It looks like you want it to be a specific connected socket that you can listen for incoming messages on. You are trying to use:
const socket = require('socket.io');
to get that socket, but that's simply the export object from the socket.io module and has nothing to do with an existing connected socket.
What it appears you need to do is to pass a particular socket to your configuration function and then use that argument in your function. First change the configuration module to this:
const socketController = require ('../controllers/SocketController');
const verify = require('../auth/index');
module.exports = {
configuration: (socket) => {
socket.on ('message', (message) => {
socket.emit ('myevent', {message: 'Ok, You are signed in'});
console.log (message);
console.log (`Socket $ {socket.id}`)
});
}
}
This removes the require('socket.io') line and adds a socket argument to the configuration function.
Then, where that configuration() function is called, you need to pass that socket argument:
const socketRouter = require('./routes/socket');
io.use(verify.passportJwtAuth);
io.on('connection', function(socket) {
console.log('Authentication passed!');
// now you can access user info through socket.request.user
// socket.request.user.logged_in will be set to true if the user was authenticated
socket.emit('success', {
message: 'success logged in!'
});
// let the socketRouter add any message handlers it wants to for this socket
socketRouter.configuration(socket);
});

Node.js rsmq - New message doesn't become visible until Node.js application is restarted

I'm trying to make this package work.
redis version: stable 4.0.6
I connect Redis like this, there's no issues there.
pubsub.js
var redis = require("redis");
var psRedis = redis.createClient();
psRedis.auth("mypasswordishere", function (callback) {
console.log("connected");
});
module.exports.psRedis = psRedis;
After starting Node.js application, I can see "connected" on the console and perform operations, I've checked this.
My test.js file is below.
test.js
var express = require('express');
var router = express.Router();
var path = require("path");
var bodyParser = require('body-parser');
var async1 = require("async");
var client = require("../databases/redis/redis.js").client;
var RedisSMQ = require("rsmq");
var psRedis = require("./realtime/operations/pubsub").psRedis;
var rsmq = new RedisSMQ({client: psRedis});
rsmq.createQueue({qname: "myqueue"}, function (err, resp) {
if (resp === 1) {
console.log("queue created");
}
});
rsmq.receiveMessage({qname: "myqueue"}, function (err, resp) {
if (resp) {
console.log(resp);
}
});
router.get('/pubsubTest', function (req, res, next) {
async1.waterfall([
function (callback) {
rsmq.sendMessage({qname: "myqueue", message: "Hello World 1"}, function (err, resp) {
if (resp) {
console.log("Message sent. ID:", resp);
}
});
callback(null, 'done!');
}
], function (err, result) {
res.sendStatus(200);
});
});
module.exports = router;
However, when I visit /pubsubTest, only message id appears on the console.
Message sent. ID: exb289xu0i7IaQPEy1wA4O7xQQ6n0CAp
If I restart my Node.js application, I get to see the result below, which is expected. Why doesn't it appear immediately?
{ id: 'exb289xu0i7IaQPEy1wA4O7xQQ6n0CAp',
message: 'Hello World 1',
rc: 1,
fr: 1515802884138,
sent: 1515802880098 }
Thank you.
receiveMessage will not "fire". You need to call it after you have sent a message.
what you are looking for is realtime option provided by rsmq.
var rsmq = new RedisSMQ({client: psRedis}, ns: "rsmq",realtime :true});
Now on every new message that is being added to a queue via sendMessage, a PUBLISH message will be sent to rsmq:rt:{qname} with the content {msg}. In your case sendMessage will emit an event namely rsmq:rt:myqueue
There can be two solutions for this , both will use the event rsmq:rt:myqueue
1.First one will use a redis client , which can subscribe to this published event with subscribe method provided by redis to implement PUB/SUB.
var redis = require('redis');
const subscribe = redis.createClient();
subscribe.subscribe('rsmq:rt:myqueue');
subscribe.on('message', function(msg) { //msg=>'rsmq:rt:myqueue'
rsmq.receiveMessage({qname: "myqueue"}, function (err, resp) {
if (resp) {
console.log(resp);
}
});
});
The whole code will look something like:
var express = require('express');
var router = express.Router();
var path = require("path");
var bodyParser = require('body-parser');
var async1 = require("async");
var client = require("../databases/redis/redis.js").client;
var psRedis = require("./realtime/operations/pubsub").psRedis;
var rsmq = new RedisSMQ({client: psRedis}, ns: "rsmq",realtime :true});
rsmq.createQueue({qname: "myqueue"}, function (err, resp) {
if (resp === 1) {
console.log("queue created");
}
});
const subscribe = redis.createClient( 6379,"127.0.0.1"); //creating new
worker and pass your credentials
subscribe.subscribe('rsmq:rt:myqueue');
subscribe.on('message', function(msg) { //msg=>'rsmq:rt:myqueue'
rsmq.receiveMessage({qname: "myqueue"}, function (err, resp) {
if (resp) {
console.log(resp);
}
});
});
router.get('/pubsubTest', function (req, res, next) {
async1.waterfall([
function (callback) {
rsmq.sendMessage({qname: "myqueue", message: "Hello World 1"},
function (err,
resp) {
if (resp) {
console.log("Message sent. ID:", resp);
}});
callback(null, 'done!');
}
], function (err, result) {
res.sendStatus(200);
});
});
module.exports = router;
2.Second solution is to use rsmq-worker which will provide you with a message event which you can listen to using .on method.
var RSMQWorker = require( "rsmq-worker" );
var worker = new RSMQWorker( "myqueue" ,{interval:.1}); // this worker
will poll the queue every .1 second.
worker.on( "message", function( message, next, msgid ){
if(message){
console.log(message);
}
next();
});
worker.start();
The whole code will look something like:
var express = require('express');
var router = express.Router();
var path = require("path");
var bodyParser = require('body-parser');
var async1 = require("async");
var client = require("../databases/redis/redis.js").client;
var psRedis = require("./realtime/operations/pubsub").psRedis;
var rsmq = new RedisSMQ({client: psRedis},{ ns: "rsmq",realtime :true});
rsmq.createQueue({qname: "myqueue"}, function (err, resp) {
if (resp === 1) {
console.log("queue created");
}
});
var RSMQWorker = require( "rsmq-worker" );
var worker = new RSMQWorker( "myqueue" ,{interval:.1});
worker.on( "message", function( message, next, msgid ){
if(message){
console.log(message);
}
next();
});
// optional error listeners
worker.on('error', function( err, msg ){
console.log( "ERROR", err, msg.id );
});
worker.on('exceeded', function( msg ){
console.log( "EXCEEDED", msg.id );
});
worker.on('timeout', function( msg ){
console.log( "TIMEOUT", msg.id, msg.rc );
});
worker.start();
router.get('/pubsubTest', function (req, res, next) {
async1.waterfall([
function (callback) {
rsmq.sendMessage({qname: "myqueue", message: "Hello World1"}
,function (err, resp) {
if (resp) {
console.log("Message sent. ID:", resp);
}});
callback(null, 'done!');
}
], function (err, result) {
res.sendStatus(200);
});
});
module.exports = router;
Note: In first solution you will need to delete the message that you received from the queue using deleteMessage or you can also use popMessage which will receive the last message and delete it for you. if you do not delete the message you will get all of the messages again until the timeout is over on that particular message.
For this reason i prefer to use the second solution , rsmq takes care of this stuff for you and also you can provide your own poll interval

Resources