Data through socket.io sent by server displays unpredictably - node.js

In my case, Socket.io acts very unstably. It is needed to make it more debuggable and stable.
Introduction
There is a Node'js based backend (Strapi CMS), wherein bootstrap.js I have a right to run my custom code - in my case the socket server:
module.exports = async () => {
process.nextTick(() =>{
var io = require('socket.io')(strapi.server);
io.on('connection', async function(socket) {
console.log(`a user connected`)
// send a message on user connection
socket.emit('question', {message: await strapi.services.question.findOne({ id: 1 })});
//Send a message after a timeout of 4seconds
setTimeout(function() {
socket.emit('endEvent', { description: 'A custom event named EndEvent!'});
}, 4000);
// listen for user diconnect
socket.on('disconnect', () =>{
console.log('a user disconnected')
});
});
strapi.io = io; // register socket io inside strapi main object to use it globally anywhere
})
};
This server is expected to send a question from the database to the socket via custom event -question. The frontend runs with ReactJS. Whenever the page is loaded the socket is created, it establishes a connection with the server and receives the question event from the server, and the data of the quiz question, which is printed as an object.
import React, { useState, useEffect } from "react";
import io from "socket.io-client";
const ENDPOINT = "http://localhost:1337";
const socket = io(ENDPOINT);
function Question() {
const [questionID,setQuestionID] = useState(0)
useEffect(() => {
socket.on('message', (data)=> {setResponse(data)});
socket.on('endEvent', ()=>{console.log('End event done.')})
socket.on('question', (msg, cb) => {
console.log( msg.message)
});
// CLEAN UP THE EFFECT
return () => socket.disconnect();
}, [questionID]);
return(<p>The question debugable from console</p>);
}
export default Question;
Problem.
When I refresh the page, the console.log( msg.message) does print on time, but not each time. Sometimes it just prints when some other component of React is being triggered. For detailed info please see screenshots. This is expected:
It prints the object of question and also End event done. Which is triggered by the server in 4 seconds right after the connection. Instability shows itself when both of the commands run twice, or the object of the question itself delays to display anyhow.
I am not sure if the question is not being sent from the server or it is not printed in the console on client-side... The useEffect() is loaded at first, then triggered each time whenever questionID is changed.
Unexplained behavior
When the countdown time component triggers that the time is expired, you may mention that the 2 important commands of printing and 4 seconds session delay are somehow triggered, which is unexplainable for me, how two independent components of ReactJS may "not fit".
On the server-side, I receive information "connected" and "disconnected" when refreshed. Please comment if an update is needed or have a suggestion for a better title.

Related

Socket.io listener getting skipped on the client-side while there is an emitted event

I am facing this weird issue. I am not a veteran of using Socket.io. I have been exploring this library as the app I am building needs a remote playing feature wherein players create invitations to other players so that they can use those invitations to join the game remotely. I am using React on the front end (client-side), and on the server side, I am using the Nodejs Express framework with Socket.io. I have also installed client-side Socket.io for React. The basic implementation is all working fine. When there is a new user accessing the client-side app, Server-side socket.io listens to the connection. Any events triggered by the client also get reported on the server side. I am also able to broadcast the events back to all the connected clients using the socket.broadcast.emit() method.
I am trying to store the past events (basically, these are the invitations created by the currently connected players) in an array and then emit the stored array for the new connections so that the new users will see the past events(invitations). Below is my implementation on the server side:
//Array to store previously emitted events
const activeInvites = [];
//SocketIO connections
io.on("connection", (socket) => {
console.log(`⚡: ${socket.id} user just connected!`);
//Listen to the new invites
socket.on("newInvite", (invite) => {
activeInvites.push(invite);
socket.broadcast.emit("newPrivateInvites", invite);
});
//Publish all previously created invites to the new connections
io.emit("activeInvites", activeInvites); //new connections emit this event however the client won't listen to "activeInvites" event
socket.on("disconnect", () => {
console.log(`🔥: ${socket.id} user disconnected`);
destroy();
});
function destroy() {
try {
socket.disconnect();
socket.removeAllListeners();
socket = null; //this will kill all event listeners working with socket
//set some other stuffs to NULL
} catch (ex) {
console.error("Error destroying socket listeners:", ex.message);
}
}
});
And, below is my client-side implementation:
useEffect(() => {
socket.on("activeInvites", (invite) => {
console.log(invite);
}); //a new connection client skips listening to this event. Can't understand why.
socket.on("newPrivateInvites", (invite) => {
setPrivateInvites((existingInvites) => [...existingInvites, invite]);
});
//I have commented below code. Even if I uncomment it, no difference
// return () => {
// socket.off("newPrivateInvites");
// socket.off("activeInvites");
// socket.removeAllListeners();
// };
}, [socket, privateInvites]);
//Below is the handler function I use to open up a Sweetalert2 dialog to create an invite
const createMyGameInviteHandler = () => {
swalert
.fire({
title: "New Invite",
text: "This will create a new game invite and unique joining code that you can share with your friends",
iconHtml: '<img src="/images/invite.png" />',
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Yeh! Let's Go!",
customClass: {
icon: "no-border",
},
})
.then((result) => {
if (result.isConfirmed) {
player.gameId = "1234";
setMyGameInvite(player);
socket.emit("newInvite", player); //This is where I create a new invitation event
}
});
};
In the above code, the "activeInvites" event is getting skipped by the new client even after socket.io on the server side triggers a new event after the new connection is created. Note that I am using io.emit() to emit the event to all the connected clients. So, even new clients should also listen. I am not able to see where the problem is. Could you please help me with this?
I tried to store the events generated by the client and consumed by the server in the past so that I could serve those events to the new clients when they establish the connection. I was expecting that io.emit() method would emit the event that will be consumed by all the clients including the new clients. However, new clients are skipping listening to this event. I am using useEffect hook in a react component.

I want to use socket.emit() outside io.on("connection"), is this approach correct?

I'm making a tweet deleter, and I want to update the user on the progress.
I'm new to socket.io, I managed to connect the react frontend to the nodejs/express backend.
io.on("connection", (socket) => {
console.log("new connection");
socket.on("disconnect", () => console.log("disconnected"));
});
When a user clicks the delete button, a delete request goes to the backend, the file containing the tweets is then processed and thanks to Bull, each tweet is queued as a job.
because I added ìo to my routes, I can use it inside of them, but io.emit() emits to connected clients, and I only want emit to sender by using socket.emit() inside my routes as well as inside my jobs in the queue.
The approach I tried was to write a function inside io.on("connection") like this and to make it global :
io.on("connection", (socket) => {
console.log("new connection");
socket.on("disconnect", () => console.log("disconnected"));
global.emitCustom = function (event, payload) {
socket.emit(event, payload);
};
});
which allowed me to use in the queue process function :
const deletionProcess = async ({
data: { tweetId, tokens, twitterId, numberOfTweets, index },
}) => {
emitCustom("deleting", {
type: "deleting",
progress: Math.round(((index + 1) / numberOfTweets) * 100),
});
};
Is there a better way to do this? is my approach wrong or does it present any flaws?
It worked in the few tests I did.

Socket.io: Other client only updates when being interacted

I'm trying to set up a realtime application using socket.io in Angular and node.js, which is not working as intended.
Whenever a client is making a new post, the other clients won't update until you interact with the client (e.g. clicking somewhere on the page, or clicking on the browsers tab).
However, having console open in the browser, I can see the new post in the console when I log the posts/objects - without the need to interact with the clients.
Angular:
import io from 'socket.io-client';
const socket = io('http://localhost:3000');
posts: Post[] = [];
...
// Inside ngOnInit:
socket.on('data123', (res) => {
console.log('Updating list..', res);
this.postService.getPosts();
this.postsSub = this.postService.getPostUpdateListener()
.subscribe((posts: Post[]) => {
this.posts = posts;
});
});
Displaying in the template:
<... *ngFor="let item of posts">
Inside PostsService:
getPosts() {
this.http.get<{ message: string, posts: Post[] }>('http://localhost:3000/api/posts')
.subscribe((postData) => {
this.posts = postData.posts;
this.postsUpdate.next([...this.posts]);
});
}
Node.js - this socket.io solution is not yet sending the actual list:
const io = socket(server);
io.sockets.on('connection', (socket) => {
console.log(`new connection id: ${socket.id}`);
sendData(socket);
})
function sendData(socket){
socket.emit('data123', 'TODO: send the actual updated list');
setTimeout(() => {
console.log('sending to client');
sendData(socket);
}, 3000);
}
What worked as intended:
Using setInterval instead "socket.on(..)" on the front-end gave the intended result, meaning the clients will update automatically without the need of interacting. I'm fully aware this solution is horrible, but I assume this pinpointing that it's something wrong with socket solution above in Angular part.
wait, every time when socket.on('data123', (res) => {... you are creating new subscribe? it's wrong way...you must create subscribe in your socket connect feature

Creating private chat in node js and unable to send message to particular socket id

Below is my Server side and client side file:
Server side file:
'use strict';
var model = require('../model/model.js');
class Socket{
constructor(socket){
this.io = socket;
this.users = [];
}
socketEvents(){
this.io.on('connection', (socket) => {
socket.on('username', (data) => {
this.users.push({
id : socket.id,
userName : data.username
});
let len = this.users.length;
len--;
model.addSocketId( data.username,this.users[len].id);
this.io.emit('userList',this.users,this.users[len].id);
});
socket.on('getMsg', (data) => {
model.insertMessages({
fromUserId: data.fromUserId,
toUserId: data.toUserId,
message: data.msg
});
console.log("socket id is:");
console.log(data.toid);
socket.broadcast.to(data.toid).emit('sendMsg',{
msg:data.msg,
name:data.name
});
});
socket.on('disconnect',()=>{
for(let i=0; i < this.users.length; i++){
if(this.users[i].id === socket.id){
this.users.splice(i,1);
}
}
this.io.emit('exit',this.users);
});
});
}
socketConfig(){
this.socketEvents();
}
}
module.exports = Socket;
Below is my Client Side File:
<script src="/socket.io/socket.io.js"></script>
<script type="text/javascript">
var socket = io.connect("http://192.168.1.12:3000");
socket.on('sendMsg', (data) => {
console.log("send message-list");
$('#message-list').append("<li class='friend-user'>"+data.msg+"</li>");
});
socket.on('userList', (completeUserList,userSocketId) => {
console.log('userlist function');
var userList = completeUserList;
});
$('document').ready(function(){
$(document).on('click', "#msg-btn", function (event) {
var messagePacket = {
toid: $("#friendSocketId").text(), //stored in hidden format from db
msg: $('#message').val(),
name: $("#myUserName").text(),
fromUserId: $("#loginUserId").text(),
toUserId: $("#friendUserId").text(),
};
socket.emit('getMsg',messagePacket);
});
</script>
In this, When user clicks on send button from front-end then "getMsg" event emitting successfully. getMsg function took receiver socket id as message and emit "sendMsg" event to particular socket id. But sendMsg event is not sending msg to particular socket id. please help.
It looks like you don't have a key called toid on data object. You might have mistyped it. Replace toid with toUserId and it should work.
Since you are sending a message to another socket, instead of a room, you can use socket.to().emit() instead of socket.broadcast.to().
From Socket.IO docs:
// a private message to another socket
socket.to(/* another socket id */).emit('hey');
So, replace socket.broadcast.to() with socket.to().emit(), like:
socket.to(data.toid).emit('sendMsg',{
msg:data.msg,
name:data.name
});
EDIT:
You asked the following question in the comments below this answer:
Does socket id changed automatically after
login when user send message or it remains same when client logged in?
Socket id is created for each client when io.connect() is executed.
In your code, this is the first line in your script file. So, a new socket id is created every time this script loads.
So, every time the page containing this script is loaded, a new socket id is created. If you refresh the page, this script loads again and a new socket id is created.
To answer your question, socket id remains same as long as user is on the same page. So, when a user sends a message it doesn't change, unless the user navigates to another page or refreshes this page after sending the message. So, if a user logs out and logs in, this page loads again, so socket id changes.
I think you are storing the socket id in database after user logs in, but are not updating it when user navigates to another page or logs out and logs in again. So, when you are emitting a message from server, you are sending it to user's previous socket id, not the current one.

node.js: How to sync socket receive in 2 different routes

I am still learning node.js basics. My flow is like this,
browser<-->node<-->backend server doing calculation.
node and backend uses socket to communicate.
From the browser there are start/stop buttons to ask backend to start/stop the
calculation.
When node asks backend to start/stop, it must query to see if backend is
alive first.
My code is like this -
app.get('/stopCmd', function(req, res)
{
socketToBackendServer.write("status", function() {
console.log("Sending:", 'Node asking for STATUS');
});
socketToBackendServer.on("data", function() {
if(status is ok) // pseudo code
{
socketToBackendServer.write("stop", function() {
console.log("Sending:", 'Node sending STOP');
});
} else {
console.log("backend server is NOT ready");
}
});
});
app.get('/startCmd', function(req, res)
{
// do similar things as stopCmd
});
/////////////////////////////////////////////////
var socketToBackendServer = net.connect(2899);
function openSocket() {
socketToBackendServer.setKeepAlive(true);
socketToBackendServer.on('connect', onConnect.bind({}, socketToBackendServer));
socketToBackendServer.on('error', onError.bind({}, socketToBackendServer));
}
function onConnect(socket) {
var myData;
console.log('Socket is open!');
socket.on('data', function(data) {
console.log('Received:', data);
io.emit('time', { time: data.toJSON() });
});
}
function onError(socket) {
console.log('Socket error!');
// Kill socket
clearInterval(interval);
socket.destroy();
socket.unref();
// Re-open socket
setTimeout(openSocket, 1e3);
}
openSocket();
server.listen(7778);
if using the same browser, if i go crazy clicking start/stop... for the "
stopCmd", how to make sure when it queries "status", the response is caught
by its function, not "startCmd"'s ?
it's this line
socketToBackendServer.on("data", function()
Thank you again !
You can use multiple connections to the backend server, so one function can freely use one channel, the responses won't mix.
Or you can use a multiplexer function, that you call from both of your functions:
It could work if you can identify your requests, like you send and id with the status, for example socketToBackendServer.write("status 1", ... , and you send the id with the status response back from the backend server (if it yours). In this way you can send multiple requests at the same time, and when the response come, you can identify it, and call the callback function that you stored in an array with the ids.
You only send one request, and you wait for the response before you send another one. You must use a waiting queue, where you store the request, and the callback functions.

Resources