From eab20aa69e32691a5fc7517b5cebea3b4093d7f5 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 5 Aug 2025 13:47:08 -0700 Subject: [PATCH] chore(extension): do not send if socket is already closed (#834) * Remove debugger listeners if closed() is called as `ws.onclosed` is dispatched asynchronously * Tabs can be closed while update badge command is in flight * Inflight CDP commands fail if the tab closes, do not try to send their response to a closed socket --- extension/src/background.ts | 15 ++++++++++----- extension/src/relayConnection.ts | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/extension/src/background.ts b/extension/src/background.ts index bfb672c..41b4399 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -130,9 +130,13 @@ class TabShareExtension { } private async _updateBadge(tabId: number, { text, color }: { text: string; color: string | null }): Promise { - await chrome.action.setBadgeText({ tabId, text }); - if (color) - await chrome.action.setBadgeBackgroundColor({ tabId, color }); + try { + await chrome.action.setBadgeText({ tabId, text }); + if (color) + await chrome.action.setBadgeBackgroundColor({ tabId, color }); + } catch (error: any) { + // Ignore errors as the tab may be closed already. + } } private async _onTabRemoved(tabId: number): Promise { @@ -165,8 +169,9 @@ class TabShareExtension { if (!tab.active && !pending.timerId) { debugLog('Starting inactivity timer', tabId); pending.timerId = window.setTimeout(() => { - this._pendingTabSelection.delete(tabId); - pending.connection.close('Tab is not active'); + const existed = this._pendingTabSelection.delete(tabId); + if (existed) + pending.connection.close('Tab is not active'); }, 5000); return; } diff --git a/extension/src/relayConnection.ts b/extension/src/relayConnection.ts index 15e835b..f3021db 100644 --- a/extension/src/relayConnection.ts +++ b/extension/src/relayConnection.ts @@ -43,6 +43,7 @@ export class RelayConnection { private _detachListener: (source: chrome.debugger.Debuggee, reason: string) => void; private _tabPromise: Promise; private _tabPromiseResolve!: () => void; + private _closed = false; onclose?: () => void; @@ -51,7 +52,7 @@ export class RelayConnection { this._tabPromise = new Promise(resolve => this._tabPromiseResolve = resolve); this._ws = ws; this._ws.onmessage = this._onMessage.bind(this); - this._ws.onclose = () => this.onclose?.(); + this._ws.onclose = () => this._onClose(); // Store listeners for cleanup this._eventListener = this._onDebuggerEvent.bind(this); this._detachListener = this._onDebuggerDetach.bind(this); @@ -66,9 +67,19 @@ export class RelayConnection { } close(message: string): void { + this._ws.close(1000, message); + // ws.onclose is called asynchronously, so we call it here to avoid forwarding + // CDP events to the closed connection. + this._onClose(); + } + + private _onClose() { + if (this._closed) + return; + this._closed = true; chrome.debugger.onEvent.removeListener(this._eventListener); chrome.debugger.onDetach.removeListener(this._detachListener); - this._ws.close(1000, message); + this.onclose?.(); } private _onDebuggerEvent(source: chrome.debugger.DebuggerSession, method: string, params: any): void { @@ -160,6 +171,7 @@ export class RelayConnection { } private _sendMessage(message: any): void { - this._ws.send(JSON.stringify(message)); + if (this._ws.readyState === WebSocket.OPEN) + this._ws.send(JSON.stringify(message)); } }