From 72edc58f7777802bc219cd2d9c1235a37e166dde Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:56:30 +0200 Subject: [PATCH 1/6] feat: Add option to hide return modal --- ...moteCommunicationPostMessageStream.test.ts | 16 ++++ .../RemoteCommunicationPostMessageStream.ts | 6 ++ .../getPostMessageStream.test.ts | 34 ++++++++ .../PostMessageStream/getPostMessageStream.ts | 1 + packages/sdk/src/sdk.test.ts | 27 +++++++ packages/sdk/src/sdk.ts | 7 ++ .../performSDKInitialization.test.ts | 1 + .../performSDKInitialization.ts | 1 + .../write.test.ts | 79 +++++++++++++++++++ .../write.ts | 4 +- .../ConnectionManager/startConnection.test.ts | 51 ++++++++++++ .../ConnectionManager/startConnection.ts | 4 +- .../RemoteConnection/RemoteConnection.ts | 2 + 13 files changed, 231 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts index 96475c678..95934d6a9 100644 --- a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts +++ b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts @@ -36,6 +36,22 @@ describe('RemoteCommunicationPostMessageStream', () => { ); }); + it('should initialize with hideReturnToAppModal option', () => { + const instanceWithOption = new RemoteCommunicationPostMessageStream({ + name: ProviderConstants.PROVIDER, + remote: mockRemoteCommunication, + deeplinkProtocol: false, + platformManager: mockPlatformManager, + hideReturnToAppModal: true, + }); + + expect(instanceWithOption.state.hideReturnToAppModal).toBe(true); + }); + + it('should have hideReturnToAppModal as false by default', () => { + expect(instance.state.hideReturnToAppModal).toBe(false); + }); + it('should call _write properly', async () => { const chunk = 'someData'; const encoding = 'utf8'; diff --git a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts index 724ba4018..bd1a5d336 100644 --- a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts +++ b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts @@ -14,6 +14,7 @@ interface RemoteCommunicationPostMessageStreamState { _name: any; remote: RemoteCommunication | null; deeplinkProtocol: boolean; + hideReturnToAppModal?: boolean; platformManager: PlatformManager | null; } @@ -25,6 +26,7 @@ export class RemoteCommunicationPostMessageStream _name: null, remote: null, deeplinkProtocol: false, + hideReturnToAppModal: false, platformManager: null, }; @@ -32,10 +34,12 @@ export class RemoteCommunicationPostMessageStream name, remote, deeplinkProtocol, + hideReturnToAppModal, platformManager, }: { name: ProviderConstants; deeplinkProtocol: boolean; + hideReturnToAppModal?: boolean; remote: RemoteCommunication; platformManager: PlatformManager; }) { @@ -45,6 +49,8 @@ export class RemoteCommunicationPostMessageStream this.state._name = name; this.state.remote = remote; this.state.deeplinkProtocol = deeplinkProtocol; + this.state.hideReturnToAppModal = + hideReturnToAppModal ?? this.state.hideReturnToAppModal; this.state.platformManager = platformManager; this._onMessage = this._onMessage.bind(this); diff --git a/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts b/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts index f1a969c36..3b434ea86 100644 --- a/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts +++ b/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts @@ -59,4 +59,38 @@ describe('getPostMessageStream', () => { platformManager: fakePlatformManager, }); }); + + it('should pass hideReturnToAppModal from remoteConnection state', () => { + const fakeConnector = {}; + const fakePlatformManager = {}; + const hideReturnToAppModal = true; + mockRemoteConnection.mockImplementation( + () => + ({ + getConnector: jest.fn().mockReturnValue(fakeConnector), + getPlatformManager: jest.fn().mockReturnValue(fakePlatformManager), + state: { + deeplinkProtocol: false, + hideReturnToAppModal, + }, + } as any), + ); + + const result = getPostMessageStream({ + name: ProviderConstants.CONTENT_SCRIPT, + remoteConnection: new RemoteConnection({ + getConnector: jest.fn().mockReturnValue(fakeConnector), + } as any), + debug: false, + } as any); + + expect(result).toBeInstanceOf(RemoteCommunicationPostMessageStream); + expect(mockPostMessageStream).toHaveBeenCalledWith({ + name: ProviderConstants.CONTENT_SCRIPT, + remote: fakeConnector, + deeplinkProtocol: false, + platformManager: fakePlatformManager, + hideReturnToAppModal, + }); + }); }); diff --git a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts index 3570f92e6..e84ba97cf 100644 --- a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts +++ b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts @@ -25,6 +25,7 @@ export const getPostMessageStream = ({ name, remote: remoteConnection?.getConnector(), deeplinkProtocol: remoteConnection?.state.deeplinkProtocol, + hideReturnToAppModal: remoteConnection?.state.hideReturnToAppModal, platformManager: remoteConnection?.getPlatformManager(), }); }; diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 49cf5a121..b89ddd4ad 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -199,6 +199,33 @@ describe('MetaMaskSDK', () => { expect(sdk.options).toMatchObject(options); }); + it('should initialize with hideReturnToAppModal option', () => { + const options: MetaMaskSDKOptions = { + hideReturnToAppModal: true, + dappMetadata: { + name: 'Test DApp', + url: 'http://test-dapp.com', + }, + }; + + sdk = new MetaMaskSDK(options); + + expect(sdk.options.hideReturnToAppModal).toBe(true); + }); + + it('should have hideReturnToAppModal as undefined by default', () => { + const options: MetaMaskSDKOptions = { + dappMetadata: { + name: 'Test DApp', + url: 'http://test-dapp.com', + }, + }; + + sdk = new MetaMaskSDK(options); + + expect(sdk.options.hideReturnToAppModal).toBeUndefined(); + }); + it('should set max listeners', () => { expect(sdk.getMaxListeners()).toBe(50); }); diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index b42308f20..b881077d5 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -91,6 +91,12 @@ export interface MetaMaskSDKOptions { */ useDeeplink?: boolean; + /** + * If true, MetaMask Mobile will hide the modal that invites the user to return to the app after performing an action on MetaMask Mobile. + * This should be used when the app is loaded in an iframe inside the MetaMask Mobile browser. + */ + hideReturnToAppModal?: boolean; + /** * If true, the SDK will shim the window.web3 object with the provider returned by the SDK (useful for compatibility with older browser). */ @@ -220,6 +226,7 @@ export class MetaMaskSDK extends EventEmitter2 { enableAnalytics: true, shouldShimWeb3: true, useDeeplink: true, + hideReturnToAppModal: false, extensionOnly: true, headless: false, dappMetadata: { diff --git a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts index ea16845eb..2f4c4d14c 100644 --- a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts +++ b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts @@ -57,6 +57,7 @@ describe('performSDKInitialization', () => { injectProvider: true, shouldShimWeb3: true, useDeeplink: true, + hideReturnToAppModal: false, storage: { enabled: true, }, diff --git a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts index 2e1e2ea84..93942e8fc 100644 --- a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts +++ b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts @@ -57,6 +57,7 @@ export async function performSDKInitialization(instance: MetaMaskSDK) { options.shouldShimWeb3 = options.shouldShimWeb3 ?? true; options.extensionOnly = options.extensionOnly ?? true; options.useDeeplink = options.useDeeplink ?? true; + options.hideReturnToAppModal = options.hideReturnToAppModal ?? false; options.storage = options.storage ?? { enabled: true, }; diff --git a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts index c5a096726..3ed2af8bf 100644 --- a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts +++ b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts @@ -317,4 +317,83 @@ describe('write function', () => { expect(mockSendMessage).toHaveBeenCalled(); }); }); + + describe('hideReturnToAppModal URL parameter', () => { + beforeEach(() => { + mockIsReady.mockReturnValue(true); + mockIsConnected.mockReturnValue(true); + mockIsAuthorized.mockReturnValue(true); + mockIsMobileWeb.mockReturnValue(false); + mockIsSecure.mockReturnValue(true); + mockGetChannelId.mockReturnValue('some_channel_id'); + mockIsMetaMaskInstalled.mockReturnValue(true); + mockGetKeyInfo.mockReturnValue({ ecies: { public: 'test_public_key' } }); + mockHasDeeplinkProtocol.mockReturnValue(false); + mockExtractMethod.mockReturnValue({ + method: Object.keys(METHODS_TO_REDIRECT)[0], + data: { + data: { + jsonrpc: '2.0', + method: Object.keys(METHODS_TO_REDIRECT)[0], + params: [], + }, + }, + triggeredInstaller: false, + }); + }); + + it('should include hr=1 in URL when hideReturnToAppModal is true', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + true; + + await write( + mockRemoteCommunicationPostMessageStream, + { jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] }, + 'utf8', + callback, + ); + + expect(mockOpenDeeplink).toHaveBeenCalledWith( + expect.stringContaining('hr=1'), + expect.stringContaining('hr=1'), + '_self', + ); + }); + + it('should include hr=0 in URL when hideReturnToAppModal is false', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + false; + + await write( + mockRemoteCommunicationPostMessageStream, + { jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] }, + 'utf8', + callback, + ); + + expect(mockOpenDeeplink).toHaveBeenCalledWith( + expect.stringContaining('hr=0'), + expect.stringContaining('hr=0'), + '_self', + ); + }); + + it('should include hr=0 in URL when hideReturnToAppModal is undefined', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + undefined; + + await write( + mockRemoteCommunicationPostMessageStream, + { jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] }, + 'utf8', + callback, + ); + + expect(mockOpenDeeplink).toHaveBeenCalledWith( + expect.stringContaining('hr=0'), + expect.stringContaining('hr=0'), + '_self', + ); + }); + }); }); diff --git a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts index 0ca239ec8..38d72496a 100644 --- a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts +++ b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts @@ -100,7 +100,9 @@ export async function write( const pubKey = instance.state.remote?.getKeyInfo()?.ecies.public ?? ''; let urlParams = encodeURI( - `channelId=${channelId}&pubkey=${pubKey}&comm=socket&t=d&v=2`, + `channelId=${channelId}&pubkey=${pubKey}&comm=socket&t=d&v=2&hr=${ + instance.state.hideReturnToAppModal ? 1 : 0 + }`, ); if (activeDeeplinkProtocol) { diff --git a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts index 38b834478..2317fdc46 100644 --- a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts +++ b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts @@ -223,4 +223,55 @@ describe('startConnection', () => { ); }); }); + + describe('hideReturnToAppModal URL parameter', () => { + beforeEach(() => { + mockIsSecure.mockReturnValue(true); + }); + + it('should pass hr=1 when hideReturnToAppModal is true', async () => { + state.hideReturnToAppModal = true; + + await startConnection(state, options); + + // Vérifier que le QR code link contient hr=1 + expect(state.qrcodeLink).toContain('hr=1'); + }); + + it('should pass hr=0 when hideReturnToAppModal is false', async () => { + state.hideReturnToAppModal = false; + + await startConnection(state, options); + + // Vérifier que le QR code link contient hr=0 + expect(state.qrcodeLink).toContain('hr=0'); + }); + + it('should pass hr=0 when hideReturnToAppModal is undefined', async () => { + state.hideReturnToAppModal = undefined; + + await startConnection(state, options); + + // Vérifier que le QR code link contient hr=0 + expect(state.qrcodeLink).toContain('hr=0'); + }); + + it('should include hr parameter in connectWith scenarios', async () => { + mockIsSecure.mockReturnValue(false); + state.hideReturnToAppModal = true; + const connectWith = { + method: 'eth_sign', + params: ['0x123456', '0xabcdef'], + id: '123', + }; + + await startConnection(state, options, { connectWith }); + + expect(mockConnectWithModalInstaller).toHaveBeenCalledWith( + state, + options, + expect.stringContaining('hr=1'), + ); + }); + }); }); diff --git a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts index 7b2feab03..e7cb11ab8 100644 --- a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts +++ b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts @@ -148,7 +148,9 @@ export async function startConnection( let linkParams = `channelId=${channelId}&v=2&comm=${ state.communicationLayerPreference ?? '' - }&pubkey=${pubKey}${qrCodeOrigin}&originatorInfo=${base64OriginatorInfo}`; + }&pubkey=${pubKey}${qrCodeOrigin}&originatorInfo=${base64OriginatorInfo}&hr=${ + state.hideReturnToAppModal ? 1 : 0 + }`; if (connectWith) { const base64Rpc = base64Encode(JSON.stringify(connectWith)); diff --git a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts index 22d4b134c..165102983 100644 --- a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts +++ b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts @@ -93,6 +93,7 @@ export interface RemoteConnectionState { connector?: RemoteCommunication; qrcodeLink?: string; useDeeplink?: boolean; + hideReturnToAppModal?: boolean; developerMode: boolean; analytics?: Analytics; authorized: boolean; @@ -146,6 +147,7 @@ export class RemoteConnection { this.state.analytics = options.analytics; this.state.preferDesktop = options.preferDesktop ?? false; this.state.useDeeplink = options.sdk.options.useDeeplink; + this.state.hideReturnToAppModal = options.sdk.options.hideReturnToAppModal; this.state.communicationLayerPreference = options.communicationLayerPreference; this.state.platformManager = options.platformManager; From 12b9cae4461668da99f25e36deeea29a549e012b Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:42:52 +0200 Subject: [PATCH 2/6] fix: review --- .../RemoteCommunicationPostMessageStream.test.ts | 10 +++++----- .../RemoteCommunicationPostMessageStream.ts | 12 ++++++------ .../getPostMessageStream.test.ts | 8 ++++---- .../PostMessageStream/getPostMessageStream.ts | 2 +- packages/sdk/src/sdk.test.ts | 10 +++++----- packages/sdk/src/sdk.ts | 4 ++-- .../performSDKInitialization.test.ts | 2 +- .../performSDKInitialization.ts | 2 +- .../write.test.ts | 14 +++++++------- .../write.ts | 2 +- .../ConnectionManager/startConnection.test.ts | 16 ++++++++-------- .../ConnectionManager/startConnection.ts | 2 +- .../RemoteConnection/RemoteConnection.ts | 4 ++-- 13 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts index 95934d6a9..5148d9741 100644 --- a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts +++ b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts @@ -36,20 +36,20 @@ describe('RemoteCommunicationPostMessageStream', () => { ); }); - it('should initialize with hideReturnToAppModal option', () => { + it('should initialize with hideReturnToAppNotification option', () => { const instanceWithOption = new RemoteCommunicationPostMessageStream({ name: ProviderConstants.PROVIDER, remote: mockRemoteCommunication, deeplinkProtocol: false, platformManager: mockPlatformManager, - hideReturnToAppModal: true, + hideReturnToAppNotification: true, }); - expect(instanceWithOption.state.hideReturnToAppModal).toBe(true); + expect(instanceWithOption.state.hideReturnToAppNotification).toBe(true); }); - it('should have hideReturnToAppModal as false by default', () => { - expect(instance.state.hideReturnToAppModal).toBe(false); + it('should have hideReturnToAppNotification as false by default', () => { + expect(instance.state.hideReturnToAppNotification).toBe(false); }); it('should call _write properly', async () => { diff --git a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts index bd1a5d336..3b6a90f82 100644 --- a/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts +++ b/packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts @@ -14,7 +14,7 @@ interface RemoteCommunicationPostMessageStreamState { _name: any; remote: RemoteCommunication | null; deeplinkProtocol: boolean; - hideReturnToAppModal?: boolean; + hideReturnToAppNotification?: boolean; platformManager: PlatformManager | null; } @@ -26,7 +26,7 @@ export class RemoteCommunicationPostMessageStream _name: null, remote: null, deeplinkProtocol: false, - hideReturnToAppModal: false, + hideReturnToAppNotification: false, platformManager: null, }; @@ -34,12 +34,12 @@ export class RemoteCommunicationPostMessageStream name, remote, deeplinkProtocol, - hideReturnToAppModal, + hideReturnToAppNotification, platformManager, }: { name: ProviderConstants; deeplinkProtocol: boolean; - hideReturnToAppModal?: boolean; + hideReturnToAppNotification?: boolean; remote: RemoteCommunication; platformManager: PlatformManager; }) { @@ -49,8 +49,8 @@ export class RemoteCommunicationPostMessageStream this.state._name = name; this.state.remote = remote; this.state.deeplinkProtocol = deeplinkProtocol; - this.state.hideReturnToAppModal = - hideReturnToAppModal ?? this.state.hideReturnToAppModal; + this.state.hideReturnToAppNotification = + hideReturnToAppNotification ?? this.state.hideReturnToAppNotification; this.state.platformManager = platformManager; this._onMessage = this._onMessage.bind(this); diff --git a/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts b/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts index 3b434ea86..237aa5120 100644 --- a/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts +++ b/packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts @@ -60,10 +60,10 @@ describe('getPostMessageStream', () => { }); }); - it('should pass hideReturnToAppModal from remoteConnection state', () => { + it('should pass hideReturnToAppNotification from remoteConnection state', () => { const fakeConnector = {}; const fakePlatformManager = {}; - const hideReturnToAppModal = true; + const hideReturnToAppNotification = true; mockRemoteConnection.mockImplementation( () => ({ @@ -71,7 +71,7 @@ describe('getPostMessageStream', () => { getPlatformManager: jest.fn().mockReturnValue(fakePlatformManager), state: { deeplinkProtocol: false, - hideReturnToAppModal, + hideReturnToAppNotification, }, } as any), ); @@ -90,7 +90,7 @@ describe('getPostMessageStream', () => { remote: fakeConnector, deeplinkProtocol: false, platformManager: fakePlatformManager, - hideReturnToAppModal, + hideReturnToAppNotification, }); }); }); diff --git a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts index e84ba97cf..ca294248d 100644 --- a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts +++ b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts @@ -25,7 +25,7 @@ export const getPostMessageStream = ({ name, remote: remoteConnection?.getConnector(), deeplinkProtocol: remoteConnection?.state.deeplinkProtocol, - hideReturnToAppModal: remoteConnection?.state.hideReturnToAppModal, + hideReturnToAppNotification: remoteConnection?.state.hideReturnToAppNotification, platformManager: remoteConnection?.getPlatformManager(), }); }; diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index b89ddd4ad..2cc89a1d5 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -199,9 +199,9 @@ describe('MetaMaskSDK', () => { expect(sdk.options).toMatchObject(options); }); - it('should initialize with hideReturnToAppModal option', () => { + it('should initialize with hideReturnToAppNotification option', () => { const options: MetaMaskSDKOptions = { - hideReturnToAppModal: true, + hideReturnToAppNotification: true, dappMetadata: { name: 'Test DApp', url: 'http://test-dapp.com', @@ -210,10 +210,10 @@ describe('MetaMaskSDK', () => { sdk = new MetaMaskSDK(options); - expect(sdk.options.hideReturnToAppModal).toBe(true); + expect(sdk.options.hideReturnToAppNotification).toBe(true); }); - it('should have hideReturnToAppModal as undefined by default', () => { + it('should have hideReturnToAppNotification as undefined by default', () => { const options: MetaMaskSDKOptions = { dappMetadata: { name: 'Test DApp', @@ -223,7 +223,7 @@ describe('MetaMaskSDK', () => { sdk = new MetaMaskSDK(options); - expect(sdk.options.hideReturnToAppModal).toBeUndefined(); + expect(sdk.options.hideReturnToAppNotification).toBeUndefined(); }); it('should set max listeners', () => { diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index b881077d5..e5995c94c 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -95,7 +95,7 @@ export interface MetaMaskSDKOptions { * If true, MetaMask Mobile will hide the modal that invites the user to return to the app after performing an action on MetaMask Mobile. * This should be used when the app is loaded in an iframe inside the MetaMask Mobile browser. */ - hideReturnToAppModal?: boolean; + hideReturnToAppNotification?: boolean; /** * If true, the SDK will shim the window.web3 object with the provider returned by the SDK (useful for compatibility with older browser). @@ -226,7 +226,7 @@ export class MetaMaskSDK extends EventEmitter2 { enableAnalytics: true, shouldShimWeb3: true, useDeeplink: true, - hideReturnToAppModal: false, + hideReturnToAppNotification: false, extensionOnly: true, headless: false, dappMetadata: { diff --git a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts index 2f4c4d14c..eeef02d26 100644 --- a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts +++ b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts @@ -57,7 +57,7 @@ describe('performSDKInitialization', () => { injectProvider: true, shouldShimWeb3: true, useDeeplink: true, - hideReturnToAppModal: false, + hideReturnToAppNotification: false, storage: { enabled: true, }, diff --git a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts index 93942e8fc..645ab068a 100644 --- a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts +++ b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts @@ -57,7 +57,7 @@ export async function performSDKInitialization(instance: MetaMaskSDK) { options.shouldShimWeb3 = options.shouldShimWeb3 ?? true; options.extensionOnly = options.extensionOnly ?? true; options.useDeeplink = options.useDeeplink ?? true; - options.hideReturnToAppModal = options.hideReturnToAppModal ?? false; + options.hideReturnToAppNotification = options.hideReturnToAppNotification ?? false; options.storage = options.storage ?? { enabled: true, }; diff --git a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts index 3ed2af8bf..e3ea92b83 100644 --- a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts +++ b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts @@ -318,7 +318,7 @@ describe('write function', () => { }); }); - describe('hideReturnToAppModal URL parameter', () => { + describe('hideReturnToAppNotification URL parameter', () => { beforeEach(() => { mockIsReady.mockReturnValue(true); mockIsConnected.mockReturnValue(true); @@ -342,8 +342,8 @@ describe('write function', () => { }); }); - it('should include hr=1 in URL when hideReturnToAppModal is true', async () => { - mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + it('should include hr=1 in URL when hideReturnToAppNotification is true', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification = true; await write( @@ -360,8 +360,8 @@ describe('write function', () => { ); }); - it('should include hr=0 in URL when hideReturnToAppModal is false', async () => { - mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + it('should include hr=0 in URL when hideReturnToAppNotification is false', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification = false; await write( @@ -378,8 +378,8 @@ describe('write function', () => { ); }); - it('should include hr=0 in URL when hideReturnToAppModal is undefined', async () => { - mockRemoteCommunicationPostMessageStream.state.hideReturnToAppModal = + it('should include hr=0 in URL when hideReturnToAppNotification is undefined', async () => { + mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification = undefined; await write( diff --git a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts index 38d72496a..5b4d34ea9 100644 --- a/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts +++ b/packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts @@ -101,7 +101,7 @@ export async function write( const pubKey = instance.state.remote?.getKeyInfo()?.ecies.public ?? ''; let urlParams = encodeURI( `channelId=${channelId}&pubkey=${pubKey}&comm=socket&t=d&v=2&hr=${ - instance.state.hideReturnToAppModal ? 1 : 0 + instance.state.hideReturnToAppNotification ? 1 : 0 }`, ); diff --git a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts index 2317fdc46..44f8d812e 100644 --- a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts +++ b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.test.ts @@ -224,13 +224,13 @@ describe('startConnection', () => { }); }); - describe('hideReturnToAppModal URL parameter', () => { + describe('hideReturnToAppNotification URL parameter', () => { beforeEach(() => { mockIsSecure.mockReturnValue(true); }); - it('should pass hr=1 when hideReturnToAppModal is true', async () => { - state.hideReturnToAppModal = true; + it('should pass hr=1 when hideReturnToAppNotification is true', async () => { + state.hideReturnToAppNotification = true; await startConnection(state, options); @@ -238,8 +238,8 @@ describe('startConnection', () => { expect(state.qrcodeLink).toContain('hr=1'); }); - it('should pass hr=0 when hideReturnToAppModal is false', async () => { - state.hideReturnToAppModal = false; + it('should pass hr=0 when hideReturnToAppNotification is false', async () => { + state.hideReturnToAppNotification = false; await startConnection(state, options); @@ -247,8 +247,8 @@ describe('startConnection', () => { expect(state.qrcodeLink).toContain('hr=0'); }); - it('should pass hr=0 when hideReturnToAppModal is undefined', async () => { - state.hideReturnToAppModal = undefined; + it('should pass hr=0 when hideReturnToAppNotification is undefined', async () => { + state.hideReturnToAppNotification = undefined; await startConnection(state, options); @@ -258,7 +258,7 @@ describe('startConnection', () => { it('should include hr parameter in connectWith scenarios', async () => { mockIsSecure.mockReturnValue(false); - state.hideReturnToAppModal = true; + state.hideReturnToAppNotification = true; const connectWith = { method: 'eth_sign', params: ['0x123456', '0xabcdef'], diff --git a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts index e7cb11ab8..03d835033 100644 --- a/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts +++ b/packages/sdk/src/services/RemoteConnection/ConnectionManager/startConnection.ts @@ -149,7 +149,7 @@ export async function startConnection( let linkParams = `channelId=${channelId}&v=2&comm=${ state.communicationLayerPreference ?? '' }&pubkey=${pubKey}${qrCodeOrigin}&originatorInfo=${base64OriginatorInfo}&hr=${ - state.hideReturnToAppModal ? 1 : 0 + state.hideReturnToAppNotification ? 1 : 0 }`; if (connectWith) { diff --git a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts index 165102983..5ff4cc4e4 100644 --- a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts +++ b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts @@ -93,7 +93,7 @@ export interface RemoteConnectionState { connector?: RemoteCommunication; qrcodeLink?: string; useDeeplink?: boolean; - hideReturnToAppModal?: boolean; + hideReturnToAppNotification?: boolean; developerMode: boolean; analytics?: Analytics; authorized: boolean; @@ -147,7 +147,7 @@ export class RemoteConnection { this.state.analytics = options.analytics; this.state.preferDesktop = options.preferDesktop ?? false; this.state.useDeeplink = options.sdk.options.useDeeplink; - this.state.hideReturnToAppModal = options.sdk.options.hideReturnToAppModal; + this.state.hideReturnToAppNotification = options.sdk.options.hideReturnToAppNotification; this.state.communicationLayerPreference = options.communicationLayerPreference; this.state.platformManager = options.platformManager; From eb60e0516a154f20e8813536b3e3f05cf91e6524 Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:21:48 +0200 Subject: [PATCH 3/6] fix: test --- packages/sdk/src/sdk.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 2cc89a1d5..40897d7d1 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -223,7 +223,7 @@ describe('MetaMaskSDK', () => { sdk = new MetaMaskSDK(options); - expect(sdk.options.hideReturnToAppNotification).toBeUndefined(); + expect(sdk.options.hideReturnToAppNotification).toBeFalsy(); }); it('should set max listeners', () => { From 44d132d46fe5ad152d8119abcaac8c7a6fd71b0c Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:38:06 +0200 Subject: [PATCH 4/6] fix: lint --- packages/sdk/src/PostMessageStream/getPostMessageStream.ts | 3 ++- packages/sdk/src/sdk.test.ts | 2 +- .../InitializerManager/performSDKInitialization.ts | 4 +++- .../sdk/src/services/RemoteConnection/RemoteConnection.ts | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts index ca294248d..52b76ad67 100644 --- a/packages/sdk/src/PostMessageStream/getPostMessageStream.ts +++ b/packages/sdk/src/PostMessageStream/getPostMessageStream.ts @@ -25,7 +25,8 @@ export const getPostMessageStream = ({ name, remote: remoteConnection?.getConnector(), deeplinkProtocol: remoteConnection?.state.deeplinkProtocol, - hideReturnToAppNotification: remoteConnection?.state.hideReturnToAppNotification, + hideReturnToAppNotification: + remoteConnection?.state.hideReturnToAppNotification, platformManager: remoteConnection?.getPlatformManager(), }); }; diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 40897d7d1..5dc80aa26 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -223,7 +223,7 @@ describe('MetaMaskSDK', () => { sdk = new MetaMaskSDK(options); - expect(sdk.options.hideReturnToAppNotification).toBeFalsy(); + expect(sdk.options.hideReturnToAppNotification).toBe(false); }); it('should set max listeners', () => { diff --git a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts index 645ab068a..5358cd3fb 100644 --- a/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts +++ b/packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts @@ -57,7 +57,9 @@ export async function performSDKInitialization(instance: MetaMaskSDK) { options.shouldShimWeb3 = options.shouldShimWeb3 ?? true; options.extensionOnly = options.extensionOnly ?? true; options.useDeeplink = options.useDeeplink ?? true; - options.hideReturnToAppNotification = options.hideReturnToAppNotification ?? false; + options.hideReturnToAppNotification = + options.hideReturnToAppNotification ?? false; + options.storage = options.storage ?? { enabled: true, }; diff --git a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts index 5ff4cc4e4..149d7616e 100644 --- a/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts +++ b/packages/sdk/src/services/RemoteConnection/RemoteConnection.ts @@ -147,7 +147,9 @@ export class RemoteConnection { this.state.analytics = options.analytics; this.state.preferDesktop = options.preferDesktop ?? false; this.state.useDeeplink = options.sdk.options.useDeeplink; - this.state.hideReturnToAppNotification = options.sdk.options.hideReturnToAppNotification; + this.state.hideReturnToAppNotification = + options.sdk.options.hideReturnToAppNotification; + this.state.communicationLayerPreference = options.communicationLayerPreference; this.state.platformManager = options.platformManager; From 413d90ba4eb0837158bfa4aa064118ab7d0d2592 Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:35:36 +0200 Subject: [PATCH 5/6] fix: test --- packages/sdk/src/sdk.test.ts | 2 +- packages/sdk/src/sdk.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/sdk.test.ts b/packages/sdk/src/sdk.test.ts index 5dc80aa26..78b1cf5ef 100644 --- a/packages/sdk/src/sdk.test.ts +++ b/packages/sdk/src/sdk.test.ts @@ -213,7 +213,7 @@ describe('MetaMaskSDK', () => { expect(sdk.options.hideReturnToAppNotification).toBe(true); }); - it('should have hideReturnToAppNotification as undefined by default', () => { + it('should have hideReturnToAppNotification as false by default', () => { const options: MetaMaskSDKOptions = { dappMetadata: { name: 'Test DApp', diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index e5995c94c..84f2ab333 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -269,6 +269,10 @@ export class MetaMaskSDK extends EventEmitter2 { options._source = DEFAULT_SDK_SOURCE; } + if (this.options.hideReturnToAppNotification === undefined) { + this.options.hideReturnToAppNotification = false; + } + // Automatically initialize the SDK to keep the same behavior as before this.init() .then(() => { From 7caf78f539d6611696a3b44d61780b8a3585652e Mon Sep 17 00:00:00 2001 From: Edouard Bougon <15703023+EdouardBougon@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:23:04 +0200 Subject: [PATCH 6/6] fix: cursor comment https://github.com/MetaMask/metamask-sdk/pull/1350#discussion_r2391277584 --- packages/sdk/src/sdk.ts | 74 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/packages/sdk/src/sdk.ts b/packages/sdk/src/sdk.ts index 84f2ab333..1c26402ac 100644 --- a/packages/sdk/src/sdk.ts +++ b/packages/sdk/src/sdk.ts @@ -181,6 +181,32 @@ export interface MetaMaskSDKOptions { }; } +/** + * Default options for the MetaMask SDK. + */ +export const defaultMetaMaskSDKOptions: MetaMaskSDKOptions = { + storage: { + enabled: true, + }, + injectProvider: true, + forceInjectProvider: false, + enableAnalytics: true, + shouldShimWeb3: true, + useDeeplink: true, + hideReturnToAppNotification: false, + extensionOnly: true, + headless: false, + dappMetadata: { + name: '', + url: '', + iconUrl: '', + }, + _source: DEFAULT_SDK_SOURCE, + i18nOptions: { + enabled: false, + }, +}; + export class MetaMaskSDK extends EventEmitter2 { public options: MetaMaskSDKOptions; @@ -216,35 +242,15 @@ export class MetaMaskSDK extends EventEmitter2 { private readonly ANON_ID_STORAGE_KEY = 'mm-sdk-anon-id'; - constructor( - options: MetaMaskSDKOptions = { - storage: { - enabled: true, - }, - injectProvider: true, - forceInjectProvider: false, - enableAnalytics: true, - shouldShimWeb3: true, - useDeeplink: true, - hideReturnToAppNotification: false, - extensionOnly: true, - headless: false, - dappMetadata: { - name: '', - url: '', - iconUrl: '', - }, - _source: DEFAULT_SDK_SOURCE, - i18nOptions: { - enabled: false, - }, - }, - ) { + constructor(options?: MetaMaskSDKOptions) { super(); debug.disable(); // initially disabled - const developerMode = options.logging?.developerMode === true; - const debugEnabled = options.logging?.sdk || developerMode; + // Merge user options with default options + this.options = { ...defaultMetaMaskSDKOptions, ...options }; + + const developerMode = this.options.logging?.developerMode === true; + const debugEnabled = this.options.logging?.sdk || developerMode; if (debugEnabled) { debug.enable('MM_SDK'); @@ -252,11 +258,12 @@ export class MetaMaskSDK extends EventEmitter2 { logger(`[MetaMaskSDK: constructor()]: begin.`); this.setMaxListeners(50); - if (!options.dappMetadata?.url) { + // If dappMetadata.url is undefined or empty string, try to set it automatically for web environments + if (!this.options.dappMetadata?.url) { // Automatically set dappMetadata on web env. if (typeof window !== 'undefined' && typeof document !== 'undefined') { - options.dappMetadata = { - ...options.dappMetadata, + this.options.dappMetadata = { + ...this.options.dappMetadata, url: `${window.location.protocol}//${window.location.host}`, }; } else { @@ -264,15 +271,6 @@ export class MetaMaskSDK extends EventEmitter2 { } } - this.options = options; - if (!this.options._source) { - options._source = DEFAULT_SDK_SOURCE; - } - - if (this.options.hideReturnToAppNotification === undefined) { - this.options.hideReturnToAppNotification = false; - } - // Automatically initialize the SDK to keep the same behavior as before this.init() .then(() => {