mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-12-23 14:52:43 +00:00
145 lines
4 KiB
JavaScript
145 lines
4 KiB
JavaScript
/**
|
|
* WebSocket client for real-time updates
|
|
*/
|
|
class WebSocketClient {
|
|
constructor(basePath = '') {
|
|
this.basePath = basePath;
|
|
this.ws = null;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 10;
|
|
this.reconnectDelay = 1000;
|
|
this.listeners = new Map();
|
|
this.isConnected = false;
|
|
this.shouldReconnect = true;
|
|
}
|
|
|
|
connect() {
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
return;
|
|
}
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
// Ensure basePath ends with '/' for proper URL construction
|
|
let basePath = this.basePath || '';
|
|
if (basePath && !basePath.endsWith('/')) {
|
|
basePath += '/';
|
|
}
|
|
const wsUrl = `${protocol}//${window.location.host}${basePath}ws`;
|
|
|
|
console.log('WebSocket connecting to:', wsUrl, 'basePath:', this.basePath);
|
|
|
|
try {
|
|
this.ws = new WebSocket(wsUrl);
|
|
|
|
this.ws.onopen = () => {
|
|
console.log('WebSocket connected');
|
|
this.isConnected = true;
|
|
this.reconnectAttempts = 0;
|
|
this.emit('connected');
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
try {
|
|
// Validate message size (prevent memory issues)
|
|
const maxMessageSize = 10 * 1024 * 1024; // 10MB
|
|
if (event.data && event.data.length > maxMessageSize) {
|
|
console.error('WebSocket message too large:', event.data.length, 'bytes');
|
|
this.ws.close();
|
|
return;
|
|
}
|
|
|
|
const message = JSON.parse(event.data);
|
|
if (!message || typeof message !== 'object') {
|
|
console.error('Invalid WebSocket message format');
|
|
return;
|
|
}
|
|
|
|
this.handleMessage(message);
|
|
} catch (e) {
|
|
console.error('Failed to parse WebSocket message:', e);
|
|
}
|
|
};
|
|
|
|
this.ws.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
this.emit('error', error);
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
console.log('WebSocket disconnected');
|
|
this.isConnected = false;
|
|
this.emit('disconnected');
|
|
|
|
if (this.shouldReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.reconnectAttempts++;
|
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
setTimeout(() => this.connect(), delay);
|
|
}
|
|
};
|
|
} catch (e) {
|
|
console.error('Failed to create WebSocket connection:', e);
|
|
this.emit('error', e);
|
|
}
|
|
}
|
|
|
|
handleMessage(message) {
|
|
const { type, payload, time } = message;
|
|
|
|
// Emit to specific type listeners
|
|
this.emit(type, payload, time);
|
|
|
|
// Emit to all listeners
|
|
this.emit('message', { type, payload, time });
|
|
}
|
|
|
|
on(event, callback) {
|
|
if (!this.listeners.has(event)) {
|
|
this.listeners.set(event, []);
|
|
}
|
|
this.listeners.get(event).push(callback);
|
|
}
|
|
|
|
off(event, callback) {
|
|
if (!this.listeners.has(event)) {
|
|
return;
|
|
}
|
|
const callbacks = this.listeners.get(event);
|
|
const index = callbacks.indexOf(callback);
|
|
if (index > -1) {
|
|
callbacks.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
emit(event, ...args) {
|
|
if (this.listeners.has(event)) {
|
|
this.listeners.get(event).forEach(callback => {
|
|
try {
|
|
callback(...args);
|
|
} catch (e) {
|
|
console.error('Error in WebSocket event handler:', e);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
disconnect() {
|
|
this.shouldReconnect = false;
|
|
if (this.ws) {
|
|
this.ws.close();
|
|
this.ws = null;
|
|
}
|
|
}
|
|
|
|
send(data) {
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(JSON.stringify(data));
|
|
} else {
|
|
console.warn('WebSocket is not connected');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create global WebSocket client instance
|
|
// Safely get basePath from global scope (defined in page.html)
|
|
window.wsClient = new WebSocketClient(typeof basePath !== 'undefined' ? basePath : '');
|