Call socket.io API from an external file - node.js

I am trying to call my Socket.io interface from another file that is express routes. Lets say I want to create the following group chat with this and then send a notification to the involved users in the group chat,
routes.js
router.post(
"/save-groupchat",
passport.authenticate("jwt", { session: false }),
(req, res) => {
new GroupChat({
creator: req.user._id,
groupName: req.body.groupName,
groupMembers: userList
})
.save()
.then(newGroup => {
GroupChat.findOne(newGroup)
.populate("groupMembers.user")
.then(groupChat => {
//Notify the user's as well
userList.map((user, key) => {
NotificationCenter.findOneAndUpdate(
{ owner: user.user },
{
$push: {
notifications: {
$each: [
{
type: "group-invite",
title:
"You have been invited to " +
req.body.groupName +
" by " +
req.user.name,
notificationTriggeredBy: req.user._id
}
],
$position: 0
}
}
}
)
.then(() => {
//How do I call the socket here <---------------------------------
console.log("Notified User: " + req.body.userList[key].name);
})
.catch(err => {
console.log(
"Error when inviting: " +
req.body.userList[key].name +
"\n " +
err
);
});
});
res.json(groupChat);
});
});
}
);
./microservice/chat/chat (Socket Interface)
And then my socket interface is like this,
let user_list = {};
io.sockets.on("connection", socket => {
socket.on("send-notification", notification => {
console.log("How do I get here from the other file?");
});
})
...
Here is how I have my server.js file
var http = require("http").Server(app);
var io = require("socket.io")(http);
ChatMicroservice = require("./microservice/chat/chat")(io);
How would I access the socket interface and use the user_list of sockets to send out the notifications?

To call socket.io API from an external file you should either create a singleton class and export it or save socket object to a global variable, and save the connected socket id in the database corresponding to the user. so whenever a socket connection is established to the server, the socket id ie, socket.id is saved in the database corresponding to the user-id.
so your socket interface becomes
global.io = io;
io.sockets.on("connection", socket => {
// add your method to save the socket id (socket.id)
// user-id passed can be accessed socket.handshake.query.userId
socket.on("send-notification", notification => {
console.log("How do I get here from the other file?");
});
})
In your other file call
global.io.to(id).emit(event, value);
If you are planning to scale your application horizontally use socket.io-redis in your Socket Interface
const redis = require('socket.io-redis');
global.io.adapter(redis({ host: redisUrl, port: redisPort }));

Related

Socket.IO is creating 2 socket IDS every time a new user joins a room instead of one

I am creating a collaborative react app, in that every time a new user is joining the room the socket io is generating 2 id's for every user, I have followed the documentation code, in the same way, I am not sure why is this happening, below is the snippet of the server-side code (server.js).
const cors = require('cors');
const axios = require('axios');
const {Server} = require('socket.io');
const http = require('http');
const ACTIONS = require('../src/Actions');
const app = express(); // Create an instance of express
const server = http.createServer(app) // Create an instance of http server
const io = new Server(server); // Create an instance of socket.io server
// Storing a client list
const clients = new Map();
// Switching on the server socket to listen for connections
io.on('connection', (socket) => {
const clientSocketId = socket.id;
console.log(clientSocketId+' connected');
socket.on(ACTIONS.JOIN,({roomId,username})=>{
console.log(roomId,username)
clients.set(socket.id,{
roomId,
username,
socketId: socket.id,
})
socket.join(roomId);
const clientlist = Array.from(clients.values())
clientlist.forEach(client=>{
io.to(client.socketId).emit(ACTIONS.JOINED,{
clientlist,
username,
socketId: socket.id,
})
})
})
// The server is listening to two events Code Change and Code Sync
// Code Change is emitted when the user changes the code
// Code Sync is called when the user joins the room to sync the previously typed code
socket.on(ACTIONS.CODE_CHANGE, ({ roomId, code }) => {
socket.in(roomId).emit(ACTIONS.CODE_CHANGE, { code });
});
socket.on(ACTIONS.SYNC_CODE, ({ socketId, code }) => {
io.to(socketId).emit(ACTIONS.CODE_CHANGE, { code });
});
// Disconnecting the current socket
socket.on('disconnecting',()=>{
console.log(clientSocketId+' disconnected')
// Getting the list of all the present rooms
const rooms = Object.keys(socket.rooms);
rooms.forEach(roomId=>{
socket.in(roomId).emit(ACTIONS.DISCONNECTED,{
socketId: socket.id,
username: clients.get(socket.id).username,
})
})
clients.delete(socket.id);
socket.leave();
})
})
const PORT = process.env.SERVER_PORT || 5000;
server.listen(PORT,()=>{console.log('Listening on '+PORT)});
And below is how I have initialized the socket on the client-side
export const initSocket = async () => {
const options = {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 'Infinity',
forceNew: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 10000,
autoConnect: true,
secure: true,
}
const socket = io(process.env.REACT_APP_SERVER_URL,options)
return socket
}
And in my Dashboard.js I have called the init function in UseEffect
React.useEffect(()=>{
// As the user joins the room we initialize the client socket which connects to the server
const init = async () => {
socketRef.current = await initSocket();
// Handling connection errors
socketRef.current.on('connect_error',(err)=>handleError(err))
socketRef.current.on('connect_failed',(err)=>handleError(err))
const handleError = (err)=>{
console.log(err)
toast({
title: 'Error connecting to the server',
status: 'error',
duration: 9000,
isClosable: true,
})
reactNavigater('/')
}
socketRef.current.emit(ACTIONS.JOIN,{
roomId: roomId,
username: location.state?.username,
});
// Listening for joined event when a even user joins
socketRef.current.on(ACTIONS.JOINED,({clientlist,username,socketId})=>{
if(username !== location.state?.username){
toast({
title: `${username} has joined the room`,
status: 'success',
duration: 9000,
isClosable: true,
})
}
setClientlist(clientlist)
socketRef.current.emit(ACTIONS.SYNC_CODE, {
socketId: socketRef.current.id,
code: codeRef.current,
});
})
// Listening for disconnected event when a even user disconnects
socketRef.current.on(ACTIONS.DISCONNECTED,({socketId,username})=>{
toast({
title: `${username} has disconnected`,
status: 'warning',
duration: 9000,
isClosable: true,
})
// Filter the clientlist to remove the disconnected client
setClientlist(Clientlist.filter(client=>client.socketId !== socketId))
}
)
}
init()
// Here we have multiple listeners, so we have to remove them when the component unmounts
return ()=>{
if(socketRef.current){
socketRef.current.disconnect()
socketRef.current.off(ACTIONS.JOINED)
socketRef.current.off(ACTIONS.DISCONNECTED)
}
}
},[])
Any help would be appreciated
If you have strict mode on, what's by default, then useEffect is called twice (from React 18). And your connection is created twice. And as every connection generates new Id, you get two id's.
https://stackoverflow.com/a/72238236/8522881
My React Component is rendering twice because of Strict Mode
You can just wrap your socket.io instance with a useRef so it will keep the same value between re-renders, like:
import React from 'react'
function MyComponent() {
const socket = React.useRef(null)
React.useEffect(() => {
if(!socket.current) {
socket.current = io('ws://my-url/');
}
}, [])
// now you can use socket.current and ensure you aways will have only one instance
return (
<p>hello</p>
)
}

Nodejs - Express - Socket.io How to send a message to a specific socket.id from an outside function?

I'm having problem finding the right answer for this. I'm trying to send a message to a specific socket.id given I'm handling multiple users but I need to do it from another function which does not have access to socket.io.
I need to send the message to the specific socket.id inside the function:
var authorizePublish = function(client, topic, payload, callback) {
//here
}
socketLib.js
/// Import Modules ///
const mosca = require('mosca');
const DeviceService = require('../services/device.service');
const config = require('../config');
const util = require('../modules/util.js');
module.exports = async function(httpServer, sessionParser) {
var io = require("socket.io")(httpServer); // For Sockets
io.use(function(socket, next) {
sessionParser(socket.request, socket.request.res, next);
});
io.sockets.on('connection', function(socket) {
const userSessionId = socket.request.session.userId;
const userId = socket.request.session.passport.user;
const socketId = socket.request.session.id;
if (userSessionId == '')
console.log('client connected');
console.log(`Client connected: ${userSessionId}, with userid: ${userId}, with socket: ${socketId} conectado`);
socket.on('msg:message', async function (data) {
socket.emit('message', data);
});
socket.on('disconnect', function(data) {
console.log('user disconnected');
});
});
/// Mosca Settings ///
var moscaServer = null;
var moscaSettings = {
interfaces: [ { type: "mqtt", port: 1884 }, { type: "http", port: 5000, bundle: true, static: './' }
],};
var debug = util.isDebug(),
isAuth = util.isAuth()
//// Mosca Server ////
var dbHost = config.dbHost;
moscaServer = new mosca.Server(moscaSettings);
moscaServer.on('ready', setup);
var authenticate = function(client, username, callback) {
console.log('-------- Authenticating MQTT user... --------');
callback(null, flag);
if(authenticate) client.user = username;
}
/// Mosca Events ///
moscaServer.on('published', function (packet, client) {
var arr = packet.topic.split('/');
if (arr.length !== 3) { return; }
});
/// Mosca Functions ///
var authorizePublish = function(client, topic, payload, callback) {
if (client.user == topic.split('/')[1]) {
// socket.emit('message', payload.toString()); (Here is where I need to get access to the client socket.id in order to send him a message with the payload.
callback(null, true);
}
else {
callback(null, false);
}
}
function setup() {
if (isAuth) {
moscaServer.authenticate = authenticate;
}
if (config.authPub === true) {
moscaServer.authorizePublish = authorizePublish;
}
if(config.authSubs == true) {
moscaServer.authorizeSubscribe = authorizeSubscribe;
}
console.log('Mosca server is up and running')
}
}
First off, the way you send to a specific socket id is with this:
io.to(id).emit(msg, data);
where id is the socket.id of the socket.io connection that you want to send to.
Or, you could put that into a function:
function sendToId(id, msg, data) {
io.to(id).emit(msg, data);
}
So, secondly to be able to do that from anywhere you have a couple options:
You can import the io instance and use it with the above line of code.
You can export a function called something like sendToId() from a module that does have access to the io instance and then you can import that function in order to be able to use it.
You can assign the io instance to a known object so that anyone with access to that more popularly known object can then retrieve it from there. An example of that is to set it as a properly on the Express app object as in app.set("io", io) and then anyone with access to the app object can you let io = app.get("io"); to get it and use it.

nodejs: does a find query to select n last records need to run asynchronously

I am a newbie in nodejs. I want to fetch n last records from mongo and write them to a socket when a(n android) client connects.
I wrote some code and it was okay when I tested it on a vps i had, but, after moving to new vps a problem appeared.
When the first client connects to the socket, it does not get the records. However, if a second client connects to the socket the find query runs again and the first client can see the related emit, but, not for second client!
I added a log after the io.emit command and it runs for every client connecting.
I also added another emit that just sends a test text and it delivered to client as soon as he connected.
My code:
const express = require('express'),
http = require('http'),
app = express(),
server = http.createServer(app),
io = require('socket.io').listen(server);
app.get('/', (req, res) => {
res.send('Chat Server is running on port ......')
});
var mongoose = require('mongoose');
var ChatMessage = require('./chatmessage');
var chatsdb = "mongodb://localhost:27017/chatsdb"
mongoose.connect(chatsdb, {useNewUrlParser: true});
mongoose.connection.on('connected', function () {
console.log('Mongoose connected!')
});
mongoose.connection.on('error', function (err) {
console.log('Mongoose default connection error:' + err);
});
io.on('connection', (socket) => {
let userId = socket.id;
console.log('user ' + userId + ' connected');//it always run
io.emit('connected_message', {"message_text": "ok!" , "user_id":userId});//it always run
ChatMessage.find().sort({created_at:-1}).limit(10).exec(function (err, posts) {
let list_of_messages = [];
for (let i = 0; i < posts.length; i++) {
if (posts[i] != null) {
let message = new ChatMessage({"message_text": posts[i].message_text, "name": posts[i].name , "created_at": posts[i].created_at});
list_of_messages.push(message);
}
}
io.emit('last_fifty_message', list_of_messages);
console.log("list_of_messages:" + list_of_messages); //it always run
});
});
server.listen(50000, () => {
console.log('Node app is running on port 50000')
});
and it's ChatMessage class:
var mongoose = require('mongoose');
const Schema = mongoose.Schema;
var ChatMessageSchema = new Schema({
name : { type: String, require:true },
message_text : { type: String , require:true },
created_at : { type:Date , default: Date.now },
message_id : { type: Number , autoIncrement:true }
});
ChatMessageSchema.pre('save',function(next){
this.created_at = new Date();
console.log('this.created_at : '+this.created_at)
next();
});
var ChatMessage = mongoose.model('ChatMessage' , ChatMessageSchema);
module.exports = ChatMessage;
I don't understand. If find records is a long process, how is it logged but not emitted and why is it emitted for clients that have connected already?
Does it need to run asynchronously? Can I use async/await or callbacks or ...??
You are using io.emit, io.emit is sending to all connected clients
For emitting only to the connected socket use socket.emit
By the way, you should check if error Isn’t null

reconnecting to nodejs websocket on failure

This is my first practice after reading some tutorials and videos. Basically, I need message to be sent from the server (nodejs) to the client (Angular 6). At first tho, when client app is booted, it sends user id to the server for authentication. The server then will send data based on that user.
Now my problem is on first load and a few calls, the connection does work. But then on refresh or so, the connection drops. My client does console out "retrying" but it never succeeds. It works only if I manually restart the server and reload the client so a new connection could be established.
How can I maintain a fairly stable connection throughout the lifetime of a client? At times the readyState stays at 3 on the server i.e. connecting, which I am confused with because the client does try to reconnect...just fails.
My Server is simple. index.js (Tried to put it up on stackblitz but failed...would appreciate if someone can figure out the dependency file: nodejs websocket server)
const express = require('express');
const bodyParser = require('body-parser');
const pg = require ('pg');
var ws = require('./ws')
var app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, function () {
console.log('We are running on port 3000!')
})
ws.js:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log'})
]
});
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({
port: 40511
});
let data = {
'packet': ['amy1', 'amy2', 'amy3']
}
const mx = 2;
const mn = 0;
wss.on("connection", function(ws) {
ws.on("message", function(user) {
// client has called now. If the connection
// fails, the client does try to connection again and again -- no limit but it simply doesn't seem to have effect. When connecting, it simply sends user name
console.log("received: %s", user);
setInterval(function(){
var random = Math.floor(Math.random() * (mx - mn + 1) + mn);
if (ws.readyState == ws.OPEN){
ws.send(data['packet'][random]);
}
}, 3000);
});
});
My front end is: service.ts
import { Observable} from 'rxjs';
export class WebSocketService {
socket: WebSocket;
constructor() { }
initConnection(): void {
if(!this.socket){
this.socket = new WebSocket('ws://localhost:40511');
// when connection is open, send user id
this.socket.onopen = () => this.socket.send(2);
}
}
manageState() : Observable<any>{
const vm = this;
return new Observable(observer => {
this.socket.onerror = (e) => {
// close it
this.socket.close();
observer.next('web socket communication closed due to error')
};
this.socket.onclose = (e) => {
//socket closed for any reason
setTimeout(function() {
console.log('try to connect')
vm.initConnection();
observer.next('still trying')
}, 1000);
}
});
}
onMessage(): Observable<any> {
// when message arrives:
return new Observable(observer => {
this.socket.onmessage = (e) => {
console.log(e.data);
observer.next(e.data)
};
});
}
}
component.ts:
// initialize the connection
this.service.initConnection();
this.service.onMessage().subscribe(
data => {
// we have got data
console.log('data came ', data)
},
err => {
console.log("error websocking service ", err);
}
);
// track state of the communication, letting the service to reconnect if connection is dropped
this.service.manageState().subscribe(data => {
console.log(data);
});

node-xmpp --- Create a Persistant and Private Chatroom

I need to create a Private and Persistent Chat room dynamically via Node that does not automatically delete itself.
I've searched the net and couldn't find much on how to do it. This is the code snippet I use to create the chatroom:
var cl = new xmpp.Client({
jid: jabber_creds.jid,
password: jabber_creds.password,
host: jabber_creds.host,
port: jabber_creds.port
});
cl.on('online', function() {
var room_jid = jabber_creds.room_jid.replace("%s", chatRoomName);
// join room (and request no chat history)
cl.send(new xmpp.Element('presence', { to: room_jid }).
c('x', { xmlns: 'http://jabber.org/protocol/muc' })
);
// create room
cl.send(new xmpp.Element('iq', { to: room_jid, id: 'create', type: 'set' }).
c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' }).
c('x', { xmlns: 'jabber:x:data',type: 'submit' })
);
});
Chat room persistence is handled at the server not the client. Yes the client can request that a server hold onto a chat room but you can't actually persist it from the client. Check with the server documentation that you are using to make sure it supports this.
Try to follow XEP-0045
http://xmpp.org/extensions/xep-0045.html#createroom
Just read The workflow for creating and configuring such rooms is as follows section
You need to do the next:
Send 1st presence to a new room
Server returns you a couple of messages that room was created, but you should unlock it
You should create configuration form and send to the server. In this form you can set 'Persistent' type & 'Only members' type
Server will return you success result if everything is OK
You can reference https://github.com/node-xmpp/node-xmpp/blob/master/examples/create_room.js and send configuration you want by XEP-0045
//create_room.js
'use strict'
var xmpp = require('../index')
,argv = process.argv
if (argv.length < 5) {
console.error('Usage: node create_room.js <my-jid> <my-password> <room-name>')
process.exit(1)
}
var cl = new xmpp.Client({ jid: argv[2], password: argv[3] })
cl.on('online', function(data) {
var userJid = data.jid.user + '#' + data.jid.domain,
roomJid = argv[4] + '#conference.' + data.jid.domain,
pres,
iq
console.log('Connected as ' + userJid + '/' + data.jid.resource)
console.log('Create room - ' + argv[4])
pres = new xmpp.Element(
'presence',
{ from: userJid, to: roomJid + '/' + data.jid.user })
.c('x', {'xmlns':'http://jabber.org/protocol/muc'})
cl.send(pres.tree())
iq = new xmpp.Element(
'iq',
{ to: roomJid, type: 'set' })
.c('query', { xmlns: 'http://jabber.org/protocol/muc#owner' })
.c('x', { xmlns: "jabber:x:data", type: "submit"})
//set room to be hidden by sending configuration. ref: http://xmpp.org/extensions/xep-0045.html
iq.c('field', { 'var': 'FORM_TYPE' })
.c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up()
.c('field', { 'var': 'muc#roomconfig_publicroom'})
.c('value').t('0').up().up()
cl.send(iq.tree())
// exit later for sending configuration done
setTimeout(function() {
cl.end()
}, 100)
})
cl.on('error', function(e) {
console.error(e)
process.exit(1)
})

Resources