From 1bf6435a60475a9200365a21af7d1468f5dbc2fe Mon Sep 17 00:00:00 2001 From: Christopher Ferreira Date: Wed, 8 Jan 2025 12:37:49 +0000 Subject: [PATCH 1/5] chore: improves the react connected hook when using extension --- .../EventsHandlers/useHandleTerminateEvent.ts | 25 +++++++++++++++++++ packages/sdk-react/src/MetaMaskProvider.tsx | 11 +++++++- .../ConnectionManager/terminate.ts | 4 +++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.ts diff --git a/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.ts b/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.ts new file mode 100644 index 000000000..cd308f54b --- /dev/null +++ b/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.ts @@ -0,0 +1,25 @@ +import { EthereumRpcError } from 'eth-rpc-errors'; +import { useCallback } from 'react'; +import { EventHandlerProps } from '../MetaMaskProvider'; +import { logger } from '../utils/logger'; + +export const useHandleTerminateEvent = ({ + debug, + setConnecting, + setConnected, + setError, +}: EventHandlerProps) => { + return useCallback( + (reason: unknown) => { + logger( + `[MetaMaskProvider: useHandleTerminateEvent()] on 'terminate' event.`, + reason, + ); + + setConnecting(false); + setConnected(false); + setError(reason as EthereumRpcError); + }, + [debug, setConnecting, setConnected, setError], + ); +}; diff --git a/packages/sdk-react/src/MetaMaskProvider.tsx b/packages/sdk-react/src/MetaMaskProvider.tsx index 1f28a9373..edecfadf5 100644 --- a/packages/sdk-react/src/MetaMaskProvider.tsx +++ b/packages/sdk-react/src/MetaMaskProvider.tsx @@ -23,6 +23,7 @@ import { useHandleInitializedEvent } from './EventsHandlers/useHandleInitialized import { useHandleOnConnectingEvent } from './EventsHandlers/useHandleOnConnectingEvent'; import { useHandleProviderEvent } from './EventsHandlers/useHandleProviderEvent'; import { useHandleSDKStatusEvent } from './EventsHandlers/useHandleSDKStatusEvent'; +import { useHandleTerminateEvent } from './EventsHandlers/useHandleTerminateEvent'; import { logger } from './utils/logger'; export interface EventHandlerProps { @@ -130,6 +131,8 @@ const MetaMaskProviderClient = ({ const onConnect = useHandleConnectEvent(eventHandlerProps); const onDisconnect = useHandleDisconnectEvent(eventHandlerProps); + + const onTerminate = useHandleTerminateEvent(eventHandlerProps); const onAccountsChanged = useHandleAccountsChangedEvent(eventHandlerProps); @@ -262,12 +265,18 @@ const MetaMaskProviderClient = ({ console.warn(`[MetaMaskProviderClient] activeProvider is undefined.`); return; } - setConnected(activeProvider.isConnected()); + + const isConnected = sdk.isExtensionActive() + ? !!account && account.length > 0 + : activeProvider.isConnected(); + + setConnected(isConnected); setAccount(activeProvider.getSelectedAddress() || undefined); setProvider(activeProvider); setChainId(activeProvider.getChainId() || undefined); activeProvider.on('_initialized', onInitialized); + activeProvider.on('terminate', onTerminate); activeProvider.on('connecting', onConnecting); activeProvider.on('connect', onConnect); activeProvider.on('disconnect', onDisconnect); diff --git a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts index 2f6d44cf0..72ae77821 100644 --- a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts +++ b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts @@ -48,6 +48,10 @@ export async function terminate(instance: MetaMaskSDK) { } if (instance.options.extensionOnly) { + instance.emit( + MetaMaskSDKEvent.ProviderUpdate, + PROVIDER_UPDATE_TYPE.TERMINATE, + ); logger( `[MetaMaskSDK: terminate()] extensionOnly --- prevent switching providers`, ); From 275ac372edc71de36651a0cb572ca934e974ef5e Mon Sep 17 00:00:00 2001 From: Christopher Ferreira Date: Wed, 8 Jan 2025 15:42:25 +0000 Subject: [PATCH 2/5] chore: fixes linting and unit tests --- packages/sdk-react/src/MetaMaskProvider.spec.tsx | 6 ++++-- packages/sdk-react/src/MetaMaskProvider.tsx | 1 + .../src/services/MetaMaskSDK/ConnectionManager/terminate.ts | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/sdk-react/src/MetaMaskProvider.spec.tsx b/packages/sdk-react/src/MetaMaskProvider.spec.tsx index 541317577..b142a7b1f 100644 --- a/packages/sdk-react/src/MetaMaskProvider.spec.tsx +++ b/packages/sdk-react/src/MetaMaskProvider.spec.tsx @@ -117,7 +117,7 @@ describe('MetaMaskProvider Component', () => { }); expect(mockSdkOn).toHaveBeenCalledTimes(2); - expect(mockProviderOn).toHaveBeenCalledTimes(6); + expect(mockProviderOn).toHaveBeenCalledTimes(7); expect(mockSdkOn.mock.calls).toEqual([ ['service_status', expect.any(Function)], @@ -126,6 +126,7 @@ describe('MetaMaskProvider Component', () => { expect(mockProviderOn.mock.calls).toEqual([ ['_initialized', expect.any(Function)], + ['terminate', expect.any(Function)], ['connecting', expect.any(Function)], ['connect', expect.any(Function)], ['disconnect', expect.any(Function)], @@ -144,7 +145,7 @@ describe('MetaMaskProvider Component', () => { cleanup(); expect(mockSdkRemoveListener).toHaveBeenCalledTimes(2); - expect(mockProviderRemoveListener).toHaveBeenCalledTimes(6); + expect(mockProviderRemoveListener).toHaveBeenCalledTimes(7); expect(mockSdkRemoveListener.mock.calls).toEqual([ ['service_status', expect.any(Function)], @@ -156,6 +157,7 @@ describe('MetaMaskProvider Component', () => { ['connecting', expect.any(Function)], ['connect', expect.any(Function)], ['disconnect', expect.any(Function)], + ['terminate', expect.any(Function)], ['accountsChanged', expect.any(Function)], ['chainChanged', expect.any(Function)], ]); diff --git a/packages/sdk-react/src/MetaMaskProvider.tsx b/packages/sdk-react/src/MetaMaskProvider.tsx index edecfadf5..f645771c7 100644 --- a/packages/sdk-react/src/MetaMaskProvider.tsx +++ b/packages/sdk-react/src/MetaMaskProvider.tsx @@ -305,6 +305,7 @@ const MetaMaskProviderClient = ({ activeProvider.removeListener('connecting', onConnecting); activeProvider.removeListener('connect', onConnect); activeProvider.removeListener('disconnect', onDisconnect); + activeProvider.removeListener('terminate', onTerminate); activeProvider.removeListener('accountsChanged', onAccountsChanged); activeProvider.removeListener('chainChanged', onChainChanged); sdk.removeListener(EventType.SERVICE_STATUS, onSDKStatusEvent); diff --git a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts index 72ae77821..256984ed0 100644 --- a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts +++ b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.ts @@ -52,6 +52,7 @@ export async function terminate(instance: MetaMaskSDK) { MetaMaskSDKEvent.ProviderUpdate, PROVIDER_UPDATE_TYPE.TERMINATE, ); + logger( `[MetaMaskSDK: terminate()] extensionOnly --- prevent switching providers`, ); From 93716efd991bc881dcade546e94118d527a051df Mon Sep 17 00:00:00 2001 From: Christopher Ferreira Date: Wed, 8 Jan 2025 15:58:48 +0000 Subject: [PATCH 3/5] chore: fixes unit test to meet the new event emit of terminate --- .../services/MetaMaskSDK/ConnectionManager/terminate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.test.ts b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.test.ts index 8e16b7c07..a680b4eca 100644 --- a/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.test.ts +++ b/packages/sdk/src/services/MetaMaskSDK/ConnectionManager/terminate.test.ts @@ -90,7 +90,7 @@ describe('terminate', () => { it('should not switch providers if extensionOnly option is true', async () => { instance.options.extensionOnly = true; await terminate(instance); - expect(mockEmit).not.toHaveBeenCalled(); + expect(mockEmit).toHaveBeenCalledTimes(1); }); }); From ae94b02521bc68cfa3bb268e72d4535b943390a5 Mon Sep 17 00:00:00 2001 From: Christopher Ferreira Date: Wed, 8 Jan 2025 16:39:46 +0000 Subject: [PATCH 4/5] chore: fixes fetching selectedAddress to ensure refreshes dont break the flow --- packages/sdk-react/src/MetaMaskProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk-react/src/MetaMaskProvider.tsx b/packages/sdk-react/src/MetaMaskProvider.tsx index f645771c7..045734583 100644 --- a/packages/sdk-react/src/MetaMaskProvider.tsx +++ b/packages/sdk-react/src/MetaMaskProvider.tsx @@ -267,9 +267,9 @@ const MetaMaskProviderClient = ({ } const isConnected = sdk.isExtensionActive() - ? !!account && account.length > 0 + ? !!activeProvider.getSelectedAddress() : activeProvider.isConnected(); - + setConnected(isConnected); setAccount(activeProvider.getSelectedAddress() || undefined); setProvider(activeProvider); From 7557bd0d6ddba7a75ff3a825ed455aa6df566506 Mon Sep 17 00:00:00 2001 From: Christopher Ferreira Date: Fri, 10 Jan 2025 10:33:58 +0000 Subject: [PATCH 5/5] chore: adds unit test for useHandleTerminateEvent --- .../useHandleTerminateEvent.test.tsx | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.test.tsx diff --git a/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.test.tsx b/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.test.tsx new file mode 100644 index 000000000..b5a84dfb5 --- /dev/null +++ b/packages/sdk-react/src/EventsHandlers/useHandleTerminateEvent.test.tsx @@ -0,0 +1,41 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useHandleTerminateEvent } from './useHandleTerminateEvent'; +import { EventHandlerProps } from '../MetaMaskProvider'; +import * as loggerModule from '../utils/logger'; + +describe('useHandleTerminateEvent', () => { + const spyLogger = jest.spyOn(loggerModule, 'logger'); + + const eventHandlerProps = { + setConnecting: jest.fn(), + setConnected: jest.fn(), + setError: jest.fn(), + debug: true, + } as unknown as EventHandlerProps; + + beforeEach(() => { + jest.clearAllMocks(); + + eventHandlerProps.setConnecting = jest.fn(); + eventHandlerProps.setConnected = jest.fn(); + eventHandlerProps.setError = jest.fn(); + }); + + it('should handle the terminate event correctly', () => { + const mockReason = { message: 'Terminated due to xyz', code: -32000 }; + + const { result } = renderHook(() => + useHandleTerminateEvent(eventHandlerProps), + ); + result.current(mockReason); + + expect(spyLogger).toHaveBeenCalledWith( + "[MetaMaskProvider: useHandleTerminateEvent()] on 'terminate' event.", + mockReason, + ); + + expect(eventHandlerProps.setConnecting).toHaveBeenCalledWith(false); + expect(eventHandlerProps.setConnected).toHaveBeenCalledWith(false); + expect(eventHandlerProps.setError).toHaveBeenCalledWith(mockReason); + }); +});