// src/stores/websocket.ts import { defineStore } from "pinia"; import { $root as protobuf } from "@/common/proto/proto"; import * as Constant from "@/common/constant/Constant"; import { useWalletStore } from "@/stores/modules/walletStore"; import { getMessageApi } from "@/api/path/im.api"; import { MSG_TYPE, MSG_TYPE_MAP } from "@/common/constant/msgType"; const IM_PATH = import.meta.env.VITE_IM_PATH_FIlE; export const useWebSocketStore = defineStore("webSocketStore", { // 状态定义 state: () => ({ socket: null, // socket实例 peer: null, // peer实例 lockConnection: false, // 锁定连接 reconnectAttempts: 0, // 重连次数 maxReconnectAttempts: 5, // 最大重连次数 reconnectInterval: 3000, // 重连间隔 messages: [], // 消息列表 toAvatar: "", unreadMessages: [], // 未读消息列表 uuid: null, // 心跳检测配置 heartCheck: { timeout: 10000, // 连接丢失后,多长时间内没有收到服务端的消息,则认为连接已断开 timeoutObj: null, // 定时器对象 serverTimeoutObj: null, // 服务器定时器对象 num: 3, // 重连次数 }, }), // 计算属性 getters: { isConnected: (state) => state.socket?.readyState === WebSocket.OPEN, hasUnreadMessages: (state) => state.unreadMessages.length > 0, }, // 方法 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 }) || []; }, // 初始化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); }, 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.disconnect(); // 确保先断开现有连接 this.peer = new RTCPeerConnection(); this.socket = new WebSocket( `ws://192.168.0.59:8888/api/v1/socket.io?user=${userUuid}` ); this.socket.onopen = () => { this.startHeartbeat(); console.log("WebSocket连接成功"); this.webrtcConnection(); }; 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(); }; }, // 处理消息 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); // 处理消息 // if (MSG_TYPE_MAP.includes(message.messageType)) { // 文本消息 message.avatar = this.toAvatar if (message.contentType === MSG_TYPE.TEXT) { this.messages.push({ ...message, toUsername: message.to, }); } // 音频消息 if (message.contentType === MSG_TYPE.AUDIO) { const audioBlob = new Blob([message.file], { type: `audio/${message.fileSuffix}`, }); // 生成可播放的 ObjectURL const audioUrl = URL.createObjectURL(audioBlob); this.messages.push({ ...message, file: audioUrl, toUsername: message.to, }); } if (message.type === "heatbeat") return; if (message.type === Constant.MESSAGE_TRANS_TYPE) { this.dealWebRtcMessage(message); return; } // 其他消息处理逻辑... } catch (error) { console.error("消息解码错误:", error); } }; reader.readAsArrayBuffer(data); }, // 处理WebRTC消息 webrtcConnection() { if (!this.peer) return; this.peer.onicecandidate = (e) => { if (e.candidate && this.socket) { const candidate = { type: "answer_ice", iceCandidate: e.candidate, }; const message = { content: JSON.stringify(candidate), type: Constant.MESSAGE_TRANS_TYPE, }; this.sendMessage(message); } }; this.peer.ontrack = (e) => { if (e && e.streams) { const remoteVideo = document.getElementById("remoteVideoReceiver"); const remoteAudio = document.getElementById("audioPhone"); if (remoteVideo) remoteVideo.srcObject = e.streams[0]; if (remoteAudio) remoteAudio.srcObject = e.streams[0]; } }; }, // 断线重连 reconnect() { if ( this.lockConnection || this.reconnectAttempts >= this.maxReconnectAttempts ) { return; } this.lockConnection = true; this.reconnectAttempts++; console.log( `重新连接中... (尝试 ${this.reconnectAttempts}/${this.maxReconnectAttempts})` ); setTimeout(() => { this.connect(localStorage.uuid); this.lockConnection = false; }, this.reconnectInterval); }, // 重连 disconnect() { if (this.socket) { this.resetHeartbeat(); this.socket.close(); this.socket = null; } if (this.peer) { this.peer.close(); this.peer = null; } this.reconnectAttempts = 0; }, // 发送消息 sendMessage(messageData) { if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { console.error("WebSocket未连接"); return false; } const walletStore = useWalletStore(); let data = { ...messageData, fromUsername: walletStore.username, from: walletStore.account, }; console.log("发送消息=", data); 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 // 文本消息 if (data.contentType == MSG_TYPE.TEXT) { this.messages.push({ ...data, toUsername: data.friendUsername, }); } // 音频消息 if (data.contentType === MSG_TYPE.AUDIO) { const blob = new Blob([data.file], { type: data.fileSuffix }); const url = URL.createObjectURL(blob); console.log("url=",url) this.messages.push({ ...data, toUsername: data.to, localUrl: url, }); } return true; } catch (error) { console.error("消息编码错误:", error); return false; } }, // 发送WebRTC消息 dealWebRtcMessage(message) { // 实现WebRTC消息处理逻辑 }, }, });