From 8e55503d7785e741ce9995f989d944e09a8f22db Mon Sep 17 00:00:00 2001 From: Iron9521 Date: Mon, 16 Feb 2026 21:04:55 +0800 Subject: [PATCH] fix(browser): track original port mapping for EADDRINUSE fallback Address review feedback: when port fallback occurs, maintain mapping from original requested port to the relay server for proper cleanup and reuse. - Add relayByOriginalPort map to track original port -> relay - Update ensureChromeExtensionRelayServer to check both maps - Update stopChromeExtensionRelayServer to clean up both mappings - Stop function now uses the relay's actual bound port for auth cleanup --- src/browser/extension-relay.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/browser/extension-relay.ts b/src/browser/extension-relay.ts index e8797f7f38..46a253d59f 100644 --- a/src/browser/extension-relay.ts +++ b/src/browser/extension-relay.ts @@ -147,6 +147,8 @@ function rejectUpgrade(socket: Duplex, status: number, bodyText: string) { const serversByPort = new Map(); const relayAuthByPort = new Map(); +// Track original requested port -> relay when fallback occurs (EADDRINUSE) +const relayByOriginalPort = new Map(); function relayAuthTokenForUrl(url: string): string | null { try { @@ -185,7 +187,7 @@ export async function ensureChromeExtensionRelayServer(opts: { throw new Error(`extension relay requires loopback cdpUrl host (got ${info.host})`); } - const existing = serversByPort.get(info.port); + const existing = serversByPort.get(info.port) ?? relayByOriginalPort.get(info.port); if (existing) { return existing; } @@ -752,6 +754,8 @@ export async function ensureChromeExtensionRelayServer(opts: { stop: async () => { serversByPort.delete(port); relayAuthByPort.delete(port); + // Also clean up original port mapping if this was a fallback + relayByOriginalPort.delete(info.port); try { extensionWs?.close(1001, "server stopping"); } catch { @@ -774,16 +778,21 @@ export async function ensureChromeExtensionRelayServer(opts: { relayAuthByPort.set(port, relayAuthToken); serversByPort.set(port, relay); + // If we fell back to a different port, also map the original requested port + if (port !== info.port) { + relayByOriginalPort.set(info.port, relay); + } return relay; } export async function stopChromeExtensionRelayServer(opts: { cdpUrl: string }): Promise { const info = parseBaseUrl(opts.cdpUrl); - const existing = serversByPort.get(info.port); + const existing = serversByPort.get(info.port) ?? relayByOriginalPort.get(info.port); if (!existing) { return false; } await existing.stop(); - relayAuthByPort.delete(info.port); + // Note: stop() cleans up both serversByPort and relayByOriginalPort + relayAuthByPort.delete(existing.port); return true; }