(
ctx: WebSocketHookContext,
url: string,
protocol?: string | readonly string[],
options: UseWebSocketOptions<T> = {},
)
| 631 | * Subscribe to a websocket endpoint with message parsing and auto-reconnect. |
| 632 | */ |
| 633 | export function useWebSocket<T = string>( |
| 634 | ctx: WebSocketHookContext, |
| 635 | url: string, |
| 636 | protocol?: string | readonly string[], |
| 637 | options: UseWebSocketOptions<T> = {}, |
| 638 | ): UseWebSocketState<T> { |
| 639 | const [value, setValue] = ctx.useState<T | undefined>(undefined); |
| 640 | const [loading, setLoading] = ctx.useState<boolean>(true); |
| 641 | const [connected, setConnected] = ctx.useState<boolean>(false); |
| 642 | const [reconnectAttempts, setReconnectAttempts] = ctx.useState(0); |
| 643 | const [error, setError] = ctx.useState<unknown>(undefined); |
| 644 | const runIdRef = ctx.useRef(0); |
| 645 | const socketRef = ctx.useRef<WebSocketLike | null>(null); |
| 646 | const manualCloseRef = ctx.useRef(false); |
| 647 | const reconnectTimerRef = ctx.useRef<ReturnType<typeof setTimeout> | undefined>(undefined); |
| 648 | const sendRef = ctx.useRef<((payload: WebSocketSendPayload) => boolean) | undefined>(undefined); |
| 649 | const closeRef = ctx.useRef<((code?: number, reason?: string) => void) | undefined>(undefined); |
| 650 | |
| 651 | if (!sendRef.current) { |
| 652 | sendRef.current = (payload: WebSocketSendPayload): boolean => { |
| 653 | const socket = socketRef.current; |
| 654 | if (!socket) return false; |
| 655 | try { |
| 656 | socket.send(payload); |
| 657 | return true; |
| 658 | } catch (nextError) { |
| 659 | setError(nextError); |
| 660 | return false; |
| 661 | } |
| 662 | }; |
| 663 | } |
| 664 | |
| 665 | if (!closeRef.current) { |
| 666 | closeRef.current = (code?: number, reason?: string): void => { |
| 667 | manualCloseRef.current = true; |
| 668 | if (reconnectTimerRef.current !== undefined) { |
| 669 | clearTimeout(reconnectTimerRef.current); |
| 670 | reconnectTimerRef.current = undefined; |
| 671 | } |
| 672 | const socket = socketRef.current; |
| 673 | if (!socket) return; |
| 674 | try { |
| 675 | socket.close(code, reason); |
| 676 | } catch (nextError) { |
| 677 | setError(nextError); |
| 678 | } finally { |
| 679 | socketRef.current = null; |
| 680 | } |
| 681 | }; |
| 682 | } |
| 683 | |
| 684 | const enabled = options.enabled ?? true; |
| 685 | const reconnectMs = normalizeReconnectDelayMs(options.reconnectMs, DEFAULT_STREAM_RECONNECT_MS); |
| 686 | const parse = options.parse; |
| 687 | |
| 688 | ctx.useEffect(() => { |
| 689 | runIdRef.current += 1; |
| 690 | const runId = runIdRef.current; |
no test coverage detected