Intial Version
This commit is contained in:
145
electron-app/src/main/websocket-client.ts
Normal file
145
electron-app/src/main/websocket-client.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import WebSocket from 'ws';
|
||||
import { EventEmitter } from 'events';
|
||||
import { PriceIndex, WebSocketMessage, HistoricalData } from '../shared/types';
|
||||
|
||||
export class WebSocketClient extends EventEmitter {
|
||||
private ws: WebSocket | null = null;
|
||||
private url: string;
|
||||
private reconnectInterval: number = 5000;
|
||||
private reconnectTimer: NodeJS.Timeout | null = null;
|
||||
private isIntentionallyClosed: boolean = false;
|
||||
private maxReconnectAttempts: number = 10;
|
||||
private reconnectAttempts: number = 0;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
connect(): void {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
console.log('WebSocket already connected');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isIntentionallyClosed = false;
|
||||
console.log(`Connecting to WebSocket: ${this.url}`);
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.url);
|
||||
|
||||
this.ws.on('open', () => {
|
||||
console.log('WebSocket connected');
|
||||
this.reconnectAttempts = 0;
|
||||
this.emit('connected');
|
||||
this.sendMessage({ type: 'subscribe', data: { channel: 'price_updates' } });
|
||||
});
|
||||
|
||||
this.ws.on('message', (data: WebSocket.Data) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(data.toString());
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on('close', (code: number, reason: Buffer) => {
|
||||
console.log(`WebSocket closed: ${code} - ${reason.toString()}`);
|
||||
this.emit('disconnected');
|
||||
|
||||
if (!this.isIntentionallyClosed) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.on('error', (error: Error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.emit('error', error);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to create WebSocket connection:', error);
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private handleMessage(message: WebSocketMessage): void {
|
||||
switch (message.type) {
|
||||
case 'price_update':
|
||||
this.emit('priceUpdate', message.data as PriceIndex);
|
||||
break;
|
||||
case 'history_data':
|
||||
this.emit('historyData', message.data as HistoricalData);
|
||||
break;
|
||||
case 'connection_status':
|
||||
this.emit('status', message.data);
|
||||
break;
|
||||
case 'error':
|
||||
this.emit('error', new Error(message.data));
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectTimer || this.isIntentionallyClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
console.error('Max reconnection attempts reached');
|
||||
this.emit('maxReconnectAttemptsReached');
|
||||
return;
|
||||
}
|
||||
|
||||
this.reconnectAttempts++;
|
||||
const delay = Math.min(this.reconnectInterval * this.reconnectAttempts, 30000);
|
||||
|
||||
console.log(`Scheduling reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
||||
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.reconnectTimer = null;
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
sendMessage(message: any): void {
|
||||
if (this.ws?.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.warn('WebSocket not connected, cannot send message');
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.isIntentionallyClosed = true;
|
||||
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
this.reconnectTimer = null;
|
||||
}
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.ws?.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
getConnectionState(): string {
|
||||
if (!this.ws) return 'DISCONNECTED';
|
||||
|
||||
switch (this.ws.readyState) {
|
||||
case WebSocket.CONNECTING: return 'CONNECTING';
|
||||
case WebSocket.OPEN: return 'CONNECTED';
|
||||
case WebSocket.CLOSING: return 'CLOSING';
|
||||
case WebSocket.CLOSED: return 'DISCONNECTED';
|
||||
default: return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user