useWebSocket.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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. onReconnect?: () => void;
  20. }
  21. export interface Result {
  22. latestMessage?: WebSocketEventMap['message'];
  23. sendMessage?: WebSocket['send'];
  24. disconnect?: () => void;
  25. connect?: () => void;
  26. readyState: ReadyState;
  27. webSocketIns?: WebSocket;
  28. }
  29. export default function useWebSocket(socketUrl: string, options: Options = {}): Result {
  30. const {
  31. // reconnectLimit = 3,
  32. // reconnectInterval = 3 * 1000,
  33. manual = false,
  34. onOpen,
  35. onClose,
  36. onMessage,
  37. onReconnect,
  38. onError,
  39. } = options;
  40. const reconnectTimesRef = useRef(0); // 重连次数
  41. const reconnectTimerRef = useRef<NodeJS.Timeout>(); // 计时器
  42. const websocketRef = useRef<WebSocket>();
  43. const lockReconnect = useRef(false); // 避免重复连接
  44. const isReconnect = useRef(false);
  45. const [latestMessage, setLatestMessage] = useState<WebSocketEventMap['message']>();
  46. const [readyState, setReadyState] = useState<ReadyState>(ReadyState.Closed);
  47. const connectWs = usePersistFn(() => {
  48. const ws = Store.get(SystemConst.GLOBAL_WEBSOCKET) as WebSocket;
  49. if (ws) {
  50. setReadyState(ws?.readyState);
  51. } else {
  52. // if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
  53. // if (websocketRef.current) {
  54. // // 此处应考虑状态。
  55. // websocketRef.current.close();
  56. // }
  57. try {
  58. // console.log(websocketRef.current, 'current');
  59. websocketRef.current = new WebSocket(socketUrl);
  60. websocketRef.current.onerror = (event) => {
  61. isReconnect.current = true; // 开启重连
  62. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  63. reconnect();
  64. onError?.(event);
  65. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  66. };
  67. websocketRef.current.onopen = (event) => {
  68. if (isReconnect.current && onReconnect) {
  69. // 是否为重连
  70. onReconnect();
  71. }
  72. onOpen?.(event);
  73. reconnectTimesRef.current = 0;
  74. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  75. };
  76. websocketRef.current.onmessage = (message: WebSocketEventMap['message']) => {
  77. onMessage?.(message);
  78. setLatestMessage(message);
  79. };
  80. websocketRef.current.onclose = (event) => {
  81. isReconnect.current = true; // 开启重连
  82. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  83. reconnect();
  84. onClose?.(event);
  85. setReadyState(websocketRef.current?.readyState || ReadyState.Closed);
  86. Store.set(SystemConst.GLOBAL_WEBSOCKET, null);
  87. };
  88. Store.set(SystemConst.GLOBAL_WEBSOCKET, websocketRef.current);
  89. } catch (error) {
  90. throw new Error(error as string);
  91. }
  92. }
  93. });
  94. const getTime = (time: number): number => {
  95. const m = 60 * 1000;
  96. if (time <= 5) {
  97. return 3000;
  98. } else if (time > 5 && time <= 10) {
  99. return 10000;
  100. } else if (time > 10 && time <= 20) {
  101. return m;
  102. }
  103. return 5 * m;
  104. };
  105. /**
  106. * 重连
  107. */
  108. const reconnect = usePersistFn(() => {
  109. if (lockReconnect.current) {
  110. return;
  111. }
  112. if (reconnectTimerRef.current) {
  113. clearTimeout(reconnectTimerRef.current);
  114. }
  115. lockReconnect.current = true;
  116. const _time = getTime(reconnectTimesRef.current);
  117. reconnectTimerRef.current = setTimeout(() => {
  118. lockReconnect.current = false;
  119. reconnectTimesRef.current += 1;
  120. connectWs();
  121. }, _time);
  122. });
  123. /**
  124. * 发送消息
  125. * @param message
  126. */
  127. const sendMessage: WebSocket['send'] = usePersistFn((message) => {
  128. const ws = Store.get(SystemConst.GLOBAL_WEBSOCKET) as WebSocket;
  129. setReadyState(ws?.readyState);
  130. if (readyState === ReadyState.Open) {
  131. ws.send(message);
  132. } else {
  133. connectWs();
  134. // todo 考虑重写
  135. setTimeout(() => {
  136. ws.send(message);
  137. }, 3000);
  138. // throw new Error('WebSocket disconnected');
  139. }
  140. });
  141. /**
  142. * 手动 connect
  143. */
  144. const connect = usePersistFn(() => {
  145. reconnectTimesRef.current = 0;
  146. connectWs();
  147. });
  148. /**
  149. * disconnect websocket
  150. */
  151. const disconnect = usePersistFn(() => {
  152. if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
  153. reconnectTimesRef.current = 0;
  154. websocketRef.current?.close();
  155. });
  156. useEffect(() => {
  157. // 初始连接
  158. if (!manual) {
  159. connect();
  160. }
  161. }, [socketUrl, manual]);
  162. useUnmount(() => {
  163. disconnect();
  164. });
  165. return {
  166. latestMessage,
  167. sendMessage,
  168. connect,
  169. disconnect,
  170. readyState,
  171. webSocketIns: websocketRef.current,
  172. };
  173. }