From f1c8c6e9d0c79c09243955b8cb53005ae17a3ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B4=D0=B8=D1=8F=D1=80=20=D0=A1=D0=B5=D0=B9?= =?UTF-8?q?=D0=BB=D1=85=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Sun, 26 May 2024 00:09:39 +0500 Subject: [PATCH 1/4] useWebSocket hook, jsdoc are already included into hook file --- src/hooks/useWebSocket/useWebSocket.ts | 62 ++++++++++++++++++++++++++ yarn.lock | 5 --- 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/hooks/useWebSocket/useWebSocket.ts diff --git a/src/hooks/useWebSocket/useWebSocket.ts b/src/hooks/useWebSocket/useWebSocket.ts new file mode 100644 index 00000000..25336323 --- /dev/null +++ b/src/hooks/useWebSocket/useWebSocket.ts @@ -0,0 +1,62 @@ +import { useEffect, useRef, useState } from 'react'; + +const connectSocket = (url: string) => { + return new WebSocket(url); +}; + +/** + * @name useWebSocket + * @description Hook connects to a websocket server and returns a event + * @param {url} string + * @param {onMessage} callback + * @returns {Ref} + * @example + * useWebSocket('wss://serverws.com/', (message) => console.log(message)); + */ +export const useWebSocket = (url: string, onMessage: (message: MessageEvent) => void) => { + const ws = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [waitingToReconnect, setWaitingToReconnect] = useState(null); + + useEffect(() => { + if (waitingToReconnect) return; + + const setupWebSocket = () => { + const client = connectSocket(url); + ws.current = client; + + client.onerror = (e) => console.error('WebSocket error:', e); + + client.onopen = () => { + setIsOpen(true); + }; + + client.onmessage = (message) => { + onMessage(message); + }; + + client.onclose = () => { + if (waitingToReconnect) return; + + setIsOpen(false); + + setWaitingToReconnect(true); + + setTimeout(() => { + setWaitingToReconnect(null); + }, 5000); + }; + }; + + setupWebSocket(); + + return () => { + if (ws.current) { + ws.current.close(); + ws.current = null; + } + }; + }, [url, waitingToReconnect]); + + return { client: ws.current, isOpen }; +}; diff --git a/yarn.lock b/yarn.lock index fcd39841..1019f5b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2359,11 +2359,6 @@ dependencies: vue-demi ">=0.14.7" -"@webcam/core@*": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@webcam/core/-/core-1.0.1.tgz#89394d0faaf97ea842952735d7fbfe0aaf45abd6" - integrity sha512-N422fDE1iJ5pc5IiLh2NhvxQPFYTMLDbw+x0YtREGMDg4S05qvRgD8Au0N0/JoQUVvS7Vw+ns16/jYPUDgljtQ== - abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" From 21617c3ddf2cd06b54b90d94828a22ca3aad4e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B4=D0=B8=D1=8F=D1=80=20=D0=A1=D0=B5=D0=B9?= =?UTF-8?q?=D0=BB=D1=85=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Sun, 26 May 2024 13:47:09 +0500 Subject: [PATCH 2/4] reworked hook, added jsdoc --- src/hooks/useWebSocket/useWebSocket.ts | 52 +++++++++++++++++++++----- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/hooks/useWebSocket/useWebSocket.ts b/src/hooks/useWebSocket/useWebSocket.ts index 25336323..cc1ffa40 100644 --- a/src/hooks/useWebSocket/useWebSocket.ts +++ b/src/hooks/useWebSocket/useWebSocket.ts @@ -4,20 +4,46 @@ const connectSocket = (url: string) => { return new WebSocket(url); }; +interface IOptions { + onFail: () => void; + onSuccess: () => void; +} + /** - * @name useWebSocket - * @description Hook connects to a websocket server and returns a event - * @param {url} string - * @param {onMessage} callback - * @returns {Ref} + * Custom React hook to connect to a WebSocket server. + * + * @function useWebSocket + * @param {string} url - The WebSocket server URL to connect to. + * @param {function} onMessage - Callback function to handle incoming WebSocket messages. + * @param {IOptions} [options] - Optional configuration object. + * @param {function} options.onFail - Callback function to execute when the WebSocket connection fails. + * @param {function} options.onSuccess - Callback function to execute when the WebSocket connection is successfully established. + * @returns {object} - An object containing the WebSocket client instance, a boolean indicating if the connection is open, and a function to send data through the WebSocket. + * @returns {WebSocket|null} return.client - The WebSocket client instance or null if not connected. + * @returns {boolean} return.open - A boolean indicating if the WebSocket connection is open. + * @returns {function} return.send - A function to send data through the WebSocket. + * * @example - * useWebSocket('wss://serverws.com/', (message) => console.log(message)); + * const { client, open, send } = useWebSocket('wss://serverws.com/', (message) => { + * console.log(message.data); + * }, { + * onFail: () => console.log('Connection failed'), + * onSuccess: () => console.log('Connection successful') + * }); */ -export const useWebSocket = (url: string, onMessage: (message: MessageEvent) => void) => { +export const useWebSocket = ( + url: string, + onMessage: (message: MessageEvent) => void, + options?: IOptions +) => { const ws = useRef(null); const [isOpen, setIsOpen] = useState(false); const [waitingToReconnect, setWaitingToReconnect] = useState(null); + const send = (data: string) => { + ws.current?.send(data); + }; + useEffect(() => { if (waitingToReconnect) return; @@ -25,10 +51,18 @@ export const useWebSocket = (url: string, onMessage: (message: MessageEvent) => const client = connectSocket(url); ws.current = client; - client.onerror = (e) => console.error('WebSocket error:', e); + client.onerror = (e) => { + if (options?.onFail) { + options?.onFail(); + } + console.error('WebSocket error:', e); + }; client.onopen = () => { setIsOpen(true); + if (options?.onSuccess) { + options?.onSuccess(); + } }; client.onmessage = (message) => { @@ -58,5 +92,5 @@ export const useWebSocket = (url: string, onMessage: (message: MessageEvent) => }; }, [url, waitingToReconnect]); - return { client: ws.current, isOpen }; + return { client: ws.current, open: isOpen, send }; }; From f5ace85261280cd1cbb70461e3679b865ddd17d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B4=D0=B8=D1=8F=D1=80=20=D0=A1=D0=B5=D0=B9?= =?UTF-8?q?=D0=BB=D1=85=D0=B0=D0=BD=D0=BE=D0=B2?= Date: Mon, 27 May 2024 19:51:25 +0500 Subject: [PATCH 3/4] feat: added status, reconnect timeout into socket hook --- src/hooks/useWebSocket/useWebSocket.ts | 27 +++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/hooks/useWebSocket/useWebSocket.ts b/src/hooks/useWebSocket/useWebSocket.ts index cc1ffa40..c7547b61 100644 --- a/src/hooks/useWebSocket/useWebSocket.ts +++ b/src/hooks/useWebSocket/useWebSocket.ts @@ -7,8 +7,11 @@ const connectSocket = (url: string) => { interface IOptions { onFail: () => void; onSuccess: () => void; + reconnectTimeout?: number; } +type IStatus = 'connecting' | 'failed' | 'connected' | 'disconnected'; + /** * Custom React hook to connect to a WebSocket server. * @@ -18,18 +21,25 @@ interface IOptions { * @param {IOptions} [options] - Optional configuration object. * @param {function} options.onFail - Callback function to execute when the WebSocket connection fails. * @param {function} options.onSuccess - Callback function to execute when the WebSocket connection is successfully established. + * @param {number} [options.reconnectTimeout=5000] - The time interval in milliseconds to wait before attempting to reconnect after the connection is closed. * @returns {object} - An object containing the WebSocket client instance, a boolean indicating if the connection is open, and a function to send data through the WebSocket. * @returns {WebSocket|null} return.client - The WebSocket client instance or null if not connected. * @returns {boolean} return.open - A boolean indicating if the WebSocket connection is open. * @returns {function} return.send - A function to send data through the WebSocket. - * + * @returns {status} return.status - A status string, which tells the user status of connection * @example + * * const { client, open, send } = useWebSocket('wss://serverws.com/', (message) => { * console.log(message.data); * }, { * onFail: () => console.log('Connection failed'), - * onSuccess: () => console.log('Connection successful') + * onSuccess: () => console.log('Connection successful'), + * reconnectTimeout: 10000 * }); + * + * const sendMessage = (data) => { + * send(data) + * } */ export const useWebSocket = ( url: string, @@ -39,9 +49,10 @@ export const useWebSocket = ( const ws = useRef(null); const [isOpen, setIsOpen] = useState(false); const [waitingToReconnect, setWaitingToReconnect] = useState(null); + const [status, setStatus] = useState('connecting'); - const send = (data: string) => { - ws.current?.send(data); + const send = (data: Record) => { + ws.current?.send(JSON.stringify(data)); }; useEffect(() => { @@ -55,11 +66,13 @@ export const useWebSocket = ( if (options?.onFail) { options?.onFail(); } + setStatus('failed'); console.error('WebSocket error:', e); }; client.onopen = () => { setIsOpen(true); + setStatus('connected'); if (options?.onSuccess) { options?.onSuccess(); } @@ -73,12 +86,12 @@ export const useWebSocket = ( if (waitingToReconnect) return; setIsOpen(false); - + setStatus('disconnected'); setWaitingToReconnect(true); setTimeout(() => { setWaitingToReconnect(null); - }, 5000); + }, options?.reconnectTimeout || 5000); }; }; @@ -92,5 +105,5 @@ export const useWebSocket = ( }; }, [url, waitingToReconnect]); - return { client: ws.current, open: isOpen, send }; + return { client: ws.current, open: isOpen, send, status }; }; From a54e54cb126e00a7937fb15f9f19b8831c7ab687 Mon Sep 17 00:00:00 2001 From: "a.seylkhanov" Date: Fri, 31 May 2024 10:40:09 +0500 Subject: [PATCH 4/4] feat: added jsdoc into send function, examples included --- src/hooks/useWebSocket/useWebSocket.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/hooks/useWebSocket/useWebSocket.ts b/src/hooks/useWebSocket/useWebSocket.ts index c7547b61..74cdf8c1 100644 --- a/src/hooks/useWebSocket/useWebSocket.ts +++ b/src/hooks/useWebSocket/useWebSocket.ts @@ -38,7 +38,7 @@ type IStatus = 'connecting' | 'failed' | 'connected' | 'disconnected'; * }); * * const sendMessage = (data) => { - * send(data) + * send(data); * } */ export const useWebSocket = ( @@ -50,9 +50,25 @@ export const useWebSocket = ( const [isOpen, setIsOpen] = useState(false); const [waitingToReconnect, setWaitingToReconnect] = useState(null); const [status, setStatus] = useState('connecting'); - - const send = (data: Record) => { - ws.current?.send(JSON.stringify(data)); + /** + * + * @param {string} data + * @description The message sent to a socket can be anything, not only object. That is the reason why you have to convert all of your data into string. + * @example + * const {send} = useWebSocket('wss://serverws.com/', (message) => { + * console.log(message.data); + * }) + * + * function handleSubmit() { + * const message = "Hello World!"; + * send(message); + * + * const object = {message: "Hello World!"}; + * send(JSON.stringify(object)); + * } + */ + const send = (data: string) => { + ws.current?.send(data); }; useEffect(() => {