@@ -18,6 +18,7 @@ let reconnectAttempts = 0;
1818const CONTEXT_ID_KEY = 'opencli_context_id_v1' ;
1919let currentContextId = 'default' ;
2020let contextIdPromise : Promise < string > | null = null ;
21+ let connectInFlight : Promise < void > | null = null ;
2122
2223async function getCurrentContextId ( ) : Promise < string > {
2324 if ( contextIdPromise ) return contextIdPromise ;
@@ -92,26 +93,41 @@ console.error = (...args: unknown[]) => { _origError(...args); forwardLog('error
9293
9394// ─── WebSocket connection ────────────────────────────────────────────
9495
96+ function isDaemonSocketActive ( socket : WebSocket | null | undefined = ws ) : boolean {
97+ return socket ?. readyState === WebSocket . OPEN || socket ?. readyState === WebSocket . CONNECTING ;
98+ }
99+
95100/**
96101 * Probe the daemon via its /ping HTTP endpoint before attempting a WebSocket
97102 * connection. fetch() failures are silently catchable; new WebSocket() is not
98103 * — Chrome logs ERR_CONNECTION_REFUSED to the extension error page before any
99104 * JS handler can intercept it. By keeping the probe inside connect() every
100105 * call site remains unchanged and the guard can never be accidentally skipped.
101106 */
102- async function connect ( ) : Promise < void > {
103- if ( ws ?. readyState === WebSocket . OPEN || ws ?. readyState === WebSocket . CONNECTING ) return ;
107+ function connect ( ) : Promise < void > {
108+ if ( isDaemonSocketActive ( ) ) return Promise . resolve ( ) ;
109+ if ( connectInFlight ) return connectInFlight ;
110+ connectInFlight = connectAttempt ( ) . finally ( ( ) => {
111+ connectInFlight = null ;
112+ } ) ;
113+ return connectInFlight ;
114+ }
115+
116+ async function connectAttempt ( ) : Promise < void > {
117+ if ( isDaemonSocketActive ( ) ) return ;
104118
105119 try {
106120 const res = await fetch ( DAEMON_PING_URL , { signal : AbortSignal . timeout ( 1000 ) } ) ;
107121 if ( ! res . ok ) return ; // unexpected response — not our daemon
108122 } catch {
109123 return ; // daemon not running — skip WebSocket to avoid console noise
110124 }
125+ if ( isDaemonSocketActive ( ) ) return ;
111126
112127 let thisWs : WebSocket ;
113128 try {
114129 const contextId = await getCurrentContextId ( ) ;
130+ if ( isDaemonSocketActive ( ) ) return ;
115131 thisWs = new WebSocket ( DAEMON_WS_URL ) ;
116132 ws = thisWs ;
117133 currentContextId = contextId ;
0 commit comments