diff --git a/extension/src/ws-client.ts b/extension/src/ws-client.ts index 28d9fda..afafda1 100644 --- a/extension/src/ws-client.ts +++ b/extension/src/ws-client.ts @@ -212,36 +212,68 @@ export class WSBridgeClient { this.logFn(`[WS] Connecting to ${this.hubUrl}...`); const ws = new WebSocket(this.hubUrl); - ws.on('open', () => { - this.logFn('[WS] Connection opened, authenticating...'); - this.ws = ws; - this.connected = true; - this._authenticate(); - }); + // Detect API style: Node.js 'ws' module has .on(), browser WebSocket doesn't + const isNodeWs = typeof ws.on === 'function'; - ws.on('message', (raw: Buffer | string) => { - try { - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString('utf-8')); - this._handleMessage(data); - } catch (e: any) { - this.logFn(`[WS] Parse error: ${e.message}`); - } - }); + if (isNodeWs) { + // ─── Node.js ws module (EventEmitter API) ─── + ws.on('open', () => { + this.logFn('[WS] Connection opened, authenticating...'); + this.ws = ws; + this.connected = true; + this._authenticate(); + }); - ws.on('close', (code: number, reason: Buffer) => { - const reasonStr = reason ? reason.toString('utf-8') : ''; - this.logFn(`[WS] Connection closed: code=${code} reason=${reasonStr}`); - this._onDisconnect(); - }); + ws.on('message', (raw: Buffer | string) => { + try { + const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString('utf-8')); + this._handleMessage(data); + } catch (e: any) { + this.logFn(`[WS] Parse error: ${e.message}`); + } + }); - ws.on('error', (err: Error) => { - this.logFn(`[WS] Error: ${err.message}`); - // close event will follow - }); + ws.on('close', (code: number, reason: Buffer) => { + const reasonStr = reason ? reason.toString('utf-8') : ''; + this.logFn(`[WS] Connection closed: code=${code} reason=${reasonStr}`); + this._onDisconnect(); + }); - ws.on('pong', () => { - // Server responded to our ping — connection is alive - }); + ws.on('error', (err: Error) => { + this.logFn(`[WS] Error: ${err.message}`); + }); + + ws.on('pong', () => { + // Server responded to our ping — connection is alive + }); + } else { + // ─── Browser-style WebSocket API (.onopen / .onmessage) ─── + ws.onopen = () => { + this.logFn('[WS] Connection opened (browser API), authenticating...'); + this.ws = ws; + this.connected = true; + this._authenticate(); + }; + + ws.onmessage = (event: any) => { + try { + const raw = typeof event.data === 'string' ? event.data : event.data.toString(); + const data = JSON.parse(raw); + this._handleMessage(data); + } catch (e: any) { + this.logFn(`[WS] Parse error: ${e.message}`); + } + }; + + ws.onclose = (event: any) => { + this.logFn(`[WS] Connection closed: code=${event.code} reason=${event.reason || ''}`); + this._onDisconnect(); + }; + + ws.onerror = (event: any) => { + this.logFn(`[WS] Error: ${event.message || 'connection error'}`); + }; + } } catch (e: any) { this.logFn(`[WS] Connect failed: ${e.message}`); @@ -251,16 +283,16 @@ export class WSBridgeClient { private async _getWebSocketClass(): Promise { try { - // Try Node.js built-in WebSocket (v21+) - if (typeof globalThis.WebSocket !== 'undefined') { - return globalThis.WebSocket; - } - // Try require('ws') — should be available in VS Code's Node.js + // Prefer require('ws') — Node.js EventEmitter API with .on() + // VS Code runs in Node.js, so this should be available const ws = require('ws'); return ws; } catch { - // ws module not available + // ws module not available — try built-in WebSocket try { + if (typeof globalThis.WebSocket !== 'undefined') { + return globalThis.WebSocket; + } // Fallback: try the built-in undici WebSocket const { WebSocket } = require('undici'); return WebSocket; @@ -424,7 +456,13 @@ export class WSBridgeClient { this.heartbeatTimer = setInterval(() => { if (this.ws && this.connected) { try { - this.ws.ping(); + // Node.js ws has .ping(), browser WebSocket doesn't + if (typeof this.ws.ping === 'function') { + this.ws.ping(); + } else { + // Fallback: send heartbeat as JSON message + this.ws.send(JSON.stringify({ type: 'heartbeat' })); + } } catch { // ping failure will trigger close event }