Skip to content

Commit 1735ae9

Browse files
Fix WebSocket browser compatibility
- Replace Node.js 'ws' import with universal WebSocket implementation - Use native browser WebSocket when available, fallback to 'ws' in Node.js - Handle different event APIs: browser (onmessage) vs Node.js (.on()) - Maintain backward compatibility for Node.js environments - Fix 'ws does not work in the browser' error in example app This allows the SDK to work in both browser and Node.js environments without requiring separate builds or polyfills. Co-authored-by: openhands <openhands@all-hands.dev>
1 parent 0146098 commit 1735ae9

File tree

1 file changed

+85
-34
lines changed

1 file changed

+85
-34
lines changed

src/events/websocket-client.ts

Lines changed: 85 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,24 @@
22
* WebSocket client for real-time event streaming
33
*/
44

5-
import WebSocket from 'ws';
65
import { Event, ConversationCallbackType } from '../types/base';
76

7+
// Use native WebSocket in browser, ws library in Node.js
8+
const WebSocketImpl = (() => {
9+
if (typeof window !== 'undefined' && window.WebSocket) {
10+
// Browser environment
11+
return window.WebSocket;
12+
} else {
13+
// Node.js environment
14+
try {
15+
const ws = require('ws');
16+
return ws;
17+
} catch (e) {
18+
throw new Error('WebSocket implementation not available. Install ws package for Node.js environments.');
19+
}
20+
}
21+
})();
22+
823
export interface WebSocketClientOptions {
924
host: string;
1025
conversationId: string;
@@ -17,7 +32,7 @@ export class WebSocketCallbackClient {
1732
private conversationId: string;
1833
private callback: ConversationCallbackType;
1934
private apiKey?: string;
20-
private ws?: WebSocket;
35+
private ws?: any; // WebSocket instance (browser or Node.js)
2136
private reconnectDelay = 1000;
2237
private maxReconnectDelay = 30000;
2338
private currentDelay = 1000;
@@ -64,38 +79,74 @@ export class WebSocketCallbackClient {
6479
// Add API key as query parameter if provided
6580
const finalUrl = this.apiKey ? `${wsUrl}?session_api_key=${this.apiKey}` : wsUrl;
6681

67-
this.ws = new WebSocket(finalUrl);
68-
69-
this.ws.on('open', () => {
70-
console.debug(`WebSocket connected to ${finalUrl}`);
71-
this.currentDelay = this.reconnectDelay; // Reset delay on successful connection
72-
});
73-
74-
this.ws.on('message', (data: WebSocket.Data) => {
75-
try {
76-
const message = data.toString();
77-
const event: Event = JSON.parse(message);
78-
this.callback(event);
79-
} catch (error) {
80-
console.error('Error processing WebSocket message:', error);
81-
}
82-
});
83-
84-
this.ws.on('close', (code: number, reason: Buffer) => {
85-
console.debug(`WebSocket closed: ${code} ${reason.toString()}`);
86-
this.ws = undefined;
87-
88-
if (this.shouldReconnect) {
89-
this.scheduleReconnect();
90-
}
91-
});
92-
93-
this.ws.on('error', (error: Error) => {
94-
console.debug('WebSocket error:', error);
95-
if (this.shouldReconnect) {
96-
this.scheduleReconnect();
97-
}
98-
});
82+
this.ws = new WebSocketImpl(finalUrl);
83+
84+
// Handle events differently for browser vs Node.js
85+
if (typeof window !== 'undefined') {
86+
// Browser WebSocket API
87+
this.ws.onopen = () => {
88+
console.debug(`WebSocket connected to ${finalUrl}`);
89+
this.currentDelay = this.reconnectDelay; // Reset delay on successful connection
90+
};
91+
92+
this.ws.onmessage = (event: MessageEvent) => {
93+
try {
94+
const message = event.data;
95+
const eventData: Event = JSON.parse(message);
96+
this.callback(eventData);
97+
} catch (error) {
98+
console.error('Error processing WebSocket message:', error);
99+
}
100+
};
101+
102+
this.ws.onclose = (event: CloseEvent) => {
103+
console.debug(`WebSocket closed: ${event.code} ${event.reason}`);
104+
this.ws = undefined;
105+
106+
if (this.shouldReconnect) {
107+
this.scheduleReconnect();
108+
}
109+
};
110+
111+
this.ws.onerror = (error: Event) => {
112+
console.debug('WebSocket error:', error);
113+
if (this.shouldReconnect) {
114+
this.scheduleReconnect();
115+
}
116+
};
117+
} else {
118+
// Node.js ws library API
119+
this.ws.on('open', () => {
120+
console.debug(`WebSocket connected to ${finalUrl}`);
121+
this.currentDelay = this.reconnectDelay; // Reset delay on successful connection
122+
});
123+
124+
this.ws.on('message', (data: any) => {
125+
try {
126+
const message = data.toString();
127+
const event: Event = JSON.parse(message);
128+
this.callback(event);
129+
} catch (error) {
130+
console.error('Error processing WebSocket message:', error);
131+
}
132+
});
133+
134+
this.ws.on('close', (code: number, reason: any) => {
135+
console.debug(`WebSocket closed: ${code} ${reason ? reason.toString() : ''}`);
136+
this.ws = undefined;
137+
138+
if (this.shouldReconnect) {
139+
this.scheduleReconnect();
140+
}
141+
});
142+
143+
this.ws.on('error', (error: Error) => {
144+
console.debug('WebSocket error:', error);
145+
if (this.shouldReconnect) {
146+
this.scheduleReconnect();
147+
}
148+
});
149+
}
99150
} catch (error) {
100151
console.error('Failed to create WebSocket connection:', error);
101152
if (this.shouldReconnect) {

0 commit comments

Comments
 (0)