Socket.io emitting multiple times to client - node.js

Basically we use the socket to call deliverers for an app made in flutter, in the part of the app we made the socket work in the foreground service, however with the loss of internet connection the socket accumulates the emits and ends up crashing the app causing a memory leak.
I'm out of ideas to fix this now.
In the server side
global.ioDeliveryman = require('./socketio.js').init(deliveryManServer, {
pingInterval: 10,
pingTimeout: 5,
});
global.ioDeliveryman.on('connect', function (socket) {
socket.on('USER_CONNECTED', function(data){
global.connectedDeliverymanUsers[data.id] = {socket_id: socket.conn.id , aceite: data.aceite}
});
socket.on('USER_DISCONNECTED', function(data){
if(global.connectedDeliverymanUsers[data.id]){
try {
global.ioDeliveryman.sockets.connected[global.connectedDeliverymanUsers[data.id].socket_id].disconnect();
delete global.connectedDeliverymanUsers[data.id]
} catch (TypeError) {
console.log("Disconnect of undefined");
}
}
});
});
Client side
if (!socket.connected) {
conectar();
var jsao;
socket.on(describeEnum(SocketFunction.ALOCAR_ENTREGADOR), (data) async => {
socket.emit(describeEnum(SocketFunction.PONG), { "id": userId } ),
Vibration.vibrate(pattern: [1, 1000, 500, 2000]),
//the var jsao is setted here
NotificationUtils.showNotificationNovaCorrida(jsao),
await FGS.ForegroundService.sendToPort(jsao),
// check if app it's in the background aind wait 45 seconds to
Timer(Duration(seconds: 43), () async {
await FGS.ForegroundService.sendToPort('{"order": {"funcao": "VERIFICAR_BACKGROUND"}}');
if (isBackground) {
Timer(Duration(seconds: 2), () {
enviarAceiteEntregador(false, "${data["order"]["user_id"]}");
});
}
isBackground = true;
}),
});
Emit inside a function callback on backend
here we do ping pong to see if the user is connected, but this memory leak sometimes happens
it`s done in a loop with callback to cycle through the available deliverers one by one until one of them accepts
// PING
try{
if (alertMap.get(entregador.deliveryman_id.toString())) {
callback();
return;
}
//emit for deliveryman with socketId
global.ioDeliveryman.to(socketId).emit('ALOCAR_ENTREGADOR', {order: actualOrder, clientName: clientName});
} catch (TypeError) { console.log("Disconnect of undefined"); }
// PONG
pongMap.set(entregador.deliveryman_id.toString(), false);
everything works until the moment when the delivery person loses connection, then the socket starts to emit madly

Related

How to listen to socketIO private message in React client?

I have a SocketIO instance in an Express app, that listens to a React client requests. A user can send private messages to a specific person. The server receives the private message, and should dispatch it back to both sender & recipient thanks to the io.to(socketId).emit(content) method.
How to listen to this event in React and update the message array? In order to ease the process, I have created a connectedUsers object, whose keys are mongoDB's user._id, and whose values are the unique socketID generated by socketIO. This way, I can easily address message to specific persons in the client. Once sent, the messages are stored in a MongoDB database.
Here is the back-end. The point of interest is io.on("privateMessage")
const connectedUsers = {};
const socketManager = (io) => {
io.on("identifyUser", (user) => {
if (!([user.id] in connectedUsers)) {
connectedUsers[user.id] = io.id;
}
});
io.on("privateMessage", (data) => {
io.to(connectedUsers[data.recipientId]).emit(data.message);
io.to(connectedUsers[data.senderId]).emit(data.message);
});
io.on("disconnect", () => console.log("user disconnected!"));
};
Here is the listening function in React. Everything works but the "privateMessage" part.
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connect", () => {
socket.emit("identifyUser", { id: res.data._id });
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
});
} catch (err) {
throw new Error(err);
}
}
Thanks for your help!
I think you need to put the socket.on("privateMessage") part outside the socket.on("connect") scope.
React must load all events at the beginning.
The backend side must be responsible for the authorization.
For the client there is connection event, not connect.
Subscription to event privateMessage should be outside connection callback.
This code should work. Hope this helps
import io from 'socket.io-client'
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connection", () => {
socket.emit("identifyUser", { id: res.data._id });
});
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
} catch (err) {
throw new Error(err);
}
}

Difference between inbound and outbound sockets in node

I am writing a function that will make sure a node instance is gracefully shut down.
To do that, I make sure I unref() all sockets. This is what I am doing:
function tidyUp(){
console.log("Closing the server...");
server.close();
console.log("Ordering a hotplate shutdown...");
process.emit( 'hotplateShutdown');
// This will give time to server.close() to actually work..
setTimeout( function() {
console.log("Calling unref() for all Sockets and for the Server:");
wtf.dump();
var handles = Array.prototype.slice.call( process._getActiveHandles() );
handles.forEach( ( h, i ) => {
var name = h.constructor.name;
if( h.unref && typeof h.unref == 'function' & ( name == 'Socket' || name == 'Server' ) ){
console.log("Unreffing:", i );
h.unref();
}
});
console.log("After unreffing:");
wtf.dump();
setTimeout( function(){
console.log("This process should soon close");
console.log("Here is the event queue keeping it alive:");
wtf.dump( true );
}, 1000);
}, 1000 );
};
I am concerned because the server also sends email, and I want to make absolute sure that any that is being sent is indeed sent.
Basically:
"Give a Socket object, how do you tell if it's an INBOUND socket (one receiving connections, as node's HTTP server would open) or an OUTBOUND socket (one open by nodemailer to send an email)."
I will want to unref() all inbound sockets, and leave the outbound ones in peace, till all email has been sent.
Hints?
I ended up keeping track of the inbound sockets opened:
var server = http.createServer(app);
// Keep track of all inbound sockets. They will be
// unref()ed at cleanup time
var inboundSockets = {};
var socketId = 0;
server.on('connection',function( socket ){
socket.__id = socketId ++;
inboundSockets[ socket.__id ] = socket;
socket.on('close',function(){
delete inboundSockets[ socket.__id ];
});
});
process.on('uncaughtException', function (err) {
console.error( "Error caught: ");
console.error(err);
tidyUp();
})
process.on('SIGTERM', function(){
console.log("TERMINATING THIS INSTANCE!");
tidyUp();
});
function tidyUp(){
console.log("Closing the server...");
server.close();
// The next line is important AS IS as it will tell naps that the
// server is no longer functional
console.log("THE SERVER HAS STOPPED");
console.log("Ordering a hotplate shutdown...");
process.emit( 'hotplateShutdown');
// This will give time to server.close() to actually work..
setTimeout( function() {
console.log("Calling unref() for inbound sockets:");
Object.keys( inboundSockets ).forEach( ( __id ) => {
var s = inboundSockets[ __id ];
console.log("Unreffing inbound socket:", s.__id );
s.unref();
});
db.unref();
console.log("After unreffing:");
wtf.dump();
setTimeout( function(){
console.log("This process should soon close");
console.log("Here is the event queue keeping it alive:");
wtf.dump( true );
}, 5000);
}, 1000 );
};
Note that I assign an ID to the socket at creation time (there doesn't seem to be a clear universal way to get a unique ID) and then unref them when the server goes down...

Emit multiple messages socket.io from one open connection

I am new to socket.io, and trying to figure out how to send multiple messages. Here is the scenario I am working on,
function setupServer(server) {
var socketIO = require("socket.io").listen(server);
socketIO.sockets.on('connection', function (socket) {
console.log("client is connected");
socket.emit('update', { progress: "starting..." })
});
}
I have to call setupServer(server) from another method, and I am receiving "starting..." on client side.
But the problem is, i want to send more/multiple messages LATER ON. Can not send array of messages as my application is building messages strings in say every 10 milliseconds (in a callback function) and i want to send as soon as they are created.
Any solution? Is it possible to get the socket object reference to reuse outside this function?
From socket.io docs: http://socket.io/docs/#broadcasting-messages
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.
function setupServer(server) {
var socketIO = require("socket.io").listen(server);
socketIO.on('connection', function (socket) {
console.log("client is connected");
socket.emit('update', { progress: "starting..." });
startBroadCastFromSocket(socket, 3);
socket.on('disconnect', function(){
stopBroadcastFromSocket(socket);
});
});
// broadcast message to all sockets
setInteval(function() {
broadcastMessageToEveryone(socketIO, {body: 'Hello everyone (FROM SERVER)', timestamp: new Date());
}, 1000);
}
function broadcastMessageToEveryone(io, body) {
io.emit('message', {body: body, timestamp: new Date()});
}
function broadcastMessageFromSocket(socket, body) {
socket.broadcast.emit('message', {body: body, timestamp: new Date()});
}
var socketIntervals = {};
function stopBroadcastFromSocket(socket) {
if(socketIntervals[socket.id]) {
clearInterval(socketIntervals[socket.id]);
}
}
function startBroadcastFromSocket(socket, seconds) {
socketIntervals[socket.id] = setInterval(function(){
broadcastMessageFromSocket(socket, 'Hello!');
}, seconds*1000);
}

React and Socket.io: Able to get initial data - but view doesn't update when a new post comes in

Not sure if the issue is how I have my sockets setup - or if I am incorrectly trying to render the data with React.
I can successfully pull in data with my socket - yet it doesn't live update state when new data is posted to the server. My intention is for the state in React to automatically render new data, which is always live because of the socket connection.
Here is my client app that gets messages from the server and renders them:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
<MessageForm submitMessage={this.submitMessage}/>
</div>
);
}
});
Just in case here is my server code that emits data:
io.on('connection', function (socket) {
socket.on('getMessages', function (data) {
Message.find(function(err, messages){
socket.emit('serverMessages', messages);
})
});
});
As of right now, you're "just" grabbing data from the server once the component has been loaded. To get something a bit more "real time" you'll want to either ping the server with the same emit statement you specified regularly (which defeats the point of using websockets, really, you could use long-polling) or have the server regularly send new data to all clients.
You can do EITHER:
A) Client side: "Polling" for information [Not Ideal]
Note: I initially put this in my answer because I saw the OP was "polling" when the controller was loaded. I didn't click on that this might be because the controller may not be loaded with the websocket so sending data on connect might not work here. My bad.
Replace socket.emit('getMessages') with something that will "poll" the websocket regularly for data:
setInterval(function () {
socket.emit('getMessages')
}, 10000); /* Request data from the socket every 10 seconds */
OR
B) Server side: Send new data as it becomes available. [Best Way]
Track all clients via a clients array and delete them from it when their session ends.
var clients = [];
io.on('connection', function (socket) {
clients.push(socket);
socket.on('end', function () {
// Could also splice the array below, but it still works.
delete clients[clients.indexOf(socket)];
});
/* Previous logic for server goes here */
});
Run this code when you need to push new messages from the database/data storage:
for (var i in clients) {
clients[i].emit('serverMessages', /* messages object */);
}
Your server code is only firing upon initial socket connection.
Server:
socket.on('getMessages', function (data) {
Message.find(function(err, messages){
socket.emit('serverMessages', messages);
})
});
Client:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
</div>
);
}
});
Based on naming convention, it also appears that your Message.find() is pulling a single message. I would recommend clarifying the labeling to match cardinality.
Try this:
var Chat = React.createClass({
getInitialState: function() {
return {
messages: null
}
},
componentWillMount: function(){
var self = this;
socket.emit('getMessages');
socket.on('serverMessages', function (data) {
self.setState({messages: data})
});
},
render: function() {
var messages = this.state.messages ? <MessageList messages={this.state.messages}/> : null
return (
<div className="jumbotron">
{ messages }
<MessageForm submitMessage={this.submitMessage}/>
</div>
);
}
});
Could it be possible its due to the componentWillMount lifecycle method? Could you try the componentDidMount instead.
It looks like render will see the state update but only gets executed once despite the state change according to facebook.

node.js setInterval doesn't work

With node.js, I'm trying to send the current server_time to all clients in every second.
Therefore, I wanted to use setInterval() to emit an event to all clients and sending the time, but it doesn't work. Did I define the setInterval function at the right place or did missed something else?
var http = require("http");
var socketIO = require('socket.io');
var connect = require('connect');
//keep track of every connected client
var clients = {};
//create Server
var httpServer = connect.createServer(
connect.static(__dirname)
).listen(8888);
//socket
var io = socketIO.listen(httpServer);
io.sockets.on('connection', function (socket) {
//add current client id to array
clients[socket.id] = socket;
socket.on('close', function() {
delete clients[socket.fd]; // remove the client.
});
//send news on connection to client
socket.emit('news', { hello: 'world' });
//this one works fine!
//send server time on connection to client
socket.emit("server_time", { time: new Date().toString() });
});
//this doesn't work!
// Write the time to all clients every second.
setInterval(function() {
var i, sock;
for (i in clients) {
sock = clients[i];
if (sock.writable) { // in case it closed while we are iterating.
sock.emit("server_time", {
console.log("server_time sended");
time: new Date().toString()
});
}
}
}, 1000); //every second
May I suggest a workaround/improvement that should fix the problem. Add the clients to a chat room. Somewhere in:
io.sockets.on('connection', function (socket) {
add a
socket.join('timer');
Then the setIntervall would be
setInterval(function() {
io.sockets.in('timer').emit("server_time", { time: new Date().toString() })
}, 1000);
Hope this works for you!
The problem is the following function:
if (sock.writable) { // in case it closed while we are iterating.
sock.emit("server_time", {
// console.log("server_time sended"); // get rid of this line -> invalid code
time: new Date().toString()
});
}
sock.writable is undefined and therefore the emit event is never sent. Set the property to true on connection and to false on close.

Resources