Closed danielcrk closed 1 year ago
Hi @danielcrk!
Thank you for the kind words and for your thoughtful consideration and suggestion. That definitely sounds like a useful feature to provide OOTB -- could you please share your current solution/wrapper? I have a few ideas myself of how this might work, but would love to start with an example of how someone using this library had to solve it by hand
Hey @robtaussig, sure, here's a stripped-down version of my wrapper.
The solution is far from perfect, but good enough for my usecase. The interval is set to 2 seconds and the ping message needs to be sent every 10 seconds, so the actual ping timing wont be exact, but keeping things simple was worth the trade-off.
So basically, each instance of the hook will have its own timer, but will only actually send the ping message if another instance hasn't already done so within the last 10-ish seconds for that specific url.
import { useEffect, useRef, useState } from 'react';
import useReactWebSocket from 'react-use-websocket';
// Multiple instances of the hook can exist simultaneously.
// This stores the timestamp of the last heartbeat for a given socket url,
// preventing other instances to send unnecessary heartbeats.
const previousHeartbeats: Record<string, number> = {};
const useWebSocket = <T>(
url?: string,
options?: IWebSocketOptions
): T | undefined => {
const [message, setMessage] = useState<T>();
// Stores the heartbeat interval.
const heartbeatIntervalRef = useRef<number>();
// Instantiate useReactWebSocket.
const { sendMessage, readyState } = useReactWebSocket(
url ? getAbsoluteWebSocketUrl(url, options?.appendCustomHeaders) : null,
{
share: options?.shareConnection,
reconnectAttempts: 6,
reconnectInterval: 1000,
onMessage: handleOnMessage,
onError: handleOnError,
shouldReconnect: handleShouldReconnect,
},
!!url
);
// Sends a periodical heartbeat message through the websocket connection.
useEffect(() => {
if (readyState === 1) {
heartbeatIntervalRef.current = window.setInterval(() => {
if (url) {
const lastHeartbeat = previousHeartbeats[url];
const deltaFromNow = (Date.now() - lastHeartbeat) / 1000;
// Send a heartbeat message if it hasn't already been sent within the last 10 seconds.
if (!lastHeartbeat || deltaFromNow > 10) {
// Send the heartbeat message and update the heartbeat history.
sendMessage('');
previousHeartbeats[url] = Date.now();
}
}
}, 2000);
}
return () => {
clearInterval(heartbeatIntervalRef.current);
};
}, [url, readyState, sendMessage]);
return message;
};
export default useWebSocket;
I'm really curious about your idea as well! :)
@danielcrk
Interesting! I'll take a closer look over the weekend, but at first glance it looks good to me!
This is also something I'd like to see added
@robtaussig awesome, thanks for considering it!
Awesome idea! Looking forward this feature.
This would be a great feature. Could potentially be enhanced further by including options to configure the heartbeat message, interval and an "onFailure" callback.
Thanks for the great work on this hook 🙌
I've made a basic heartbeat implementation, it's released in version 4.5.0
.
Here is an example:
const { sendMessage, lastMessage, readyState } = useWebSocket(
'ws://localhost:3000',
{
heartbeat: {
message: 'ping',
returnMessage: 'pong',
timeout: 60000, // 1 minute, if no response is received, the connection will be closed
interval: 25000, // every 25 seconds, a ping message will be sent
},
}
);
Hi Robert! First of all, thank you so much for this amazing hook - the ability to share a single connection between multiple instances really sets it apart!
I'm currently using the hook in a project where the backend requires a periodical heartbeat (just an empty message every 10 seconds or so) to keep the connection alive. I think this is a pretty common pattern.
Given the fact that the hook supports connection sharing and invites the usage of multiple instances, writing the heartbeat functionality isn't super trivial. I've written my own wrapper for your hook (mostly to implement message type generics), and that wrapper hook must basically use and share static variables between instances, as to not send multiple heartbeat messages from each of them.
It would be amazing if this was provided out of the box by
react-use-websocket
.Thank you again for your amazing work!