94 lines
2.6 KiB
JavaScript
94 lines
2.6 KiB
JavaScript
// WebSocket client for real-time features
|
|
const WS = {
|
|
_ws: null,
|
|
_reconnectTimer: null,
|
|
_listeners: {},
|
|
_connected: false,
|
|
|
|
connect() {
|
|
if (this._ws && this._ws.readyState === WebSocket.OPEN) return;
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const url = `${protocol}//${window.location.host}/ws`;
|
|
|
|
try {
|
|
this._ws = new WebSocket(url);
|
|
} catch (e) {
|
|
console.warn('WS connection failed, retrying in 5s');
|
|
this._scheduleReconnect();
|
|
return;
|
|
}
|
|
|
|
this._ws.onopen = () => {
|
|
this._connected = true;
|
|
this._emit('connected');
|
|
};
|
|
|
|
this._ws.onclose = () => {
|
|
this._connected = false;
|
|
this._emit('disconnected');
|
|
this._scheduleReconnect();
|
|
};
|
|
|
|
this._ws.onerror = () => {
|
|
this._emit('error');
|
|
};
|
|
|
|
this._ws.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
this._emit(data.type, data);
|
|
} catch (e) {
|
|
// ignore non-JSON messages
|
|
}
|
|
};
|
|
|
|
// Keepalive ping every 30s
|
|
this._pingInterval = setInterval(() => {
|
|
if (this._ws && this._ws.readyState === WebSocket.OPEN) {
|
|
this._ws.send(JSON.stringify({ type: 'ping' }));
|
|
}
|
|
}, 30000);
|
|
},
|
|
|
|
disconnect() {
|
|
if (this._pingInterval) clearInterval(this._pingInterval);
|
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
if (this._ws) {
|
|
this._ws.onclose = null;
|
|
this._ws.close();
|
|
this._ws = null;
|
|
}
|
|
this._connected = false;
|
|
},
|
|
|
|
_scheduleReconnect() {
|
|
if (this._reconnectTimer) return;
|
|
this._reconnectTimer = setTimeout(() => {
|
|
this._reconnectTimer = null;
|
|
this.connect();
|
|
}, 5000);
|
|
},
|
|
|
|
on(event, callback) {
|
|
if (!this._listeners[event]) this._listeners[event] = [];
|
|
this._listeners[event].push(callback);
|
|
return () => {
|
|
this._listeners[event] = this._listeners[event].filter(cb => cb !== callback);
|
|
};
|
|
},
|
|
|
|
_emit(event, data) {
|
|
(this._listeners[event] || []).forEach(cb => cb(data));
|
|
// Also emit all for catch-all listeners
|
|
(this._listeners['*'] || []).forEach(cb => cb(event, data));
|
|
},
|
|
|
|
isConnected() {
|
|
return this._connected;
|
|
}
|
|
};
|
|
|
|
// Auto-connect on page load
|
|
document.addEventListener('DOMContentLoaded', () => WS.connect());
|