import React from "react"; export type UseWebsocketHookParams = { debounce?: number; url: string; disableReconnect?: boolean; }; let reconnectInterval: any; let msgInterval: any; let sendInterval: any; export const WebSocketEventNames = ["wsDataEvent", "wsMessageEvent"] as const; let tries = 0; /** * # 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< T extends { [key: string]: any } = { [key: string]: any } >({ url, debounce, disableReconnect }: UseWebsocketHookParams) { const DEBOUNCE = debounce || 200; const [socket, setSocket] = React.useState( undefined ); const messageQueueRef = React.useRef([]); const sendMessageQueueRef = React.useRef([]); const dispatchCustomEvent = React.useCallback( (evtName: (typeof WebSocketEventNames)[number], value: string | T) => { const event = new CustomEvent(evtName, { detail: { data: value, message: value, }, }); window.dispatchEvent(event); }, [] ); const connect = React.useCallback(() => { const wsURL = url; if (!wsURL) return; let ws = new WebSocket(wsURL); ws.onopen = (ev) => { window.clearInterval(reconnectInterval); setSocket(ws); tries = 0; console.log(`Websocket connected to ${wsURL}`); }; ws.onmessage = (ev) => { window.clearInterval(msgInterval); messageQueueRef.current.push(ev.data); msgInterval = setInterval(handleReceivedMessageQueue, DEBOUNCE); }; ws.onclose = (ev) => { if (disableReconnect) return; console.log("Websocket closed ... Attempting to reconnect ..."); console.log("URL:", url); reconnectInterval = setInterval(() => { if (tries >= 3) { return window.clearInterval(reconnectInterval); } console.log("Attempting to reconnect ..."); tries++; connect(); }, 1000); }; }, []); React.useEffect(() => { connect(); return function () { window.clearInterval(reconnectInterval); }; }, []); /** * 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); 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 }; }