chatSocket.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. "use strict";
  2. const common_vendor = require("../common/vendor.js");
  3. const utils_request = require("./request.js");
  4. const BASE_URL = utils_request.UPLOAD_URL;
  5. function getWsUrl() {
  6. return BASE_URL.replace(/^http/, "ws") + "/api/chat/ws/chat/websocket";
  7. }
  8. let socketTask = null;
  9. let isConnected = false;
  10. let subscriptionId = 0;
  11. const subscriptions = {};
  12. let reconnectCount = 0;
  13. const MAX_RECONNECT = 5;
  14. let reconnectTimer = null;
  15. let heartbeatTimer = null;
  16. let connectHeaders = {};
  17. let onConnectCallback = null;
  18. let onDisconnectCallback = null;
  19. function connectChat(options = {}) {
  20. const { token, onConnect, onDisconnect } = options;
  21. onConnectCallback = onConnect || null;
  22. onDisconnectCallback = onDisconnect || null;
  23. connectHeaders = {};
  24. if (token) {
  25. connectHeaders["Authorization"] = "Bearer " + token;
  26. }
  27. const wsUrl = getWsUrl();
  28. common_vendor.index.__f__("log", "at utils/chatSocket.js:46", "[ChatSocket] 连接中...", wsUrl);
  29. socketTask = common_vendor.index.connectSocket({
  30. url: wsUrl,
  31. header: {
  32. "Authorization": connectHeaders["Authorization"] || ""
  33. },
  34. success: () => {
  35. common_vendor.index.__f__("log", "at utils/chatSocket.js:54", "[ChatSocket] 连接请求已发送");
  36. },
  37. fail: (err) => {
  38. common_vendor.index.__f__("error", "at utils/chatSocket.js:57", "[ChatSocket] 连接失败", err);
  39. scheduleReconnect();
  40. }
  41. });
  42. socketTask.onOpen(() => {
  43. common_vendor.index.__f__("log", "at utils/chatSocket.js:63", "[ChatSocket] WebSocket 已打开,发送 STOMP CONNECT");
  44. sendStompConnect();
  45. });
  46. socketTask.onMessage((res) => {
  47. handleStompFrame(res.data);
  48. });
  49. socketTask.onClose((res) => {
  50. common_vendor.index.__f__("log", "at utils/chatSocket.js:72", "[ChatSocket] WebSocket 已关闭", res);
  51. isConnected = false;
  52. stopHeartbeat();
  53. if (onDisconnectCallback)
  54. onDisconnectCallback();
  55. scheduleReconnect();
  56. });
  57. socketTask.onError((err) => {
  58. common_vendor.index.__f__("error", "at utils/chatSocket.js:80", "[ChatSocket] WebSocket 错误", err);
  59. isConnected = false;
  60. });
  61. }
  62. function sendStompConnect() {
  63. const headers = {
  64. "accept-version": "1.1,1.0",
  65. "heart-beat": "10000,10000"
  66. };
  67. if (connectHeaders["Authorization"]) {
  68. headers["Authorization"] = connectHeaders["Authorization"];
  69. }
  70. sendFrame("CONNECT", headers, "");
  71. }
  72. function handleStompFrame(data) {
  73. if (!data || typeof data !== "string")
  74. return;
  75. const frames = data.split("\0");
  76. for (const raw of frames) {
  77. if (!raw.trim())
  78. continue;
  79. parseFrame(raw.trim());
  80. }
  81. }
  82. function parseFrame(raw) {
  83. const lines = raw.split("\n");
  84. if (lines.length === 0)
  85. return;
  86. const command = lines[0].trim();
  87. const headers = {};
  88. let bodyStart = 0;
  89. for (let i = 1; i < lines.length; i++) {
  90. const line = lines[i];
  91. if (line === "") {
  92. bodyStart = i + 1;
  93. break;
  94. }
  95. const colonIdx = line.indexOf(":");
  96. if (colonIdx > 0) {
  97. headers[line.substring(0, colonIdx).trim()] = line.substring(colonIdx + 1).trim();
  98. }
  99. }
  100. const body = lines.slice(bodyStart).join("\n").replace(/\0$/, "").trim();
  101. switch (command) {
  102. case "CONNECTED":
  103. common_vendor.index.__f__("log", "at utils/chatSocket.js:142", "[ChatSocket] STOMP 连接成功");
  104. isConnected = true;
  105. reconnectCount = 0;
  106. startHeartbeat();
  107. Object.keys(subscriptions).forEach((id) => {
  108. const sub = subscriptions[id];
  109. sendFrame("SUBSCRIBE", { id, destination: sub.destination, ack: "auto" }, "");
  110. });
  111. if (onConnectCallback)
  112. onConnectCallback();
  113. break;
  114. case "MESSAGE":
  115. handleStompMessage(headers, body);
  116. break;
  117. case "RECEIPT":
  118. common_vendor.index.__f__("log", "at utils/chatSocket.js:159", "[ChatSocket] 收到回执:", headers["receipt-id"]);
  119. break;
  120. case "ERROR":
  121. common_vendor.index.__f__("error", "at utils/chatSocket.js:163", "[ChatSocket] STOMP 错误:", headers["message"], body);
  122. break;
  123. }
  124. }
  125. function handleStompMessage(headers, body) {
  126. const destination = headers["destination"];
  127. if (!destination)
  128. return;
  129. let data = body;
  130. try {
  131. data = JSON.parse(body);
  132. } catch (e) {
  133. }
  134. Object.keys(subscriptions).forEach((id) => {
  135. const sub = subscriptions[id];
  136. if (destination === sub.destination || destination.startsWith(sub.destination)) {
  137. sub.callback(data, headers);
  138. }
  139. });
  140. }
  141. function subscribe(destination, callback) {
  142. const id = "sub-" + ++subscriptionId;
  143. subscriptions[id] = { destination, callback };
  144. if (isConnected) {
  145. sendFrame("SUBSCRIBE", { id, destination, ack: "auto" }, "");
  146. }
  147. return id;
  148. }
  149. function unsubscribe(id) {
  150. if (subscriptions[id]) {
  151. if (isConnected) {
  152. sendFrame("UNSUBSCRIBE", { id }, "");
  153. }
  154. delete subscriptions[id];
  155. }
  156. }
  157. function sendTextByWs(sessionId, content, msgType = "text", msgNo = "", senderId = null) {
  158. if (!isConnected) {
  159. common_vendor.index.__f__("warn", "at utils/chatSocket.js:238", "[ChatSocket] 未连接,无法发送消息");
  160. return false;
  161. }
  162. if (!msgNo) {
  163. msgNo = generateMsgNo();
  164. }
  165. const bodyObj = {
  166. msgType,
  167. sessionId,
  168. msgNo,
  169. content
  170. };
  171. if (senderId) {
  172. bodyObj.senderId = senderId;
  173. }
  174. const body = JSON.stringify(bodyObj);
  175. sendFrame("SEND", { destination: "/app/chat/send", "content-type": "application/json" }, body);
  176. return true;
  177. }
  178. function disconnectChat() {
  179. if (socketTask && isConnected) {
  180. sendFrame("DISCONNECT", { receipt: "disconnect-" + Date.now() }, "");
  181. }
  182. clearAll();
  183. if (socketTask) {
  184. socketTask.close({});
  185. socketTask = null;
  186. }
  187. }
  188. function clearAll() {
  189. isConnected = false;
  190. stopHeartbeat();
  191. if (reconnectTimer) {
  192. clearTimeout(reconnectTimer);
  193. reconnectTimer = null;
  194. }
  195. Object.keys(subscriptions).forEach((id) => delete subscriptions[id]);
  196. }
  197. function sendFrame(command, headers, body) {
  198. if (!socketTask)
  199. return;
  200. let frame = command + "\n";
  201. if (headers) {
  202. Object.keys(headers).forEach((key) => {
  203. frame += key + ":" + headers[key] + "\n";
  204. });
  205. }
  206. frame += "\n";
  207. if (body) {
  208. frame += body;
  209. }
  210. frame += "\0";
  211. socketTask.send({
  212. data: frame,
  213. fail: (err) => {
  214. common_vendor.index.__f__("error", "at utils/chatSocket.js:314", "[ChatSocket] 发送帧失败:", command, err);
  215. }
  216. });
  217. }
  218. function startHeartbeat() {
  219. stopHeartbeat();
  220. heartbeatTimer = setInterval(() => {
  221. if (isConnected && socketTask) {
  222. socketTask.send({
  223. data: "\n",
  224. fail: () => {
  225. }
  226. });
  227. }
  228. }, 1e4);
  229. }
  230. function stopHeartbeat() {
  231. if (heartbeatTimer) {
  232. clearInterval(heartbeatTimer);
  233. heartbeatTimer = null;
  234. }
  235. }
  236. function scheduleReconnect() {
  237. if (reconnectCount >= MAX_RECONNECT) {
  238. common_vendor.index.__f__("log", "at utils/chatSocket.js:347", "[ChatSocket] 达到最大重连次数,停止重连");
  239. return;
  240. }
  241. reconnectCount++;
  242. const delay = Math.min(3e3 * reconnectCount, 15e3);
  243. common_vendor.index.__f__("log", "at utils/chatSocket.js:352", `[ChatSocket] ${delay}ms 后进行第 ${reconnectCount} 次重连...`);
  244. reconnectTimer = setTimeout(() => {
  245. const token = common_vendor.index.getStorageSync("token");
  246. connectChat({
  247. token,
  248. onConnect: onConnectCallback,
  249. onDisconnect: onDisconnectCallback
  250. });
  251. }, delay);
  252. }
  253. function generateMsgNo() {
  254. return "MSG_" + Date.now() + "_" + Math.random().toString(36).slice(2, 8);
  255. }
  256. function isSocketConnected() {
  257. return isConnected;
  258. }
  259. exports.connectChat = connectChat;
  260. exports.disconnectChat = disconnectChat;
  261. exports.generateMsgNo = generateMsgNo;
  262. exports.isSocketConnected = isSocketConnected;
  263. exports.sendTextByWs = sendTextByWs;
  264. exports.subscribe = subscribe;
  265. exports.unsubscribe = unsubscribe;
  266. //# sourceMappingURL=../../.sourcemap/mp-weixin/utils/chatSocket.js.map