In my Node+Express server app I'd like to emit notifications from specific controllers triggered by requests coming from the HTTP API:
For example:
router.route('/status').put(Order.changeStatus)
class Order {
static changeStatus(req, res) {
// Modifying database record with changed status
// Emiting message to specific socket room
}
}
I'm handling socket connections as follows:
import AuthMiddleware from '../middleware/AuthMiddleware';
const socketio = require('socket.io');
let io;
module.exports.listen = function(app){
io = socketio.listen(app);
io.on('connection', function(socket){
console.log('Client connected');
AuthMiddleware.authenticateSocket(socket, (err, user) => {
socket.join(user._id);
console.log('Client joined to: ' + user._id);
})
socket.on('disconnect', () => {
console.log('Client disconnected')
});
});
return io;
}
After authenticating the incoming socket connection, it is subscribed to the room of its own ID.
The socket is initialised at the top of the project as follows:
import app from './app';
import http from 'http';
const PORT = process.env.PORT || 7235;
const server = http.createServer(app);
const io = require('./socket/main').listen(server);
// App start
server.listen(PORT, err => {
console.log(err || `Server listening on port ${PORT}`);
});
Although I'm able to process incoming events and respond to those, the problem with this structure is that I can't access the io object within the controllers to be able to emit data in a particular room as follows:
io.sockets.in(userID).emit('notification', data);
What would be the proper structure that enables to trigger socket events from the controllers of the HTTP API?
Your controllers need to have access to the configured io object. The easiest way to do that is to export it in your module instead of just exporting the listen function, perhaps like so:
import AuthMiddleware from '../middleware/AuthMiddleware';
const socketio = require('socket.io');
let io;
function listen(app){
io = socketio.listen(app);
io.on('connection', function(socket){
console.log('Client connected');
AuthMiddleware.authenticateSocket(socket, (err, user) => {
socket.join(user._id);
console.log('Client joined to: ' + user._id);
})
socket.on('disconnect', () => {
console.log('Client disconnected')
});
});
return io;
}
module.exports = {
listen, io
}
Then your controllers can just do const io = require('./path/to/that').io;
Related
I'm trying to output the response I receive from MQTT to a node serve setup using express.
There will be a json string message received from the mqtt service at every second.
The response would be output on /main API, which I would call from a Ionic 4 mobile app I'm working on.
However, now I can't display the data on the server itself just to check, I haven't thought of how I would constantly update the data as well on the server. The page doesn't refresh it just keeps loading.
const mqtt = require('mqtt')
const express = require('express')
const PORT = 8000
const app = express()
var client = mqtt.connect("mqtt://bac.com")
client.on('connect', () => {
console.log("Connected")
client.subscribe('/test')
})
app.get("/", (req, res) => {
res.send("ROOT");
});
app.get("/main", (req, res) => {
client.on('message', (topic, message) => {
res.send(message)
})
});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
You would need to store your data somehow on the server side for your approach to work.
Implement some kind of storage service that stores the messages. Your client will need to respond to the queue messages and push these to storage, your api action will retrieve them from the storage, not from the queue.
const mqtt = require('mqtt');
const express = require('express');
const PORT = 8000;
const app = express();
const storageService = require("SOME KIND OF STORAGE SERVICE");
var client = mqtt.connect("mqtt://bac.com");
client.on('connect', () => {
console.log("Connected")
client.subscribe('/test')
});
client.on('message', (topic, message) => {
storageService.save(topic, message); //or this has to provide storage and enterpretation of the data comming in
});
app.get("/", (req, res) => {
res.send("ROOT");
});
app.get("/main", (req, res) => {
res.send(storageService.getAll());
});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
You could also revisit the entire implementation, and push messages to the frontend via a socket or some other kind of persistant connection.
I have found a workaround from a similar question here. On the server side it will send data on message received. On the client side, it is an ajax call for every second to retrieve the data on the server side.
As described in the link, it really is a bad pattern. However, this would be suitable for those who have constraints to making changes to the MQTT service.
// Server.js
const mqtt = require('mqtt')
const express = require('express')
const PORT = 8000
const app = express()
var client = mqtt.connect("mqtt://bac.com")
var mqttMessage
client.on('connect', () => {
console.log("Connected")
client.subscribe('/test')
})
client.on('message', (topic, message) => {
console.log('Topic: ' + topic + '\nMessage: ' + message)
mqttMessage = message
})
app.get("/", (req, res) => {
res.sendFile("__dirname + "/index.html");
});
app.get("/main", (req, res) => {
if(mqttMessage)
res.send(mqttMessage);
else
res.status(404).send();
});
app.listen(PORT, () => {
console.log(`Server is listening on port ${PORT}`);
});
and on the index.html page:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
$(document).ready(
function () {
setInterval(function () {
$.get('/main', function (res) {
$('#data').text(res);
});
}, 1000);
}
);
</script>
<p id="data"></p>
</body>
I am looking to share the a socket.io instance with my express route files.
I previously had the socket.io listener in one routes file, orders.js on port 5200 and the express server listening in app.js on port 5000, however my cloud service required me to use one port for both, so I did have moved them both to app.js. The code below has been drastically simplified to remove noise
./app.js
const port = process.env.PORT || 8000;
const socket = require('socket.io');
const server = app.listen(port, () => console.log(`[!] Listening on
port: ${chalk.green(port)}`))
const io = module.exports = socket(server);
app.use(express.json());
app.use(cors());
app.use('/', router)
./routes/index
const express = require('express');
const router = express.Router();
router.use('/orders', require('./orders'));
module.exports = router;
./routes/orders.js
const express = require('express');
const router = express.Router();
const io = require('../index');
io.on('connection', (client) => {
console.log("CLIENT CONNECTED");
})
router.get(... etc etc
I expect to get a connection console but instead I'm getting an error that IO is not a function. When I console.log(io) I get {}
Try this way
const user = require('{SET FILE PATH}');
io.on('connection', function (socket) {
console.log('Socket connected...', socket.id);
socket.on('login', function (req) {
user.login(socket, req, 'login'); // socketObj , req , methodName
})
});
user.js
class User {
login(socket, req, methodName) {
console.log("Socket ref is ::: " , socket);
}
}
module.exports = new User();
My socket.connected is always false, cannot emit or receive messages too.
app.js
var app = require('./config/server');
var http = require('http').Server(app);
var io = require("socket.io")(http);
http.listen(80, function(err)
{
console.log(err);
console.log('Server client instagram_clone_v01 online');
});
io.sockets.on('connection', function (socket)
{
console.log("new user connected");
});
server side
var sockets = io();
sockets.on('connection', function ()
{
console.log("connected");
sockets.emit("newPhoto");
});
client side
const socket = io.connect("http://localhost:80");
console.log(socket.connected);
socket.on('error', function()
{
console.log("Sorry, there seems to be an issue with the connection!");
});
socket.on('connect_error', function(err)
{
console.log("connect failed"+err);
});
socket.on('connection', function ()
{
console.log("connected");
socket.on('newPhoto',function()
{
load_posts();
});
});
None of the "on"s are received, not even "error". So how can i make it work, please?
I've checked Your code locally.
So issue was that You're checking: .on('connection',...) when it should be .on('connect', ...)
So try this fix:
socket.on('connect', function() {
console.log("Connected to WS server");
console.log(socket.connected);
load_posts();
});
socket.on('newPhoto', function(){
load_posts();
});
When I was building a basic socketio app using React frontend and Node Express backend, I came across a similar problem in v4. With the typical Node/React setup, your react app will live on port 3000 and your express server will live on port 5000, and according to the docs this results in you needing to set an open CORS policy to enable connections to the socketio server on port 5000.
Since Socket.IO v3, you need to explicitly enable Cross-Origin
Resource Sharing (CORS).
(docs)
I simply made sure my socketio definition was as such:
const app = express();
const httpServer = http.createServer(app);
const io = require("socket.io")(httpServer, {
cors: {
origin: "http://localhost:8080",
methods: ["GET", "POST"]
}
});
and I Was able to connect from a my react app inside of a useEffect. I was seeing all of the expected logs thereafter.
useEffect(() => {
socket = io('localhost:5000');
socket.on('connect', () => {
console.log('socket connected');
console.log(socket);
});
}, []);
I was following the second example here:
https://github.com/socketio/socket.io-client
and trying to connect to a website that uses websockets, using socket.io-client.js in node.
My code is as follows:
var socket = require('socket.io-client')('ws://ws.website.com/socket.io/?EIO=3&transport=websocket');
socket.on('connect', function() {
console.log("Successfully connected!");
});
Unfortunately, nothing gets logged.
I also tried:
var socket = require('socket.io-client')('http://website.com/');
socket.on('connect', function() {
console.log("Successfully connected!");
});
but nothing.
Please tell me what I'm doing wrong. Thank you!
Although the code posted above should work another way to connect to a socket.io server is to call the connect() method on the client.
Socket.io Client
const io = require('socket.io-client');
const socket = io.connect('http://website.com');
socket.on('connect', () => {
console.log('Successfully connected!');
});
Socket.io Server w/ Express
const express = require('express');
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const port = process.env.PORT || 1337;
server.listen(port, () => {
console.log(`Listening on ${port}`);
});
io.on('connection', (socket) => {
// add handlers for socket events
});
Edit
Added Socket.io server code example.
I have socket.io working in app.js but when i am trying to call it from other modules its not creating io.connection not sure ?
app.js
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var ditconsumer = require('./app/consumers/ditconsumer');
ditconsumer.start(io);
server.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
consumer.js
module.exports = {
start: function (io) {
consumer.on('message', function (message) {
logger.log('info', message.value);
io.on('connection', function (socket) {
socket.on('message', function(message) {
socket.emit('ditConsumer',message.value);
console.log('from console',message.value);
});
});
});
}
}
Since app.js is usually kind of the main initialization module in your app, it will typically both initialize the web server and socket.io and will load other things that are needed by the app.
As such a typical way to share io with other modules is by passing them to the other modules in that module's constructor function. That would work like this:
var server = require('http').createServer(app);
var io = require('socket.io')(server);
// load consumer.js and pass it the socket.io object
require('./consumer.js')(io);
// other app.js code follows
Then, in consumer.js:
// define constructor function that gets `io` send to it
module.exports = function(io) {
io.on('connection', function(socket) {
socket.on('message', function(message) {
logger.log('info',message.value);
socket.emit('ditConsumer',message.value);
console.log('from console',message.value);
});
});
};
Or, if you want to use a .start() method to initialize things, you can do the same thing with that (minor differences):
// app.js
var server = require('http').createServer(app);
var io = require('socket.io')(server);
// load consumer.js and pass it the socket.io object
var consumer = require('./consumer.js');
consumer.start(io);
// other app.js code follows
And the start method in consumer.js
// consumer.js
// define start method that gets `io` send to it
module.exports = {
start: function(io) {
io.on('connection', function(socket) {
socket.on('message', function(message) {
logger.log('info',message.value);
socket.emit('ditConsumer',message.value);
console.log('from console',message.value);
});
});
};
}
This is what is known as the "push" module of resource sharing. The module that is loading you pushes some shared info to you by passing it in the constructor.
There are also "pull" models where the module itself calls a method in some other module to retrieve the shared info (in this case the io object).
Often, either model can be made to work, but usually one or the other will feel more natural given how modules are being loaded and who has the desired information and how you intend for modules to be reused in other circumstances.
If you want to avoid the global scope, make your io exist in a separate file like this:
var sio = require('socket.io');
var io = null;
exports.io = function () {
return io;
};
exports.initialize = function(server) {
return io = sio(server);
};
Then in app.js:
var server = require('http').createServer(app);
var io = require('./io').initialize(server);
require('./app/consumers/ditconsumer'); // loading module will cause desired side-effect
server.listen(...);
and in consumer.js:
require('../io').io().on('connection', function(socket) {
logger.log('info', message.value);
socket.on('message', function(message) {
socket.emit('ditConsumer',message.value);
console.log('from console',message.value);
});
});
I found a simple solution.
Use a global variable in app.js and access it from the other files.
global.io = require('socket.io').listen(server);
You can make a singleton instance in just 4 lines.
In websocket.js write your server configuration code.
const socketIO = require('socket.io');
const server = require('http').createServer();
server.listen(8000);
module.exports = socketIO(server);
Then in your consumer.js just require the file
const socket = require('./websocket');
/* API logic here */
socket.emit('userRegistered', `${user.name} has registered.`);
What worked best for me was to use a callback function which exports the socket.io instance.
app.js:
var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function(socket) {
socket.on('message', function(message) {
logger.log('info',message.value);
socket.emit('ditConsumer',message.value);
console.log('from console',message.value);
});
});
function getSocketIo(){
return io;
}
module.exports.getSocketIo=getSocketIo
and in the consumer.js
const app=require('./app.js')
const io=app.getSocketIo()
You can do this very easily you have to only write socket connection in app.js and than you can use socket anywhere you want
In app.js file put code like below
var http = require('http').createServer(app);
const io = require('socket.io')(http);
io.sockets.on("connection", function (socket) {
// Everytime a client logs in, display a connected message
console.log("Server-Client Connected!");
socket.join("_room" + socket.handshake.query.room_id);
socket.on('connected', function (data) {
});
});
const socketIoObject = io;
module.exports.ioObject = socketIoObject;
http.listen(port, () => {
console.log('Magic happens on port ' + port); // shoutout to the user
});
In any file or in controller you can import that object like below
const socket = require('../app'); //import socket from app.js
//you can emit or on the events as shown
socket.ioObject.sockets.in("_room" + req.body.id).emit("msg", "How are You ?");
This solved my problem easily
You can create a singleton class for socket-io.
It looks so clean and modular.
Here is my folder structure.
Socket.io.ts
In this file, I initializing socket-io, and created publishEvent() method so I can publish events.
import {
Server,
Socket
} from "socket.io";
import {
Events
} from "./utils";
export class SocketInit {
private static _instance: SocketInit;
socketIo: Server;
constructor(io: Server) {
this.socketIo = io;
this.socketIo.on("connection", (socket: Socket) => {
console.log("User connected");
});
SocketInit._instance = this;
}
public static getInstance(): SocketInit {
return SocketInit._instance;
}
public publishEvent(event: Events, data: any) {
this.socketIo.emit(event, data);
}
}
Server.ts
import "reflect-metadata";
import {
config
} from "dotenv";
config();
import http from "http";
import express, {
Request,
Response
} from "express";
import {
Server,
Socket
} from "socket.io";
import mongoose from "mongoose";
import cors from "cors";
import path from "path";
import morgan from "morgan";
import {
SocketInit
} from "./socket.io";
import {
downloadsRouter
} from "./routes/downloads";
const app = express();
const server = http.createServer(app);
export const io = new Server(server, {
cors: {
origin: "*"
},
});
//Initilize socket
new SocketInit(io);
mongoose
.connect("mongodb://localhost:27017/youtube", {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to database");
})
.catch((error) => {
throw error;
});
app.use(morgan("dev"));
app.use(express.json());
app.use(express.urlencoded({
extended: true
}));
app.set("view engine", "ejs");
app.use(express.static(path.join(__dirname, "views")));
app.use(cors());
app.use(downloadsRouter);
app.get("/", (req: Request, res: Response) => {
res.render("index");
});
server.listen(3000, () => {
console.log("Server running up 3000");
});
download-queue.ts In this file where I performing some download tasks and emit events for clients.
import Bull from "bull";
import ytdl from "ytdl-core";
import fs from "fs";
import {
Video
} from "../models/video";
import {
Events
} from "../utils";
import {
SocketInit
} from "../socket.io";
const downloadQueue = new Bull("download queue", {
redis: {
host: process.env.REDIS_HOST!,
port: parseInt(process.env.REDIS_PORT!),
},
});
);
downloadQueue.process((job, done) => {
return new Promise((resolve, reject) => {
const title = Math.random().toString();
const {
youtubeUrl
} = job.data;
//Get singleton instance
const socket = SocketInit.getInstance();
ytdl(youtubeUrl)
.pipe(fs.createWriteStream(`${process.cwd()}/downloads/${title}.mp4`))
.on("finish", async() => {
socket.publishEvent(Events.VIDEO_DOWNLOADED, title);
console.log("Download complete");
const file = `${process.cwd()}/downloads/${title}.mp4`;
const video = new Video({
title,
file,
});
await video.save();
done();
resolve({
title
});
})
.on("ready", () => {
console.log("Download started");
socket.publishEvent(Events.VIDEO_STARTED, title);
})
.on("error", (error) => {
socket.publishEvent(Events.VIDEO_ERROR, error);
done(error);
reject(error);
});
});
});
export {
downloadQueue
};
I created a file socket.service.ts with a class SocketService and in app.tsI called the constructor with the http. This will also work with pure javascript, just change the imports and exports ...
import * as socketIo from 'socket.io';
export class SocketService {
io: any;
constructor(http) {
this.io = socketIo(http)
this.io.set('origins', '*:*');
this.io.on('connection', function (socket) {
console.log('an user connected');
socket.on('disconnect', function () {
console.log('user disconnected');
});
});
http.listen(3001, function () {
console.log('socket listening on *:3001');
});
}
}
in app.ts call is like:
import * as express from 'express';
import { SocketService } from './services/socket.service';
const app = express();
var http = require('http').Server(app);
// ...
new SocketService(http);
// ...
module.exports = app;
Please notice that everytime you call the constructor a new instance will be created. To avoid this use the singleton pattern :)