Skip to content

Commit b0f3f11

Browse files
authored
Move onSettingsPage example to Preinstalled example snap (#2962)
This PR removes the `settings-page-example-snap` and moves its logic to the `preinstalled-example-snap` since this handler is restricted to preinstalled snaps for now.
1 parent e1621d8 commit b0f3f11

File tree

29 files changed

+185
-715
lines changed

29 files changed

+185
-715
lines changed

packages/examples/README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ The following is a list of the snaps in this directory.
6666
- [**`packages/notifications`**](./packages/notifications): This snap
6767
demonstrates how to use the `snap_notify` method to display notifications to
6868
the user, either as a MetaMask notification or as a desktop notification.
69-
- [**`packages/settings-page`**](./packages/settings-page):
70-
This snap demonstrates how to use `endowment:page-settings` permission,
71-
showing a settings page to the user.
7269
- [**`packages/transaction-insights`**](./packages/transaction-insights):
7370
This snap demonstrates how to use `endowment:transaction-insights` permission,
7471
and provide transaction insights to the user.
@@ -90,7 +87,8 @@ The following is a list of the snaps in this directory.
9087
how the Snaps platform handles errors thrown by snaps.
9188
- [**`packages/preinstalled`**](./packages/preinstalled): This snap demonstrates
9289
preinstalled snaps, i.e., snaps that are installed in the MetaMask extension
93-
by default.
90+
by default. It also demonstrates the use of the `endowment:page-settings` permission,
91+
showing a settings page to the user.
9492
- [**`packages/send-flow`**](./packages/send-flow): This snap demonstrates
9593
a simple send flow using custom UI.
9694
- [**`packages/wasm`**](./packages/wasm): This snap demonstrates how

packages/examples/packages/preinstalled/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,26 @@ MetaMask extension by default.
55

66
> [!NOTE]
77
> Preinstalled Snaps are primarily for internal use by MetaMask.
8+
9+
This snap also demonstrates how to use the `endowment:page-settings` permission to show a settings page that leverages custom UI components.
10+
11+
> [!NOTE]
12+
> This endowment is initially restricted to preinstalled snaps only.
13+
14+
## Snap manifest
15+
16+
The manifest of this snap includes the `endowment:page-settings` permission:
17+
18+
```json
19+
{
20+
"initialPermissions": {
21+
"endowment:page-settings": {}
22+
}
23+
}
24+
```
25+
26+
This permission does not require any additional configuration.
27+
28+
## Snap usage
29+
30+
This snap exposes an `onSettingsPage` handler, which returns the UI to be shown.

packages/examples/packages/preinstalled/snap.manifest.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"url": "https://github.com/MetaMask/snaps.git"
88
},
99
"source": {
10-
"shasum": "WF9+BfnWRznf3otVkb8p0M38ULIIPywefZdHiAAk7jM=",
10+
"shasum": "uDEC4wnc/rEN1OKqXidpOvejRN3h3dR85cvQLnLmJB8=",
1111
"location": {
1212
"npm": {
1313
"filePath": "dist/bundle.js",
@@ -20,7 +20,9 @@
2020
"endowment:rpc": {
2121
"dapps": true
2222
},
23-
"snap_dialog": {}
23+
"snap_dialog": {},
24+
"endowment:page-settings": {},
25+
"snap_manageState": {}
2426
},
2527
"platformVersion": "6.13.0",
2628
"manifestVersion": "0.1"

packages/examples/packages/preinstalled/src/components/dialog.tsx renamed to packages/examples/packages/preinstalled/src/components/Dialog.tsx

File renamed without changes.

packages/examples/packages/preinstalled/src/components/result.tsx renamed to packages/examples/packages/preinstalled/src/components/Result.tsx

File renamed without changes.

packages/examples/packages/settings-page/src/components/SettingsPage.tsx renamed to packages/examples/packages/preinstalled/src/components/Settings.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
type SnapComponent,
1111
} from '@metamask/snaps-sdk/jsx';
1212

13-
export type SettingsPageProps = {
13+
export type SettingsProps = {
1414
setting1?: boolean;
1515
setting2?: 'option1' | 'option2';
1616
setting3?: 'option1' | 'option2';
@@ -25,7 +25,7 @@ export type SettingsPageProps = {
2525
* @param param.setting3 - The third setting.
2626
* @returns The settings page component.
2727
*/
28-
export const SettingsPage: SnapComponent<SettingsPageProps> = ({
28+
export const Settings: SnapComponent<SettingsProps> = ({
2929
setting1,
3030
setting2,
3131
setting3,
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export * from './dialog';
2-
export * from './result';
1+
export * from './Dialog';
2+
export * from './Result';
3+
export * from './Settings';

packages/examples/packages/preinstalled/src/index.test.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from '@jest/globals';
22
import { installSnap } from '@metamask/snaps-jest';
33

4-
import { Dialog, Result } from './components';
4+
import { Dialog, Result, Settings } from './components';
55

66
describe('onRpcRequest', () => {
77
it('throws an error if the requested method does not exist', async () => {
@@ -61,4 +61,42 @@ describe('onRpcRequest', () => {
6161
expect(result).toRespondWith('foo bar');
6262
});
6363
});
64+
65+
describe('getSettings', () => {
66+
it('returns the settings state', async () => {
67+
const { request, onSettingsPage } = await installSnap();
68+
69+
const settingPageResponse = await onSettingsPage();
70+
71+
const screen = settingPageResponse.getInterface();
72+
73+
await screen.clickElement('setting1');
74+
75+
await screen.selectFromRadioGroup('setting2', 'option1');
76+
77+
await screen.selectInDropdown('setting3', 'option2');
78+
79+
expect(
80+
await request({
81+
method: 'getSettings',
82+
}),
83+
).toRespondWith({
84+
setting1: true,
85+
setting2: 'option1',
86+
setting3: 'option2',
87+
});
88+
});
89+
});
90+
});
91+
92+
describe('onSettingsPage', () => {
93+
it('returns custom UI', async () => {
94+
const { onSettingsPage } = await installSnap();
95+
96+
const response = await onSettingsPage();
97+
98+
const screen = response.getInterface();
99+
100+
expect(screen).toRender(<Settings />);
101+
});
64102
});

packages/examples/packages/preinstalled/src/index.tsx

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
import { MethodNotFoundError } from '@metamask/snaps-sdk';
1+
import { MethodNotFoundError, UserInputEventType } from '@metamask/snaps-sdk';
22
import type {
33
OnRpcRequestHandler,
4+
OnSettingsPageHandler,
45
OnUserInputHandler,
56
} from '@metamask/snaps-sdk';
67

7-
import { Dialog, Result } from './components';
8+
import { Dialog, Result, Settings } from './components';
9+
10+
type SnapState = {
11+
setting1?: boolean;
12+
setting2?: 'option1' | 'option2';
13+
setting3?: 'option1' | 'option2';
14+
};
815

916
/**
1017
* Handle incoming JSON-RPC requests from the dapp, sent through the
1118
* `wallet_invokeSnap` method. This handler handles a single method:
1219
*
1320
* - `showDialog` - Opens a dialog.
21+
* - `getSettings`: Get the settings state from the snap state.
1422
*
1523
* @param params - The request parameters.
1624
* @param params.request - The JSON-RPC request object.
1725
* @returns The JSON-RPC response.
1826
* @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest
1927
* @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap
20-
* @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_notify
28+
* @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_dialog
29+
* @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_managestate
2130
*/
2231
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
2332
switch (request.method) {
@@ -29,18 +38,62 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
2938
},
3039
});
3140

41+
case 'getSettings':
42+
return await snap.request({
43+
method: 'snap_manageState',
44+
params: {
45+
operation: 'get',
46+
encrypted: false,
47+
},
48+
});
49+
3250
default:
3351
// eslint-disable-next-line @typescript-eslint/no-throw-literal
3452
throw new MethodNotFoundError({ method: request.method });
3553
}
3654
};
3755

56+
/**
57+
* Handle incoming settings page requests from the MetaMask clients.
58+
*
59+
* @returns A static panel rendered with custom UI.
60+
* @see https://docs.metamask.io/snaps/reference/exports/#onsettingspage
61+
*/
62+
export const onSettingsPage: OnSettingsPageHandler = async () => {
63+
const state: SnapState | null = await snap.request({
64+
method: 'snap_manageState',
65+
params: {
66+
operation: 'get',
67+
encrypted: false,
68+
},
69+
});
70+
71+
return {
72+
content: (
73+
<Settings
74+
setting1={state?.setting1}
75+
setting2={state?.setting2}
76+
setting3={state?.setting3}
77+
/>
78+
),
79+
};
80+
};
81+
82+
/**
83+
* Handle incoming user events coming from the MetaMask clients open interfaces.
84+
*
85+
* @param params - The event parameters.
86+
* @param params.event - The event object containing the event type, name and value.
87+
* @param params.id - The unique identifier of the open interface.
88+
* @param params.context - The context object containing the current state of the open interface.
89+
* @see https://docs.metamask.io/snaps/reference/exports/#onuserinput
90+
*/
3891
export const onUserInput: OnUserInputHandler = async ({
3992
event,
4093
id,
4194
context,
4295
}) => {
43-
if (event.type === 'ButtonClickEvent') {
96+
if (event.type === UserInputEventType.ButtonClickEvent) {
4497
if (event.name === 'cancel') {
4598
await snap.request({
4699
method: 'snap_resolveInterface',
@@ -62,7 +115,7 @@ export const onUserInput: OnUserInputHandler = async ({
62115
}
63116
}
64117

65-
if (event.type === 'FormSubmitEvent') {
118+
if (event.type === UserInputEventType.FormSubmitEvent) {
66119
if (event.name === 'form') {
67120
const value = String(event.value['custom-input']);
68121
await snap.request({
@@ -75,4 +128,31 @@ export const onUserInput: OnUserInputHandler = async ({
75128
});
76129
}
77130
}
131+
132+
if (
133+
event.type === UserInputEventType.InputChangeEvent &&
134+
(event.name === 'setting1' ||
135+
event.name === 'setting2' ||
136+
event.name === 'setting3')
137+
) {
138+
const state = await snap.request({
139+
method: 'snap_manageState',
140+
params: {
141+
operation: 'get',
142+
encrypted: false,
143+
},
144+
});
145+
146+
await snap.request({
147+
method: 'snap_manageState',
148+
params: {
149+
operation: 'update',
150+
encrypted: false,
151+
newState: {
152+
...state,
153+
[event.name]: event.value,
154+
},
155+
},
156+
});
157+
}
78158
};

packages/examples/packages/settings-page/.depcheckrc.json

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)