|
@@ -1,136 +1,199 @@
|
|
-// src/stores/websocket.ts
|
|
|
|
|
|
+// src/stores/websocket.js
|
|
import { defineStore } from "pinia";
|
|
import { defineStore } from "pinia";
|
|
import { $root as protobuf } from "@/common/proto/proto";
|
|
import { $root as protobuf } from "@/common/proto/proto";
|
|
import * as Constant from "@/common/constant/Constant";
|
|
import * as Constant from "@/common/constant/Constant";
|
|
import { useWalletStore } from "@/stores/modules/walletStore";
|
|
import { useWalletStore } from "@/stores/modules/walletStore";
|
|
import { getMessageApi } from "@/api/path/im.api";
|
|
import { getMessageApi } from "@/api/path/im.api";
|
|
-import { MSG_TYPE, MSG_TYPE_MAP } from "@/common/constant/msgType";
|
|
|
|
-
|
|
|
|
import {
|
|
import {
|
|
setMessageHook,
|
|
setMessageHook,
|
|
handleMessageHook,
|
|
handleMessageHook,
|
|
} from "@/views/im/hook/messagesHook";
|
|
} from "@/views/im/hook/messagesHook";
|
|
|
|
|
|
-const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE;
|
|
|
|
|
|
+// 配置常量
|
|
|
|
+const WS_CONFIG = {
|
|
|
|
+ HEARTBEAT_INTERVAL: 30000, // 30秒
|
|
|
|
+ HEARTBEAT_TIMEOUT: 10000, // 10秒
|
|
|
|
+ HEARTBEAT_RETRY_LIMIT: 3,
|
|
|
|
+ RECONNECT_BASE_INTERVAL: 1000,
|
|
|
|
+ RECONNECT_MAX_INTERVAL: 30000,
|
|
|
|
+ RECONNECT_MAX_ATTEMPTS: Infinity,
|
|
|
|
+ RECONNECT_MULTIPLIER: 1.5,
|
|
|
|
+ MESSAGE_QUEUE_MAX_SIZE: 100,
|
|
|
|
+ MESSAGE_QUEUE_FLUSH_INTERVAL: 5000,
|
|
|
|
+};
|
|
|
|
|
|
export const useWebSocketStore = defineStore("webSocketStore", {
|
|
export const useWebSocketStore = defineStore("webSocketStore", {
|
|
- // 状态定义
|
|
|
|
state: () => ({
|
|
state: () => ({
|
|
- socket: null, // socket实例
|
|
|
|
- peer: null, // peer实例
|
|
|
|
- lockConnection: false, // 锁定连接
|
|
|
|
- reconnectAttempts: 0, // 重连次数
|
|
|
|
- messages: [], // 消息列表
|
|
|
|
|
|
+ socket: null,
|
|
|
|
+ peer: null,
|
|
|
|
+ connectionState: "disconnected", // 'connecting', 'connected', 'disconnecting', 'error'
|
|
|
|
+ reconnectAttempts: 0,
|
|
|
|
+ messages: [],
|
|
toUserInfo: {},
|
|
toUserInfo: {},
|
|
- unreadMessages: [], // 未读消息列表
|
|
|
|
|
|
+ unreadMessages: [],
|
|
uuid: null,
|
|
uuid: null,
|
|
onMessageCallbacks: [],
|
|
onMessageCallbacks: [],
|
|
-
|
|
|
|
- // 心跳检测配置
|
|
|
|
- heartCheck: {
|
|
|
|
- interval: 30000, // 心跳发送间隔(30秒)
|
|
|
|
- timeout: 10000, // 响应超时时间(10秒)
|
|
|
|
- timeoutObj: null,
|
|
|
|
- serverTimeoutObj: null,
|
|
|
|
- retryLimit: 3, // 重试次数
|
|
|
|
- retryCount: 0, // 当前重试计数
|
|
|
|
|
|
+ messageQueue: [],
|
|
|
|
+ lastActivityTime: 0,
|
|
|
|
+ stats: {
|
|
|
|
+ sentMessages: 0,
|
|
|
|
+ receivedMessages: 0,
|
|
|
|
+ failedMessages: 0,
|
|
|
|
+ reconnects: 0,
|
|
|
|
+ },
|
|
|
|
+ heartbeat: {
|
|
|
|
+ timer: null,
|
|
|
|
+ timeout: null,
|
|
|
|
+ retryCount: 0,
|
|
},
|
|
},
|
|
|
|
+ reconnect: {
|
|
|
|
+ timer: null,
|
|
|
|
+ currentAttempt: 0,
|
|
|
|
+ },
|
|
|
|
+ queueFlushTimer: null,
|
|
}),
|
|
}),
|
|
|
|
+
|
|
persist: {
|
|
persist: {
|
|
key: "toUserInfo",
|
|
key: "toUserInfo",
|
|
storage: localStorage,
|
|
storage: localStorage,
|
|
- paths: ["toUserInfo"], // 正确的简单路径格式
|
|
|
|
|
|
+ paths: ["toUserInfo", "uuid"],
|
|
serializer: {
|
|
serializer: {
|
|
- serialize: (state) => JSON.stringify(state.toUserInfo),
|
|
|
|
- deserialize: (str) => ({ toUserInfo: JSON.parse(str) }),
|
|
|
|
|
|
+ serialize: (state) =>
|
|
|
|
+ JSON.stringify({
|
|
|
|
+ toUserInfo: state.toUserInfo,
|
|
|
|
+ uuid: state.uuid,
|
|
|
|
+ }),
|
|
|
|
+ deserialize: (str) => {
|
|
|
|
+ const data = JSON.parse(str);
|
|
|
|
+ return {
|
|
|
|
+ toUserInfo: data.toUserInfo || {},
|
|
|
|
+ uuid: data.uuid || null,
|
|
|
|
+ };
|
|
|
|
+ },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
- // 计算属性
|
|
|
|
|
|
+
|
|
getters: {
|
|
getters: {
|
|
- isConnected: (state) => state.socket?.readyState === WebSocket.OPEN,
|
|
|
|
- hasUnreadMessages: (state) => state.unreadMessages.length > 0,
|
|
|
|
|
|
+ isConnected(state) {
|
|
|
|
+ return state.connectionState === "connected";
|
|
|
|
+ },
|
|
|
|
+ hasUnreadMessages(state) {
|
|
|
|
+ return state.unreadMessages.length > 0;
|
|
|
|
+ },
|
|
|
|
+ connectionStatus(state) {
|
|
|
|
+ return state.connectionState;
|
|
|
|
+ },
|
|
},
|
|
},
|
|
|
|
|
|
- // 方法
|
|
|
|
actions: {
|
|
actions: {
|
|
- // 获取消息
|
|
|
|
- async getMessages(params) {
|
|
|
|
- const { data } = await getMessageApi({
|
|
|
|
- messageType: params.messageType,
|
|
|
|
- uuid: params.uuid,
|
|
|
|
- friendUsername: params.friendUsername,
|
|
|
|
- });
|
|
|
|
- this.messages =
|
|
|
|
- data?.map((item) => {
|
|
|
|
- item.avatar = item.avatar ? IM_PATH + item.avatar : item.avatar;
|
|
|
|
- return item;
|
|
|
|
- }) || [];
|
|
|
|
- },
|
|
|
|
- // 发送消息
|
|
|
|
- sendMessage(messageData) {
|
|
|
|
- if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
|
|
- console.error("WebSocket未连接");
|
|
|
|
- return false;
|
|
|
|
|
|
+ // 初始化连接
|
|
|
|
+ connect(userUuid, callback) {
|
|
|
|
+ if (!userUuid) {
|
|
|
|
+ console.error("连接失败: 缺少 userUuid");
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
|
|
- // 获取url上uuid参数
|
|
|
|
- const walletStore = useWalletStore();
|
|
|
|
- let data = {
|
|
|
|
- ...messageData,
|
|
|
|
- fromUsername: walletStore.username,
|
|
|
|
- from: walletStore.account,
|
|
|
|
- to: this.toUserInfo.uuid,
|
|
|
|
- };
|
|
|
|
- console.log("发送消息=", data);
|
|
|
|
|
|
+ // 如果正在连接或已连接,则先断开
|
|
|
|
+ if (
|
|
|
|
+ this.connectionState === "connected" ||
|
|
|
|
+ this.connectionState === "connecting"
|
|
|
|
+ ) {
|
|
|
|
+ this.disconnect();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.uuid = userUuid;
|
|
|
|
+ this.connectionState = "connecting";
|
|
|
|
+ this.stats.reconnects++;
|
|
|
|
+
|
|
try {
|
|
try {
|
|
- const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
- const messagePB = MessageType.create(data);
|
|
|
|
- const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
- this.socket.send(buffer);
|
|
|
|
- // 发送完成后:添加头像
|
|
|
|
- data.avatar = walletStore.avatar;
|
|
|
|
- setMessageHook(data, this);
|
|
|
|
- return true;
|
|
|
|
|
|
+ const wsUrl = `${import.meta.env.VITE_PRO_IM_WSS}?user=${userUuid}`;
|
|
|
|
+ this.socket = new WebSocket(wsUrl);
|
|
|
|
+
|
|
|
|
+ this.socket.onopen = () => {
|
|
|
|
+ this.connectionState = "connected";
|
|
|
|
+ this.reconnect.currentAttempt = 0;
|
|
|
|
+ this.lastActivityTime = Date.now();
|
|
|
|
+ this.startHeartbeat();
|
|
|
|
+ this.startQueueFlush();
|
|
|
|
+
|
|
|
|
+ if (callback && typeof callback === "function") {
|
|
|
|
+ callback();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ console.log("WebSocket 连接成功");
|
|
|
|
+ this.flushMessageQueue(); // 连接成功后立即发送队列中的消息
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.socket.onmessage = (event) => {
|
|
|
|
+ this.lastActivityTime = Date.now();
|
|
|
|
+ this.handleMessage(event.data);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.socket.onclose = () => {
|
|
|
|
+ this.connectionState = "disconnected";
|
|
|
|
+ this.cleanupTimers();
|
|
|
|
+
|
|
|
|
+ if (
|
|
|
|
+ this.reconnect.currentAttempt < WS_CONFIG.RECONNECT_MAX_ATTEMPTS
|
|
|
|
+ ) {
|
|
|
|
+ this.scheduleReconnect();
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.socket.onerror = (error) => {
|
|
|
|
+ this.connectionState = "error";
|
|
|
|
+ console.error("WebSocket 错误:", error);
|
|
|
|
+ this.cleanupTimers();
|
|
|
|
+ this.scheduleReconnect();
|
|
|
|
+ };
|
|
} catch (error) {
|
|
} catch (error) {
|
|
- console.error("消息编码错误:", error);
|
|
|
|
- return false;
|
|
|
|
|
|
+ console.error("创建 WebSocket 失败:", error);
|
|
|
|
+ this.connectionState = "error";
|
|
|
|
+ this.scheduleReconnect();
|
|
}
|
|
}
|
|
},
|
|
},
|
|
- // 初始化socket
|
|
|
|
- // startHeartbeat() {
|
|
|
|
- // const self = this;
|
|
|
|
- // const _num = this.heartCheck.num;
|
|
|
|
-
|
|
|
|
- // this.heartCheck.timeoutObj && clearTimeout(this.heartCheck.timeoutObj);
|
|
|
|
- // this.heartCheck.serverTimeoutObj &&
|
|
|
|
- // clearTimeout(this.heartCheck.serverTimeoutObj);
|
|
|
|
-
|
|
|
|
- // this.heartCheck.timeoutObj = setTimeout(() => {
|
|
|
|
- // if (this.socket?.readyState === WebSocket.OPEN) {
|
|
|
|
- // const data = {
|
|
|
|
- // type: "heatbeat",
|
|
|
|
- // content: "ping",
|
|
|
|
- // };
|
|
|
|
- // const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
- // const messagePB = MessageType.create(data);
|
|
|
|
- // const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
- // this.socket.send(buffer);
|
|
|
|
- // }
|
|
|
|
-
|
|
|
|
- // self.heartCheck.serverTimeoutObj = setTimeout(() => {
|
|
|
|
- // _num--;
|
|
|
|
- // if (_num <= 0) {
|
|
|
|
- // console.log("the ping num is more then 3, close socket!");
|
|
|
|
- // this.socket?.close();
|
|
|
|
- // }
|
|
|
|
- // }, self.heartCheck.timeout);
|
|
|
|
- // }, this.heartCheck.timeout);
|
|
|
|
- // },
|
|
|
|
|
|
|
|
|
|
+ // 断开连接
|
|
|
|
+ disconnect() {
|
|
|
|
+ this.connectionState = "disconnecting";
|
|
|
|
+ this.cleanupTimers();
|
|
|
|
+
|
|
|
|
+ if (this.socket) {
|
|
|
|
+ try {
|
|
|
|
+ this.socket.close();
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error("关闭 WebSocket 时出错:", e);
|
|
|
|
+ }
|
|
|
|
+ this.socket = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (this.peer) {
|
|
|
|
+ this.peer.close();
|
|
|
|
+ this.peer = null;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ this.connectionState = "disconnected";
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 清理所有定时器
|
|
|
|
+ cleanupTimers() {
|
|
|
|
+ if (this.heartbeat.timer) clearTimeout(this.heartbeat.timer);
|
|
|
|
+ if (this.heartbeat.timeout) clearTimeout(this.heartbeat.timeout);
|
|
|
|
+ if (this.reconnect.timer) clearTimeout(this.reconnect.timer);
|
|
|
|
+ if (this.queueFlushTimer) clearInterval(this.queueFlushTimer);
|
|
|
|
+
|
|
|
|
+ this.heartbeat.timer = null;
|
|
|
|
+ this.heartbeat.timeout = null;
|
|
|
|
+ this.reconnect.timer = null;
|
|
|
|
+ this.queueFlushTimer = null;
|
|
|
|
+ this.heartbeat.retryCount = 0;
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 启动心跳检测
|
|
startHeartbeat() {
|
|
startHeartbeat() {
|
|
- this.resetHeartbeat();
|
|
|
|
|
|
+ this.cleanupHeartbeat();
|
|
|
|
|
|
- this.heartCheck.timeoutObj = setTimeout(() => {
|
|
|
|
|
|
+ this.heartbeat.timer = setTimeout(() => {
|
|
if (this.isConnected) {
|
|
if (this.isConnected) {
|
|
try {
|
|
try {
|
|
const pingMsg = {
|
|
const pingMsg = {
|
|
@@ -139,155 +202,155 @@ export const useWebSocketStore = defineStore("webSocketStore", {
|
|
timestamp: Date.now(),
|
|
timestamp: Date.now(),
|
|
};
|
|
};
|
|
|
|
|
|
- const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
- const messagePB = MessageType.create(pingMsg);
|
|
|
|
- const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
- this.socket.send(buffer);
|
|
|
|
|
|
+ this.sendRawMessage(pingMsg);
|
|
|
|
|
|
// 设置响应超时检测
|
|
// 设置响应超时检测
|
|
- this.heartCheck.serverTimeoutObj = setTimeout(() => {
|
|
|
|
- this.heartCheck.retryCount++;
|
|
|
|
|
|
+ this.heartbeat.timeout = setTimeout(() => {
|
|
|
|
+ this.heartbeat.retryCount++;
|
|
|
|
|
|
- if (this.heartCheck.retryCount >= this.heartCheck.retryLimit) {
|
|
|
|
|
|
+ if (
|
|
|
|
+ this.heartbeat.retryCount >= WS_CONFIG.HEARTBEAT_RETRY_LIMIT
|
|
|
|
+ ) {
|
|
console.error("心跳响应超时,关闭连接");
|
|
console.error("心跳响应超时,关闭连接");
|
|
- this.socket?.close();
|
|
|
|
|
|
+ this.disconnect();
|
|
} else {
|
|
} else {
|
|
console.warn(
|
|
console.warn(
|
|
- `心跳无响应,第${this.heartCheck.retryCount}次重试`
|
|
|
|
|
|
+ `心跳无响应,第${this.heartbeat.retryCount}次重试`
|
|
);
|
|
);
|
|
this.startHeartbeat(); // 重新发送心跳
|
|
this.startHeartbeat(); // 重新发送心跳
|
|
}
|
|
}
|
|
- }, this.heartCheck.timeout);
|
|
|
|
|
|
+ }, WS_CONFIG.HEARTBEAT_TIMEOUT);
|
|
} catch (error) {
|
|
} catch (error) {
|
|
console.error("心跳发送失败:", error);
|
|
console.error("心跳发送失败:", error);
|
|
- this.reconnect();
|
|
|
|
|
|
+ this.scheduleReconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- }, this.heartCheck.interval);
|
|
|
|
- },
|
|
|
|
-
|
|
|
|
- resetHeartbeat() {
|
|
|
|
- this.heartCheck.timeoutObj && clearTimeout(this.heartCheck.timeoutObj);
|
|
|
|
- this.heartCheck.serverTimeoutObj &&
|
|
|
|
- clearTimeout(this.heartCheck.serverTimeoutObj);
|
|
|
|
- this.heartCheck.num = 3;
|
|
|
|
- },
|
|
|
|
- // 链接ws
|
|
|
|
- connect(userUuid) {
|
|
|
|
- if (!userUuid) return;
|
|
|
|
- console.log("开始连接...");
|
|
|
|
- this.uuid = userUuid;
|
|
|
|
- this.disconnect(); // 确保先断开现有连接
|
|
|
|
-
|
|
|
|
- this.peer = new RTCPeerConnection();
|
|
|
|
- this.socket = new WebSocket(
|
|
|
|
- `${import.meta.env.VITE_PRO_IM_WSS}?user=${userUuid}`
|
|
|
|
- );
|
|
|
|
- // 连接成功
|
|
|
|
- this.socket.onopen = () => {
|
|
|
|
- this.startHeartbeat();
|
|
|
|
- console.log("WebSocket连接成功");
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- this.socket.onmessage = (event) => {
|
|
|
|
- this.startHeartbeat();
|
|
|
|
- this.handleMessage(event.data);
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- this.socket.onclose = () => {
|
|
|
|
- console.log("连接关闭,尝试重新连接...");
|
|
|
|
- this.resetHeartbeat();
|
|
|
|
- this.reconnect();
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- this.socket.onerror = (error) => {
|
|
|
|
- console.error("WebSocket错误:", error);
|
|
|
|
- this.resetHeartbeat();
|
|
|
|
- this.reconnect();
|
|
|
|
- };
|
|
|
|
|
|
+ }, WS_CONFIG.HEARTBEAT_INTERVAL);
|
|
},
|
|
},
|
|
|
|
|
|
- // 处理消息
|
|
|
|
- handleMessage(data) {
|
|
|
|
- const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
- const reader = new FileReader();
|
|
|
|
-
|
|
|
|
- reader.onload = (event) => {
|
|
|
|
- try {
|
|
|
|
- const messagePB = MessageType.decode(
|
|
|
|
- new Uint8Array(event.target?.result)
|
|
|
|
- );
|
|
|
|
- const message = MessageType.toObject(messagePB, {
|
|
|
|
- longs: String,
|
|
|
|
- enums: String,
|
|
|
|
- bytes: String,
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- console.log("收到消息:", message);
|
|
|
|
- // 处理消息
|
|
|
|
- handleMessageHook(message, this);
|
|
|
|
- if (message.type === "heatbeat") return;
|
|
|
|
-
|
|
|
|
- if (message.type === Constant.MESSAGE_TRANS_TYPE) {
|
|
|
|
- this.dealWebRtcMessage(message);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- // 其他消息处理逻辑...
|
|
|
|
- } catch (error) {
|
|
|
|
- console.error("消息解码错误:", error);
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- reader.readAsArrayBuffer(data);
|
|
|
|
|
|
+ // 清理心跳定时器
|
|
|
|
+ cleanupHeartbeat() {
|
|
|
|
+ if (this.heartbeat.timer) clearTimeout(this.heartbeat.timer);
|
|
|
|
+ if (this.heartbeat.timeout) clearTimeout(this.heartbeat.timeout);
|
|
|
|
+ this.heartbeat.timer = null;
|
|
|
|
+ this.heartbeat.timeout = null;
|
|
},
|
|
},
|
|
- // 断线重连
|
|
|
|
|
|
|
|
- // 替换现有的reconnect方法
|
|
|
|
- reconnect() {
|
|
|
|
- if (this.lockConnection) return;
|
|
|
|
|
|
+ // 安排重连
|
|
|
|
+ scheduleReconnect() {
|
|
|
|
+ if (this.reconnect.timer) return;
|
|
|
|
|
|
- this.lockConnection = true;
|
|
|
|
- this.reconnectConfig.currentAttempt++;
|
|
|
|
|
|
+ this.reconnect.currentAttempt++;
|
|
|
|
|
|
// 指数退避算法计算重连间隔
|
|
// 指数退避算法计算重连间隔
|
|
const delay = Math.min(
|
|
const delay = Math.min(
|
|
- this.reconnectConfig.baseInterval *
|
|
|
|
|
|
+ WS_CONFIG.RECONNECT_BASE_INTERVAL *
|
|
Math.pow(
|
|
Math.pow(
|
|
- this.reconnectConfig.multiplier,
|
|
|
|
- this.reconnectConfig.currentAttempt - 1
|
|
|
|
|
|
+ WS_CONFIG.RECONNECT_MULTIPLIER,
|
|
|
|
+ this.reconnect.currentAttempt - 1
|
|
),
|
|
),
|
|
- this.reconnectConfig.maxInterval
|
|
|
|
|
|
+ WS_CONFIG.RECONNECT_MAX_INTERVAL
|
|
);
|
|
);
|
|
|
|
|
|
console.log(
|
|
console.log(
|
|
- `将在 ${delay}ms 后尝试第 ${this.reconnectConfig.currentAttempt} 次重连`
|
|
|
|
|
|
+ `将在 ${delay}ms 后尝试第 ${this.reconnect.currentAttempt} 次重连`
|
|
);
|
|
);
|
|
|
|
|
|
- setTimeout(() => {
|
|
|
|
- if (!this.isConnected) {
|
|
|
|
|
|
+ this.reconnect.timer = setTimeout(() => {
|
|
|
|
+ this.reconnect.timer = null;
|
|
|
|
+ if (!this.isConnected && this.uuid) {
|
|
this.connect(this.uuid);
|
|
this.connect(this.uuid);
|
|
}
|
|
}
|
|
- this.lockConnection = false;
|
|
|
|
}, delay);
|
|
}, delay);
|
|
},
|
|
},
|
|
- // 重连
|
|
|
|
- disconnect() {
|
|
|
|
- if (this.socket) {
|
|
|
|
- this.resetHeartbeat();
|
|
|
|
- this.socket.close();
|
|
|
|
- this.socket = null;
|
|
|
|
|
|
+
|
|
|
|
+ // 发送原始消息(不经过队列)
|
|
|
|
+ sendRawMessage(messageData) {
|
|
|
|
+ if (!this.isConnected) {
|
|
|
|
+ throw new Error("WebSocket 未连接");
|
|
}
|
|
}
|
|
- if (this.peer) {
|
|
|
|
- this.peer.close();
|
|
|
|
- this.peer = null;
|
|
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
+ const messagePB = MessageType.create(messageData);
|
|
|
|
+ const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
+ this.socket.send(buffer);
|
|
|
|
+ this.stats.sentMessages++;
|
|
|
|
+ return true;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("消息编码错误:", error);
|
|
|
|
+ this.stats.failedMessages++;
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 发送消息(自动排队)
|
|
|
|
+ sendMessage(messageData) {
|
|
|
|
+ const walletStore = useWalletStore();
|
|
|
|
+
|
|
|
|
+ // 构造完整消息
|
|
|
|
+ const fullMessage = {
|
|
|
|
+ ...messageData,
|
|
|
|
+ fromUsername: walletStore.username,
|
|
|
|
+ from: walletStore.account,
|
|
|
|
+ to: this.toUserInfo.uuid,
|
|
|
|
+ avatar: walletStore.avatar,
|
|
|
|
+ timestamp: Date.now(),
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 如果连接正常,直接发送
|
|
|
|
+ if (this.isConnected) {
|
|
|
|
+ const success = this.sendRawMessage(fullMessage);
|
|
|
|
+ if (success) {
|
|
|
|
+ setMessageHook(fullMessage, this);
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- this.heartCheck.retryCount = 0;
|
|
|
|
|
|
+
|
|
|
|
+ // 如果发送失败或未连接,加入队列
|
|
|
|
+ if (this.messageQueue.length < WS_CONFIG.MESSAGE_QUEUE_MAX_SIZE) {
|
|
|
|
+ this.messageQueue.push(fullMessage);
|
|
|
|
+ console.log("消息已加入队列,等待发送");
|
|
|
|
+ return true;
|
|
|
|
+ } else {
|
|
|
|
+ console.error("消息队列已满,丢弃消息");
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // 启动队列刷新定时器
|
|
|
|
+ startQueueFlush() {
|
|
|
|
+ if (this.queueFlushTimer) return;
|
|
|
|
+
|
|
|
|
+ this.queueFlushTimer = setInterval(() => {
|
|
|
|
+ this.flushMessageQueue();
|
|
|
|
+ }, WS_CONFIG.MESSAGE_QUEUE_FLUSH_INTERVAL);
|
|
},
|
|
},
|
|
|
|
|
|
- // 发送WebRTC消息
|
|
|
|
- dealWebRtcMessage(message) {
|
|
|
|
- // 实现WebRTC消息处理逻辑
|
|
|
|
|
|
+ // 发送队列中的所有消息
|
|
|
|
+ flushMessageQueue() {
|
|
|
|
+ if (!this.isConnected || this.messageQueue.length === 0) return;
|
|
|
|
+
|
|
|
|
+ while (this.messageQueue.length > 0) {
|
|
|
|
+ const message = this.messageQueue.shift();
|
|
|
|
+ try {
|
|
|
|
+ const success = this.sendRawMessage(message);
|
|
|
|
+ if (success) {
|
|
|
|
+ setMessageHook(message, this);
|
|
|
|
+ } else {
|
|
|
|
+ // 发送失败,重新放回队列开头
|
|
|
|
+ this.messageQueue.unshift(message);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("发送队列消息失败:", error);
|
|
|
|
+ this.messageQueue.unshift(message);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
},
|
|
},
|
|
|
|
+
|
|
|
|
+ // 处理接收到的消息
|
|
handleMessage(data) {
|
|
handleMessage(data) {
|
|
const MessageType = protobuf.lookupType("protocol.Message");
|
|
const MessageType = protobuf.lookupType("protocol.Message");
|
|
const reader = new FileReader();
|
|
const reader = new FileReader();
|
|
@@ -295,7 +358,7 @@ export const useWebSocketStore = defineStore("webSocketStore", {
|
|
reader.onload = (event) => {
|
|
reader.onload = (event) => {
|
|
try {
|
|
try {
|
|
const messagePB = MessageType.decode(
|
|
const messagePB = MessageType.decode(
|
|
- new Uint8Array(event.target?.result)
|
|
|
|
|
|
+ new Uint8Array(event.target.result)
|
|
);
|
|
);
|
|
const message = MessageType.toObject(messagePB, {
|
|
const message = MessageType.toObject(messagePB, {
|
|
longs: String,
|
|
longs: String,
|
|
@@ -303,6 +366,9 @@ export const useWebSocketStore = defineStore("webSocketStore", {
|
|
bytes: String,
|
|
bytes: String,
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+ this.stats.receivedMessages++;
|
|
|
|
+ this.lastActivityTime = Date.now();
|
|
|
|
+
|
|
console.log("收到消息:", message);
|
|
console.log("收到消息:", message);
|
|
handleMessageHook(message, this);
|
|
handleMessageHook(message, this);
|
|
|
|
|
|
@@ -315,31 +381,54 @@ export const useWebSocketStore = defineStore("webSocketStore", {
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
- if (message.type === "heatbeat") return;
|
|
|
|
-
|
|
|
|
- if (message.type === Constant.MESSAGE_TRANS_TYPE) {
|
|
|
|
- this.dealWebRtcMessage(message);
|
|
|
|
|
|
+ if (message.type === "heatbeat") {
|
|
|
|
+ this.heartbeat.retryCount = 0; // 重置心跳重试计数
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
- // 其他消息处理逻辑...
|
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
console.error("消息解码错误:", error);
|
|
console.error("消息解码错误:", error);
|
|
|
|
+ this.stats.failedMessages++;
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
reader.readAsArrayBuffer(data);
|
|
reader.readAsArrayBuffer(data);
|
|
},
|
|
},
|
|
|
|
|
|
|
|
+ // 添加消息回调
|
|
addOnMessageCallback(cb) {
|
|
addOnMessageCallback(cb) {
|
|
- if (typeof cb === "function") {
|
|
|
|
|
|
+ if (typeof cb === "function" && !this.onMessageCallbacks.includes(cb)) {
|
|
this.onMessageCallbacks.push(cb);
|
|
this.onMessageCallbacks.push(cb);
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
|
|
|
|
+ // 移除消息回调
|
|
removeOnMessageCallback(cb) {
|
|
removeOnMessageCallback(cb) {
|
|
this.onMessageCallbacks = this.onMessageCallbacks.filter(
|
|
this.onMessageCallbacks = this.onMessageCallbacks.filter(
|
|
(item) => item !== cb
|
|
(item) => item !== cb
|
|
);
|
|
);
|
|
},
|
|
},
|
|
|
|
+
|
|
|
|
+ // 获取历史消息
|
|
|
|
+ async getMessages(params) {
|
|
|
|
+ try {
|
|
|
|
+ const { data } = await getMessageApi({
|
|
|
|
+ messageType: params.messageType,
|
|
|
|
+ uuid: params.uuid,
|
|
|
|
+ friendUsername: params.friendUsername,
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ this.messages = (data || []).map((item) => {
|
|
|
|
+ item.avatar = item.avatar
|
|
|
|
+ ? `${import.meta.env.VITE_IM_PATH_FIlE}${item.avatar}`
|
|
|
|
+ : item.avatar;
|
|
|
|
+ return item;
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ return this.messages;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("获取消息失败:", error);
|
|
|
|
+ throw error;
|
|
|
|
+ }
|
|
|
|
+ },
|
|
},
|
|
},
|
|
});
|
|
});
|