canvas-website/multmux/packages/cli/src/connection/WebSocketClient.ts

121 lines
2.9 KiB
TypeScript

import WebSocket from 'ws';
import { EventEmitter } from 'events';
export interface TerminalMessage {
type: 'output' | 'input' | 'resize' | 'join' | 'leave' | 'presence' | 'joined' | 'error';
data?: any;
clientId?: string;
timestamp?: number;
sessionId?: string;
sessionName?: string;
message?: string;
}
export class WebSocketClient extends EventEmitter {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
constructor(private url: string, private token: string) {
super();
}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
const wsUrl = `${this.url}?token=${this.token}`;
this.ws = new WebSocket(wsUrl);
this.ws.on('open', () => {
this.reconnectAttempts = 0;
this.emit('connected');
resolve();
});
this.ws.on('message', (data) => {
try {
const message: TerminalMessage = JSON.parse(data.toString());
this.handleMessage(message);
} catch (error) {
console.error('Failed to parse message:', error);
}
});
this.ws.on('close', () => {
this.emit('disconnected');
this.attemptReconnect();
});
this.ws.on('error', (error) => {
this.emit('error', error);
reject(error);
});
});
}
private handleMessage(message: TerminalMessage): void {
switch (message.type) {
case 'output':
this.emit('output', message.data);
break;
case 'joined':
this.emit('joined', {
sessionId: message.sessionId,
sessionName: message.sessionName,
clientId: message.clientId,
});
break;
case 'presence':
this.emit('presence', message.data);
break;
case 'error':
this.emit('error', new Error(message.message || 'Unknown error'));
break;
}
}
sendInput(data: string): void {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
type: 'input',
data,
timestamp: Date.now(),
})
);
}
}
resize(cols: number, rows: number): void {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
type: 'resize',
data: { cols, rows },
timestamp: Date.now(),
})
);
}
}
disconnect(): void {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
private attemptReconnect(): void {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
this.emit('reconnecting', this.reconnectAttempts);
this.connect().catch(() => {
// Reconnection failed, will retry
});
}, 1000 * this.reconnectAttempts);
} else {
this.emit('reconnect-failed');
}
}
}