Really-amin's picture
Upload 196 files
dd7ffbd verified
class WSClient {
constructor() {
this.socket = null;
this.status = 'disconnected';
this.statusSubscribers = new Set();
this.globalSubscribers = new Set();
this.typeSubscribers = new Map();
this.eventLog = [];
this.backoff = 1000;
this.maxBackoff = 16000;
this.shouldReconnect = true;
}
get url() {
const { protocol, host } = window.location;
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
return `${wsProtocol}//${host}/ws`;
}
logEvent(event) {
const entry = { ...event, time: new Date().toISOString() };
this.eventLog.push(entry);
this.eventLog = this.eventLog.slice(-100);
}
onStatusChange(callback) {
this.statusSubscribers.add(callback);
callback(this.status);
return () => this.statusSubscribers.delete(callback);
}
onMessage(callback) {
this.globalSubscribers.add(callback);
return () => this.globalSubscribers.delete(callback);
}
subscribe(type, callback) {
if (!this.typeSubscribers.has(type)) {
this.typeSubscribers.set(type, new Set());
}
const set = this.typeSubscribers.get(type);
set.add(callback);
return () => set.delete(callback);
}
updateStatus(newStatus) {
this.status = newStatus;
this.statusSubscribers.forEach((cb) => cb(newStatus));
}
connect() {
if (this.socket && (this.status === 'connecting' || this.status === 'connected')) {
return;
}
this.updateStatus('connecting');
this.socket = new WebSocket(this.url);
this.logEvent({ type: 'status', status: 'connecting' });
this.socket.addEventListener('open', () => {
this.backoff = 1000;
this.updateStatus('connected');
this.logEvent({ type: 'status', status: 'connected' });
});
this.socket.addEventListener('message', (event) => {
try {
const data = JSON.parse(event.data);
this.logEvent({ type: 'message', messageType: data.type || 'unknown' });
this.globalSubscribers.forEach((cb) => cb(data));
if (data.type && this.typeSubscribers.has(data.type)) {
this.typeSubscribers.get(data.type).forEach((cb) => cb(data));
}
} catch (error) {
console.error('WS message parse error', error);
}
});
this.socket.addEventListener('close', () => {
this.updateStatus('disconnected');
this.logEvent({ type: 'status', status: 'disconnected' });
if (this.shouldReconnect) {
const delay = this.backoff;
this.backoff = Math.min(this.backoff * 2, this.maxBackoff);
setTimeout(() => this.connect(), delay);
}
});
this.socket.addEventListener('error', (error) => {
console.error('WebSocket error', error);
this.logEvent({ type: 'error', details: error.message || 'unknown' });
if (this.socket) {
this.socket.close();
}
});
}
disconnect() {
this.shouldReconnect = false;
if (this.socket) {
this.socket.close();
}
}
getEvents() {
return [...this.eventLog];
}
}
const wsClient = new WSClient();
export default wsClient;