Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move onSettingsPage example to Preinstalled example snap #2962

Merged
merged 2 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
FrederikBolding marked this conversation as resolved.
Show resolved Hide resolved

```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
*/
GuillaumeRx marked this conversation as resolved.
Show resolved Hide resolved
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({
GuillaumeRx marked this conversation as resolved.
Show resolved Hide resolved
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
Loading