I am using socket.io for real time communication. I am facing problem when user's browser is idle. I mean, suppose there is two entity 'User A' and 'User B'. User A wants to talk with User B. If User B has opened the application but is not active on that page then User A cannot contact User B because the socket of User B is disconnected after some minutes when the system is idle. Also I don't see continuous polling on ws tab inside network
Here is the code to configure socket. I am using context API
const SocketProvider = ({ children }) => {
const [socketClient, setSocketClient] = React.useState();
const [socketUpdated, setSocketUpdated] = React.useState(false);
React.useEffect(() => {
const socket = io(SOCKET_URL, {
path: "/socket.io"
});
setSocketClient(socket);
// return () => {
// io.disconnect();
// };
}, []);
React.useEffect(() => {
if (socketClient !== undefined) {
if (user) { // from localstorage
console.log("user", user);
socketClient.on("connect", () => {
socketClient.emit("userData", {
_id: user._id,
socketId: socketClient.id
});
});
// socketClient.on("connected", msg => {
// console.log("connected msg", msg);
// setSocketUpdated(true);
// });
// setSocketUpdated(true);
}
}
}, [socketClient]);
return (
<>
<SocketContext.Provider value={{ socket: socketClient, socketUpdated }}>
{children}
</SocketContext.Provider>
</>
);
};
export { SocketContext, SocketProvider };
I want socket on multiple pages so for single socket reference I have used socket in context. Wherever I need socket, I import this context and use it accordingly.
How can I handle disconnection issue so the socket is still alive when user's system/browser is idle?
Related
When I'm emitting message from client side.
I'm receiving lot of same duplicated message
on server side or in my message displaying container div here is the code
Global Socket Provider Context
A simple context which provide socket to all children or components
// fileName : SocketProvider.js
import { createContext } from "react";
import { io } from "socket.io-client";
const socket = io("http://localhost:8080/", { withCredentials: true });
const SocketContext = createContext();
export const SocketProvider = ({ children }) => {
return (
<SocketContext.Provider value={{ socket }}>
{children}
</SocketContext.Provider>
);
};
export default SocketContext;
And I've wrapped SocketProvider in App.js like this
<SocketProvider>
<Main/>
</SocketProvider>
In Dashboard Page or Main Page
Where I've div for displaying messages and input message box whenever user press enter or click send button which is img I'm emitting a message here is the code
// filename : Main.jsx
// all things imported correctly
import ...
const Main = ()=> {
const { socket } = useContext(SocketContext);
const [message, setMessage] = useState("");
const [conversation, setConversation] = useState([]);
// another global context
const { currentUserId } = useContext(UserContext);
const { selectedUserId, setSelectedUserId } = useContext(SelectedUserContext);
// code for changing selectedUserId
// ...
...
.... //
// on start sending userJoined
useEffect(() => {
socket.emit("userJoined", {
senderUserId: currentUserId,
receiverUserId: selectedUserId,
});
}, [socket, currentUserId, selectedUserId]);
// useEffect for listening messages
useEffect(() => {
const onMessageReceived = (msg) => {
setConversation((conversations) => [...conversations, msg]);
};
socket.on("message", onMessageReceived);
return () => {
socket.off("message", onMessageReceived);
};
}, [socket]);
// this function will send message
const sendMessage = useCallback(() => {
if (!message) return;
socket.emit("message", {
msg: message,
sentBy: "Unknown"
});
setMessage("");
}, [socket, message]);
// on enter btn pressed
useEffect(() => {
const handleEnterKeyPressed = (e) => {
if (e.keyCode === 13 && e.shiftKey === false && e.ctrlKey === false)
sendMessage();
};
element.addEventListener("keydown", handleEnterKeyPressed);
return () => {
element.removeEventListener("keydown", handleEnterKeyPressed);
};
}, [sendMessage]);
return (
<div className="container">
<div className="msgContainer">
{conversation.map((data)=>((
<h1 className="...">{data.msg}</h1>
<p className="...">{data.sentBy}</p>
)}
</div>
<input
value={message}
onChange={(e)=>setMessage(e.target.value)}
placeholder="Enter message here"/>
<img className="..." src={sendIcon} onClick={sendMessage}>
</div>
);
}
export default Main;
Code In Server Side Where I'm Listening for message
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
socket.on("message", async (msgData) => {
// this console log many times or more than 11
console.log(`DEBUG: New Message Arrived :`, msgData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});
});
and even I got following warning in terminal :
(node:14040) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added
to [Socket]. Use emitter.setMaxListeners() to increase limit
Any help will be always appreciated :)
As #NickParsons mentioned -
Is that peice of code being executed multiple times?
Whenever currentUserId or selectedUserId changes you are emiting userJoined in server code. And userJoined listener on server side get called and your message listener listen again your data so that's why you are getting duplicated data.
To fix this you have to place message listener outside of userJoined listener!
I know you are getting some info of userJoinedData so that's why you have place message listener inside userJoined.
So you have to also pass that userJoinedData while emitting message in your client side and change code in your server side like this -
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
// .. your other codes
});
// but place message listener outside here
socket.on("message", async (msgData, userJoinedData) => {
// now here also you can generate chatRoomId
const chatRoomId = generateChatRoomId(userJoinedData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});
I am trying to establish a websocket connection.
I'm writing the structure inside a useEffect in a context.
But there is one problem. When I open the page for the first time, the readyState of the websocket is not 1 in the console. 1 means open.
It comes as 0, that is, in the Connecting state. At the same time, when you write console.log to a function in socket.onclose event, it gives that console.
After the page is opened, when you refresh the page once, it makes the websocket connection.
You need to be able to control this situation. I wrote a few recursive solutions for this, but I could not find a solution.
export const NotificationsContext = createContext(initialState);
export const NotificationsProvider = (props) => {
useEffect(async () => {
let url = `http://localhost:8080/notifs`.replace('http', 'ws');
let socket = new WebSocket(url)
console.log(socket);
console.log(socket.readyState); // readyState is 0. mean CONNECTING.
socket.onclose = () => {
console.log('closed check') // in console.
socket = new WebSocket(url)
}
socket.onmessage = (data) => {
const notifData = JSON.parse(data.data);
console.log(notifData) // not in console.
}
console.log('connected');
}, []);
const [my_data, dispatch] = useReducer(userDetailsReducer, initialState);
return <NotificationsContext.Provider value={{ notifications, my_data, dispatch }}>.
{children}</NotificationsContext.Provider>;
};
I am using the following server code to retrieve data from a postgres db:
const express = require('express')
const app = express()
const server = require('http').createServer(app);
const pool = require("postgresql");
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server:server });
const getTempData = async () => {
try {
const tempData = await pool.query("select country, temp from my_temp_table");
return JSON.stringify(tempData.rows)
} catch(err) {
console.error(err.messasge);
}
}
wss.on('connection', async (webSocketClient) => {
console.log('A new client Connected!');
const tempDetails = await getTempData();
webSocketClient.send(tempDetails);
webSocketClient.on('message', (message) => {
console.log('received: %s', message);
});
});
server.listen(3000, () => console.log(`Listening on port :3000`))
Now on the client side, I have created the following websocket connection to localhost 3000.
When first rendering the below client code, the data displays where I also get all the console log messages, i.e. ws opened, getting data.... and finally console logging the actual data.
isPaused is also set to false.
The problem I'm facing and unsure what the issue is, is that I expected to see my client page update the country/temp data (no page refresh), when I updated the country/temp values in my_temp_table database table, but it didn't.
The result that I expected was that via the websocket, anytime my table on the server-side updated, the client would update the tempData, via the second useEffect hook below.
I basically would like the client to pull in and display changes from the server via websocket when the data changes in the backend db table.
import React, { useState, useEffect, useRef } from 'react';
export default function Temperature() {
const [isPaused, setPause] = useState(false);
const [tempData, setTempData] = useState([]);
const [name, setName] = useState(null);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket("ws://localhost:3000");
ws.current.onopen = () => {
console.log("ws opened");
}
ws.current.onclose = () => console.log("ws closed");
return () => {
ws.current.close();
};
}, []);
useEffect(() => {
if (!ws.current) return;
ws.current.onmessage = e => {
if (isPaused) return;
console.log("getting temp data....");
const data = JSON.parse(e.data);
setTempData(data)
console.log("data: ",data);
};
}, [isPaused]);
return (
<div>
<button onClick={() => setPause(!isPaused)}>
{isPaused ? "Resume" : "Pause"}
</button>
{ tempData?
tempData.map((data, i) => (
<div>
<span>{data.country}</span>
<span>{data.temp}</span>
</div>
))
: null }
</div>
)
}
The code is executing only once because there are no recurrying calls to the web socket send event. When the web socket is created it gets the data from the database and sends it, and thats it.
You probably want some kind of action that triggers this event multiple times. For example, in your code:
wss.on("connection", async webSocketClient => {
console.log("A new client Connected!");
setInterval(() => {
const timeNow = Date.now();
webSocketClient.send(
JSON.stringify([
{ country: "country-a", temp: timeNow },
{ country: "country-b", temp: timeNow },
])
);
}, 1000);
webSocketClient.on("message", message => {
console.log("received: %s", message);
});
});
I see you are using some package to pool from a PostgreSQL db. Take a look at this other example.
How would your clients know if there is any change in database on server side ?
You can create an event that triggers each time a particular data changes and listen to those event on your client sockets. Like you did with onmessage event in your current code.
You can render the react component based on this event.
Hi I'm making a card game and i've been able to connect my client side with my socket.io server but the problem is my client sends a lot of requests when connecting to the server.
I'm using ReactJS on the client side and ExpressJS + socket.io on the server side.
On the client side:
const Room = () => {
const dispatch = useAppDispatch();
const params = useParams() as any;
const user = useAppSelector(selectUser);
const room = useAppSelector(selectRoom);
const [socket] = useState<Socket<DefaultEventsMap, DefaultEventsMap>>(io('http://localhost:3000'));
useEffect(() => {
if (params.id) {
dispatch(getRoom(params.id));
}
return () => { socket.disconnect(); };
}, []);
useEffect(() => {
if (user.name && room.id) {
socket.emit(JOIN_ROOM, {
user: {
name: user.name,
money: user.money,
},
roomId: room.id,
random_seat: room.random_seat,
max_number_of_player: room.max_number_of_player,
});
}
return () => {
socket.emit(LEAVE_ROOM, {
username: user.name,
roomId: room.id,
});
};
}, [user.name, room.id]);
And on the server side:
const onConnection = (socket) => {
roomHandler(io, socket, store);
chatHandler(io, socket);
socket.on('disconnect', function() {
console.log("User has disconnected: " + socket.id)
});
}
When i reload the page i see this log:
User has disconnected: mxh8AqLWSvpB9IqtAAAs
User has disconnected: KefLTWmzHwt4yxi7AAAt
User has disconnected: cNyOJtqX4gLRlkSFAAAv
User has disconnected: hWjpCSx6-fypEcp1AAAu
User has disconnected: D407Grg1YgpLz6V-AAA2
User has disconnected: KN5gWEZSkI4tvqZ2AAA1
User has disconnected: 6QY_pzmugv7hQuZiAAA0
User has disconnected: nunKDWVRiishLsCbAAA3
So when i reload, only one user has disconnected but along with it are multiple socket connections.
I'm initiating the connection by using useState and store the client socket instance as a state which is passed down as props so children component can use it and emits/listens to event.
If i open my network tab in the browser i can also see lots of request made to my server side.
What am i doing wrong here?
Every time this component renders, it will call io('url') and create a new connection. Instead, create the connection in useEffect once and keep a reference.
const socket = useRef();
useEffect(() => {
socket.current = io('url');
return () => {
socker.current.disconnect()
};
}, []);
I'm creating a chat app with sockets where you first come to a landing page to enter your nickname, and then join the chat.
In the chatpage amongst the form and the message-area, I have a disconnect button that takes you back to the landing page. The idea is that you should be able to enter a nickname (same or new) and join the chat again (without seeing previous history).
I manage to send them to the landing page but for some reason they can't reconnect again.
Is there something I'm missing to make that work?
Listen on disconnect on server file
socket.on('disconnect', () => {
socket.broadcast.emit('user-disconnected', users[socket.id]);
delete users[socket.id];
console.log('bye bye');
});
chat.js emits disconnect and sends back to landing page
const feedbackBox = message => {
showFeedback.innerText = message;
showFeedback.classList.add('feedback-I-disconnect');
showFeedback.classList.remove('hide');
container.appendChild(showFeedback);
};
disconnectButton.addEventListener('click', event => {
if (event.target.classList.contains('disconnect-button')) {
socket.disconnect();
messageContainer.classList.add('hide');
messageForm.classList.add('hide');
disconnectButton.classList.add('hide');
appendForm();
feedbackBox('You disconnected from the chat');
}
});
name.js is the landing page
const getName = () => {
form.addEventListener('submit', e => {
e.preventDefault();
const name = nameInput.value;
socket.emit('new-user', name);
nameInput.value = '';
socket.off('name-taken');
socket.on('name-taken', () => {
feedbackBox('Nickname already taken');
});
socket.off('user-accepted');
socket.on('user-accepted', () => {
title.classList.add('hide');
nameContainer.classList.add('hide');
addMessageForm();
});
});
};
const appendForm = () => {
nameInput.classList.add('name_input');
form.appendChild(nameInput);
submitName.classList.add('submit_name');
form.appendChild(submitName);
nameContainer.appendChild(form);
nameContainer.classList.add('name_container');
nameContainer.classList.remove('hide');
title.classList.remove('hide');
title.classList.add('name_title');
container.appendChild(title);
container.appendChild(nameContainer);
getName();
};