diff --git a/extension/src/bridge/BridgeContext.tsx b/extension/src/bridge/BridgeContext.tsx
new file mode 100644
index 00000000..4d881edc
--- /dev/null
+++ b/extension/src/bridge/BridgeContext.tsx
@@ -0,0 +1,25 @@
+import { invariant } from '@epic-web/invariant'
+import { createContext, PropsWithChildren, useContext } from 'react'
+
+const BridgeContext = createContext<{ windowId: number | null }>({
+ windowId: null,
+})
+
+type ProvideBridgeContextProps = PropsWithChildren<{ windowId: number }>
+
+export const ProvideBridgeContext = ({
+ children,
+ windowId,
+}: ProvideBridgeContextProps) => (
+
+ {children}
+
+)
+
+export const useWindowId = () => {
+ const { windowId } = useContext(BridgeContext)
+
+ invariant(windowId != null, '"windowId" not set on BridgeContext')
+
+ return windowId
+}
diff --git a/extension/src/bridge/index.ts b/extension/src/bridge/index.ts
new file mode 100644
index 00000000..a89b84a0
--- /dev/null
+++ b/extension/src/bridge/index.ts
@@ -0,0 +1,2 @@
+export { ProvideBridgeContext, useWindowId } from './BridgeContext'
+export { useProviderBridge } from './useProviderBridge'
diff --git a/extension/src/panel/useProviderBridge.spec.ts b/extension/src/bridge/useProviderBridge.spec.tsx
similarity index 77%
rename from extension/src/panel/useProviderBridge.spec.ts
rename to extension/src/bridge/useProviderBridge.spec.tsx
index b17dd421..ddf89588 100644
--- a/extension/src/panel/useProviderBridge.spec.ts
+++ b/extension/src/bridge/useProviderBridge.spec.tsx
@@ -1,9 +1,16 @@
import { ZERO_ADDRESS } from '@/chains'
import { InjectedProviderMessage, InjectedProviderMessageTyp } from '@/messages'
-import { callListeners, chromeMock, mockActiveTab } from '@/test-utils'
+import {
+ callListeners,
+ chromeMock,
+ createMockTab,
+ mockActiveTab,
+ renderHook,
+} from '@/test-utils'
import { Eip1193Provider } from '@/types'
-import { cleanup, renderHook, waitFor } from '@testing-library/react'
+import { cleanup, waitFor } from '@testing-library/react'
import { toQuantity } from 'ethers'
+import { PropsWithChildren } from 'react'
import { ChainId } from 'ser-kit'
import {
afterEach,
@@ -14,6 +21,7 @@ import {
MockedFunction,
vi,
} from 'vitest'
+import { ProvideBridgeContext } from './BridgeContext'
import { useProviderBridge } from './useProviderBridge'
describe('Bridge', () => {
@@ -28,17 +36,23 @@ describe('Bridge', () => {
}
}
+ const Wrapper = ({ children }: PropsWithChildren) => (
+ {children}
+ )
+
afterEach(cleanup)
describe('Provider handling', () => {
beforeEach(() => {
- mockActiveTab()
+ mockActiveTab({ windowId: 1 })
})
it('relays requests to the provider', async () => {
const provider = new MockProvider()
- renderHook(() => useProviderBridge({ provider }))
+ await renderHook(() => useProviderBridge({ provider }), {
+ wrapper: Wrapper,
+ })
const request = { method: 'eth_chainId' }
@@ -49,7 +63,7 @@ describe('Bridge', () => {
request,
requestId: 'test-id',
} satisfies InjectedProviderMessage,
- { id: chromeMock.runtime.id },
+ { id: chromeMock.runtime.id, tab: createMockTab({ windowId: 1 }) },
vi.fn()
)
@@ -63,7 +77,9 @@ describe('Bridge', () => {
provider.request.mockResolvedValue(response)
- renderHook(() => useProviderBridge({ provider }))
+ await renderHook(() => useProviderBridge({ provider }), {
+ wrapper: Wrapper,
+ })
const request = { method: 'eth_chainId' }
@@ -76,7 +92,7 @@ describe('Bridge', () => {
request,
requestId: 'test-id',
} satisfies InjectedProviderMessage,
- { id: chromeMock.runtime.id },
+ { id: chromeMock.runtime.id, tab: createMockTab({ windowId: 1 }) },
sendMessage
)
@@ -94,7 +110,9 @@ describe('Bridge', () => {
provider.request.mockRejectedValue(error)
- renderHook(() => useProviderBridge({ provider }))
+ await renderHook(() => useProviderBridge({ provider }), {
+ wrapper: Wrapper,
+ })
const request = { method: 'eth_chainId' }
@@ -107,7 +125,7 @@ describe('Bridge', () => {
request,
requestId: 'test-id',
} satisfies InjectedProviderMessage,
- { id: chromeMock.runtime.id },
+ { id: chromeMock.runtime.id, tab: createMockTab({ windowId: 1 }) },
sendMessage
)
@@ -124,10 +142,10 @@ describe('Bridge', () => {
const request = { method: 'eth_chainId' }
- const { rerender } = renderHook(
+ const { rerender } = await renderHook(
({ provider }: { provider: Eip1193Provider }) =>
useProviderBridge({ provider }),
- { initialProps: { provider: providerA } }
+ { initialProps: { provider: providerA }, wrapper: Wrapper }
)
rerender({ provider: providerB })
@@ -139,7 +157,7 @@ describe('Bridge', () => {
request,
requestId: 'test-id',
} satisfies InjectedProviderMessage,
- { id: chromeMock.runtime.id },
+ { id: chromeMock.runtime.id, tab: createMockTab({ windowId: 1 }) },
vi.fn()
)
@@ -154,7 +172,10 @@ describe('Bridge', () => {
it('emits an "accountsChanged" event when the hook initially renders with an account', async () => {
const tab = mockActiveTab()
- renderHook(() => useProviderBridge({ provider, account: ZERO_ADDRESS }))
+ await renderHook(
+ () => useProviderBridge({ provider, account: ZERO_ADDRESS }),
+ { wrapper: Wrapper }
+ )
await waitFor(() => {
expect(chromeMock.tabs.sendMessage).toHaveBeenCalledWith(tab.id, {
@@ -168,7 +189,9 @@ describe('Bridge', () => {
it('does not emit an "accountsChanged" event when there is no account on the first render', async () => {
mockActiveTab()
- renderHook(() => useProviderBridge({ provider }))
+ await renderHook(() => useProviderBridge({ provider }), {
+ wrapper: Wrapper,
+ })
expect(chromeMock.tabs.sendMessage).not.toHaveBeenCalledWith()
})
@@ -176,11 +199,12 @@ describe('Bridge', () => {
it('does emit an "accountsChanged" event when the account resets on later renders', async () => {
const tab = mockActiveTab()
- const { rerender } = renderHook<
+ const { rerender } = await renderHook<
void,
{ account: `0x${string}` | undefined }
>(({ account }) => useProviderBridge({ provider, account }), {
initialProps: { account: ZERO_ADDRESS },
+ wrapper: Wrapper,
})
rerender({ account: undefined })
@@ -192,10 +216,6 @@ describe('Bridge', () => {
eventData: [],
})
})
-
- renderHook(() => useProviderBridge({ provider }))
-
- expect(chromeMock.tabs.sendMessage).not.toHaveBeenCalledWith()
})
})
@@ -205,7 +225,9 @@ describe('Bridge', () => {
it('emits a "connect" event when the chainId is initially set', async () => {
const tab = mockActiveTab()
- renderHook(() => useProviderBridge({ provider, chainId: 1 }))
+ await renderHook(() => useProviderBridge({ provider, chainId: 1 }), {
+ wrapper: Wrapper,
+ })
await waitFor(() => {
expect(chromeMock.tabs.sendMessage).toHaveBeenCalledWith(tab.id, {
@@ -219,9 +241,9 @@ describe('Bridge', () => {
it('emits a "chainChanged" event when the chain changes on a later render', async () => {
const tab = mockActiveTab()
- const { rerender } = renderHook(
+ const { rerender } = await renderHook(
({ chainId }) => useProviderBridge({ provider, chainId }),
- { initialProps: { chainId: 1 } }
+ { initialProps: { chainId: 1 }, wrapper: Wrapper }
)
rerender({ chainId: 10 })
diff --git a/extension/src/panel/useProviderBridge.ts b/extension/src/bridge/useProviderBridge.ts
similarity index 94%
rename from extension/src/panel/useProviderBridge.ts
rename to extension/src/bridge/useProviderBridge.ts
index 2b8ed119..481fce5d 100644
--- a/extension/src/panel/useProviderBridge.ts
+++ b/extension/src/bridge/useProviderBridge.ts
@@ -6,13 +6,7 @@ import { invariant } from '@epic-web/invariant'
import { toQuantity } from 'ethers'
import { useCallback, useEffect, useRef } from 'react'
import { ChainId } from 'ser-kit'
-
-let windowId: number | undefined
-
-/** Set the window ID RPC events will only be relayed to tabs in this window */
-export const setWindowId = (id: number) => {
- windowId = id
-}
+import { useWindowId } from './BridgeContext'
const emitEvent = async (eventName: string, eventData: any) => {
const tab = await getActiveTab()
@@ -71,6 +65,8 @@ export const useProviderBridge = ({
}
const useHandleProviderRequests = (provider: Eip1193Provider) => {
+ const windowId = useWindowId()
+
const handleMessage = useCallback(
(
message: InjectedProviderMessage,
@@ -116,7 +112,7 @@ const useHandleProviderRequests = (provider: Eip1193Provider) => {
// without this the response won't be sent
return true
},
- [provider]
+ [provider, windowId]
)
useEffect(() => {
diff --git a/extension/src/panel/app-routes/index.tsx b/extension/src/panel/app-routes/index.tsx
index 4cc97b81..89def0a5 100644
--- a/extension/src/panel/app-routes/index.tsx
+++ b/extension/src/panel/app-routes/index.tsx
@@ -1,9 +1,9 @@
+import { useProviderBridge } from '@/bridge'
import { getChainId } from '@/chains'
import { useProvider } from '@/providers'
import { useMarkRouteAsUsed, useZodiacRoute } from '@/zodiac-routes'
import { Outlet, RouteObject } from 'react-router-dom'
import { parsePrefixedAddress } from 'ser-kit'
-import { useProviderBridge } from '../useProviderBridge'
import { useStorage } from '../utils'
import { EditRoute } from './edit-route'
import { ListRoutes } from './list-routes'
diff --git a/extension/src/panel/app.tsx b/extension/src/panel/app.tsx
index 403679b9..0a244266 100644
--- a/extension/src/panel/app.tsx
+++ b/extension/src/panel/app.tsx
@@ -1,5 +1,6 @@
// This is the entrypoint to the panel app.
// It has access to chrome.* APIs, but it can't interact with other extensions such as MetaMask.
+import { ProvideBridgeContext } from '@/bridge'
import { ZodiacToastContainer } from '@/components'
import { ProvideInjectedWallet, ProvideProvider } from '@/providers'
import { ProvideZodiacRoutes } from '@/zodiac-routes'
@@ -10,24 +11,30 @@ import { createHashRouter, RouterProvider } from 'react-router-dom'
import 'react-toastify/dist/ReactToastify.css'
import { appRoutes } from './app-routes'
import './global.css'
-import { initPort } from './port'
import { ProvideState } from './state'
-
-initPort()
+import { usePilotPort } from './usePilotPort'
const router = createHashRouter(appRoutes)
const Root = () => {
+ const { activeWindowId } = usePilotPort()
+
+ if (activeWindowId == null) {
+ return null
+ }
+
return (
-
-
-
-
+
+
+
+
+
+
diff --git a/extension/src/panel/port.ts b/extension/src/panel/port.ts
deleted file mode 100644
index 1079d7ab..00000000
--- a/extension/src/panel/port.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import { Message, PilotMessageType } from '@/messages'
-import { getActiveTab, isValidTab } from '@/utils'
-import { PILOT_PANEL_PORT } from '../const'
-import { setWindowId } from './useProviderBridge'
-
-// notify the background script that the panel has been opened
-export const initPort = async () => {
- const activeTab = await getActiveTab()
-
- const windowId = activeTab.windowId
- setWindowId(activeTab.windowId)
-
- const { promise, resolve } = Promise.withResolvers()
-
- if (!isValidTab(activeTab.url)) {
- const handleActivate = () => {
- chrome.tabs.onActivated.removeListener(handleActivate)
- chrome.tabs.onUpdated.removeListener(handleUpdated)
-
- resolve(initPort())
- }
-
- chrome.tabs.onActivated.addListener(handleActivate)
-
- const handleUpdated = (
- updatedTabId: number,
- changeInfo: chrome.tabs.TabChangeInfo
- ) => {
- if (updatedTabId !== activeTab.id) {
- return
- }
-
- if (changeInfo.url == null) {
- return
- }
-
- if (!isValidTab(changeInfo.url)) {
- return
- }
-
- chrome.tabs.onUpdated.removeListener(handleUpdated)
- chrome.tabs.onActivated.removeListener(handleActivate)
-
- resolve(initPort())
- }
-
- chrome.tabs.onUpdated.addListener(handleUpdated)
-
- return promise
- }
-
- const connectPort = () => {
- const port = chrome.runtime.connect({ name: PILOT_PANEL_PORT })
-
- port.postMessage({
- type: PilotMessageType.PILOT_PANEL_OPENED,
- windowId,
- tabId: activeTab.id,
- } satisfies Message)
- }
-
- if (activeTab.status === 'loading') {
- const handleTabLoad = (
- tabId: number,
- changeInfo: chrome.tabs.TabChangeInfo
- ) => {
- if (tabId !== activeTab.id) {
- return
- }
-
- if (changeInfo.status !== 'complete') {
- return
- }
-
- chrome.tabs.onUpdated.removeListener(handleTabLoad)
-
- connectPort()
- }
-
- chrome.tabs.onUpdated.addListener(handleTabLoad)
- } else {
- connectPort()
- }
-}
diff --git a/extension/src/panel/port.spec.ts b/extension/src/panel/usePilotPort.spec.ts
similarity index 59%
rename from extension/src/panel/port.spec.ts
rename to extension/src/panel/usePilotPort.spec.ts
index 02fbdefa..bc8c49ee 100644
--- a/extension/src/panel/port.spec.ts
+++ b/extension/src/panel/usePilotPort.spec.ts
@@ -6,24 +6,30 @@ import {
createMockTab,
mockActiveTab,
mockRuntimeConnect,
+ renderHook,
} from '@/test-utils'
import { sleep } from '@/utils'
-import { describe, expect, it } from 'vitest'
-import { initPort } from './port'
+import { cleanup, waitFor } from '@testing-library/react'
+import { afterEach, describe, expect, it } from 'vitest'
+import { usePilotPort } from './usePilotPort'
+
+describe('usePilotPort', () => {
+ afterEach(cleanup)
-describe('Init port', () => {
it('sends the PILOT_PANEL_OPEN event to the current tab', async () => {
const tab = mockActiveTab()
const port = createMockPort()
mockRuntimeConnect(port)
- await initPort()
+ await renderHook(() => usePilotPort())
- expect(port.postMessage).toHaveBeenCalledWith({
- type: PilotMessageType.PILOT_PANEL_OPENED,
- windowId: tab.windowId,
- tabId: tab.id,
+ await waitFor(() => {
+ expect(port.postMessage).toHaveBeenCalledWith({
+ type: PilotMessageType.PILOT_PANEL_OPENED,
+ windowId: tab.windowId,
+ tabId: tab.id,
+ })
})
})
@@ -33,16 +39,25 @@ describe('Init port', () => {
mockRuntimeConnect(port)
- await initPort()
+ await renderHook(() => usePilotPort())
expect(port.postMessage).not.toHaveBeenCalled()
- chromeMock.tabs.onUpdated.callListeners(tab.id, { status: 'complete' }, tab)
+ mockActiveTab({ ...tab, status: 'complete' })
- expect(port.postMessage).toHaveBeenCalledWith({
- type: PilotMessageType.PILOT_PANEL_OPENED,
- windowId: tab.windowId,
- tabId: tab.id,
+ await callListeners(
+ chromeMock.tabs.onUpdated,
+ tab.id,
+ { status: 'complete' },
+ tab
+ )
+
+ await waitFor(() => {
+ expect(port.postMessage).toHaveBeenCalledWith({
+ type: PilotMessageType.PILOT_PANEL_OPENED,
+ windowId: tab.windowId,
+ tabId: tab.id,
+ })
})
})
@@ -56,11 +71,7 @@ describe('Init port', () => {
mockRuntimeConnect(port)
- const { promise, resolve } = Promise.withResolvers()
-
- initPort().then(resolve)
-
- await sleep(1)
+ await renderHook(() => usePilotPort())
expect(port.postMessage).not.toHaveBeenCalled()
@@ -78,8 +89,6 @@ describe('Init port', () => {
windowId: regularTab.windowId,
tabId: regularTab.id,
})
-
- return promise
})
it('sends the message to the same tab when it moves to a proper URL', async () => {
@@ -91,11 +100,7 @@ describe('Init port', () => {
mockRuntimeConnect(port)
- const { promise, resolve } = Promise.withResolvers()
-
- initPort().then(resolve)
-
- await sleep(1)
+ await renderHook(() => usePilotPort())
expect(port.postMessage).not.toHaveBeenCalled()
@@ -117,7 +122,23 @@ describe('Init port', () => {
windowId: tab.windowId,
tabId: tab.id,
})
+ })
+
+ it('does not connect again, when another tab becomes active', async () => {
+ mockActiveTab({ id: 1, windowId: 1 })
+
+ const port = createMockPort()
+
+ mockRuntimeConnect(port)
+
+ await renderHook(() => usePilotPort())
+
+ expect(port.postMessage).toHaveBeenCalledTimes(1)
+
+ mockActiveTab({ id: 2, windowId: 1 })
+
+ await callListeners(chromeMock.tabs.onActivated, { tabId: 2, windowId: 2 })
- return promise
+ expect(port.postMessage).toHaveBeenCalledTimes(1)
})
})
diff --git a/extension/src/panel/usePilotPort.ts b/extension/src/panel/usePilotPort.ts
new file mode 100644
index 00000000..f17f484b
--- /dev/null
+++ b/extension/src/panel/usePilotPort.ts
@@ -0,0 +1,51 @@
+import { Message, PilotMessageType } from '@/messages'
+import { isValidTab, useActiveTab } from '@/utils'
+import { useEffect, useState } from 'react'
+import { PILOT_PANEL_PORT } from '../const'
+
+// notify the background script that the panel has been opened
+export const usePilotPort = () => {
+ const activeTab = useActiveTab()
+ const [portIsActive, setPortIsActive] = useState(false)
+ const [activeWindowId, setActiveWindowId] = useState(null)
+
+ useEffect(() => {
+ if (portIsActive) {
+ return
+ }
+
+ if (activeTab == null) {
+ return
+ }
+
+ if (!isValidTab(activeTab.url)) {
+ return
+ }
+
+ if (activeTab.status !== 'complete') {
+ return
+ }
+
+ connectPort({ windowId: activeTab.windowId, tabId: activeTab.id })
+
+ setActiveWindowId(activeTab.windowId)
+ setPortIsActive(true)
+ }, [activeTab, portIsActive])
+
+ return { activeWindowId }
+}
+
+type ConnectPortOptions = {
+ tabId?: number
+ windowId: number
+}
+
+const connectPort = ({ tabId, windowId }: ConnectPortOptions) => {
+ const port = chrome.runtime.connect({ name: PILOT_PANEL_PORT })
+
+ port.postMessage({
+ type: PilotMessageType.PILOT_PANEL_OPENED,
+ windowId,
+ tabId,
+ } satisfies Message)
+}
diff --git a/extension/src/utils/getActiveTab.ts b/extension/src/utils/getActiveTab.ts
index 858e174e..0f3bd1c8 100644
--- a/extension/src/utils/getActiveTab.ts
+++ b/extension/src/utils/getActiveTab.ts
@@ -1,4 +1,5 @@
import { invariant } from '@epic-web/invariant'
+import { useEffect, useState } from 'react'
export const getActiveTab = async () => {
const [activeTab] = await chrome.tabs.query({
@@ -10,3 +11,43 @@ export const getActiveTab = async () => {
return activeTab
}
+
+export const useActiveTab = () => {
+ const [activeTab, setActiveTab] = useState(null)
+
+ useEffect(() => {
+ getActiveTab().then(setActiveTab)
+ }, [])
+
+ useEffect(() => {
+ const handleActivate = () => getActiveTab().then(setActiveTab)
+
+ chrome.tabs.onActivated.addListener(handleActivate)
+
+ return () => {
+ chrome.tabs.onActivated.removeListener(handleActivate)
+ }
+ }, [])
+
+ useEffect(() => {
+ if (activeTab == null) {
+ return
+ }
+
+ const handleUpdate = (tabId: number) => {
+ if (tabId !== activeTab.id) {
+ return
+ }
+
+ getActiveTab().then(setActiveTab)
+ }
+
+ chrome.tabs.onUpdated.addListener(handleUpdate)
+
+ return () => {
+ chrome.tabs.onUpdated.removeListener(handleUpdate)
+ }
+ }, [activeTab])
+
+ return activeTab
+}
diff --git a/extension/src/utils/index.ts b/extension/src/utils/index.ts
index 1be23b78..c949bf80 100644
--- a/extension/src/utils/index.ts
+++ b/extension/src/utils/index.ts
@@ -1,5 +1,5 @@
export * from './addressValidation'
-export { getActiveTab } from './getActiveTab'
+export { getActiveTab, useActiveTab } from './getActiveTab'
export { isValidTab } from './isValidTab'
export { reloadActiveTab } from './reloadActiveTab'
export { reloadTab } from './reloadTab'
diff --git a/extension/test-utils/RenderWrapper.tsx b/extension/test-utils/RenderWrapper.tsx
new file mode 100644
index 00000000..29a36183
--- /dev/null
+++ b/extension/test-utils/RenderWrapper.tsx
@@ -0,0 +1,26 @@
+import { ProvideBridgeContext } from '@/bridge'
+import { ProvideInjectedWallet, ProvideProvider } from '@/providers'
+import { ProvideState } from '@/state'
+import { ProvideZodiacRoutes } from '@/zodiac-routes'
+import { PropsWithChildren } from 'react'
+
+type RenderWraperProps = PropsWithChildren<{
+ windowId?: number
+}>
+
+export const RenderWrapper = ({
+ children,
+ windowId = 1,
+}: RenderWraperProps) => (
+
+
+
+
+
+ {children}
+
+
+
+
+
+)
diff --git a/extension/test-utils/index.ts b/extension/test-utils/index.ts
index 2e91e98a..5948d9b2 100644
--- a/extension/test-utils/index.ts
+++ b/extension/test-utils/index.ts
@@ -3,6 +3,7 @@ export * from './creators'
export { mockRoute } from './mockRoute'
export { mockRoutes } from './mockRoutes'
export { expectRouteToBe, render } from './render'
+export { renderHook } from './renderHook'
export { startPilotSession } from './startPilotSession'
export { startSimulation } from './startSimulation'
export { stopSimulation } from './stopSimulation'
diff --git a/extension/test-utils/render.tsx b/extension/test-utils/render.tsx
index d5c16dcb..469000f2 100644
--- a/extension/test-utils/render.tsx
+++ b/extension/test-utils/render.tsx
@@ -1,6 +1,3 @@
-import { ProvideInjectedWallet, ProvideProvider } from '@/providers'
-import { ProvideState } from '@/state'
-import { ProvideZodiacRoutes } from '@/zodiac-routes'
import { render as baseRender, screen, waitFor } from '@testing-library/react'
import { ComponentType } from 'react'
import {
@@ -13,6 +10,7 @@ import {
import { expect } from 'vitest'
import { mockActiveTab, mockTabConnect } from './chrome'
import { createMockPort } from './creators'
+import { RenderWrapper } from './RenderWrapper'
type Route = {
path: string
@@ -41,31 +39,21 @@ export const render = async (
mockTabConnect(mockedPort)
const result = baseRender(
-
-
-
-
-
-
- }>
- {routes.map(({ path, Component }) => (
- } />
- ))}
+
+
+
+ }>
+ {routes.map(({ path, Component }) => (
+ } />
+ ))}
- {inspectRoutes.map((route) => (
- }
- />
- ))}
-
-
-
-
-
-
- ,
+ {inspectRoutes.map((route) => (
+ } />
+ ))}
+
+
+
+ ,
options
)
diff --git a/extension/test-utils/renderHook.ts b/extension/test-utils/renderHook.ts
new file mode 100644
index 00000000..b7f84c89
--- /dev/null
+++ b/extension/test-utils/renderHook.ts
@@ -0,0 +1,18 @@
+import { sleep } from '@/utils'
+import {
+ renderHook as renderHookBase,
+ RenderHookOptions,
+} from '@testing-library/react'
+
+type Fn = (props: Props) => Result
+
+export const renderHook = async (
+ fn: Fn,
+ options?: RenderHookOptions
+) => {
+ const result = renderHookBase(fn, options)
+
+ await sleep(1)
+
+ return result
+}
diff --git a/extension/tsconfig.json b/extension/tsconfig.json
index 1773b954..5c3f0cbb 100644
--- a/extension/tsconfig.json
+++ b/extension/tsconfig.json
@@ -27,7 +27,8 @@
"@/providers": ["./src/panel/providers/index.ts"],
"@/e2e-utils": ["./e2e/utils/index.ts"],
"@/test-utils": ["./test-utils/index.ts"],
- "@/messages": ["./src/messages.ts"]
+ "@/messages": ["./src/messages.ts"],
+ "@/bridge": ["./src/bridge/index.ts"]
}
},
"include": ["src", "e2e", "test-utils", "./vitest.setup.mts"]