Skip to content

Commit 0f310be

Browse files
committed
Merge branch 'macae-UIWS-wip2' of https://github.com/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator into macae-v3-dev-marktayl
2 parents 7cbda41 + 71b9f33 commit 0f310be

File tree

1 file changed

+78
-34
lines changed

1 file changed

+78
-34
lines changed

src/frontend/src/services/WebSocketService.tsx

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ class WebSocketService {
4848
private processId: string | null = null; // Add this to store the process ID
4949
private reconnectAttempts = 0;
5050
private maxReconnectAttempts = 5;
51-
private reconnectDelay = 1000;
51+
private reconnectDelay = 12000; // Changed from 1000ms to 12000ms (12 seconds)
5252
private listeners: Map<string, Set<(message: StreamMessage) => void>> = new Map();
5353
private planSubscriptions: Set<string> = new Set();
54+
private reconnectTimer: NodeJS.Timeout | null = null; // Add timer tracking
55+
private isConnecting = false; // Add connection state tracking
5456

5557
/**
5658
* Connect to WebSocket server
@@ -79,51 +81,72 @@ class WebSocketService {
7981
});
8082
}
8183
return new Promise((resolve, reject) => {
84+
// Prevent multiple simultaneous connection attempts
85+
if (this.isConnecting) {
86+
console.log('Connection attempt already in progress');
87+
return;
88+
}
89+
90+
// Clear any existing reconnection timer
91+
if (this.reconnectTimer) {
92+
clearTimeout(this.reconnectTimer);
93+
this.reconnectTimer = null;
94+
}
95+
8296
try {
97+
this.isConnecting = true;
98+
8399
// Get WebSocket URL from environment or default to localhost
84100
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
85101
const wsHost = process.env.REACT_APP_WS_HOST || '127.0.0.1:8000';
86102
const processId = crypto.randomUUID(); // Generate unique process ID for this session
87103

88-
// const wsUrl = `${wsProtocol}//${wsHost}/api/v3/socket/${processId}`;
89104
// Build WebSocket URL with authentication headers as query parameters
90-
const userId = getUserId(); // Import this from config
91-
const wsUrl = `${wsProtocol}//${wsHost}/api/v3/socket/${processId}?user_id=${encodeURIComponent(userId)}`;
105+
const userId = getUserId(); // Import this from config
106+
const wsUrl = `${wsProtocol}//${wsHost}/api/v3/socket/${processId}?user_id=${encodeURIComponent(userId)}`;
92107

93108
console.log('Connecting to WebSocket:', wsUrl);
94109

95110
this.ws = new WebSocket(wsUrl);
96111

97112
this.ws.onopen = () => {
98-
console.log('WebSocket connected');
113+
console.log('WebSocket connected successfully');
99114
this.reconnectAttempts = 0;
115+
this.isConnecting = false;
100116
this.emit('connection_status', { connected: true });
101117
resolve();
102118
};
103119

104120
this.ws.onmessage = (event) => {
105121
try {
106-
const message: StreamMessage = JSON.parse(event.data);
122+
const message: StreamMessage = JSON.parse(event.data);
107123
this.handleMessage(message);
108124
} catch (error) {
109125
console.error('Error parsing WebSocket message:', error, 'Raw data:', event.data);
110126
this.emit('error', { error: 'Failed to parse WebSocket message' });
111127
}
112128
};
113129

114-
this.ws.onclose = () => {
115-
console.log('WebSocket disconnected');
130+
this.ws.onclose = (event) => {
131+
console.log('WebSocket disconnected', event.code, event.reason);
132+
this.isConnecting = false;
116133
this.emit('connection_status', { connected: false });
117-
this.attemptReconnect();
134+
135+
// Only attempt reconnect if it wasn't a manual disconnect
136+
if (event.code !== 1000) { // 1000 = normal closure
137+
this.attemptReconnect();
138+
}
118139
};
119140

120141
this.ws.onerror = (error) => {
121142
console.error('WebSocket error:', error);
143+
this.isConnecting = false;
122144
this.emit('error', { error: 'WebSocket connection failed' });
123145
reject(error);
124146
};
125147

126148
} catch (error) {
149+
this.isConnecting = false;
127150
reject(error);
128151
}
129152
});
@@ -133,11 +156,22 @@ class WebSocketService {
133156
* Disconnect from WebSocket server
134157
*/
135158
disconnect(): void {
159+
console.log('Manually disconnecting WebSocket');
160+
161+
// Clear any pending reconnection attempts
162+
if (this.reconnectTimer) {
163+
clearTimeout(this.reconnectTimer);
164+
this.reconnectTimer = null;
165+
}
166+
167+
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
168+
136169
if (this.ws) {
137-
this.ws.close();
170+
this.ws.close(1000, 'Manual disconnect'); // Use normal closure code
138171
this.ws = null;
139172
}
140173
this.planSubscriptions.clear();
174+
this.isConnecting = false;
141175
}
142176

143177
/**
@@ -232,9 +266,6 @@ class WebSocketService {
232266
/**
233267
* Handle incoming WebSocket messages
234268
*/
235-
/**
236-
* Handle incoming WebSocket messages
237-
*/
238269
private handleMessage(message: StreamMessage): void {
239270
console.log('WebSocket message received:', message);
240271

@@ -257,26 +288,39 @@ class WebSocketService {
257288
*/
258289
private attemptReconnect(): void {
259290
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
260-
console.log('Max reconnection attempts reached');
291+
console.log('Max reconnection attempts reached - stopping reconnect attempts');
261292
this.emit('error', { error: 'Max reconnection attempts reached' });
262293
return;
263294
}
264295

296+
// Prevent multiple simultaneous reconnection attempts
297+
if (this.isConnecting || this.reconnectTimer) {
298+
console.log('Reconnection attempt already in progress');
299+
return;
300+
}
301+
265302
this.reconnectAttempts++;
303+
// Use exponential backoff: 12s, 24s, 48s, 96s, 192s
266304
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
267305

268-
console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
306+
console.log(`Scheduling reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay/1000}s`);
269307

270-
setTimeout(() => {
308+
this.reconnectTimer = setTimeout(() => {
309+
this.reconnectTimer = null;
310+
console.log(`Attempting reconnection (attempt ${this.reconnectAttempts})`);
311+
271312
this.connect()
272313
.then(() => {
314+
console.log('Reconnection successful - re-subscribing to plans');
273315
// Re-subscribe to all plans
274316
this.planSubscriptions.forEach(planId => {
275317
this.subscribeToPlan(planId);
276318
});
277319
})
278320
.catch((error) => {
279321
console.error('Reconnection failed:', error);
322+
// The connect() method will trigger another reconnection attempt
323+
// through the onclose handler if needed
280324
});
281325
}, delay);
282326
}
@@ -300,28 +344,28 @@ class WebSocketService {
300344
}
301345

302346
/**
303-
* Send plan approval response
304-
*/
347+
* Send plan approval response
348+
*/
305349
sendPlanApprovalResponse(response: PlanApprovalResponseData): void {
306-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
307-
console.error('WebSocket not connected - cannot send plan approval response');
308-
this.emit('error', { error: 'Cannot send plan approval response - WebSocket not connected' });
309-
return;
310-
}
350+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
351+
console.error('WebSocket not connected - cannot send plan approval response');
352+
this.emit('error', { error: 'Cannot send plan approval response - WebSocket not connected' });
353+
return;
354+
}
311355

312-
try {
313-
const message = {
314-
type: 'plan_approval_response',
315-
data: response
316-
};
317-
this.ws.send(JSON.stringify(message));
318-
console.log('Plan approval response sent:', response);
319-
} catch (error) {
320-
console.error('Failed to send plan approval response:', error);
321-
this.emit('error', { error: 'Failed to send plan approval response' });
356+
try {
357+
const message = {
358+
type: 'plan_approval_response',
359+
data: response
360+
};
361+
this.ws.send(JSON.stringify(message));
362+
console.log('Plan approval response sent:', response);
363+
} catch (error) {
364+
console.error('Failed to send plan approval response:', error);
365+
this.emit('error', { error: 'Failed to send plan approval response' });
366+
}
322367
}
323368
}
324-
}
325369

326370
// Export singleton instance
327371
export const webSocketService = new WebSocketService();

0 commit comments

Comments
 (0)