Skip to content

Commit

Permalink
Move onSettingsPage example to Preinstalled example snap (#2962)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
GuillaumeRx authored Dec 17, 2024
1 parent e1621d8 commit b0f3f11
Show file tree
Hide file tree
Showing 29 changed files with 185 additions and 715 deletions.
6 changes: 2 additions & 4 deletions packages/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ The following is a list of the snaps in this directory.
- [**`packages/notifications`**](./packages/notifications): This snap
demonstrates how to use the `snap_notify` method to display notifications to
the user, either as a MetaMask notification or as a desktop notification.
- [**`packages/settings-page`**](./packages/settings-page):
This snap demonstrates how to use `endowment:page-settings` permission,
showing a settings page to the user.
- [**`packages/transaction-insights`**](./packages/transaction-insights):
This snap demonstrates how to use `endowment:transaction-insights` permission,
and provide transaction insights to the user.
Expand All @@ -90,7 +87,8 @@ The following is a list of the snaps in this directory.
how the Snaps platform handles errors thrown by snaps.
- [**`packages/preinstalled`**](./packages/preinstalled): This snap demonstrates
preinstalled snaps, i.e., snaps that are installed in the MetaMask extension
by default.
by default. It also demonstrates the use of the `endowment:page-settings` permission,
showing a settings page to the user.
- [**`packages/send-flow`**](./packages/send-flow): This snap demonstrates
a simple send flow using custom UI.
- [**`packages/wasm`**](./packages/wasm): This snap demonstrates how
Expand Down
23 changes: 23 additions & 0 deletions packages/examples/packages/preinstalled/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,26 @@ MetaMask extension by default.

> [!NOTE]
> Preinstalled Snaps are primarily for internal use by MetaMask.
This snap also demonstrates how to use the `endowment:page-settings` permission to show a settings page that leverages custom UI components.

> [!NOTE]
> This endowment is initially restricted to preinstalled snaps only.
## Snap manifest

The manifest of this snap includes the `endowment:page-settings` permission:

```json
{
"initialPermissions": {
"endowment:page-settings": {}
}
}
```

This permission does not require any additional configuration.

## Snap usage

This snap exposes an `onSettingsPage` handler, which returns the UI to be shown.
6 changes: 4 additions & 2 deletions packages/examples/packages/preinstalled/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/snaps.git"
},
"source": {
"shasum": "WF9+BfnWRznf3otVkb8p0M38ULIIPywefZdHiAAk7jM=",
"shasum": "uDEC4wnc/rEN1OKqXidpOvejRN3h3dR85cvQLnLmJB8=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand All @@ -20,7 +20,9 @@
"endowment:rpc": {
"dapps": true
},
"snap_dialog": {}
"snap_dialog": {},
"endowment:page-settings": {},
"snap_manageState": {}
},
"platformVersion": "6.13.0",
"manifestVersion": "0.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type SnapComponent,
} from '@metamask/snaps-sdk/jsx';

export type SettingsPageProps = {
export type SettingsProps = {
setting1?: boolean;
setting2?: 'option1' | 'option2';
setting3?: 'option1' | 'option2';
Expand All @@ -25,7 +25,7 @@ export type SettingsPageProps = {
* @param param.setting3 - The third setting.
* @returns The settings page component.
*/
export const SettingsPage: SnapComponent<SettingsPageProps> = ({
export const Settings: SnapComponent<SettingsProps> = ({
setting1,
setting2,
setting3,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './dialog';
export * from './result';
export * from './Dialog';
export * from './Result';
export * from './Settings';
40 changes: 39 additions & 1 deletion packages/examples/packages/preinstalled/src/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from '@jest/globals';
import { installSnap } from '@metamask/snaps-jest';

import { Dialog, Result } from './components';
import { Dialog, Result, Settings } from './components';

describe('onRpcRequest', () => {
it('throws an error if the requested method does not exist', async () => {
Expand Down Expand Up @@ -61,4 +61,42 @@ describe('onRpcRequest', () => {
expect(result).toRespondWith('foo bar');
});
});

describe('getSettings', () => {
it('returns the settings state', async () => {
const { request, onSettingsPage } = await installSnap();

const settingPageResponse = await onSettingsPage();

const screen = settingPageResponse.getInterface();

await screen.clickElement('setting1');

await screen.selectFromRadioGroup('setting2', 'option1');

await screen.selectInDropdown('setting3', 'option2');

expect(
await request({
method: 'getSettings',
}),
).toRespondWith({
setting1: true,
setting2: 'option1',
setting3: 'option2',
});
});
});
});

describe('onSettingsPage', () => {
it('returns custom UI', async () => {
const { onSettingsPage } = await installSnap();

const response = await onSettingsPage();

const screen = response.getInterface();

expect(screen).toRender(<Settings />);
});
});
90 changes: 85 additions & 5 deletions packages/examples/packages/preinstalled/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { MethodNotFoundError } from '@metamask/snaps-sdk';
import { MethodNotFoundError, UserInputEventType } from '@metamask/snaps-sdk';
import type {
OnRpcRequestHandler,
OnSettingsPageHandler,
OnUserInputHandler,
} from '@metamask/snaps-sdk';

import { Dialog, Result } from './components';
import { Dialog, Result, Settings } from './components';

type SnapState = {
setting1?: boolean;
setting2?: 'option1' | 'option2';
setting3?: 'option1' | 'option2';
};

/**
* Handle incoming JSON-RPC requests from the dapp, sent through the
* `wallet_invokeSnap` method. This handler handles a single method:
*
* - `showDialog` - Opens a dialog.
* - `getSettings`: Get the settings state from the snap state.
*
* @param params - The request parameters.
* @param params.request - The JSON-RPC request object.
* @returns The JSON-RPC response.
* @see https://docs.metamask.io/snaps/reference/exports/#onrpcrequest
* @see https://docs.metamask.io/snaps/reference/rpc-api/#wallet_invokesnap
* @see https://docs.metamask.io/snaps/reference/rpc-api/#snap_notify
* @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_dialog
* @see https://docs.metamask.io/snaps/reference/snaps-api/#snap_managestate
*/
export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
switch (request.method) {
Expand All @@ -29,18 +38,62 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
},
});

case 'getSettings':
return await snap.request({
method: 'snap_manageState',
params: {
operation: 'get',
encrypted: false,
},
});

default:
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw new MethodNotFoundError({ method: request.method });
}
};

/**
* Handle incoming settings page requests from the MetaMask clients.
*
* @returns A static panel rendered with custom UI.
* @see https://docs.metamask.io/snaps/reference/exports/#onsettingspage
*/
export const onSettingsPage: OnSettingsPageHandler = async () => {
const state: SnapState | null = await snap.request({
method: 'snap_manageState',
params: {
operation: 'get',
encrypted: false,
},
});

return {
content: (
<Settings
setting1={state?.setting1}
setting2={state?.setting2}
setting3={state?.setting3}
/>
),
};
};

/**
* Handle incoming user events coming from the MetaMask clients open interfaces.
*
* @param params - The event parameters.
* @param params.event - The event object containing the event type, name and value.
* @param params.id - The unique identifier of the open interface.
* @param params.context - The context object containing the current state of the open interface.
* @see https://docs.metamask.io/snaps/reference/exports/#onuserinput
*/
export const onUserInput: OnUserInputHandler = async ({
event,
id,
context,
}) => {
if (event.type === 'ButtonClickEvent') {
if (event.type === UserInputEventType.ButtonClickEvent) {
if (event.name === 'cancel') {
await snap.request({
method: 'snap_resolveInterface',
Expand All @@ -62,7 +115,7 @@ export const onUserInput: OnUserInputHandler = async ({
}
}

if (event.type === 'FormSubmitEvent') {
if (event.type === UserInputEventType.FormSubmitEvent) {
if (event.name === 'form') {
const value = String(event.value['custom-input']);
await snap.request({
Expand All @@ -75,4 +128,31 @@ export const onUserInput: OnUserInputHandler = async ({
});
}
}

if (
event.type === UserInputEventType.InputChangeEvent &&
(event.name === 'setting1' ||
event.name === 'setting2' ||
event.name === 'setting3')
) {
const state = await snap.request({
method: 'snap_manageState',
params: {
operation: 'get',
encrypted: false,
},
});

await snap.request({
method: 'snap_manageState',
params: {
operation: 'update',
encrypted: false,
newState: {
...state,
[event.name]: event.value,
},
},
});
}
};
18 changes: 0 additions & 18 deletions packages/examples/packages/settings-page/.depcheckrc.json

This file was deleted.

7 changes: 0 additions & 7 deletions packages/examples/packages/settings-page/.eslintrc.js

This file was deleted.

10 changes: 0 additions & 10 deletions packages/examples/packages/settings-page/CHANGELOG.md

This file was deleted.

Loading

0 comments on commit b0f3f11

Please sign in to comment.