|
@@ -1,184 +1,236 @@
|
|
// src/stores/websocket.ts
|
|
// src/stores/websocket.ts
|
|
-import { defineStore } from 'pinia'
|
|
|
|
-import { ref } from 'vue'
|
|
|
|
-import { $root as protobuf } from '@/common/proto/proto'
|
|
|
|
-import * as Constant from '@/common/constant/Constant'
|
|
|
|
|
|
+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 { useWalletStore } from "@/stores/modules/walletStore";
|
|
|
|
|
|
-// MessageType = $root.lookupType("protocol.Message");
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-export const useWebSocketStore = defineStore('webSocketStore', () => {
|
|
|
|
- const socket = ref(null) // 创建WebSocket对象
|
|
|
|
- const peer = ref(null) // 创建RTCPeerConnection对象
|
|
|
|
- const lockConnection = ref(false)
|
|
|
|
- const reconnectAttempts = ref(0)
|
|
|
|
- const maxReconnectAttempts = 5
|
|
|
|
- const reconnectInterval = 3000
|
|
|
|
-
|
|
|
|
- // 心跳检测
|
|
|
|
- const heartCheck = {
|
|
|
|
- timeout: 10000,
|
|
|
|
- timeoutObj: null,
|
|
|
|
- serverTimeoutObj: null,
|
|
|
|
- num: 3,
|
|
|
|
- start: function () {
|
|
|
|
- const self = this
|
|
|
|
- const _num = this.num
|
|
|
|
-
|
|
|
|
- this.timeoutObj && clearTimeout(this.timeoutObj)
|
|
|
|
- this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj)
|
|
|
|
-
|
|
|
|
- this.timeoutObj = setTimeout(() => {
|
|
|
|
- if (socket.value?.readyState === 1) {
|
|
|
|
|
|
+export const useWebSocketStore = defineStore("webSocketStore", {
|
|
|
|
+ // 状态定义
|
|
|
|
+ state: () => ({
|
|
|
|
+ socket: null, // socket实例
|
|
|
|
+ peer: null, // peer实例
|
|
|
|
+ lockConnection: false, // 锁定连接
|
|
|
|
+ reconnectAttempts: 0, // 重连次数
|
|
|
|
+ maxReconnectAttempts: 5, // 最大重连次数
|
|
|
|
+ reconnectInterval: 3000, // 重连间隔
|
|
|
|
+ messages: [], // 消息列表
|
|
|
|
+ unreadMessages: [], // 未读消息列表
|
|
|
|
+ lastMessage: 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: {
|
|
|
|
+ // 初始化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 = {
|
|
const data = {
|
|
type: "heatbeat",
|
|
type: "heatbeat",
|
|
content: "ping",
|
|
content: "ping",
|
|
- }
|
|
|
|
- const message = protobuf.lookup("protocol.Message")
|
|
|
|
- const messagePB = message.create(data)
|
|
|
|
- socket.value.send(message.encode(messagePB).finish())
|
|
|
|
|
|
+ };
|
|
|
|
+ const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
+ const messagePB = MessageType.create(data);
|
|
|
|
+ const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
+ this.socket.send(buffer);
|
|
}
|
|
}
|
|
|
|
|
|
- self.serverTimeoutObj = setTimeout(() => {
|
|
|
|
- _num--
|
|
|
|
|
|
+ self.heartCheck.serverTimeoutObj = setTimeout(() => {
|
|
|
|
+ _num--;
|
|
if (_num <= 0) {
|
|
if (_num <= 0) {
|
|
- console.log("the ping num is more then 3, close socket!")
|
|
|
|
- socket.value?.close()
|
|
|
|
|
|
+ console.log("the ping num is more then 3, close socket!");
|
|
|
|
+ this.socket?.close();
|
|
}
|
|
}
|
|
- }, self.timeout)
|
|
|
|
- }, this.timeout)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 连接WebSocket
|
|
|
|
- const connect = (userUuid) => {
|
|
|
|
- console.log("开始连接...")
|
|
|
|
- peer.value = new RTCPeerConnection()
|
|
|
|
- socket.value = new WebSocket(`ws://192.168.0.59:8888/api/v1/socket.io?user=${userUuid}`)
|
|
|
|
-
|
|
|
|
- socket.value.onopen = () => {
|
|
|
|
- heartCheck.start()
|
|
|
|
- console.log("链接")
|
|
|
|
- webrtcConnection()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- socket.value.onmessage = (event) => {
|
|
|
|
- heartCheck.start()
|
|
|
|
- handleMessage(event.data)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- socket.value.onclose = () => {
|
|
|
|
- console.log("关闭并重新连接-->--->")
|
|
|
|
- reconnect()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- socket.value.onerror = () => {
|
|
|
|
- console.log("error----->>>>")
|
|
|
|
- reconnect()
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理接收到的消息
|
|
|
|
- const handleMessage = (data) => {
|
|
|
|
- const messageProto = protobuf.lookupType("protocol.Message")
|
|
|
|
- const reader = new FileReader()
|
|
|
|
-
|
|
|
|
- reader.readAsArrayBuffer(data)
|
|
|
|
- reader.onload = (event) => {
|
|
|
|
- const messagePB = messageProto.decode(new Uint8Array(event.target?.result))
|
|
|
|
- // 单独解码
|
|
|
|
- console.log(messagePB)
|
|
|
|
-
|
|
|
|
- if (messagePB.type === "heatbeat") return
|
|
|
|
-
|
|
|
|
- if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) {
|
|
|
|
- dealWebRtcMessage(messagePB)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 其他消息处理...
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // WebRTC 连接
|
|
|
|
- const webrtcConnection = () => {
|
|
|
|
- if (!peer.value) return
|
|
|
|
-
|
|
|
|
- peer.value.onicecandidate = (e) => {
|
|
|
|
- if (e.candidate && socket.value) {
|
|
|
|
- const candidate = {
|
|
|
|
- type: 'answer_ice',
|
|
|
|
- iceCandidate: e.candidate
|
|
|
|
|
|
+ }, 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) {
|
|
|
|
+ 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);
|
|
|
|
+
|
|
|
|
+ // 更新状态
|
|
|
|
+ this.messages.push(message);
|
|
|
|
+ this.lastMessage = message;
|
|
|
|
+
|
|
|
|
+ if (message.type === "heatbeat") return;
|
|
|
|
+
|
|
|
|
+ if (message.type === Constant.MESSAGE_TRANS_TYPE) {
|
|
|
|
+ this.dealWebRtcMessage(message);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 其他消息处理逻辑...
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("消息解码错误:", error);
|
|
}
|
|
}
|
|
- const message = {
|
|
|
|
- content: JSON.stringify(candidate),
|
|
|
|
- type: Constant.MESSAGE_TRANS_TYPE,
|
|
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ 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);
|
|
}
|
|
}
|
|
- 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;
|
|
}
|
|
}
|
|
- }
|
|
|
|
-
|
|
|
|
- peer.value.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]
|
|
|
|
|
|
+ if (this.peer) {
|
|
|
|
+ this.peer.close();
|
|
|
|
+ this.peer = null;
|
|
}
|
|
}
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 处理WebRTC消息
|
|
|
|
- const dealWebRtcMessage = (messagePB) => {
|
|
|
|
- // 实现与React版本相同的逻辑
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 重新连接
|
|
|
|
- const reconnect = () => {
|
|
|
|
- if (lockConnection.value) return
|
|
|
|
- lockConnection.value = true
|
|
|
|
-
|
|
|
|
- setTimeout(() => {
|
|
|
|
- if (socket.value?.readyState !== 1) {
|
|
|
|
- connect(localStorage.uuid)
|
|
|
|
|
|
+ this.reconnectAttempts = 0;
|
|
|
|
+ },
|
|
|
|
+ // 发送消息
|
|
|
|
+ sendMessage(messageData) {
|
|
|
|
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
|
|
|
+ console.error("WebSocket未连接");
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const walletStore = useWalletStore();
|
|
|
|
+ const data = {
|
|
|
|
+ ...messageData,
|
|
|
|
+ fromUsername: walletStore.username,
|
|
|
|
+ from: walletStore.account,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const MessageType = protobuf.lookupType("protocol.Message");
|
|
|
|
+ const messagePB = MessageType.create(data);
|
|
|
|
+ const buffer = MessageType.encode(messagePB).finish();
|
|
|
|
+ this.socket.send(buffer);
|
|
|
|
+ return true;
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error("消息编码错误:", error);
|
|
|
|
+ return false;
|
|
}
|
|
}
|
|
- lockConnection.value = false
|
|
|
|
- }, 10000)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 发送消息
|
|
|
|
- const sendMessage = (messageData) => {
|
|
|
|
- if (!socket.value) return
|
|
|
|
- const walletStore = useWalletStore()
|
|
|
|
- const data = {
|
|
|
|
- ...messageData,
|
|
|
|
- fromUsername: walletStore.username,
|
|
|
|
- from: walletStore.account,
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const message = protobuf.lookup("protocol.Message")
|
|
|
|
- const messagePB = message.create(data)
|
|
|
|
- socket.value.send(message.encode(messagePB).finish())
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- // 关闭连接
|
|
|
|
- const close = () => {
|
|
|
|
- socket.value?.close()
|
|
|
|
- peer.value?.close()
|
|
|
|
- socket.value = null
|
|
|
|
- peer.value = null
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return {
|
|
|
|
- socket,
|
|
|
|
- peer,
|
|
|
|
- connect,
|
|
|
|
- sendMessage,
|
|
|
|
- close,
|
|
|
|
- dealWebRtcMessage,
|
|
|
|
- webrtcConnection
|
|
|
|
- }
|
|
|
|
-})
|
|
|
|
|
|
+ },
|
|
|
|
+ // 发送WebRTC消息
|
|
|
|
+ dealWebRtcMessage(message) {
|
|
|
|
+ // 实现WebRTC消息处理逻辑
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+});
|