webSocketClient.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import WebSocket from "ws";
  2. import Logs from "./logs.js";
  3. /**
  4. * 解析推送过来的消息
  5. * @param {*} data
  6. * @returns {object}
  7. */
  8. const parseMessage = (data) => {
  9. let message;
  10. try {
  11. message = JSON.parse(data);
  12. }
  13. catch(e) {
  14. message = { text: data.toString() };
  15. }
  16. return message;
  17. }
  18. /**
  19. * 自定义Websocket类
  20. * 添加自动发送ping指令
  21. */
  22. class WsClient {
  23. #websocket;
  24. #pingTimer;
  25. constructor(wsserver, options) {
  26. this.#websocket = new WebSocket(wsserver, options);
  27. }
  28. get readyState() {
  29. return this.#websocket.readyState;
  30. }
  31. on(type, callback) {
  32. switch(type) {
  33. case 'open':
  34. this.#websocket.on('open', event => {
  35. this.#pingTimer = setInterval(() => {
  36. if (this.readyState === WebSocket.OPEN) {
  37. this.#websocket.send('PING');
  38. }
  39. }, 10_000);
  40. callback(event);
  41. });
  42. break;
  43. case 'message':
  44. this.#websocket.on('message', message => {
  45. const data = parseMessage(message);
  46. callback(data);
  47. });
  48. break;
  49. case 'error':
  50. this.#websocket.on('error', error => {
  51. clearInterval(this.#pingTimer);
  52. callback(error);
  53. });
  54. break;
  55. case 'close':
  56. this.#websocket.on('close', code => {
  57. clearInterval(this.#pingTimer);
  58. callback(code);
  59. });
  60. break;
  61. default:
  62. console.log(type, callback);
  63. }
  64. return this;
  65. }
  66. send(message) {
  67. this.#websocket.send(JSON.stringify(message));
  68. }
  69. close(code) {
  70. this.#websocket.close(code);
  71. }
  72. }
  73. /**
  74. * 建立长连接
  75. */
  76. class WebSocketClient {
  77. #url;
  78. #wsClient = null;
  79. #connecting = 0;
  80. #retryTimer = null;
  81. #retryCount = 0;
  82. #options = {};
  83. #callback = {};
  84. constructor(url, options) {
  85. this.#url = url;
  86. this.#options = options;
  87. }
  88. get wsClient() {
  89. return this.#wsClient;
  90. }
  91. get connecting() {
  92. return this.#connecting;
  93. }
  94. #setCallback(type, callback) {
  95. if (['open', 'message', 'error', 'close'].includes(type)) {
  96. this.#callback[type] = callback;
  97. }
  98. else {
  99. throw new Error(`Invalid callback type: ${type}`);
  100. }
  101. }
  102. #runCallback(type, event) {
  103. if (this.#callback[type]) {
  104. this.#callback[type](event);
  105. }
  106. }
  107. /**
  108. * 建立连接(若已在连接中/已连接,会先关闭旧连接再重建)
  109. * @returns {WsClient}
  110. */
  111. connect() {
  112. if (this.#retryTimer) {
  113. clearTimeout(this.#retryTimer);
  114. this.#retryTimer = null;
  115. }
  116. // const { url, onmessage } = this.#connectInfo;
  117. const wsClient = new WsClient(this.#url, this.#options);
  118. this.#wsClient = wsClient;
  119. this.#connecting = 1;
  120. wsClient.on('open', event => {
  121. this.#retryCount = 0;
  122. this.#connecting = 0;
  123. if (this.#retryTimer) {
  124. clearTimeout(this.#retryTimer);
  125. this.#retryTimer = null;
  126. }
  127. this.#runCallback('open', event);
  128. // Logs.outDev('connect success');
  129. });
  130. wsClient.on('message', data => {
  131. const message = data;
  132. if (message.toUpperCase?.() == 'PONG' || message?.text?.toUpperCase?.() == 'PONG') {
  133. return;
  134. }
  135. this.#runCallback('message', data);
  136. // Logs.outDev('receive message', message);
  137. });
  138. wsClient.on('error', event => {
  139. Logs.outDev('connect error', event);
  140. this.#runCallback('error', event);
  141. });
  142. wsClient.on('close', event => {
  143. this.#runCallback('close', event);
  144. if (event.code == 1000) { // 手动断开,直接结束
  145. this.#wsClient = null;
  146. this.#connecting = 0;
  147. if (this.#retryTimer) {
  148. clearTimeout(this.#retryTimer);
  149. this.#retryTimer = null;
  150. }
  151. Logs.outDev('close connection autonomously');
  152. return;
  153. }
  154. // 异常断开,重新尝试连接
  155. this.#retryCount += 1;
  156. if (this.#retryCount > 180) {
  157. this.#wsClient = null;
  158. this.#connecting = 0;
  159. Logs.outDev('ws connect retry reached maximum limit');
  160. return;
  161. }
  162. let timeout = 5;
  163. if (this.#retryCount > 24) {
  164. timeout = 30;
  165. }
  166. else if (this.#retryCount > 40) {
  167. timeout = 60;
  168. }
  169. this.#connecting = 1;
  170. this.#retryTimer = setTimeout(() => {
  171. this.connect();
  172. }, 1000 * timeout);
  173. Logs.outDev('connect closed, try again after %d seconds', timeout);
  174. });
  175. return wsClient;
  176. }
  177. /**
  178. * 手动关闭连接(默认 code=1000)
  179. */
  180. close(code = 1000) {
  181. if (this.#retryTimer) {
  182. clearTimeout(this.#retryTimer);
  183. this.#retryTimer = null;
  184. }
  185. this.#retryCount = 0;
  186. this.#connecting = 0;
  187. this.#wsClient?.close(code);
  188. }
  189. /**
  190. * 发送消息
  191. * @param {*} message
  192. */
  193. send(message) {
  194. this.#wsClient?.send(message);
  195. }
  196. /**
  197. * 绑定事件
  198. * @param {*} type
  199. * @param {*} callback
  200. * @returns
  201. */
  202. on(type, callback) {
  203. this.#setCallback(type, callback);
  204. return this;
  205. }
  206. }
  207. export default WebSocketClient;