137 lines
3.4 KiB
TypeScript
137 lines
3.4 KiB
TypeScript
// stores/websocket.ts
|
|
import { defineStore } from "pinia";
|
|
import { ref, onScopeDispose } from "vue";
|
|
import { WebSocketType } from "@/types/websocket"; // 确保类型导入路径正确
|
|
|
|
export const useWebSocketStore = defineStore("websocket", () => {
|
|
// 状态定义
|
|
const socket = ref<WebSocket | null>(null);
|
|
const isConnected = ref(false);
|
|
const listeners = ref<Set<(msg: WebSocketType.AcceptMessageType) => void>>(new Set());
|
|
|
|
// 私有状态
|
|
let reconnectAttempts = 0;
|
|
let pingInterval: number;
|
|
let pongTimeout: number;
|
|
const PING_INTERVAL = 5000;
|
|
const PONG_TIMEOUT = 10000;
|
|
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
|
|
// 公共方法 ----
|
|
const connect = (url: string) => {
|
|
if (isConnected.value) {
|
|
return;
|
|
}
|
|
if (socket.value) disconnect();
|
|
|
|
const newSocket = new WebSocket(url);
|
|
|
|
newSocket.onopen = () => {
|
|
isConnected.value = true;
|
|
reconnectAttempts = 0;
|
|
startHeartbeat();
|
|
};
|
|
|
|
newSocket.onmessage = (event) => {
|
|
try {
|
|
const message: WebSocketType.AcceptMessageType = JSON.parse(event.data);
|
|
// 处理心跳响应
|
|
if (message.type === WebSocketType.AcceptMessageEnums.PONG) {
|
|
resetPongTimeout();
|
|
return;
|
|
}
|
|
|
|
notifyListeners(message);
|
|
} catch (error) {
|
|
console.error("消息解析失败:", error);
|
|
notifyListeners({
|
|
type: WebSocketType.AcceptMessageEnums.ERROR,
|
|
data: "消息解析失败"
|
|
});
|
|
}
|
|
};
|
|
|
|
newSocket.onclose = () => {
|
|
handleDisconnect();
|
|
scheduleReconnect(url);
|
|
};
|
|
|
|
newSocket.onerror = (error) => {
|
|
console.error("WebSocket错误:", error);
|
|
};
|
|
|
|
socket.value = newSocket;
|
|
};
|
|
|
|
const disconnect = () => {
|
|
socket.value?.close();
|
|
cleanup();
|
|
};
|
|
|
|
const sendMessage = <T = any>(type: WebSocketType.SendMessageEnums, data?: T) => {
|
|
if (isConnected.value && socket.value) {
|
|
const message: WebSocketType.ImSendMessageType<T> = {
|
|
type,
|
|
data
|
|
};
|
|
socket.value.send(JSON.stringify(message));
|
|
}
|
|
};
|
|
|
|
const onMessage = (callback: (msg: WebSocketType.AcceptMessageType) => void) => {
|
|
listeners.value.add(callback);
|
|
return () => listeners.value.delete(callback);
|
|
};
|
|
|
|
// 私有方法 ----
|
|
const startHeartbeat = () => {
|
|
pingInterval = window.setInterval(() => {
|
|
sendPing();
|
|
}, PING_INTERVAL);
|
|
};
|
|
|
|
const sendPing = () => {
|
|
if (socket.value?.readyState === WebSocket.OPEN) {
|
|
sendMessage(WebSocketType.SendMessageEnums.PING);
|
|
pongTimeout = window.setTimeout(() => socket.value?.close(), PONG_TIMEOUT);
|
|
}
|
|
};
|
|
|
|
const resetPongTimeout = () => {
|
|
window.clearTimeout(pongTimeout);
|
|
};
|
|
|
|
const notifyListeners = (message: WebSocketType.AcceptMessageType) => {
|
|
listeners.value.forEach((cb) => cb(message));
|
|
};
|
|
|
|
const handleDisconnect = () => {
|
|
isConnected.value = false;
|
|
cleanup();
|
|
};
|
|
|
|
const scheduleReconnect = (url: string) => {
|
|
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
|
|
const delay = Math.min(3000 * 2 ** reconnectAttempts, 30000);
|
|
setTimeout(() => connect(url), delay);
|
|
reconnectAttempts++;
|
|
}
|
|
};
|
|
|
|
const cleanup = () => {
|
|
window.clearInterval(pingInterval);
|
|
window.clearTimeout(pongTimeout);
|
|
};
|
|
|
|
// 自动清理
|
|
onScopeDispose(() => disconnect());
|
|
|
|
return {
|
|
isConnected,
|
|
connect,
|
|
disconnect,
|
|
sendMessage,
|
|
onMessage
|
|
};
|
|
});
|