From a8b6d0c83edc4ebd2f832e40acb24cb05aa89c03 Mon Sep 17 00:00:00 2001 From: katspaugh Date: Sat, 23 Sep 2023 09:38:31 +0200 Subject: [PATCH] Simplify and fix useSessions --- .../ProposalForm/styles.module.css | 2 +- .../walletconnect/SessionList/index.tsx | 2 +- .../walletconnect/SessionManager/index.tsx | 4 +- .../SessionManager/useSessions.ts | 48 ------------------- .../walletconnect/WalletConnectContext.tsx | 4 +- .../walletconnect/WalletConnectWallet.ts | 33 +++++++++++-- .../walletconnect/useWalletConnectSessions.ts | 28 +++++++++++ 7 files changed, 63 insertions(+), 58 deletions(-) delete mode 100644 src/components/walletconnect/SessionManager/useSessions.ts create mode 100644 src/services/walletconnect/useWalletConnectSessions.ts diff --git a/src/components/walletconnect/ProposalForm/styles.module.css b/src/components/walletconnect/ProposalForm/styles.module.css index 817a47f673..57e85843f8 100644 --- a/src/components/walletconnect/ProposalForm/styles.module.css +++ b/src/components/walletconnect/ProposalForm/styles.module.css @@ -27,4 +27,4 @@ .alert { width: 100%; text-align: left; -} \ No newline at end of file +} diff --git a/src/components/walletconnect/SessionList/index.tsx b/src/components/walletconnect/SessionList/index.tsx index 87f73dc88b..0adcfda1c9 100644 --- a/src/components/walletconnect/SessionList/index.tsx +++ b/src/components/walletconnect/SessionList/index.tsx @@ -3,7 +3,7 @@ import { Button, Typography } from '@mui/material' import type { SessionTypes } from '@walletconnect/types' type SesstionListProps = { - sessions: Record + sessions: SessionTypes.Struct[] onDisconnect: (session: SessionTypes.Struct) => void } diff --git a/src/components/walletconnect/SessionManager/index.tsx b/src/components/walletconnect/SessionManager/index.tsx index 16c4c1fe84..0430265c83 100644 --- a/src/components/walletconnect/SessionManager/index.tsx +++ b/src/components/walletconnect/SessionManager/index.tsx @@ -4,18 +4,18 @@ import type { SessionTypes } from '@walletconnect/types' import useSafeInfo from '@/hooks/useSafeInfo' import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext' +import useWalletConnectSessions from '@/services/walletconnect/useWalletConnectSessions' import { asError } from '@/services/exceptions/utils' import ProposalForm from '../ProposalForm' import WcInput from '../WcInput' import ErrorMessage from '@/components/tx/ErrorMessage' import SessionList from '../SessionList' -import { useSessions } from './useSessions' const SessionManager = () => { const { safe, safeAddress } = useSafeInfo() const { chainId } = safe const { walletConnect, error: walletConnectError } = useContext(WalletConnectContext) - const sessions = useSessions() + const sessions = useWalletConnectSessions() const [proposal, setProposal] = useState() const [error, setError] = useState() diff --git a/src/components/walletconnect/SessionManager/useSessions.ts b/src/components/walletconnect/SessionManager/useSessions.ts deleted file mode 100644 index 7cb104cdd2..0000000000 --- a/src/components/walletconnect/SessionManager/useSessions.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { useSyncExternalStore, useContext, useEffect, useCallback } from 'react' -import { memoize } from 'lodash' -import type { SessionTypes } from '@walletconnect/types' - -import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext' -import type WalletConnectWallet from '@/services/walletconnect/WalletConnectWallet' - -const listeners: Set<() => void> = new Set() - -const subscribe = (sessionListener: () => void) => { - listeners.add(sessionListener) - return () => { - listeners.delete(sessionListener) - } -} - -const observe = () => { - listeners.forEach((listener) => { - listener() - }) -} - -const _getSessions = memoize( - (walletConnect: WalletConnectWallet) => walletConnect.getActiveSessions(), - (walletConnect) => JSON.stringify(walletConnect.getActiveSessions()), -) - -export function useSessions(): Record { - const { walletConnect } = useContext(WalletConnectContext) - - const getSessions = useCallback(() => { - return _getSessions(walletConnect) - }, [walletConnect]) - - useEffect(() => { - return walletConnect.onSessionPropose(observe) - }, [walletConnect]) - - useEffect(() => { - return walletConnect.onSessionRequest(observe) - }, [walletConnect]) - - useEffect(() => { - return walletConnect.onSessionDelete(observe) - }, [walletConnect]) - - return useSyncExternalStore(subscribe, getSessions, getSessions) -} diff --git a/src/services/walletconnect/WalletConnectContext.tsx b/src/services/walletconnect/WalletConnectContext.tsx index 6c01b13a8f..82ef5b60bd 100644 --- a/src/services/walletconnect/WalletConnectContext.tsx +++ b/src/services/walletconnect/WalletConnectContext.tsx @@ -31,14 +31,14 @@ export const WalletConnectProvider = ({ children }: { children: ReactNode }) => // Update chainId useEffect(() => { if (chainId) { - walletConnect.chainChanged(chainId) + walletConnect.chainChanged(chainId).catch(setError) } }, [chainId]) // Update accounts useEffect(() => { if (safeAddress && chainId) { - walletConnect.accountsChanged(chainId, safeAddress) + walletConnect.accountsChanged(chainId, safeAddress).catch(setError) } }, [chainId, safeAddress]) diff --git a/src/services/walletconnect/WalletConnectWallet.ts b/src/services/walletconnect/WalletConnectWallet.ts index 3bf0c76db7..7335fcb0ed 100644 --- a/src/services/walletconnect/WalletConnectWallet.ts +++ b/src/services/walletconnect/WalletConnectWallet.ts @@ -12,10 +12,17 @@ import { invariant } from '@/utils/helpers' import { getEip155ChainId } from './utils' import type { Eip155ChainId } from './utils' +const SESSION_ADD_EVENT = 'session_add' as 'session_delete' // Workaround: WalletConnect doesn't emit session_add event + function assertWeb3Wallet(web3Wallet: T): asserts web3Wallet { return invariant(web3Wallet, 'WalletConnect not initialized') } +/** + * An abstraction over the WalletConnect SDK to simplify event subscriptions + * and add workarounds for dapps requesting wrong required chains. + * Should be kept stateless exept for the web3Wallet instance. + */ class WalletConnectWallet { private web3Wallet: Web3WalletType | null = null @@ -52,7 +59,7 @@ class WalletConnectWallet { const eipChainId = getEip155ChainId(chainId) await Promise.all( - Object.keys(sessions).map((topic) => { + sessions.map(({ topic }) => { return this.web3Wallet?.emitSessionEvent({ topic, event: { @@ -70,7 +77,7 @@ class WalletConnectWallet { const eipChainId = getEip155ChainId(chainId) await Promise.all( - Object.keys(sessions).map((topic) => { + sessions.map(({ topic }) => { return this.web3Wallet?.emitSessionEvent({ topic, event: { @@ -132,6 +139,9 @@ class WalletConnectWallet { // Ignore } + // Workaround: WalletConnect doesn't have a session_add event + this.web3Wallet?.events.emit(SESSION_ADD_EVENT, session) + return session } } @@ -158,6 +168,17 @@ class WalletConnectWallet { } } + /** + * Subscribe to session add + */ + public onSessionAdd(handler: () => void) { + this.web3Wallet?.on(SESSION_ADD_EVENT, handler) + + return () => { + this.web3Wallet?.off(SESSION_ADD_EVENT, handler) + } + } + /** * Subscribe to session delete */ @@ -179,13 +200,17 @@ class WalletConnectWallet { topic: session.topic, reason: getSdkError('USER_DISCONNECTED'), }) + + // Workaround: WalletConnect doesn't emit session_approve event + this.web3Wallet?.events.emit('session_delete', session) } /** * Get active sessions */ - public getActiveSessions() { - return this.web3Wallet?.getActiveSessions() || {} + public getActiveSessions(): SessionTypes.Struct[] { + const sessionsMap = this.web3Wallet?.getActiveSessions() || {} + return Object.values(sessionsMap) } /** diff --git a/src/services/walletconnect/useWalletConnectSessions.ts b/src/services/walletconnect/useWalletConnectSessions.ts new file mode 100644 index 0000000000..e4866233cb --- /dev/null +++ b/src/services/walletconnect/useWalletConnectSessions.ts @@ -0,0 +1,28 @@ +import { useContext, useEffect, useCallback, useState } from 'react' +import type { SessionTypes } from '@walletconnect/types' +import { WalletConnectContext } from '@/services/walletconnect/WalletConnectContext' + +function useWalletConnectSessions(): SessionTypes.Struct[] { + const { walletConnect } = useContext(WalletConnectContext) + const [sessions, setSessions] = useState([]) + + const updateSessions = useCallback(() => { + setSessions(walletConnect.getActiveSessions()) + }, [walletConnect]) + + useEffect(() => { + walletConnect.init().then(updateSessions) + }, [walletConnect, updateSessions]) + + useEffect(() => { + return walletConnect.onSessionAdd(updateSessions) + }, [walletConnect]) + + useEffect(() => { + return walletConnect.onSessionDelete(updateSessions) + }, [walletConnect]) + + return sessions +} + +export default useWalletConnectSessions