I created a queue system via Rabbitmq and send notifications to users via socket. There are 3 files basically.
-index.js //some functions to save data and send to Rabbitmq
-socket.js //socket io modules
-consumer.js //some functions to consume data from Rabbitmq
When npm run index.js and consumer.js are started.
index.js
const Express = require('express');
const app = Express();
//init server
server = app.listen(3000);
//init socket io
require('./socket').initialize(server);
socket.js
const sio = require('socket.io');
let io;
module.exports = {
//Initialize the socket server
initialize: (server) => {
io = sio(server, {
cors: {
origin: '*',
}
});
io.on('connection', (socket) => {
console.log('New client connected with id = ', socket.id);
socket.on('disconnect', () => {
console.log("User disconnected");
});
});
},
//return the io instance
getInstance: () => {
return io;
}
}
consumer.js
const socket = require('./socket');
const io = socket.getInstance();
console.log("io", io);
//io is undefined
Io returns undefined in consumer.js. Where is mistake?
You can't call socket.getInstance() until someone has called socket.initialize() first.
You don't show where consumer.js is loaded so we can't follow the full sequence of module loading, but it appears that consumer.js is getting loaded before you initialize socket and thus it is trying to call socket.getInstance() before there's a socket instance to get.
When using methods that return state that built from previous methods, it is obviously important that things are called in the right order.
Related
good afternoon. I am new to programming sockets in node.js and I need to implement socket.io in a controller of my application. The architecture I have is the following:
The file that starts the server is index.js
const express = require('express');
const app = express();
const port = 3000;
const socketRouter = require('./routes/socket')
app.use(express.json());
//Route
app.use('/socket', socketRouter);
app.listen(port, () => {
console.log(`Server connection on http://127.0.0.1:${port}`); // Server Connnected
});
The file where I define the routes is socket.js
const { Router } = require('express');
const { showData } = require('../controllers/socket');
const router = Router();
router.post('/send-notification', showData);
module.exports = router;
And my controller is:
const { response } = require('express');
const showData = (req, res = response) => {
const notify = { data: req.body };
//socket.emit('notification', notify); // Updates Live Notification
res.send(notify);
}
module.exports={
showData
}
I need to implement socket.io in this controller to be able to emit from it but I can't get it to work. Could you tell me how to do it?
Thanks a lot
CLARIFICATION: if I implement socket.io in the main file it works, but I want to have some order and separate things. This is how it works:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
app.post('/send-notification', (req, res) => {
const notify = { data: req.body };
socket.emit('notification', notify); // Updates Live Notification
res.send(notify);
});
const server = app.listen(port, () => {
console.log(`Server connection on http://127.0.0.1:${port}`); // Server Connnected
});
const socket = require('socket.io')(server);
socket.on('connection', socket => {
console.log('Socket: client connected');
});
Move your socket.io code to its own module where you can export a method that shares the socket.io server instance:
// local socketio.js module
const socketio = require('socket.io');
let io;
modules.exports = {
init: function(server) {
io = socketio(server);
return io;
},
getIO: function() {
if (!io) {
throw new Error("Can't get io instance before calling .init()");
}
return io;
}
}
Then, initialize the socketio.js module in your main app file:
const express = require('express');
const app = express();
const port = 3000;
app.use(express.json());
const server = app.listen(port, () => {
console.log(`Server connection on http://127.0.0.1:${port}`); // Server Connnected
});
// initialize your local socket.io module
const sio = require('./socketio.js');
sio.init(server);
// now load socket.io dependent routes
// only after .init() has been called on socket.io module
const socketRouter = require('./routes/socket')
app.use('/socket', socketRouter);
Then, anywhere you want to access the socket.io server instance, you can
require("./socketio.js") and use the .getIO() method to get the socket.io instance:
// use correct path to socketio.js depending upon where this module
// is located in the file system
const io = require("../../socketio.js").getIO();
// some Express route in a controller
const showData = (req, res) => {
const notify = { data: req.body };
// send notification to all connected clients
io.emit('notification', notify);
res.send(notify);
};
module.exports= {
showData
};
Note: A typical socket.io usage convention on the server is to use io as the server instance and socket as an individual client connection socket instance. Please don't try to use socket for both. This makes it clear that io.emit(...) is attempting to send to all connected clients and socket.emit() is attempting to send to a single connected client.
Also note that if your route is triggered by a form post where the browser itself sends the form post, then that particular client will not receive the results of io.emit(...) done from that form post route because that browser will be in the process of loading a new web page based on the response of the form post and will be destroying its current socket.io connection. If the form post is done entirely via Javascript using an Ajax call, then that webpage will stay active and will receive the results of the io.emit(...).
You can use the same socket and app (if you need to expose APIs as well) in other files if you want to separate socket messages and REST endpoints by functionality or however you choose to organize it. Here's an example of how this can be done:
Create a new file, let's say controller1.js:
function initialize(socket, app) {
socket.on('some-socket-message', socket => {
// Whatever you want to do
});
app.get('/some-endpoint', (req, res) => {
// whatever you want to do
});
}
module.exports = {initialize}
And then add the following to your controller.js
const controller1 = require('path/to/controller1');
...
// At some point after socket and app have been defined
controller1.initalize(socket, app);
This will be the bases of separating your controller however you want, while still using the same socket connection and API port in all of your controllers. You can also refactor the initialize method into different methods, but that would be at your own discretion and how you want to name functions, etc. It also does not need to be called initalize, that was just my name of preference.
I am using nodeJs as backend and reactJs as my frontend the thing is I emitted a socket emit function from node
var server = require('http').createServer();
var io = require('socket.io')(server);
io.emit('quantity_check', 'KR')
now the issue is I'm unable to catch the emit
let serverUrl = 'localhost:3008'
const socket = socketIOClient(serverUrl);
socket.on("quantity_check", data => this.setState({ kiiii: data }));`
const socket = socketIOClient(serverUrl);
I'm checking this locally even i tried with my ip its not connecting I am not sure where the issue occurs
pc:nodejs and reactjs running on different ports
Can you post the code of you node server file and the react file where are you are listening to the sockets?. Anyway i hope that emit event is inside the connection
io.on('connection', function(socket) {
io.emit('quantity_check', 'KR')
}
and did you use the life cycle method componentDidMount to receive message
componentDidMount() {
socket.on("quantity_check", data => {
console.log(data);
});
}
Try something like this.
server
const server = require('http').createServer();
const io = require('socket.io')(server);
io.on('connect', (socket) => {
io.emit('quantity_check', 'KR');
});
Client(React side)
import io from 'socket.io-client';
const socket = io('http://localhost:3008');
export class App extends Component {
componentDidMount() {
socket.on('connect', () => {
console.log(socket.connected);
});
socket.on("quantity_checke", data => {
console.log(data);
});
}
render().......
}
I am using Adonis 4.1.0 and Adonis-websocket is only been available for v3. Can anyone tell me workaround for using socket.io with Adonis 4.1.0?
apparently they have been working on this not long ago, it was based on socket.io but because of some issues like memory leaks and others, they decided to use websockets directly instead, check these discussions :
https://github.com/adonisjs/discussion/issues/51
https://forum.adonisjs.com/t/integrating-socket-io-with-adonis-4/519
have you tried using socket.io without relying on Adonis ? ,
something like :
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html');
});
io.on('connection', function(socket){
console.log('a user connected');
});
http.listen(3000, function(){
console.log('listening on *:3000');
});
But you should be able to do this with Adonis by now according to : https://github.com/adonisjs/adonis-websocket-protocol
Example :
const filereader = require('simple-filereader')
const msgpack = require('msgpack-lite')
const packets = require('#adonisjs/websocket-packets')
const client = new WebSocket('ws://localhost:3000/adonis-ws')
client.onopen = function () {
// TCP connection created
}
client.onerror = function () {
// TCP connection error
}
client.onmessage = function (message) {
filereader(message, function (error, payload) {
const packet = msgpack.decode(payload)
handlePacket(packet)
})
}
function handlePacket (packet) {
if (packets.isOpenPacket(packet)) {
console.log('Server ack connection. Make channel subscriptions now')
}
if (packets.isJoinAck(packet)) {
console.log('subscription created for %s', packet.d.topic)
}
}
check this for broadcast examples using WS : https://github.com/websockets/ws#broadcast-example
Create start/socket.js file and paste following code inside it.
const Server = use('Server')
const io = use('socket.io')(Server.getInstance())
io.on('connection', function (socket) {
console.log(socket.id)
})
From Virk Himself in this forum:https://forum.adonisjs.com/t/integrating-socket-io-with-adonis-4/519
create a standalone socket io configuration file in start/socket.js
const io = require('socket.io')();
io.listen(3000);
io.on('connection', function (socket) {
console.log(socket.id)
})
to start your socket io server you can configure your server.js as below
new Ignitor(require('#adonisjs/fold'))
.appRoot(__dirname)
.preLoad('start/socket') //path of socket.js
.fireHttpServer()
.catch(console.error)
now when you start your server then it will start along with socket io
In Socket.IO you can send a message to *everyone* with the following:
const io = require("socket.io");
io.broadcast.emit("message", data);
You can also send a message to *everyone but one person* using a pattern like so:
const io = require("socket.io");
io.on("connection", function(socket) {
socket.on("message", function(msg) {
socket.broadcast.emit("message", msg);
});
});
This allows for the creation of simple chat apps and the like
Now suppose I have the socket handler defined in another file:
// index.js
const io = require("socket.io");
const handler = require("./handler.js");
io.on("connection", function(socket) {
socket.on("message", handler);
});
// handler.js
module.exports = function(socket) {
socket.broadcast.emit("message", msg);
}
In this situation, I want to send a message to everyone - including the socket that initiated the message. So given the socket, how do I get access to its parent io object?
I've tried some things like:
socket.io.broadcast
socket.server.broadcast
socket.server.eio.broadcast
etc
But none of these seem to work
What about passing in the io to the handler? Originally the handler function would accept a payload (msg in your handler) not a socket?
// index.js
const io = require("socket.io");
const handler = require("./handler.js");
io.on("connection", function(socket) {
socket.on("message", data => handler(io, data) );
});
// handler.js
module.exports = function(socket, msg) {
socket.broadcast.emit("message", msg);
}
Version 2 of post
Okay, first the file structure:
app
|___app.js
|___models/
|_user.js
|___routes/
|___admin.js
|___public/
|___js/
|___script.js
app.js:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
http.listen(3000, function (err){
if (err) throw err;
console.log("Server is running");
});
script.js:
var socket = io();
//Next, this tells the browser that is has connected to the socket.io server
socket.on('connect', function() {
console.log('Connected to socket.io server!');
});
socket.on('message', function(message){
console.log('New message: ');
console.log(message.text);
});
var $newUsername = $('#Username');
$newUsername.on('blur', function(event){
socket.emit('message', {
text: $newUsername.val()
});
});
So on a registration page, if a user enters a 'username' already in the database, it will console.log 'This user already exists'. At least, that's the idea.
There is still sockets.io code I need to add on the server side. I am trying to put it here:
admin.js:
var router = require('express').Router();
var User = require('../models/user');
io.on('connection', function(socket) {
console.log('User connected via socket.io!');
socket.on('message', function(message) {
Username.findOne({username: message.text}, function (err, existingUsername) {
if (existingUsername) {
console.log('This user already exists: ' + message.text);
}
});
});
});
So, as this stands, the sockets.io code in admin.js won't work because it can't access the io function. I would like to know how I can fix this.
To add to this: The sockets.io code I have in the admin.js file will work fine if I placed it in my app.js file.
The key here is to pass the io instance to any module that needs it when that module is first loaded. This is called the "push" method of sharing as you share by pushing data from one module to another by passing it in the constructor function of the other module.
There is also a "pull" module where one module asks some other module for some shared data by calling a method in that module.
Here's how you could implement the "push" model:
In your admin.js file, you define a constructor function that you call and pass the io instance to when you load it:
var router = require('express').Router();
var User = require('../models/user');
var io;
// define constructor function that receives the io instance so the rest
// of the module can use it
module.exports = function(ioInstance) {
io = ioInstance;
io.on('connection', function(socket) {
console.log('User connected via socket.io!');
socket.on('message', function(message) {
Username.findOne({
username: message.text
}, function(err, existingUsername) {
if (existingUsername) {
console.log('This user already exists: ' + message.text);
}
});
});
});
}
Then, in your app.js file:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
http.listen(3000, function (err){
if (err) throw err;
console.log("Server is running");
});
// when you load the admin.js file, you pass it the io instance
require('./routes/admin.js')(io);
Below are 2 answers. While both answers will technically work, it is advised that you do not use either of them. Use the answer provided above.
Answer 2
So my route files all have this:
var router = require('express').Router();
Now, in any of the route files where I need sockets.io I did this:
var http = require('http').Server(router);
var io = require('socket.io')(http);
And this got my sockets.io code working.
Answer 1
So to get sockets.io code working in my route files, I simply removed 'var' in front of my http and io variables.
I changed:
//Socket io config
var http = require('http').Server(app);
var io = require('socket.io')(http);
to this:
//Socket io config
http = require('http').Server(app);
io = require('socket.io')(http);
Now my sockets.io code in my route files work fine.