Skip to content

Commit 1cff69d

Browse files
feat: Add option to hide return modal (#1350)
* feat: Add option to hide return modal * fix: review * fix: test * fix: lint * fix: test * fix: cursor comment #1350 (comment)
1 parent 13cd092 commit 1cff69d

File tree

13 files changed

+271
-35
lines changed

13 files changed

+271
-35
lines changed

packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ describe('RemoteCommunicationPostMessageStream', () => {
3636
);
3737
});
3838

39+
it('should initialize with hideReturnToAppNotification option', () => {
40+
const instanceWithOption = new RemoteCommunicationPostMessageStream({
41+
name: ProviderConstants.PROVIDER,
42+
remote: mockRemoteCommunication,
43+
deeplinkProtocol: false,
44+
platformManager: mockPlatformManager,
45+
hideReturnToAppNotification: true,
46+
});
47+
48+
expect(instanceWithOption.state.hideReturnToAppNotification).toBe(true);
49+
});
50+
51+
it('should have hideReturnToAppNotification as false by default', () => {
52+
expect(instance.state.hideReturnToAppNotification).toBe(false);
53+
});
54+
3955
it('should call _write properly', async () => {
4056
const chunk = 'someData';
4157
const encoding = 'utf8';

packages/sdk/src/PostMessageStream/RemoteCommunicationPostMessageStream.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface RemoteCommunicationPostMessageStreamState {
1414
_name: any;
1515
remote: RemoteCommunication | null;
1616
deeplinkProtocol: boolean;
17+
hideReturnToAppNotification?: boolean;
1718
platformManager: PlatformManager | null;
1819
}
1920

@@ -25,17 +26,20 @@ export class RemoteCommunicationPostMessageStream
2526
_name: null,
2627
remote: null,
2728
deeplinkProtocol: false,
29+
hideReturnToAppNotification: false,
2830
platformManager: null,
2931
};
3032

3133
constructor({
3234
name,
3335
remote,
3436
deeplinkProtocol,
37+
hideReturnToAppNotification,
3538
platformManager,
3639
}: {
3740
name: ProviderConstants;
3841
deeplinkProtocol: boolean;
42+
hideReturnToAppNotification?: boolean;
3943
remote: RemoteCommunication;
4044
platformManager: PlatformManager;
4145
}) {
@@ -45,6 +49,8 @@ export class RemoteCommunicationPostMessageStream
4549
this.state._name = name;
4650
this.state.remote = remote;
4751
this.state.deeplinkProtocol = deeplinkProtocol;
52+
this.state.hideReturnToAppNotification =
53+
hideReturnToAppNotification ?? this.state.hideReturnToAppNotification;
4854
this.state.platformManager = platformManager;
4955

5056
this._onMessage = this._onMessage.bind(this);

packages/sdk/src/PostMessageStream/getPostMessageStream.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,38 @@ describe('getPostMessageStream', () => {
5959
platformManager: fakePlatformManager,
6060
});
6161
});
62+
63+
it('should pass hideReturnToAppNotification from remoteConnection state', () => {
64+
const fakeConnector = {};
65+
const fakePlatformManager = {};
66+
const hideReturnToAppNotification = true;
67+
mockRemoteConnection.mockImplementation(
68+
() =>
69+
({
70+
getConnector: jest.fn().mockReturnValue(fakeConnector),
71+
getPlatformManager: jest.fn().mockReturnValue(fakePlatformManager),
72+
state: {
73+
deeplinkProtocol: false,
74+
hideReturnToAppNotification,
75+
},
76+
} as any),
77+
);
78+
79+
const result = getPostMessageStream({
80+
name: ProviderConstants.CONTENT_SCRIPT,
81+
remoteConnection: new RemoteConnection({
82+
getConnector: jest.fn().mockReturnValue(fakeConnector),
83+
} as any),
84+
debug: false,
85+
} as any);
86+
87+
expect(result).toBeInstanceOf(RemoteCommunicationPostMessageStream);
88+
expect(mockPostMessageStream).toHaveBeenCalledWith({
89+
name: ProviderConstants.CONTENT_SCRIPT,
90+
remote: fakeConnector,
91+
deeplinkProtocol: false,
92+
platformManager: fakePlatformManager,
93+
hideReturnToAppNotification,
94+
});
95+
});
6296
});

packages/sdk/src/PostMessageStream/getPostMessageStream.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export const getPostMessageStream = ({
2525
name,
2626
remote: remoteConnection?.getConnector(),
2727
deeplinkProtocol: remoteConnection?.state.deeplinkProtocol,
28+
hideReturnToAppNotification:
29+
remoteConnection?.state.hideReturnToAppNotification,
2830
platformManager: remoteConnection?.getPlatformManager(),
2931
});
3032
};

packages/sdk/src/sdk.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,33 @@ describe('MetaMaskSDK', () => {
199199
expect(sdk.options).toMatchObject(options);
200200
});
201201

202+
it('should initialize with hideReturnToAppNotification option', () => {
203+
const options: MetaMaskSDKOptions = {
204+
hideReturnToAppNotification: true,
205+
dappMetadata: {
206+
name: 'Test DApp',
207+
url: 'http://test-dapp.com',
208+
},
209+
};
210+
211+
sdk = new MetaMaskSDK(options);
212+
213+
expect(sdk.options.hideReturnToAppNotification).toBe(true);
214+
});
215+
216+
it('should have hideReturnToAppNotification as false by default', () => {
217+
const options: MetaMaskSDKOptions = {
218+
dappMetadata: {
219+
name: 'Test DApp',
220+
url: 'http://test-dapp.com',
221+
},
222+
};
223+
224+
sdk = new MetaMaskSDK(options);
225+
226+
expect(sdk.options.hideReturnToAppNotification).toBe(false);
227+
});
228+
202229
it('should set max listeners', () => {
203230
expect(sdk.getMaxListeners()).toBe(50);
204231
});

packages/sdk/src/sdk.ts

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ export interface MetaMaskSDKOptions {
9191
*/
9292
useDeeplink?: boolean;
9393

94+
/**
95+
* If true, MetaMask Mobile will hide the modal that invites the user to return to the app after performing an action on MetaMask Mobile.
96+
* This should be used when the app is loaded in an iframe inside the MetaMask Mobile browser.
97+
*/
98+
hideReturnToAppNotification?: boolean;
99+
94100
/**
95101
* If true, the SDK will shim the window.web3 object with the provider returned by the SDK (useful for compatibility with older browser).
96102
*/
@@ -175,6 +181,32 @@ export interface MetaMaskSDKOptions {
175181
};
176182
}
177183

184+
/**
185+
* Default options for the MetaMask SDK.
186+
*/
187+
export const defaultMetaMaskSDKOptions: MetaMaskSDKOptions = {
188+
storage: {
189+
enabled: true,
190+
},
191+
injectProvider: true,
192+
forceInjectProvider: false,
193+
enableAnalytics: true,
194+
shouldShimWeb3: true,
195+
useDeeplink: true,
196+
hideReturnToAppNotification: false,
197+
extensionOnly: true,
198+
headless: false,
199+
dappMetadata: {
200+
name: '',
201+
url: '',
202+
iconUrl: '',
203+
},
204+
_source: DEFAULT_SDK_SOURCE,
205+
i18nOptions: {
206+
enabled: false,
207+
},
208+
};
209+
178210
export class MetaMaskSDK extends EventEmitter2 {
179211
public options: MetaMaskSDKOptions;
180212

@@ -210,58 +242,35 @@ export class MetaMaskSDK extends EventEmitter2 {
210242

211243
private readonly ANON_ID_STORAGE_KEY = 'mm-sdk-anon-id';
212244

213-
constructor(
214-
options: MetaMaskSDKOptions = {
215-
storage: {
216-
enabled: true,
217-
},
218-
injectProvider: true,
219-
forceInjectProvider: false,
220-
enableAnalytics: true,
221-
shouldShimWeb3: true,
222-
useDeeplink: true,
223-
extensionOnly: true,
224-
headless: false,
225-
dappMetadata: {
226-
name: '',
227-
url: '',
228-
iconUrl: '',
229-
},
230-
_source: DEFAULT_SDK_SOURCE,
231-
i18nOptions: {
232-
enabled: false,
233-
},
234-
},
235-
) {
245+
constructor(options?: MetaMaskSDKOptions) {
236246
super();
237247
debug.disable(); // initially disabled
238248

239-
const developerMode = options.logging?.developerMode === true;
240-
const debugEnabled = options.logging?.sdk || developerMode;
249+
// Merge user options with default options
250+
this.options = { ...defaultMetaMaskSDKOptions, ...options };
251+
252+
const developerMode = this.options.logging?.developerMode === true;
253+
const debugEnabled = this.options.logging?.sdk || developerMode;
241254

242255
if (debugEnabled) {
243256
debug.enable('MM_SDK');
244257
}
245258
logger(`[MetaMaskSDK: constructor()]: begin.`);
246259
this.setMaxListeners(50);
247260

248-
if (!options.dappMetadata?.url) {
261+
// If dappMetadata.url is undefined or empty string, try to set it automatically for web environments
262+
if (!this.options.dappMetadata?.url) {
249263
// Automatically set dappMetadata on web env.
250264
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
251-
options.dappMetadata = {
252-
...options.dappMetadata,
265+
this.options.dappMetadata = {
266+
...this.options.dappMetadata,
253267
url: `${window.location.protocol}//${window.location.host}`,
254268
};
255269
} else {
256270
throw new Error(`You must provide dAppMetadata url`);
257271
}
258272
}
259273

260-
this.options = options;
261-
if (!this.options._source) {
262-
options._source = DEFAULT_SDK_SOURCE;
263-
}
264-
265274
// Automatically initialize the SDK to keep the same behavior as before
266275
this.init()
267276
.then(() => {

packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe('performSDKInitialization', () => {
5757
injectProvider: true,
5858
shouldShimWeb3: true,
5959
useDeeplink: true,
60+
hideReturnToAppNotification: false,
6061
storage: {
6162
enabled: true,
6263
},

packages/sdk/src/services/MetaMaskSDK/InitializerManager/performSDKInitialization.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ export async function performSDKInitialization(instance: MetaMaskSDK) {
5757
options.shouldShimWeb3 = options.shouldShimWeb3 ?? true;
5858
options.extensionOnly = options.extensionOnly ?? true;
5959
options.useDeeplink = options.useDeeplink ?? true;
60+
options.hideReturnToAppNotification =
61+
options.hideReturnToAppNotification ?? false;
62+
6063
options.storage = options.storage ?? {
6164
enabled: true,
6265
};

packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,4 +317,83 @@ describe('write function', () => {
317317
expect(mockSendMessage).toHaveBeenCalled();
318318
});
319319
});
320+
321+
describe('hideReturnToAppNotification URL parameter', () => {
322+
beforeEach(() => {
323+
mockIsReady.mockReturnValue(true);
324+
mockIsConnected.mockReturnValue(true);
325+
mockIsAuthorized.mockReturnValue(true);
326+
mockIsMobileWeb.mockReturnValue(false);
327+
mockIsSecure.mockReturnValue(true);
328+
mockGetChannelId.mockReturnValue('some_channel_id');
329+
mockIsMetaMaskInstalled.mockReturnValue(true);
330+
mockGetKeyInfo.mockReturnValue({ ecies: { public: 'test_public_key' } });
331+
mockHasDeeplinkProtocol.mockReturnValue(false);
332+
mockExtractMethod.mockReturnValue({
333+
method: Object.keys(METHODS_TO_REDIRECT)[0],
334+
data: {
335+
data: {
336+
jsonrpc: '2.0',
337+
method: Object.keys(METHODS_TO_REDIRECT)[0],
338+
params: [],
339+
},
340+
},
341+
triggeredInstaller: false,
342+
});
343+
});
344+
345+
it('should include hr=1 in URL when hideReturnToAppNotification is true', async () => {
346+
mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification =
347+
true;
348+
349+
await write(
350+
mockRemoteCommunicationPostMessageStream,
351+
{ jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] },
352+
'utf8',
353+
callback,
354+
);
355+
356+
expect(mockOpenDeeplink).toHaveBeenCalledWith(
357+
expect.stringContaining('hr=1'),
358+
expect.stringContaining('hr=1'),
359+
'_self',
360+
);
361+
});
362+
363+
it('should include hr=0 in URL when hideReturnToAppNotification is false', async () => {
364+
mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification =
365+
false;
366+
367+
await write(
368+
mockRemoteCommunicationPostMessageStream,
369+
{ jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] },
370+
'utf8',
371+
callback,
372+
);
373+
374+
expect(mockOpenDeeplink).toHaveBeenCalledWith(
375+
expect.stringContaining('hr=0'),
376+
expect.stringContaining('hr=0'),
377+
'_self',
378+
);
379+
});
380+
381+
it('should include hr=0 in URL when hideReturnToAppNotification is undefined', async () => {
382+
mockRemoteCommunicationPostMessageStream.state.hideReturnToAppNotification =
383+
undefined;
384+
385+
await write(
386+
mockRemoteCommunicationPostMessageStream,
387+
{ jsonrpc: '2.0', method: Object.keys(METHODS_TO_REDIRECT)[0] },
388+
'utf8',
389+
callback,
390+
);
391+
392+
expect(mockOpenDeeplink).toHaveBeenCalledWith(
393+
expect.stringContaining('hr=0'),
394+
expect.stringContaining('hr=0'),
395+
'_self',
396+
);
397+
});
398+
});
320399
});

packages/sdk/src/services/RemoteCommunicationPostMessageStream/write.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ export async function write(
100100

101101
const pubKey = instance.state.remote?.getKeyInfo()?.ecies.public ?? '';
102102
let urlParams = encodeURI(
103-
`channelId=${channelId}&pubkey=${pubKey}&comm=socket&t=d&v=2`,
103+
`channelId=${channelId}&pubkey=${pubKey}&comm=socket&t=d&v=2&hr=${
104+
instance.state.hideReturnToAppNotification ? 1 : 0
105+
}`,
104106
);
105107

106108
if (activeDeeplinkProtocol) {

0 commit comments

Comments
 (0)