|
@@ -0,0 +1,166 @@
|
|
|
+import { onUnmounted } from "vue";
|
|
|
+import { useTimers } from "./useTimers";
|
|
|
+
|
|
|
+/** 调用就连接,被断开就连接,onUnmouted就断开连接,同时清除自动连接的机制
|
|
|
+ * onMessage 接收websocket的消息
|
|
|
+ * 本应用没有发消息的场景
|
|
|
+ */
|
|
|
+export function useWebSocket() {
|
|
|
+ let ws: WebSocket;
|
|
|
+ let heartbeatIds: number[] = [];
|
|
|
+ const RECONNECT_INTERVAL = 6 * 1000;
|
|
|
+ const HEARTBEAT_INTERVAL = 50 * 1000;
|
|
|
+ let reconnectNumber = 0;
|
|
|
+
|
|
|
+ let closeExplicitly = false;
|
|
|
+
|
|
|
+ let url: string;
|
|
|
+ let onMessage: (e: MessageEvent) => void;
|
|
|
+
|
|
|
+ const { addTimeout, addInterval } = useTimers();
|
|
|
+
|
|
|
+ function startWS(_url: string, _onMessage: (e: MessageEvent) => void) {
|
|
|
+ url = _url;
|
|
|
+ onMessage = _onMessage;
|
|
|
+ openWS();
|
|
|
+ }
|
|
|
+
|
|
|
+ // new WebSocket -> open -> heartbeat -> onmessage happy path
|
|
|
+ // new WebSocket -> onerror -> reconnect
|
|
|
+ // new WebSocket -> onclose(by server) -> reconnect
|
|
|
+ function openWS() {
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "准备连接",
|
|
|
+ // 连接websocket时,ws要么还没初始化,要么是closed,否则均不正常
|
|
|
+ dtl: [undefined, 3].includes(ws?.readyState)
|
|
|
+ ? "websocket未初始化或已关闭"
|
|
|
+ : "不可能的事情发生了",
|
|
|
+ ext: { url, websocketState: ws?.readyState },
|
|
|
+ });
|
|
|
+ try {
|
|
|
+ ws = new WebSocket(url);
|
|
|
+ } catch (error) {
|
|
|
+ // 理论上不该出现:SECURITY_ERR SyntaxError
|
|
|
+ $message.error("Websocket初始化失败", { duration: 5, closable: true });
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "Websocket初始化失败",
|
|
|
+ possibleError: error,
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onopen = () => {
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "连接成功",
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+
|
|
|
+ reconnectNumber = 0;
|
|
|
+ heartbeat();
|
|
|
+ };
|
|
|
+
|
|
|
+ ws.onmessage = onMessage;
|
|
|
+
|
|
|
+ ws.onclose = (event) => {
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "ws closed by server",
|
|
|
+ dtl: JSON.stringify(event),
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ for (const heartbeatId of heartbeatIds) {
|
|
|
+ clearInterval(heartbeatId);
|
|
|
+ }
|
|
|
+ heartbeatIds = [];
|
|
|
+
|
|
|
+ if (!closeExplicitly) reconnect("onclose-by-server");
|
|
|
+ };
|
|
|
+
|
|
|
+ function reconnect(cause: string) {
|
|
|
+ addTimeout(() => {
|
|
|
+ reconnectNumber++;
|
|
|
+ if (reconnectNumber >= 5) {
|
|
|
+ reconnectNumber = 0;
|
|
|
+ $message.error("Websocket重连失败", {
|
|
|
+ duration: 5,
|
|
|
+ closable: true,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "连接被关闭后-准备连接-" + cause,
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ // 会让断开就重连
|
|
|
+ openWS();
|
|
|
+ }, RECONNECT_INTERVAL);
|
|
|
+ }
|
|
|
+
|
|
|
+ ws.onerror = (event) => {
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "onerror",
|
|
|
+ dtl: JSON.stringify(event),
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ if (!closeExplicitly) reconnect("onerror");
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ function heartbeat() {
|
|
|
+ for (const heartbeatId of heartbeatIds) {
|
|
|
+ clearInterval(heartbeatId);
|
|
|
+ }
|
|
|
+ heartbeatIds = [];
|
|
|
+
|
|
|
+ const heartbeatId = addInterval(() => {
|
|
|
+ logger({
|
|
|
+ lvl: "debug",
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "websocket heartbeat",
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ ws.send(JSON.stringify({ eventType: "HEARTBEAT" }));
|
|
|
+ }, HEARTBEAT_INTERVAL);
|
|
|
+
|
|
|
+ heartbeatIds.push(heartbeatId);
|
|
|
+ }
|
|
|
+
|
|
|
+ function closeWsWithoutReconnect() {
|
|
|
+ closeExplicitly = true;
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "客户端准备关闭ws。",
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ // The WebSocket.close() method closes the WebSocket connection or connection attempt, if any.
|
|
|
+ // If the connection is already CLOSED, this method does nothing.
|
|
|
+ if (ws) ws.close();
|
|
|
+ } catch (e) {
|
|
|
+ logger({
|
|
|
+ cnl: ["server", "console"],
|
|
|
+ key: "微信小程序websocket",
|
|
|
+ act: "关闭ws异常。",
|
|
|
+ possibleError: e,
|
|
|
+ ext: { url },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ onUnmounted(closeWsWithoutReconnect);
|
|
|
+
|
|
|
+ return { startWS };
|
|
|
+}
|