import React from "react"; export type UseWebsocketHookParams = { debounce?: number; url: string; }; let reconnectInterval: any; let msgInterval: any; let sendInterval: any; let tries = 0; export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const; /** * # Use Websocket Hook * @event wsDataEvent Listen for event named `wsDataEvent` on `window` to receive Data events * @event wsMessageEvent Listen for event named `wsMessageEvent` on `window` to receive Message events * * @example window.addEventLiatener("wsDataEvent", (e)=>{ * console.log(e.detail.data) // type object * }) * @example window.addEventLiatener("wsMessageEvent", (e)=>{ * console.log(e.detail.message) // type string * }) */ export default function useWebSocket({ url, debounce, }: UseWebsocketHookParams) { const DEBOUNCE = debounce || 200; const [socket, setSocket] = React.useState( undefined ); const messageQueueRef = React.useRef([]); const sendMessageQueueRef = React.useRef([]); // const [message, setMessage] = React.useState(""); // const [data, setData] = React.useState(null); const [refresh, setRefresh] = React.useState(0); const dispatchCustomEvent = React.useCallback( (evtName: (typeof WebSocketEventNames)[number], value: string | T) => { const event = new CustomEvent(evtName, { detail: { data: value, message: value, }, }); window.dispatchEvent(event); }, [] ); React.useEffect(() => { const wsURL = url; if (!wsURL) return; const ws = new WebSocket(wsURL); ws.onopen = (ev) => { window.clearInterval(reconnectInterval); setSocket(ws); tries = 0; }; ws.onmessage = (ev) => { window.clearInterval(msgInterval); messageQueueRef.current.push(ev.data); msgInterval = setInterval(handleReceivedMessageQueue, DEBOUNCE); }; ws.onclose = (ev) => { console.log("Websocket closed ... Attempting to reconnect ..."); reconnectInterval = setInterval(() => { if (tries >= 3) { return window.clearInterval(reconnectInterval); } console.log("Attempting to reconnect ..."); setRefresh(refresh + 1); tries++; }, 1000); }; return function () { window.clearInterval(reconnectInterval); }; }, [refresh]); /** * Received Message Queue Handler */ const handleReceivedMessageQueue = React.useCallback(() => { if (messageQueueRef.current.length > 0) { const newMessage = messageQueueRef.current.shift(); if (!newMessage) return; try { const jsonData = JSON.parse(newMessage); // setData(jsonData); dispatchCustomEvent("wsMessageEvent", newMessage); dispatchCustomEvent("wsDataEvent", jsonData); } catch (error) { console.log("Unable to parse string. Returning string."); } } else { window.clearInterval(msgInterval); } }, []); /** * Send Message Queue Handler */ const handleSendMessageQueue = React.useCallback(() => { if (sendMessageQueueRef.current.length > 0) { const newMessage = sendMessageQueueRef.current.shift(); if (!newMessage) return; socket?.send(newMessage); } else { window.clearInterval(sendInterval); } }, [socket]); /** * # Send Data Function */ const sendData = React.useCallback( (data: T) => { try { window.clearInterval(sendInterval); sendMessageQueueRef.current.push(JSON.stringify(data)); sendInterval = setInterval(handleSendMessageQueue, DEBOUNCE); } catch (error: any) { console.log("Error Sending socket message", error.message); } }, [socket] ); return { socket, sendData }; }