Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: make init port a hook #205

Merged
merged 3 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions extension/src/bridge/BridgeContext.tsx
Original file line number Diff line number Diff line change
@@ -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) => (
<BridgeContext.Provider value={{ windowId }}>
{children}
</BridgeContext.Provider>
)

export const useWindowId = () => {
const { windowId } = useContext(BridgeContext)

invariant(windowId != null, '"windowId" not set on BridgeContext')

return windowId
}
2 changes: 2 additions & 0 deletions extension/src/bridge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { ProvideBridgeContext, useWindowId } from './BridgeContext'
export { useProviderBridge } from './useProviderBridge'
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,6 +21,7 @@ import {
MockedFunction,
vi,
} from 'vitest'
import { ProvideBridgeContext } from './BridgeContext'
import { useProviderBridge } from './useProviderBridge'

describe('Bridge', () => {
Expand All @@ -28,17 +36,23 @@ describe('Bridge', () => {
}
}

const Wrapper = ({ children }: PropsWithChildren) => (
<ProvideBridgeContext windowId={1}>{children}</ProvideBridgeContext>
)

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' }

Expand All @@ -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()
)

Expand All @@ -63,7 +77,9 @@ describe('Bridge', () => {

provider.request.mockResolvedValue(response)

renderHook(() => useProviderBridge({ provider }))
await renderHook(() => useProviderBridge({ provider }), {
wrapper: Wrapper,
})

const request = { method: 'eth_chainId' }

Expand All @@ -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
)

Expand All @@ -94,7 +110,9 @@ describe('Bridge', () => {

provider.request.mockRejectedValue(error)

renderHook(() => useProviderBridge({ provider }))
await renderHook(() => useProviderBridge({ provider }), {
wrapper: Wrapper,
})

const request = { method: 'eth_chainId' }

Expand All @@ -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
)

Expand All @@ -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 })
Expand All @@ -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()
)

Expand All @@ -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, {
Expand All @@ -168,19 +189,22 @@ 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()
})

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 })
Expand All @@ -192,10 +216,6 @@ describe('Bridge', () => {
eventData: [],
})
})

renderHook(() => useProviderBridge({ provider }))

expect(chromeMock.tabs.sendMessage).not.toHaveBeenCalledWith()
})
})

Expand All @@ -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, {
Expand All @@ -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<void, { chainId: ChainId }>(
const { rerender } = await renderHook<void, { chainId: ChainId }>(
({ chainId }) => useProviderBridge({ provider, chainId }),
{ initialProps: { chainId: 1 } }
{ initialProps: { chainId: 1 }, wrapper: Wrapper }
)

rerender({ chainId: 10 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -71,6 +65,8 @@ export const useProviderBridge = ({
}

const useHandleProviderRequests = (provider: Eip1193Provider) => {
const windowId = useWindowId()

const handleMessage = useCallback(
(
message: InjectedProviderMessage,
Expand Down Expand Up @@ -116,7 +112,7 @@ const useHandleProviderRequests = (provider: Eip1193Provider) => {
// without this the response won't be sent
return true
},
[provider]
[provider, windowId]
)

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion extension/src/panel/app-routes/index.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
21 changes: 14 additions & 7 deletions extension/src/panel/app.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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 (
<StrictMode>
<ProvideState>
<ProvideZodiacRoutes>
<ProvideInjectedWallet>
<ProvideProvider>
<div className="flex flex-1 flex-col">
<RouterProvider router={router} />
<ZodiacToastContainer />
</div>
<ProvideBridgeContext windowId={activeWindowId}>
<div className="flex flex-1 flex-col">
<RouterProvider router={router} />
<ZodiacToastContainer />
</div>
</ProvideBridgeContext>
</ProvideProvider>
</ProvideInjectedWallet>
</ProvideZodiacRoutes>
Expand Down
84 changes: 0 additions & 84 deletions extension/src/panel/port.ts

This file was deleted.

Loading
Loading