useWebSocket.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { useEffect, useRef, useState } from 'react';
  2. import { usePersistFn, useUnmount } from 'ahooks';
  3. import { Store } from 'jetlinks-store';
  4. import SystemConst from '@/utils/const';
  5. export enum ReadyState {
  6. Connecting = 0,
  7. Open = 1,
  8. Closing = 2,
  9. Closed = 3,
  10. }
  11. export interface Options {
  12. reconnectLimit?: number;
  13. reconnectInterval?: number;
  14. manual?: boolean;
  15. onOpen?: (event: WebSocketEventMap['open']) => void;
  16. onClose?: (event: WebSocketEventMap['close']) => void;
  17. onMessage?: (message: WebSocketEventMap['message']) => void;
  18. onError?: (event: WebSocketEventMap['error']) => void;
  19. }
  20. export interface Result {
  21. latestMessage?: WebSocketEventMap['message'];
  22. sendMessage?: WebSocket['send'];
  23. disconnect?: () => void;
  24. connect?: () => void;
  25. readyState: ReadyState;
  26. webSocketIns?: WebSocket;
  27. }
  28. export default function useWebSocket(socketUrl: string, options: Options = {}): Result {
  29. const {
  30. reconnectLimit = 3,
  31. reconnectInterval = 3 * 1000,
  32. manual = false,
  33. onOpen,
  34. onClose,
  35. onMessage,
  36. onError,
  37. } = options;
  38. const reconnectTimesRef = useRef(0);
  39. const reconnectTimerRef = useRef<NodeJS.Timeout>();
  40. const websocketRef = useRef<WebSocket>();
  41. const [latestMessage, setLatestMessage] = useState<WebSocketEventMap['message']>();
  42. const [readyState, setReadyState] = useState<ReadyState>(ReadyState.Closed);
  43. const connectWs = usePersistFn(() => {
  44. const ws = Store.get(SystemConst.GLOBAL_WEBSOCKET) as WebSocket;
  45. if (ws) {
  46. setReadyState(ws?.readyState);
  47. } else {
  48. if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
  49. // if (websocketRef.current) {
  50. // // 此处应考虑状态。
  51. // websocketRef.current.close();
  52. // }
  53. try {
  54. console.log(websocketRef.current, 'current');
  55. websocketRef.current = new WebSocket(socketUrl);
  56. websocketRef.current.onerror = (event) => {
  57. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  58. reconnect();
  59. onError?.(event);
  60. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  61. };
  62. websocketRef.current.onopen = (event) => {
  63. onOpen?.(event);
  64. reconnectTimesRef.current = 0;
  65. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  66. };
  67. websocketRef.current.onmessage = (message: WebSocketEventMap['message']) => {
  68. onMessage?.(message);
  69. setLatestMessage(message);
  70. };
  71. websocketRef.current.onclose = (event) => {
  72. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  73. reconnect();
  74. onClose?.(event);
  75. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  76. };
  77. Store.set(SystemConst.GLOBAL_WEBSOCKET, websocketRef.current);
  78. } catch (error) {
  79. throw new Error(error as string);
  80. }
  81. }
  82. });
  83. /**
  84. * 重连
  85. */
  86. const reconnect = usePersistFn(() => {
  87. if (
  88. reconnectTimesRef.current < reconnectLimit &&
  89. websocketRef.current?.readyState !== ReadyState.Open
  90. ) {
  91. if (reconnectTimerRef.current) {
  92. clearTimeout(reconnectTimerRef.current);
  93. }
  94. reconnectTimerRef.current = setTimeout(() => {
  95. connectWs();
  96. reconnectTimesRef.current += 1;
  97. }, reconnectInterval);
  98. }
  99. });
  100. /**
  101. * 发送消息
  102. * @param message
  103. */
  104. const sendMessage: WebSocket['send'] = usePersistFn((message) => {
  105. const ws = Store.get(SystemConst.GLOBAL_WEBSOCKET) as WebSocket;
  106. setReadyState(ws?.readyState);
  107. if (readyState === ReadyState.Open) {
  108. ws.send(message);
  109. } else {
  110. connectWs();
  111. // todo 考虑重写
  112. setTimeout(() => {
  113. ws.send(message);
  114. }, 3000);
  115. // throw new Error('WebSocket disconnected');
  116. }
  117. });
  118. /**
  119. * 手动 connect
  120. */
  121. const connect = usePersistFn(() => {
  122. reconnectTimesRef.current = 0;
  123. connectWs();
  124. });
  125. /**
  126. * disconnect websocket
  127. */
  128. const disconnect = usePersistFn(() => {
  129. if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
  130. reconnectTimesRef.current = reconnectLimit;
  131. websocketRef.current?.close();
  132. });
  133. useEffect(() => {
  134. // 初始连接
  135. if (!manual) {
  136. connect();
  137. }
  138. }, [socketUrl, manual]);
  139. useUnmount(() => {
  140. disconnect();
  141. });
  142. return {
  143. latestMessage,
  144. sendMessage,
  145. connect,
  146. disconnect,
  147. readyState,
  148. webSocketIns: websocketRef.current,
  149. };
  150. }