Skip to content

Commit

Permalink
refactor: create useDeferredListener hook
Browse files Browse the repository at this point in the history
  • Loading branch information
iamacook committed Oct 20, 2023
1 parent e96e146 commit 231ac0c
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 86 deletions.
108 changes: 25 additions & 83 deletions src/components/walletconnect/HeaderWidget/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import type { Dispatch, SetStateAction } from 'react'
import type { CoreTypes, SessionTypes } from '@walletconnect/types'
import { useCallback, useContext, useEffect, useRef } from 'react'
import type { ReactElement } from 'react'

import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext'
Expand All @@ -12,7 +10,8 @@ import SessionManager from '../SessionManager'
import Popup from '../Popup'
import { ConnectionBanner } from '../ConnectionBanner'
import useSafeInfo from '@/hooks/useSafeInfo'
import { getEip155ChainId, getSupportedEip155ChainIds } from '@/services/walletconnect/utils'
import { isUnsupportedChain } from '@/services/walletconnect/utils'
import { useDeferredListener } from '@/hooks/useDefferedListener'

const usePrepopulatedUri = (): [string, () => void] => {
const [searchParamWcUri, setSearchParamWcUri] = useWalletConnectSearchParamUri()
Expand All @@ -30,87 +29,28 @@ const usePrepopulatedUri = (): [string, () => void] => {

const BANNER_TIMEOUT = 2_000

const useSuccessMetadata = (
onCloseSessionManager: () => void,
): [CoreTypes.Metadata | undefined, Dispatch<SetStateAction<CoreTypes.Metadata | undefined>>] => {
const useSuccessSession = (onCloseSessionManager: () => void) => {
const { walletConnect } = useContext(WalletConnectContext)
const [metadata, setMetadata] = useState<CoreTypes.Metadata>()

const onSuccess = useCallback(
({ peer }: SessionTypes.Struct) => {
onCloseSessionManager()

// Show success banner
setMetadata(peer.metadata)

return setTimeout(() => {
setMetadata(undefined)
}, BANNER_TIMEOUT)
},
[onCloseSessionManager],
)

useEffect(() => {
if (!walletConnect) return

let timeout: NodeJS.Timeout

walletConnect.onSessionAdd((session) => {
timeout = onSuccess(session)
})

return () => clearTimeout(timeout)
}, [onSuccess, walletConnect])

return [metadata, setMetadata]
return useDeferredListener({
listener: walletConnect?.onSessionAdd,
cb: onCloseSessionManager,
ms: BANNER_TIMEOUT,
})
}

const useDeleteMetadata = (): [
CoreTypes.Metadata | undefined,
Dispatch<SetStateAction<CoreTypes.Metadata | undefined>>,
] => {
const useDeleteSession = () => {
const { walletConnect } = useContext(WalletConnectContext)
const { safe } = useSafeInfo()
const [metadata, setMetadata] = useState<CoreTypes.Metadata>()

const onDelete = useCallback(
({ optionalNamespaces, requiredNamespaces, peer }: SessionTypes.Struct) => {
const supportedEip155ChainIds = getSupportedEip155ChainIds(requiredNamespaces, optionalNamespaces)

const eipChainId = getEip155ChainId(safe.chainId)
const isUnsupportedChain = !supportedEip155ChainIds.includes(eipChainId)

if (!isUnsupportedChain) {
return
}

// Show success banner
setMetadata(peer.metadata)

return setTimeout(() => {
setMetadata(undefined)
}, BANNER_TIMEOUT * 2)
},
[safe.chainId],
)

useEffect(() => {
if (!walletConnect) return

let timeout: NodeJS.Timeout | undefined

walletConnect.onSessionDelete((session) => {
timeout = onDelete(session)
})

return () => clearTimeout(timeout)
}, [onDelete, walletConnect])

return [metadata, setMetadata]
return useDeferredListener({
listener: walletConnect?.onSessionDelete,
ms: BANNER_TIMEOUT * 2,
})
}

const WalletConnectHeaderWidget = (): ReactElement => {
const { walletConnect, setError, open, setOpen } = useContext(WalletConnectContext)
const { safe } = useSafeInfo()
const iconRef = useRef<HTMLDivElement>(null)
const sessions = useWalletConnectSessions()
const [uri, clearUri] = usePrepopulatedUri()
Expand All @@ -123,15 +63,17 @@ const WalletConnectHeaderWidget = (): ReactElement => {
setError(null)
}, [setOpen, clearUri, setError])

const [successMetadata, setSuccessMetadata] = useSuccessMetadata(onCloseSessionManager)
const [deleteMetadata, setDeleteMetadata] = useDeleteMetadata()
const [successSession, setSuccessSession] = useSuccessSession(onCloseSessionManager)
const [deleteSession, setDeleteSession] = useDeleteSession()

const bannerMetadata = successMetadata || deleteMetadata
const session = successSession || deleteSession
const metadata = session?.peer?.metadata
const isUnsupported = deleteSession ? isUnsupportedChain(deleteSession, safe.chainId) : false

const onCloseConnectionBanner = useCallback(() => {
setSuccessMetadata(undefined)
setDeleteMetadata(undefined)
}, [setDeleteMetadata, setSuccessMetadata])
setSuccessSession(undefined)
setDeleteSession(undefined)
}, [setSuccessSession, setDeleteSession])

// Clear search param/clipboard state to prevent it being automatically entered again
useEffect(() => {
Expand All @@ -155,8 +97,8 @@ const WalletConnectHeaderWidget = (): ReactElement => {
<SessionManager sessions={sessions} uri={uri} />
</Popup>

<Popup anchorEl={iconRef.current} open={!!bannerMetadata} onClose={onCloseConnectionBanner}>
<ConnectionBanner metadata={bannerMetadata} isDelete={!!deleteMetadata} />
<Popup anchorEl={iconRef.current} open={!!metadata} onClose={onCloseConnectionBanner}>
<ConnectionBanner metadata={metadata} isDelete={isUnsupported} />
</Popup>
</>
)
Expand Down
36 changes: 36 additions & 0 deletions src/hooks/useDefferedListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState, useEffect } from 'react'
import type { Dispatch, SetStateAction } from 'react'

export const useDeferredListener = <T>({
listener,
cb,
ms,
}: {
listener?: (handler: (e: T) => void) => () => void
cb?: () => void
ms: number
}): [T | undefined, Dispatch<SetStateAction<T | undefined>>] => {
const [value, setValue] = useState<T>()

useEffect(() => {
if (!listener) {
return
}

const unsubscribe = listener((newValue) => {
setValue(newValue)
cb?.()
})

const timeout = setTimeout(() => {
setValue(undefined)
}, ms)

return () => {
unsubscribe()
clearTimeout(timeout)
}
}, [cb, listener, ms])

return [value, setValue]
}
4 changes: 2 additions & 2 deletions src/services/walletconnect/WalletConnectWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class WalletConnectWallet {
/**
* Subscribe to session add
*/
public onSessionAdd(handler: (e: SessionTypes.Struct) => void) {
public onSessionAdd = (handler: (e: SessionTypes.Struct) => void) => {
// @ts-expect-error - custom event payload
this.web3Wallet?.on(SESSION_ADD_EVENT, handler)

Expand All @@ -234,7 +234,7 @@ class WalletConnectWallet {
/**
* Subscribe to session delete
*/
public onSessionDelete(handler: (session: SessionTypes.Struct) => void) {
public onSessionDelete = (handler: (session: SessionTypes.Struct) => void) => {
// @ts-expect-error - custom event payload
this.web3Wallet?.on('session_delete', handler)

Expand Down
9 changes: 8 additions & 1 deletion src/services/walletconnect/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ChainInfo } from '@safe-global/safe-apps-sdk'
import type { ProposalTypes } from '@walletconnect/types'
import type { ProposalTypes, SessionTypes } from '@walletconnect/types'

import { EIP155 } from './constants'

Expand Down Expand Up @@ -38,3 +38,10 @@ export const getSupportedChainIds = (
})
.map((chain) => chain.chainId)
}

export const isUnsupportedChain = (session: SessionTypes.Struct, chainId: string) => {
const supportedEip155ChainIds = getSupportedEip155ChainIds(session.requiredNamespaces, session.optionalNamespaces)

const eipChainId = getEip155ChainId(chainId)
return !supportedEip155ChainIds.includes(eipChainId)
}

0 comments on commit 231ac0c

Please sign in to comment.