Files
p2ns/plugin-sites/domain.consensus/www/js/websocket.js
T
2025-12-17 20:05:50 -05:00

197 lines
4.6 KiB
JavaScript

/**
* WebSocket client for real-time consensus updates
*/
class ConsensusWebSocket {
constructor() {
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1000;
this.listeners = new Map();
this.isConnected = false;
this.reconnectTimeout = null;
}
/**
* Connect to WebSocket server
*/
connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
try {
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.emit('connected');
this.updateStatus(true);
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (err) {
console.error('Error parsing WebSocket message:', err);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.emit('error', error);
};
this.ws.onclose = () => {
this.isConnected = false;
this.updateStatus(false);
this.emit('disconnected');
this.attemptReconnect();
};
} catch (err) {
console.error('Error connecting to WebSocket:', err);
this.attemptReconnect();
}
}
/**
* Handle incoming messages
*/
handleMessage(message) {
const { type, data } = message;
switch (type) {
case 'init':
this.emit('init', data);
break;
case 'update':
this.emit('update', data);
break;
case 'consensus-update':
// Real-time consensus change detected
this.emit('consensus-update', data);
break;
case 'domain-added':
this.emit('domain-added', data);
break;
case 'domain-removed':
this.emit('domain-removed', data);
break;
default:
// Unknown message type
console.debug('Unknown WebSocket message type:', type);
}
}
/**
* Send message to server
*/
send(type, data = {}) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({ type, ...data }));
}
}
/**
* Attempt to reconnect
*/
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
this.reconnectAttempts++;
const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 30000);
this.reconnectTimeout = setTimeout(() => {
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
this.connect();
}, delay);
}
/**
* Update connection status indicator
*/
updateStatus(connected) {
const indicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
if (indicator && statusText) {
if (connected) {
indicator.classList.remove('disconnected');
statusText.textContent = 'Connected';
} else {
indicator.classList.add('disconnected');
statusText.textContent = 'Disconnected - Reconnecting...';
}
}
}
/**
* Add event listener
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
/**
* Remove event listener
*/
off(event, callback) {
if (this.listeners.has(event)) {
const callbacks = this.listeners.get(event);
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
}
/**
* Emit event to listeners
*/
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(callback => {
try {
callback(data);
} catch (err) {
console.error(`Error in WebSocket event listener for ${event}:`, err);
}
});
}
}
/**
* Disconnect
*/
disconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
}
if (this.ws) {
this.ws.close();
this.ws = null;
}
this.isConnected = false;
this.listeners.clear();
}
}
// Create singleton instance
window.wsClient = new ConsensusWebSocket();