Socket.io connected property is always false - reactjs

I am trying to create a private messaging app. The socket connects at first but then when I try to emit any event from the client side, it shows that socket.connected property is false.
Please help me out.
Here's is my client side code, Please note that socket.on("users") part works correctly because all of it happens when the socket it connected. It means the connection part is happening correctly. After that whenever I try to call a function that emits a socket event, it shows that socket.connected property is false and doesnt do anything.
Any help would be appreciated.
var connectionOptions = {
transports: ["websocket"],
autoConnect: false,
};
socket = io("http://localhost:3001", connectionOptions);
socket.on("connection _error", (err) => {
if (err.message === "invalid username") {
console.log("ERROR");
}
});
socket.on("users", (users) => {
users.forEach((user) => {
user.self = user.userID === socket.id;
//initReactiveProperties(user);
});
socket.on("user connected", (user) => {
// TODO
setUsers((existingusers) => [...existingusers, user]);
console.log(user);
});
// put the current user first, and then sort by username
users = users.sort((a, b) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
//console.log(users);
});
socket.on("private message", ({ content, from }) => {
console.log(content);
});
useEffect(() => {
const username = localStorage.getItem("username");
console.log(username);
socket.auth = { username };
socket.connect();
}, []);
function SendMessage() {
socket.emit("test", "hello");
// selectedChatUser
console.log(socket.connected);
if (selectChatUser) {
socket.emit("private message", {
content: "hello there",
to: selectChatUser.userID,
});
console.log("Message Sent");
}
}
And here is my server side code:
const app = require("express")();
const httpServer = require("http").createServer(app);
const cors = require("cors");
app.use(cors());
const options = {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
};
const io = require("socket.io")(httpServer, options);
io.use((socket, next) => {
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid usernmae"));
}
socket.username = username;
next();
});
io.on("connect", (socket) => {
console.log("New connection");
const users = [];
for (let [id, socket] of io.of("/").sockets) {
users.push({
userID: id,
username: socket.username,
});
}
socket.broadcast.emit("user connected", {
userID: socket.id,
username: socket.username,
});
socket.emit("users", users);
socket.on("test", () => {
console.log("test");
});
socket.on("private message", ({ content, to }) => {
console.log(content);
console.log("hello there");
socket.to(to).emit("private message", {
content,
from: socket.id,
});
});
});
httpServer.listen(3001, () => {
console.log("Server has started");
});
// https://socket.io/

Following line will re-run every time your component renders, losing reference to the socket that was actually connected:
socket = io("http://localhost:3001", connectionOptions);
You can use a ref to persist it between renders:
const socketRef = useRef();
socketRef.current = socket;
// use socketRef.current everywhere else in your code

Related

state used inside socket.on keeps its default value

const [allUsers, setUsers] = useState<ChatUser[]>([]);
const [currentUser, setCurrentUser] = useState<any>();
useEffect(() => {
console.log("all users : ", allUsers);
}, [allUsers]);
useEffect(() => {
const sessionID = localStorage.getItem("sessionID");
if (sessionID) {
socket.auth = { sessionID, username: "AOA_A", isAOA_A: true };
socket.connect();
} else {
socket.auth = { username: "AOA_A" };
socket.connect();
socket.on("connect error", (err) => {
if (err.message === "invalid username") {
console.log("error");
}
});
}
socket.on("session", ({ sessionID, userID }) => {
console.log("session");
// attach the session ID to the next reconnection attempts
socket.auth = { sessionID };
// store it in the localStorage
localStorage.setItem("sessionID", sessionID);
// save the ID of the user
socket.userID = userID;
});
socket.on("users", (data: ChatUser[]) => {
setUsers(() => {
const _users_: any[] = [];
data.forEach((user: ChatUser) => {
user.self = user.userID === socket.userID;
user.messages = [];
/* initReactiveProperties(user); */
_users_.push(user);
});
// put the current user first, and sort by username
_users_.sort((a, b) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
return _users_;
});
});
socket.on("private message", ({ message, from }) => {
console.log("setting messages", allUsers.length);
for (let i = 0; i < allUsers.length; i++) {
const user = allUsers[i];
if (user.userID === from) {
user.messages.push({
content: message.content,
from: from,
dateTime: "",
});
const _allUsers = [...allUsers];
_allUsers[currentUser.key] = currentUser!.user;
setUsers(_allUsers);
// if (user !== this.selectedUser) {
// user.hasNewMessages = true;
// }
break;
}
}
});
return () => {
socket.off("connect");
socket.off("disconnect");
socket.off("users");
socket.off("user connected");
socket.off("user disconnected");
socket.off("private message");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
I want to setup a private messaging with MERN stack an socket.io .
At component rendering , it connect to server an the server will emit the list of all actif users , at the this point , the state is successfully updated , the first useEffect will log the updated state but when the socket.on("privated message") is called , when log the value of allUsers inside it is empty.

Nock not mocking GET request

I'm trying to follow run a cypress test with next.js and nock. Based on other examples and following the video, I tried to mock a simple GET request. However, my test fails on the cy.request as it makes an actual call instead of the mock call.
index.js
const nock = require('nock');
const http = require('http');
const next = require('next');
const injectDevServer = require('#cypress/react/plugins/next');
// start the Next.js server when Cypress starts
module.exports = async (on, config) => {
if (process.env.CUSTOM_SERVER == 'false') {
injectDevServer(on, config);
} else {
await startCustomServer(on, config);
}
return config;
};
async function startCustomServer(on, config) {
config.supportFile = false;
const app = next({ dev: true });
const handleNextRequests = app.getRequestHandler();
await app.prepare();
const customServer = new http.Server(async (req, res) => {
return handleNextRequests(req, res);
});
await new Promise((resolve, reject) => {
customServer.listen(3000, (err) => {
if (err) {
return reject(err);
}
console.log('> Ready on http://localhost:3000');
resolve();
});
});
// register handlers for cy.task command
on('task', {
clearNock() {
nock.restore();
nock.cleanAll();
return null;
},
async nock({ hostname, method, path, statusCode, body }) {
nock.activate();
console.log(
'nock will: %s %s%s respond with %d %o',
method,
hostname,
path,
statusCode,
body
);
// add one-time network stub like
method = method.toLowerCase();
nock(hostname)[method](path).reply(statusCode, body);
return null;
},
});
}
my-test.spec.js
describe('my-test', () => {
beforeEach(() => {
cy.task('clearNock');
})
it('execute', () => {
cy.task('nock', {
hostname: 'https://localhost:3000',
method: 'GET',
path: '/myapi/api/layout',
statusCode: 200,
body: {
id: 'NmbFtH69hFd',
status: 200,
success: true
}
})
cy.request(
'https://localhost:3000/myapi/api/layout/'
).as('API'); // <-- Fails here with error
cy.get('API').then((response) => {
assert.exists(response.success);
});
});
});

How to send a notification instead of a message when user is offline using socket.io

So whenever another user is offline and an online user tries to send a message, it returns an error. The error is caused because it didn't find a socket where the user is in, but I can console.log something from the error response, since I put it in if/else block. So i was wondering how could I send a response to the client and push a notification for the user who is offline. The explanation is probably confusing, so here is the code:
index.js(socket):
const io = require('socket.io')(8900, {
cors: {
origin: 'http://localhost:3000',
},
});
let users = [];
const addUser = (userId, socketId) => {
!users.some((user) => user.userId === userId) &&
users.push({ userId, socketId });
};
const removeUser = (socketId) => {
users = users.filter((user) => user.socketId !== socketId);
};
const getUser = (userId) => {
return foundUser = users.find((user) => user.userId === userId)
};
io.on('connection', (socket) => {
//when ceonnect
console.log('a user connected.');
//take userId and socketId from user
socket.on('addUser', (userId) => {
addUser(userId, socket.id);
io.emit('getUsers', users);
});
//send and get message
socket.on('sendMessage', ({ senderId, recieverId, text }) => {
const user = getUser(recieverId);
console.log('RECIEVERRRR', user);
if (user) {
io.to(user.socketId).emit('getMessage', {
senderId,
text,
});
} else {
// IMPORTANT: This is where I can console.log the error, but can't figure out how to send the response to the client
}
});
//when disconnect
socket.on('disconnect', () => {
console.log('a user disconnected!');
removeUser(socket.id);
io.emit('getUsers', users);
});
});
dashboard.js (I cut out most of the code, but this is where i'm sending request to socket):
const handleSubmit = async (e) => {
e.preventDefault();
const message = {
sender: user._id,
text: newMessage,
conversationId: currentChat._id,
};
const recieverId = currentChat.members.find(
(member) => member !== user._id
);
console.log('REC from FRONT END', recieverId);
socket.current.emit('sendMessage', {
senderId: user._id,
recieverId,
text: newMessage,
});
try {
const { data } = await axios.post('/api/messages/addMessage', message);
setMessages([...messages, data]);
setNewMessage('');
} catch (err) {
console.log(err);
}
};
Any help would be appreciated. Thanks.
You can emit a new event sendMessageFailed:
//send and get message
socket.on('sendMessage', ({ senderId, recieverId, text }) => {
const user = getUser(recieverId);
console.log('RECIEVERRRR', user);
if (user) {
io.to(user.socketId).emit('getMessage', {
senderId,
text,
});
} else {
io.to(user.socketId).emit('sendMessageFailed', "Your friend is offline");
}
});
At the client side listen to the event:
socket.current.on('sendMessageFailed', (err) => {
//Sending message has failed!
});

Socket works the for a few seconds then fails

I've managed to establish a connection using socket. It works great for the first few seconds after that it runs super slow takes like almost 2-3 mins to complete a request. And then it produces this error continuously. The app doesn't crash it just runs slowly with the error displaying countless times.
Firefox can’t establish a connection to the server at ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=9S6kqHJdHHXQgrobAAHp..
Error on console.
Main.js
function Home(props) {
const [Username , setUsername] = useState("")
const [SearchedData, setSearchedData] = useState()
const [Data, setData] = useState()
const socket = io('http://localhost:5000')
React.useEffect(() => {
// socket.current = io('http://localhost:5000')
socket.emit("content","yada")
socket.on("get-data", data => {
setData(data)
})
})
function NavBar(props){
const handleClick = (e) => {
const {id} = e.target
if(id === "Post-btn"){
if(Content.length > 0){
let data = {
Username: "yada", Content
}
props.socket.emit("store-data", data)
}
}
return(
Tags....
)}
function Content (props) {
const onLike = (e) => {
const { id } = e.target.dataset
const data = {
username: "yada",
id : id
}
// console.log(data)
props.socket.emit("like", data)
}
return(
Tags.....
)
}
server.js
mongoose.connect(process.env.MongoDB,
{ useNewUrlParser: true, useUnifiedTopology: true }).then(() => {
console.log("Database Connected")
}).catch(err => {
console.log(err)
});
const server = app.listen(process.env.Port, () => {
console.log("Connected on " + process.env.Port)
})
const io = socket(server, {
cors:{
origin: "http://localhost:3000",
credential: true,
}
})
let cuid;
io.on("connection", (socket) => {
socket.on("content", username => {
Comments.find({},(err, data) => {
if(!err)
socket.emit("get-data", data)
})
})
socket.on("store-data", data => {
const {Username, Content} = data
const newdata = new Comments({
userName: Username,
content: Content,
createdAt: new Date().toDateString(),
replies: []
})
newdata.save().then(data => {
for(const d in data)
if(d === "_id"){
Users.findOneAndUpdate({username: Username}, {$push: {UserContent: data[d]}}, {new: true}, (err, save) => {
if(err)
console.log(err)
else
console.log(save)
})
}
})
})
socket.on("like", data => {
const {username, id} = data
Users.findOne({username:username}, (err, data) => {
if(!err){
cuid = data['id']
console.log(cuid)
Comments.findByIdAndUpdate(id, {$set: {score: data['_id']}}, {upsert: true}, (err, d) => {
if(!err){
console.log(d)
}
})
}
})
})
})
Looking at the code provided, I noticed there is an useEffect without params. This may be causing a loop until the application crashes.
React.useEffect(() => {
// socket.current = io('http://localhost:5000')
socket.emit("content","yada")
socket.on("get-data", data => {
setData(data)
})
socket.on("Updated", data => {
setData(data)
})
}, []); <- this is missing
This empty array indicates that the content inside the useEffect will only run once.
More about this https://reactjs.org/docs/hooks-intro.html

the problem: the code below work flawlessly in dev but after uploaded to Heroku it's working sometimes,

I am trying to get socket.io to work with Heroku but it doesn't play well.
the problem: the code below work flawlessly in dev but after uploaded to Heroku it's working sometimes,
1.don't see anything weird on logs.
2.the data saved to DB and will appear after refresh
3.refresh helps to sockets sometimes.
4.there is no pattern to it, sometime it will work ok for an hour and some time won't last a minute
5.heroku features:enable http-session-affinity also done
server:
const mongoose = require("mongoose");
const Rooms = mongoose.model("Rooms");
const Chat = mongoose.model("Chats");
const jwt = require("jwt-then");
const socketChat = (app, io) => {
io.use(async (socket, next) => {
try {
const token = socket.handshake.query.token;
const payload = await jwt.verify(token, process.env.SECRET_KEY);
socket.userId = payload.id;
socket.id = payload.id;
socket.name = payload.username;
console.log({ socketisd: socket.userId, name: socket.name });
next();
} catch (err) { }
});
io.on("connection", (socket) => {
console.log("Connected: " + socket.name);
socket.on("disconnect", () => {
console.log("Disconnected: " + socket.name);
});
socket.on("joinRoom", async ({ roomId },callback) => {
socket.join(roomId);
console.log(` ${socket.name} joined room: ` + roomId);
socket.to(roomId).emit("live", { name: socket.name, live: true, roomId });
callback({
status: "ok"
});
});
socket.on("leaveRoom", async ({ roomId },callback) => {
socket.leave(roomId);
console.log(` ${socket.name} left room: ` + roomId);
socket.to(roomId).emit("live", { name: socket.name, live: false, roomId });
callback({
status: "ok"
});
});
socket.on("typing", async ({ msg, roomId }) => {
let name = "";
if (msg.text && msg.text.trim().length > 0) {
let length = msg.text.length;
name = length > 0 ? socket.name : "";
}
socket.to(roomId).emit("typingclient", { name });
});
socket.on(
"chatroomMessage",
async ({ roomId, message, name, profileImg, timestamp, type, date }) => {
if (message.trim().length > 0) {
io.to(roomId).emit("newMessage", {
roomId,
user: socket.userId,
message,
name,
type,
date,
profileImg,
timestamp,
recived: true,
});
let room = await Rooms.findById(roomId).populate("messages");
if (type === "reject") {
await Chat.findOneAndUpdate(
{ roomId, type: "dateConfirm" },
{ type: "reject", message },
{ new: true }
);
}
else {
const newMessage = new Chat({
roomId,
date,
type,
user: socket.userId,
message,
name,
profileImg,
timestamp,
recived: true,
});
await newMessage.save();
room.messages.push(newMessage);
await room.save();
}
let theOtherGuy =await room.users.find((user) => user != socket.userId);
io.to(theOtherGuy).emit("room", room);
}
}
);
});
};
module.exports = socketChat;
client:
/**
* Sends message with emit socket to server
* #param {Object} event Default Browser Event Object
* #param {String} text content of message
* #param {String} date Date for schedualing
* #param {String} type type of the message (reject,request etc...)
*/
const sendMessage = (event, text, date = null, type = null) => {
event && event.preventDefault();
if (socket) {
socket.emit("chatroomMessage", {
roomId,
date,
type,
name: currentUser.user.username,
profileImg: currentUser.user.profileImageUrl,
timestamp: new Date(),
recived: false,
message: text,
});
setText("");
socket.emit("typing", {
msg: "",
roomId,
});
}
};
React.useEffect(() => {
if (socket) {
socket.emit("joinRoom", {roomId},(answer)=>
console.log("joinRoom",roomId,answer)
);
socket.on("newMessage", (message) => {
console.log({message})
if (message.type === "reject")
setMessages((prevMessages) => [...prevMessages.filter(m => m.type !== 'dateConfirm'), message]);
else
setMessages((prevMessages) => [...prevMessages, message]);
});
socket.on("live", (message) => {
console.log(message)
message.live ? setSucess(`user ${message.name} has connected`) : setErr(`user ${message.name} has left`)
});
socket.on("typingclient", (name) => {
setTyping(name);
});
}
return () => {
if (socket) {
socket.emit("leaveRoom", {roomId},(answer)=>
console.log("leaveRoom",roomId,answer)
);
}
//Component Unmount
};
//eslint-disable-next-line
}, [socket]);
and main where i define my socket:
const [socket, setSocket] = React.useState(null);
const setupSocket = () => {
console.log("socket4")
const token = sessionStorage.getItem("jwtToken");
if (token && !socket) {
const newSocket = io("/", {
query: {
token: sessionStorage.getItem("jwtToken"),
},
path: '/socket'
});
newSocket.on("disconnect", () => {
// setSocket(null);
// makeToast("error", "Socket Disconnected!");
});
newSocket.on("connect", () => {
// makeToast("success", "Socket Connected!");
console.log("Socket Connected");
});
setSocket(newSocket);
}
};
React.useEffect(() => {
if (currentUser && !socket) setupSocket();
//eslint-disable-next-line
}, [currentUser, socket]);
ststic.json:
{
"root":"build/",
"routes":{
"/**":"index.html"
},
"proxies":{
"/api/":{"origin":"${API_URL}"},
"/socket/":{"origin":"${SOCKET_URL}"}
}
}
it looks like the Io object did not like
socket.id = payload.id;
I have removed it and everything is working now.
I think it may have resulted from different keys in the Io object resulting in unexpected behavior.

Resources