132 lines
4.5 KiB
TypeScript
132 lines
4.5 KiB
TypeScript
// Define the runtime configuration interface
|
|
interface RuntimeConfig {
|
|
API_URL: string;
|
|
}
|
|
declare global {
|
|
interface Window {
|
|
runtimeConfig?: RuntimeConfig;
|
|
}
|
|
}
|
|
|
|
class WebSocketService {
|
|
private ws: WebSocket | null = null;
|
|
private messageHandlers: ((message: any) => void)[] = [];
|
|
private errorHandlers: ((error: Event) => void)[] = [];
|
|
private sessionTerminatedHandlers: (() => void)[] = [];
|
|
private currentSessionId: string | null = null;
|
|
private currentClientId: string | null = null;
|
|
private heartbeatInterval: NodeJS.Timeout | null = null;
|
|
|
|
connect(sessionId: string, clientId: string) {
|
|
// Prevent multiple connections
|
|
if (this.ws) {
|
|
return;
|
|
}
|
|
|
|
this.currentSessionId = sessionId;
|
|
this.currentClientId = clientId;
|
|
// Read the API_URL from the runtime configuration
|
|
const apiUrl = window.runtimeConfig?.API_URL || 'ws://localhost:8000';
|
|
const wsUrl = `${apiUrl.replace(/^http/, 'ws')}/sessions/${sessionId}`;
|
|
this.ws = new WebSocket(wsUrl);
|
|
|
|
this.ws.onopen = () => {
|
|
console.log('WebSocket connected');
|
|
// Send JOIN_SESSION message on open to inform the server of the client and session IDs
|
|
this.sendMessage({ type: 'JOIN_SESSION', payload: { clientId: this.currentClientId, sessionId: this.currentSessionId } });
|
|
|
|
// Start heartbeat to keep connection alive
|
|
this.heartbeatInterval = setInterval(() => {
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
this.ws.send(JSON.stringify({ type: 'PING', clientId: this.currentClientId, sessionId: this.currentSessionId }));
|
|
}
|
|
}, 30000); // Send ping every 30 seconds
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
console.log('WebSocketService: Received and parsed message:', message);
|
|
this.messageHandlers.forEach(handler => handler(message));
|
|
} catch (error) {
|
|
console.error('Error parsing incoming message:', error);
|
|
}
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
console.log('WebSocket disconnected');
|
|
if (this.heartbeatInterval) {
|
|
clearInterval(this.heartbeatInterval);
|
|
this.heartbeatInterval = null;
|
|
}
|
|
this.sessionTerminatedHandlers.forEach(handler => handler());
|
|
this.ws = null;
|
|
this.currentSessionId = null;
|
|
this.currentClientId = null;
|
|
};
|
|
|
|
this.ws.onerror = (event) => {
|
|
console.error('WebSocket error:', event);
|
|
if (this.heartbeatInterval) {
|
|
clearInterval(this.heartbeatInterval);
|
|
this.heartbeatInterval = null;
|
|
}
|
|
this.errorHandlers.forEach(handler => handler(event));
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.ws) {
|
|
this.ws.close();
|
|
}
|
|
if (this.heartbeatInterval) {
|
|
clearInterval(this.heartbeatInterval);
|
|
this.heartbeatInterval = null;
|
|
}
|
|
}
|
|
|
|
sendMessage(message: any) {
|
|
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.currentClientId && this.currentSessionId) {
|
|
const messageToSend = {
|
|
...message,
|
|
clientId: this.currentClientId,
|
|
sessionId: this.currentSessionId,
|
|
};
|
|
this.ws.send(JSON.stringify(messageToSend));
|
|
} else {
|
|
// This error can be ignored if it happens during initial connection in StrictMode
|
|
// console.error('WebSocket is not connected or clientId/sessionId is missing.');
|
|
}
|
|
}
|
|
|
|
onMessage(handler: (message: any) => void) {
|
|
this.messageHandlers.push(handler);
|
|
}
|
|
|
|
removeMessageHandler(handler: (message: any) => void) {
|
|
this.messageHandlers = this.messageHandlers.filter(h => h !== handler);
|
|
}
|
|
|
|
onError(handler: (error: Event) => void) {
|
|
this.errorHandlers.push(handler);
|
|
}
|
|
|
|
removeErrorHandler(handler: (error: Event) => void) {
|
|
this.errorHandlers = this.errorHandlers.filter(h => h !== handler);
|
|
}
|
|
|
|
onSessionTerminated(handler: () => void) {
|
|
this.sessionTerminatedHandlers.push(handler);
|
|
}
|
|
|
|
removeSessionTerminatedHandler(handler: () => void) {
|
|
this.sessionTerminatedHandlers = this.sessionTerminatedHandlers.filter(h => h !== handler);
|
|
}
|
|
}
|
|
|
|
export const webSocketService = new WebSocketService();
|