diff --git a/deployables/extension/src/background/companionEnablement.spec.ts b/deployables/extension/src/background/companionEnablement.spec.ts index 4225859e0..67ff9d20b 100644 --- a/deployables/extension/src/background/companionEnablement.spec.ts +++ b/deployables/extension/src/background/companionEnablement.spec.ts @@ -19,7 +19,7 @@ import { type CompanionResponseMessage, type Message, } from '@zodiac/messages' -import { mockActiveTab, mockTab } from '@zodiac/test-utils/chrome' +import { mockActiveTab, mockTab, mockTabClose } from '@zodiac/test-utils/chrome' import { beforeEach, describe, expect, it, vi } from 'vitest' import { companionEnablement } from './companionEnablement' import { trackRequests } from './rpcTracking' @@ -68,6 +68,22 @@ describe('Companion Enablement', () => { forkUrl: 'http://test-rpc.com', }) }) + + it('does not notify the companion app when the tab has been closed', async () => { + await startPilotSession({ windowId: 1, tabId: 2 }) + + const tab = await connectCompanionApp({ id: 2, windowId: 1 }) + + await mockTabClose(tab.id) + + await startSimulation({ + windowId: 1, + chainId: Chain.ETH, + rpcUrl: 'http://test-rpc.com', + }) + + expect(chromeMock.tabs.sendMessage).toHaveBeenCalledTimes(1) + }) }) describe('Connection status', () => { diff --git a/deployables/extension/src/background/companionEnablement.ts b/deployables/extension/src/background/companionEnablement.ts index 3ba9472c0..15571ec9d 100644 --- a/deployables/extension/src/background/companionEnablement.ts +++ b/deployables/extension/src/background/companionEnablement.ts @@ -39,7 +39,7 @@ export const companionEnablement = ( console.debug('Companion App connected!') - onSimulationUpdate.addListener(async (fork) => { + const dispose = onSimulationUpdate.addListener(async (fork) => { invariant(tab.id != null, 'Tab needs an ID') console.debug('Sending updated fork to companion app', { fork }) @@ -52,6 +52,18 @@ export const companionEnablement = ( captureLastError() }) + const handleTabClose = (tabId: number) => { + if (tabId !== tab.id) { + return + } + + chrome.tabs.onRemoved.removeListener(handleTabClose) + + dispose() + } + + chrome.tabs.onRemoved.addListener(handleTabClose) + break } diff --git a/deployables/extension/src/background/createEventListener.ts b/deployables/extension/src/background/createEventListener.ts index 67965054f..cbd3f35a6 100644 --- a/deployables/extension/src/background/createEventListener.ts +++ b/deployables/extension/src/background/createEventListener.ts @@ -17,6 +17,10 @@ export const createEventListener = < return { addListener(listener) { listeners.add(listener) + + return () => { + listeners.delete(listener) + } }, removeAllListeners() { listeners.clear() diff --git a/deployables/extension/src/background/types.ts b/deployables/extension/src/background/types.ts index 21cc46ae0..0f8661e8f 100644 --- a/deployables/extension/src/background/types.ts +++ b/deployables/extension/src/background/types.ts @@ -7,8 +7,10 @@ export interface Fork { export type EventFn = (...args: any) => void +type DisposeFn = () => void + export type Event void> = { - addListener: (listener: T) => void + addListener: (listener: T) => DisposeFn removeListener: (listener: T) => void removeAllListeners: () => void } diff --git a/deployables/extension/test-utils/connectCompanionApp.ts b/deployables/extension/test-utils/connectCompanionApp.ts index 02145b1e7..0f4dd770f 100644 --- a/deployables/extension/test-utils/connectCompanionApp.ts +++ b/deployables/extension/test-utils/connectCompanionApp.ts @@ -8,12 +8,16 @@ import { callListeners, chromeMock, createMockTab } from './chrome' export const connectCompanionApp = async ( tab: Partial = {}, ) => { + const connectedTab = createMockTab(tab) + await callListeners( chromeMock.runtime.onMessage, { type: CompanionAppMessageType.REQUEST_FORK_INFO, } satisfies CompanionAppMessage, - { id: chrome.runtime.id, tab: createMockTab(tab) }, + { id: chrome.runtime.id, tab: connectedTab }, vi.fn(), ) + + return connectedTab } diff --git a/packages/test-utils/src/chrome/index.ts b/packages/test-utils/src/chrome/index.ts index 21359d8de..e9b33d2b6 100644 --- a/packages/test-utils/src/chrome/index.ts +++ b/packages/test-utils/src/chrome/index.ts @@ -4,6 +4,7 @@ export * from './creators' export { mockActiveTab } from './mockActiveTab' export { mockRuntimeConnect } from './mockRuntimeConnect' export { mockTab } from './mockTab' +export { mockTabClose } from './mockTabClose' export { mockTabConnect } from './mockTabConnect' export { mockTabSwitch } from './mockTabSwitch' export { createStorageMock } from './storageMock' diff --git a/packages/test-utils/src/chrome/mockTabClose.ts b/packages/test-utils/src/chrome/mockTabClose.ts new file mode 100644 index 000000000..a5dcbfd9e --- /dev/null +++ b/packages/test-utils/src/chrome/mockTabClose.ts @@ -0,0 +1,8 @@ +import { callListeners } from './callListeners' +import { chromeMock } from './chromeMock' + +export const mockTabClose = (tabId: number) => + callListeners(chromeMock.tabs.onRemoved, tabId, { + isWindowClosing: false, + windowId: 1, + })