nodejs express server stops unexpectedly - node.js

I have a server built with express and socket IO. The problem is that every time the server just stops responding 2 minutes after first client connection. Things I have already done/checked:
Express logs show no errors.
socket.IO logs show no errors on server side.
socket.IO logs show ping timeout on client side (after a few successful pings).
Listening to the uncaughtException/UnhandeledRejection doesn't help.
So, when I start the server, and GET /login through the browser, without doing anything else, the server will stop responding on its own after ~2 minutes, and the only way to close it is closing the cmd (CTRL+C doesn't work even after listening to SIGINT).
I'll post the relevant parts of the code here:
server.js
const http = require('http');
const express = require('express');
const socketIO = require('socket.io');
let app = express();
let server = http.createServer(app);
var io = socketIO(server);
app.get(`/login`, (req, res) => {
console.log(`got GET /login`);
res.sendFile(publicPath + '/login.html');
});
io.on(`connection`, (socket) => {
socket.on(`login`, async (details, cb) => {
login(details.username, details.password, async (res) => {
if (res === true) {
let user = new User(details.username, details.password);
let token = await user.generateAuthToken();
cb(`/set?token=${token}`);
} else if (res === false) {
socket.emit(`loginFailed`, 'Password incorrect. Please try again!')
} else if (res === `Username doesn't exist`) {
socket.emit(`loginFailed`, res)
}
});
});
socket.on('getAllLeads', async (callback) => {
leads = await getAllLeads();
callback(leads);
});
socket.on('getSomeLeads', async (options, callback) => {
leads = await getSomeLeads(options);
callback(leads);
});
socket.on(`newFile`, ({text, words, options, fileName}) => {
console.log('Starting...');
words.forEach((wordSet, index) => {
wordSet.forEach(word => {
regexp = `\\s+${word}+\\s`;
re = new RegExp(regexp);
text = text.replace(re, `${options[index]}`);
});
});
fs.writeFileSync(`${fileName}.txt`, text, 'utf8');
console.log(`Finished writing to ${fileName}.txt successfully!`);
});
});
server.listen(3000, () => {
console.log(chalk.yellow(`Server is up on port ${port}`));
});
login.js
const socket = io();
socket.on(`reconnect_error`, (err) => {
console.log(err);
});
socket.on(`connect`, () => {
console.log('connected to login page');
if (getQueryVariable('failed')) {
$('.error, .error p').css('opacity', 1);
$('.error p').text('Unauthorized! PLEASE log in.');
}
socket.on(`loginFailed`, (reason) => {
$('.error, .error p').css('opacity', 1);
$('.error p').text(reason);
console.log(`got login failed`);
});
$('#form').submit((e) => {
e.preventDefault();
let username = $('#username').val();
let password = $('#pass').val();
socket.emit(`login`, {username, password}, (path) => {
window.location.href = path;
});
});
});
function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) return pair[1];
}
return (false);
}

Related

Socket.io - server receives message but doesn't emit it to clients using nodejs and mongodb?

when i run the code, the server server side receive the message but the client side doesnt get anything until they send message. however in html doesnt show anything wrong
i have this server code:
var http = require('http').Server(app);
var io = require('socket.io')(http);
var cors = require('cors')
const { socket } = require('socket.io');
io.on('connection', () =>{
console.log('a user is connected')
})
i have this route:
var http = require('http').Server(router);
var io = require('socket.io')(http);
// Render Message
router.get('/messages/:id', async function (req, res, next) {
user = await User.findOne({_id: req.session.userId}, {username: 1})
return res.render("user/messages.ejs", {user: user, booking: req.params.id});
});
// Display Message from DB
router.get('/messageslist/:booking', (req, res) => {
Message.find({booking: req.params.booking})
.populate({
path: "pro",
model: Pro,
}).populate({
path: "user",
model: User,
}).exec().then((data) => {
res.json(data)
})
})
router.post('/messages', async (req, res) => {
const {booking, user, message} = req.body;
try {
var msg = new Message({
booking: booking,
message: message,
user: user
});
var savedMessage = await msg.save()
console.log('saved');
io.emit('message', req.body);
res.sendStatus(200);
} catch (error) {
res.sendStatus(500);
return console.log('error', error);
} finally {
console.log('Message Posted')
}
})
this is my html:
var socket = io();
$(() => {
$("#send").click(() => {
sendMessage({
booking: $("input[name=booking]").val(),
user: $("input[name=user]").val(),
message: $("#message").val()
});
})
getMessages()
})
socket.on('message', addMessages)
function addMessages(message) {
if (message.user && message.user != '') {
if ($("input[name=user]").val() == message.user._id) {
html = '<div class="msg right-msg"><div class="msg-img" style="background-image: url(' + message.user.image +
')"></div>'
html += '<div class="msg-bubble"><div class="msg-info"><div class="msg-info-name">' + message.user.username +
'</div><div class="msg-info-time">' + message.createdAt + '</div></div>'
}
} else {
html = '<div class="msg left-msg"><div class="msg-img" style="background-image: url(' + message.pro.image +
')"></div>'
html += '<div class="msg-bubble"><div class="msg-info"><div class="msg-info-name">' + message.pro.username +
'</div><div class="msg-info-time">' + message.createdAt + '</div></div>'
}
html += '<div class="msg-text">' + message.message + '</div></div></div>'
window.scrollTo(0, document.body.scrollHeight);
$("#msger-chat")+$(".msger-chat").append(html)
}
function getMessages() {
$.get('http://127.0.0.1:3000/messageslist/<%=booking%>', (data) => {
data.forEach(addMessages);
})
}
function sendMessage(message) {
$.post('http://127.0.0.1:3000/messages', message)
}
When i run it, my server outputs: User connected and message saved;. But my client doesn't get a response
I think the problem is you are creating two io instances. One in server code
var http = require('http').Server(app);
var io = require('socket.io')(http);
var cors = require('cors')
const { socket } = require('socket.io');
io.on('connection', () =>{
console.log('a user is connected')
})
another one is in route handlers
var http = require('http').Server(router);
var io = require('socket.io')(http);
You should have a singleton io object which is responsible to handle all the activities. This is an approach how to create such design. you will have mainSocketServer
createSocketServer = (server) => {
// this is how you implemented
var io = require('socket.io')(http);
// this is the part to create singleton object
serverStore.setSocketServerInstance(io);
io.on("connection", (socket) => {
// this socket obj will include
about connected user. this socket is actually connected user client
console.log('a user is connected')
socket.on("direct-message", (data) => {
// you create socket events handler in different file
directMessageHandler(socket, data);
}); });};
module.exports = {
createSocketServer,
};
you need another file where you store all the connected users. usually in socket server, users are stored in a Map
const connectedUsers = new Map();
let activeRooms = [];
let io = null;
// we call this in mainSocketServer file
const setSocketServerInstance = (ioInstance) => {
io = ioInstance;
};
// you export this and use it anywhere on your server
const getSocketServerInstance = () => {
return io;
};
On the client side where you listen for sockets, you do not pass reference to the function. 2nd argument is callback function that has received data as parameter and you should call addMessage from inside. Here is the example code:
socket.on('message', (data) => {
addMessages(data)
})
Just update this in front end code and it should work

Generate custom socket id for socket.io

According to socket.io documentation for generating custom id this should work:
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
require('dotenv').config();
io.engine.generateId = (req) => {
// generate a new custom id here
var randomCode = '';
for (let r = 0; r < 6; r++) {
randomCode += Math.floor(Math.random() * 10)+'';
}
return randomCode
}
io.on('connection', (socket) => {
console.log("a user connected :D", socket.id);
socket.on('storeClientInfo', (data) => {
console.log(socket.id)
});
socket.on("private message", (anotherSocketId, msg) => {
console.log(anotherSocketId, msg)
socket.to(anotherSocketId).emit("private message", socket.id, msg);
});
});
server.listen(4000);
app.listen(process.env.PORT, () => console.log('Server is running'));
but it doesn't work for me the console outputs:
a user connected :D wJ8d-SC2su9YYCdWAAAC
it should be 6 random numbers, the random number is just for testing I want to assign the id to the user id in the database later.
use can assign custom params to socket
var users = []
io.use((socket, next) => {
socket.customId = 123456789;
users[socket.customId] = socket;
next()
});
and you can use custom params in
io.on("connection", (socket) => {
socket.on("sendPrivateMessage", params => {
users[params.toUserId].emit("message", "you have a private message")
})
});

Using Sockets.io with React.js + Hooks; sockets.emit Not Working

I am at a complete loss as to why this is not working, I am new to using Hooks in react however want to try.
This app basically connects sockets.io to the server using sockets-auth server emits the time every 1s <- this shows me that the connection is definitely live. It then uses sockets.emit to receive an array of data, this functions. The part that doesn't is a simple button press fails to achieve anything (see both 'Save Changes' buttons at bottom of React element). No error, no response, no acknowledgment at all. I have tried it inline, in its own function. The exact same server routes are functional simultaneously running a separate app with class components.
Help would be much appreciated. Anyway here is some code..
Client-Side (React.js)
import React, { Component,useState, useEffect } from "react";
import io from "socket.io-client";
import './Secret.css'
const Secret = () => {
const [time, setTime] = useState('');
const [userlist, setUserlist] = useState([]);
const socketUrl = 'http://localhost:3001';
let socket = io(socketUrl, {
autoConnect: false,
});
useEffect(() => {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'React POST Request Example' })
};
fetch('/api/connectsocket', requestOptions)
.then(async response => {
const data = await response.json();
if (!response.ok) {
const error = (data && data.message) || response.status;
return Promise.reject(error);
}
console.log(data)
connectSocket(data)
})
.catch(error => {
console.error('There was an error!', error);
});
}, [socketUrl]);
const connectSocket= (data)=>{
let dataUser = data
console.log(dataUser.email)
let error = null;
socket.on('connect', () => {
console.log('Connected');
socket.emit('authentication', {
token: dataUser.email,
i: dataUser.i
});
});
socket.on('unauthorized', (reason) => {
console.log('Unauthorized:', reason);
error = reason.message;
socket.disconnect();
});
socket.on('disconnect', (reason) => {
console.log(`Disconnected: ${error || reason}`);
error = null;
});
socket.on("admin connected", data => {
socket.emit('admin userlist', { get:'x',for:'who' }, (error) => {
if(error) {
alert(error);
}
});
console.log(data)
});
socket.on("admin user list", data => {
setUserlist(data)
console.log(data)
});
socket.on("FromAPI", data => {
setTime(data)
console.log(data)
});
socket.open();
}
const sendMessage = (x)=>{
console.log(x)
socket.emit('hello', 'JSON.stringify(x)');
}
const doSomething = ()=>{
socket.emit('chatmessage', {msg:"Hello"});
}
return (
<div>
<div className="outerContainer">
<div className="container">
{time}
<button onClick={sendMessage}>Save Changes</button>
<button onClick={doSomething}>Save Changes</button>
{userlist.map(user => (
<tr key={user.id} value={user.id}>
<td>{user.email}</td>
<td>{user.email}</td>
<td>{user.email}</td>
</tr>
))}
</div>
</div>
</div>
);
}
export default Secret
Server-side (Node.js)
const http = require('http');
const cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const io = require('socket.io')();
const socketAuth = require('socketio-auth');
//custom modules
const router = require('./modules/router');
const db = require('./modules/db.js');
const prettyDate = require('./modules/prettyDate.js');
const sortArray = require('./modules/sortArray.js');
const app = express();
const server = http.createServer(app);
io.attach(server);
app.use(cors()); ///delete for production
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload({
createParentPath: true
}));
app.use(bodyParser.json());
app.use(cookieParser());
app.use(router);
app.use(express.static('public'))
db.i12345.sessions.persistence.setAutocompactionInterval(400)
////Sessions Store; not important if you want to run the code without
function setUser(id,user,i){
console.log('setuser')
let institution = i
db[institution].sessions.find({ user:user }, function (err, docs) {
if(docs.length){
db[institution].sessions.update({user:user }, { $set: { id: id } }, { multi: true }, function (err, numReplaced) {
});
} else {
var d = new Date();
var n = d.getTime();
var doc = { user: user
, id: id
, t:n
};
db[institution].sessions.insert(doc, function (err, newDoc) {
});
}
});
}
///user verification; could easily mock; if wanting to actually run code
async function verifyUser (token,i) {
var institution =i
return new Promise((resolve, reject) => {
// setTimeout to mock a cache or database call
setTimeout(() => {
var user = new Promise((resolve,reject) =>{
db[institution].users.find({email:token}, function (err, docs) {
if(docs.length){
resolve(docs);
} else {
reject(false)
}
});
});
if (!user) {
return reject('USER_NOT_FOUND');
}
return resolve(user);
}, 200);
});
}
//// Sockets auth implementation
socketAuth(io, {
authenticate: async (socket, data, callback) => {
const { token, i } = data;
console.log(data)
try {
const user = await verifyUser(token, i);
socket.user = user;
socket.i = i
console.log(i)
return callback(null, true);
} catch (e) {
console.log(`Socket ${socket.id} unauthorized.`);
return callback({ message: 'UNAUTHORIZED' });
}
},
postAuthenticate: async (socket) => {
console.log(`Socket ${socket.id} authenticated.`);
console.log(socket.user)
setUser(socket.id,socket.user, socket.i)
socket.emit('empty list', {queryList:true});
io.emit('admin connected', {admin:true});
socket.conn.on('packet', async (packet) => {
if (socket.auth && packet.type === 'ping') {
}
});
socket.on('chatmessage', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});
socket.on('hello', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});
socket.on('get tasks', (get) => {
let i = socket.i
let user = socket.user
getTasksChunk(get,i,user)
});
socket.on('admin userlist', (get) => {
let i = socket.i
let user = socket.user
adminGetUserList(get,i,user)
});
socket.on('admin roles', (data) => {
let i = socket.i
let user = socket.user
console.log(data)
console.log('reaced')
});
interval = setInterval(() => getApiAndEmit(socket), 1000);
},
disconnect: async (socket) => {
console.log(`Socket ${socket.id} disconnected.`);
if (socket.user) {
}
},
})
function getTasksChunk(get,i, user){
console.log(get,i,user);
let institution = i
db[institution].tasks24.find({}, async function (err, docs) {
if(docs.length){
for(i=0;i<docs.length;i++){
docs[i].location = `Ward: ${docs[i].location.Ward} Bed: ${docs[i].location.Bed}`
docs[i].patient = docs[i].patient.join(' | ')
docs[i].timestamp = prettyDate(new Date(+docs[i].timestamp))
}
console.log(docs)
let sorted = await sortArray(docs,'timestamp')
let chunk = sorted.slice(0,10)
io.emit('tasks in', chunk);
} else {
}
});
}
const getApiAndEmit = socket => {
const response = new Date();
// Emitting a new message. Will be consumed by the client
socket.emit("FromAPI", response);
};
///////ADMIN????????
function adminGetUserList(get,i,user){
var institution = i
console.log('hello')
db[institution].users.find({}, async function (err, docs) {
if(docs.length){
console.log(docs)
let sorted = await sortArray(docs,'timestamp')
io.emit('admin user list', sorted);
} else {
}
});
}
server.listen(process.env.PORT || 3001, () => console.log(`Server has started.`));
You must store your socket instance in a ref otherwise you would loose the connected socket instance when your component re-renders.
In short you need the socket reference to be the same across renders
const socketUrl = 'http://localhost:3001';
let socket = useRef(null);
useEffect(() => {
socket.current = io(socketUrl, {
autoConnect: false,
});
...
}, [socketUrl]);
Now note that whereever you are using socket you would use socket.current.
Ex:
socket,.current.on('hello', (msg) => {
io.emit('chatmessage', msg);
console.log(msg);
});

Node js Socket.iO accessing socket outside for multiple client

How can we access socket object outside for multiple socket connection. I created a object globally and tried to do this. But it always works for last connected socket.
'use strict';
const path = require('path')
const express = require('express');
const http = require('http');
const chalk = require('chalk');
const socketio = require('socket.io');
var connectionString = '';
const eventHubConsumerGroup = ""
const app = express()
const server = http.createServer(app ,() => {
console.log(chalk.green('Server created'))
})
const io = socketio(server)
const port = process.env.port || 3000
const publicDirectoryPath = path.join(__dirname , '../public')
var server_token = "1234567890";
app.use(express.static(publicDirectoryPath))
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
console.log(socket.handshake.query.deviceID)
console.log('on user connected '+socket.id);
//report = new Report(socket);
socket.auth = false;
socket.on('authenticate',function(token){
console.log('token recieved is '+token);
if(server_token == token){
socket.auth = true;
console.log('connection is authenticated '+socket.id);
socket.emit("authenticate",true);
} else {
console.log("Connection not established")
socket.emit("authenticate",false);
}
})
socket.on('sendSocketEvent' , message => {
console.log(chalk.yellowBright(`Message recieved from ${socket.id} + ${message}`));
io.to(socket.id).emit('recieveSocketEvent', `Hello test`);
})
socket.on('disconnect',function(){
console.log('one user disconnected '+socket.id);
})
setTimeout(function(){
if(!socket.auth){
console.log('disconnecting the socket '+socket.id);
socket.emit("timeOut");
socket.disconnect();
}
},1000);
})
server.listen(port,() => {
console.log(chalk.redBright(`Server is up on port ${port}`))
})
var printMessage = function (message) {
console.log(JSON.stringify(message));
console.log(message.DeviceId);
if (localSocket != null){
if (message.DeviceId == localSocket.handshake.query.deviceID) {
localSocket.emit('recieveSocketEvent', message);
}
}
};
class EventHubReader {
constructor(connectionString, consumerGroup) {
this.connectionString = connectionString;
this.consumerGroup = consumerGroup;
this.eventHubClient = undefined;
this.receiveHandlers = undefined;
}
async startReadMessage(startReadMessageCallback) {
try {
console.log(this.connectionString)
const client = await EventHubClient.createFromIotHubConnectionString(this.connectionString);
console.log('Successfully created the EventHub Client from IoT Hub connection string.');
this.eventHubClient = client;
const partitionIds = await this.eventHubClient.getPartitionIds();
console.log('The partition ids are: ', partitionIds);
const onError = (err) => {
console.error(err.message || err);
};
const onMessage = (message) => {
const deviceId = message.annotations['iothub-connection-device-id'];
return startReadMessageCallback(message.body, message.enqueuedTimeUtc, deviceId);
};
this.receiveHandlers = partitionIds.map(id => this.eventHubClient.receive(id, onMessage, onError, {
eventPosition: EventPosition.fromEnqueuedTime(Date.now()),
consumerGroup: this.consumerGroup,
}));
} catch (ex) {
console.error(ex.message || ex);
}
}
// Close connection to Event Hub.
async stopReadMessage() {
const disposeHandlers = [];
this.receiveHandlers.forEach((receiveHandler) => {
disposeHandlers.push(receiveHandler.stop());
});
await Promise.all(disposeHandlers);
this.eventHubClient.close();
}
}
var { EventHubClient, EventPosition } = require('#azure/event-hubs');
const eventHubReader = new EventHubReader(connectionString, eventHubConsumerGroup);
(async () => {
console.log("Step1")
await eventHubReader.startReadMessage((message, date, deviceId) => {
console.log("Here getting called");
try {
const payload = {
IotData: message,
MessageDate: date || Date.now().toISOString(),
DeviceId: deviceId,
};
printMessage(payload);
} catch (err) {
console.error('Error broadcasting: [%s] from [%s].', err, message);
}
});
})().catch();
the problem is in condition "printMessage" . here I am trying to restrict the emit based on socket deviceID, but it's only working for last connected socket.
Can You please help me in this.
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
})
You're overwriting the same variable, on each new connection, which means it will always point to the last socket connected.
What exactly do you want to do? To send this message to all connected sockets?

NodeJS + WS access currently running WS server instance

I have implemented a simple REST API using NodeJS, ExpressJS and routing-controllers. I have also implemented a basic WebSocket server running alongside the REST API and using WS.
const app = express();
app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
useExpressServer(app, {
controllers: [
UserController
]
});
const server = app.listen(21443, (err: Error) => {
console.log("listening on port 21443");
});
const wss = new WebSocket.Server({server});
wss.on("connection", (ws: WebSocket) => {
ws.on("message", (message: string) => {
console.log("received: %s", message);
ws.send(`Hello, you sent -> ${message}`);
});
ws.send("Hi there, I am a WebSocket server");
});
My question is how to I get access to the currently running WS instance so that I am able to send or broadcast from my controller methods. I have a number of POST methods that run long processes and so return a HTTP 200 to the client, I then would like to either send or broadcast to all connected WS clients.
What is the correct way to access the WebSocket.Server instance from within my controller classes?
You can create the websocket earlier and pass the instance around:
const notifier = new NotifierService();
notifier.connect(http.createServer(app));
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
app.use(routes(notifier))
Full code:
app.js
Pass the websocket to the other routes:
const express = require("express");
const http = require("http");
const NotifierService = require("../server/NotifierService.js");
const routes = require("./routes");
const app = express();
const server = http.createServer(app);
const notifier = new NotifierService();
notifier.connect(server);
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
// to demonstrate how the notifier instance can be
// passed around to different routes
app.use(routes(notifier));
server
.listen(4000)
.on("listening", () =>
console.log("info", `HTTP server listening on port 4000`)
);
NotifierService.js class that handles the websocket
const url = require("url");
const { Server } = require("ws");
class NotifierService {
constructor() {
this.connections = new Map();
}
connect(server) {
this.server = new Server({ noServer: true });
this.interval = setInterval(this.checkAll.bind(this), 10000);
this.server.on("close", this.close.bind(this));
this.server.on("connection", this.add.bind(this));
server.on("upgrade", (request, socket, head) => {
console.log("ws upgrade");
const id = url.parse(request.url, true).query.storeId;
if (id) {
this.server.handleUpgrade(request, socket, head, (ws) =>
this.server.emit("connection", id, ws)
);
} else {
socket.destroy();
}
});
}
add(id, socket) {
console.log("ws add");
socket.isAlive = true;
socket.on("pong", () => (socket.isAlive = true));
socket.on("close", this.remove.bind(this, id));
this.connections.set(id, socket);
}
send(id, message) {
console.log("ws sending message");
const connection = this.connections.get(id);
connection.send(JSON.stringify(message));
}
broadcast(message) {
console.log("ws broadcast");
this.connections.forEach((connection) =>
connection.send(JSON.stringify(message))
);
}
isAlive(id) {
return !!this.connections.get(id);
}
checkAll() {
this.connections.forEach((connection) => {
if (!connection.isAlive) {
return connection.terminate();
}
connection.isAlive = false;
connection.ping("");
});
}
remove(id) {
this.connections.delete(id);
}
close() {
clearInterval(this.interval);
}
}
module.exports = NotifierService;
routes.js
const express = require("express");
const router = express.Router();
module.exports = (webSocketNotifier) => {
router.post("/newPurchase/:id", (req, res, next) => {
webSocketNotifier.send(req.params.id, "purchase made");
res.status(200).send();
});
return router;
};
List of connected clients are stored inside wss object. You can receive and loop through them like this:
wss.clients.forEach((client) => {
if (client.userId === current_user_id && client.readyState === WebSocket.OPEN) {
// this is the socket of your current user
}
})
Now you need to somehow identify your client. You can do it by assigning some id to this client on connection:
wss.on('connection', async (ws, req) => {
// req.url is the url that user connected with
// use a query parameter on connection, or an authorization token by which you can identify the user
// so your connection url will look like
// http://example.com/socket?token=your_token
ws.userId = your_user_identifier
....
})
To broadcast use:
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
If your controller and socket will be in different files (and I am sure they will), you will have to export the wss object in your socket file and import it in controller.

Resources