export class WebSocketClient {
constructor(url) {
if (!url) {
throw new Error("WebSocket URL is required.");
}
this.url = url;
this.websocket = null;
this.listeners = {};
this.heartbeatInterval = 30000;
this.reconnectDelay = 10000;
this.pingTimeout = null;
this.reconnectTimeout = null;
this.isManuallyClosed = false;
}
connect() {
this.websocket = new WebSocket(this.url);
this.websocket.onopen = () => {
this.dispatch("open");
};
this.websocket.onmessage = (event) => {
const data = JSON.parse(event.data ?? "{}");
this.dispatch("message", data);
};
this.websocket.onclose = () => {
this.dispatch("close");
if (!this.isManuallyClosed) {
this.reconnect();
}
};
this.websocket.onerror = (error) => {
console.error("WebSocket error:", error);
this.dispatch("error", error);
};
}
close() {
this.isManuallyClosed = true;
if (this.websocket) {
this.websocket.close();
}
}
addListener(eventType, callback) {
if (!this.listeners[eventType]) {
this.listeners[eventType] = [];
}
this.listeners[eventType].push(callback);
}
removeListener(eventType, callback) {
if (!this.listeners[eventType]) return;
this.listeners[eventType] = this.listeners[eventType].filter(
(listener) => listener !== callback
);
}
dispatch(eventType, data) {
if (!this.listeners[eventType]) return;
this.listeners[eventType].forEach((listener) => listener(data));
}
startHeartbeat() {
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
}
this.pingTimeout = setTimeout(() => {
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
this.websocket.send("ping");
console.log("Ping sent to server.");
}
this.startHeartbeat();
}, this.heartbeatInterval);
}
stopHeartbeat() {
if (this.pingTimeout) {
clearTimeout(this.pingTimeout);
}
}
reconnect() {
console.log(`正在重连${this.url} ${this.reconnectDelay / 1000} 秒...`);
this.reconnectTimeout = setTimeout(() => {
this.connect();
}, this.reconnectDelay);
}
send(data) {
this.websocket.send(data);
}
}