Skip to content
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: 3 additions & 3 deletions packages/snaps-rpc-methods/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ module.exports = deepmerge(baseConfig, {
],
coverageThreshold: {
global: {
branches: 96.55,
branches: 96.6,
functions: 99.2,
lines: 99.05,
statements: 98.77,
lines: 99.06,
statements: 98.79,
},
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,69 @@ describe('snap_createInterface', () => {
methodNames: ['snap_createInterface'],
implementation: expect.any(Function),
hookNames: {
hasPermission: true,
createInterface: true,
},
});
});
});

describe('implementation', () => {
it('throws if the origin does not have permission to show UI', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(false);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = { hasPermission, createInterface };

const engine = new JsonRpcEngine();

engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<CreateInterfaceParameters>,
response as PendingJsonRpcResponse<CreateInterfaceResult>,
next,
end,
hooks,
);

result?.catch(end);
});

const response = await engine.handle({
jsonrpc: '2.0',
id: 1,
method: 'snap_createInterface',
params: {
ui: (
<Box>
<Text>Hello, world!</Text>
</Box>
) as JSXElement,
},
});

expect(response).toStrictEqual({
error: {
code: 4100,
message:
'This method can only be used if the Snap has one of the following permissions: snap_dialog, snap_notify, endowment:page-home, endowment:page-settings, endowment:transaction-insight, endowment:signature-insight.',
stack: expect.any(String),
},
id: 1,
jsonrpc: '2.0',
});
});

it('returns the result from the `createInterface` hook', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = {
hasPermission,
createInterface,
};

Expand Down Expand Up @@ -68,9 +118,11 @@ describe('snap_createInterface', () => {
it('creates an interface from a JSX element', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = {
hasPermission,
createInterface,
};

Expand Down Expand Up @@ -108,9 +160,11 @@ describe('snap_createInterface', () => {
it('throws on invalid params', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = {
hasPermission,
createInterface,
};

Expand Down Expand Up @@ -152,9 +206,11 @@ describe('snap_createInterface', () => {
it('throws on invalid UI', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = {
hasPermission,
createInterface,
};

Expand Down Expand Up @@ -202,9 +258,11 @@ describe('snap_createInterface', () => {
it('throws on invalid nested UI', async () => {
const { implementation } = createInterfaceHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const createInterface = jest.fn().mockReturnValue('foo');

const hooks = {
hasPermission,
createInterface,
};

Expand Down
24 changes: 22 additions & 2 deletions packages/snaps-rpc-methods/src/permitted/createInterface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
import type { PermittedHandlerExport } from '@metamask/permission-controller';
import { rpcErrors } from '@metamask/rpc-errors';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type {
CreateInterfaceParams,
CreateInterfaceResult,
Expand All @@ -18,16 +18,26 @@ import { StructError, create, object, optional } from '@metamask/superstruct';
import type { PendingJsonRpcResponse } from '@metamask/utils';

import type { MethodHooksObject } from '../utils';
import { UI_PERMISSIONS } from '../utils';

const methodName = 'snap_createInterface';

const hookNames: MethodHooksObject<CreateInterfaceMethodHooks> = {
hasPermission: true,
createInterface: true,
};

export type CreateInterfaceMethodHooks = {
/**
* @param permissionName - The name of the permission to check.
* @returns Whether the Snap has the permission.
*/
hasPermission: (permissionName: string) => boolean;

/**
* @param ui - The UI components.
* @param context - An optional interface context object.
* @param contentType - The optional content type.
* @returns The unique identifier of the interface.
*/
createInterface: (
Expand Down Expand Up @@ -66,6 +76,8 @@ export type CreateInterfaceParameters = InferMatching<
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.hasPermission - The function to check if the Snap has a given
* permission.
* @param hooks.createInterface - The function to create the interface.
* @returns Nothing.
*/
Expand All @@ -74,8 +86,16 @@ function getCreateInterfaceImplementation(
res: PendingJsonRpcResponse<CreateInterfaceResult>,
_next: unknown,
end: JsonRpcEngineEndCallback,
{ createInterface }: CreateInterfaceMethodHooks,
{ hasPermission, createInterface }: CreateInterfaceMethodHooks,
): void {
if (!UI_PERMISSIONS.some(hasPermission)) {
return end(
providerErrors.unauthorized({
message: `This method can only be used if the Snap has one of the following permissions: ${UI_PERMISSIONS.join(', ')}.`,
}),
);
}

const { params } = req;

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,65 @@ describe('snap_getInterfaceContext', () => {
methodNames: ['snap_getInterfaceContext'],
implementation: expect.any(Function),
hookNames: {
hasPermission: true,
getInterfaceContext: true,
},
});
});
});

describe('implementation', () => {
it('throws if the origin does not have permission to show UI', async () => {
const { implementation } = getInterfaceContextHandler;

const hasPermission = jest.fn().mockReturnValue(false);
const getInterfaceContext = jest.fn().mockReturnValue({ foo: 'bar' });

const hooks = { hasPermission, getInterfaceContext };

const engine = new JsonRpcEngine();

engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<GetInterfaceContextParameters>,
response as PendingJsonRpcResponse<GetInterfaceContextResult>,
next,
end,
hooks,
);

result?.catch(end);
});

const response = await engine.handle({
jsonrpc: '2.0',
id: 1,
method: 'snap_getInterfaceContext',
params: {
id: 'foo',
},
});

expect(response).toStrictEqual({
error: {
code: 4100,
message:
'This method can only be used if the Snap has one of the following permissions: snap_dialog, snap_notify, endowment:page-home, endowment:page-settings, endowment:transaction-insight, endowment:signature-insight.',
stack: expect.any(String),
},
id: 1,
jsonrpc: '2.0',
});
});

it('returns the result from the `getInterfaceContext` hook', async () => {
const { implementation } = getInterfaceContextHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const getInterfaceContext = jest.fn().mockReturnValue({ foo: 'bar' });

const hooks = {
hasPermission,
getInterfaceContext,
};

Expand Down Expand Up @@ -61,9 +107,11 @@ describe('snap_getInterfaceContext', () => {
it('throws on invalid params', async () => {
const { implementation } = getInterfaceContextHandler;

const hasPermission = jest.fn().mockReturnValue(true);
const getInterfaceContext = jest.fn().mockReturnValue({ foo: 'bar' });

const hooks = {
hasPermission,
getInterfaceContext,
};

Expand Down
24 changes: 21 additions & 3 deletions packages/snaps-rpc-methods/src/permitted/getInterfaceContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
import type { PermittedHandlerExport } from '@metamask/permission-controller';
import { rpcErrors } from '@metamask/rpc-errors';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type {
GetInterfaceContextParams,
GetInterfaceContextResult,
Expand All @@ -12,14 +12,22 @@ import { StructError, create, object, string } from '@metamask/superstruct';
import type { PendingJsonRpcResponse } from '@metamask/utils';

import type { MethodHooksObject } from '../utils';
import { UI_PERMISSIONS } from '../utils';

const methodName = 'snap_getInterfaceContext';

const hookNames: MethodHooksObject<GetInterfaceContextMethodHooks> = {
hasPermission: true,
getInterfaceContext: true,
};

export type GetInterfaceContextMethodHooks = {
/**
* @param permissionName - The name of the permission to check.
* @returns Whether the Snap has the permission.
*/
hasPermission: (permissionName: string) => boolean;

/**
* @param id - The interface ID.
* @returns The interface context.
Expand Down Expand Up @@ -55,16 +63,26 @@ export type GetInterfaceContextParameters = InferMatching<
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.hasPermission - The function to check if the Snap has a given
* permission.
* @param hooks.getInterfaceContext - The function to get the interface context.
* @returns Noting.
* @returns Nothing.
*/
function getInterfaceContextImplementation(
req: JsonRpcRequest<GetInterfaceContextParameters>,
res: PendingJsonRpcResponse<GetInterfaceContextResult>,
_next: unknown,
end: JsonRpcEngineEndCallback,
{ getInterfaceContext }: GetInterfaceContextMethodHooks,
{ hasPermission, getInterfaceContext }: GetInterfaceContextMethodHooks,
): void {
if (!UI_PERMISSIONS.some(hasPermission)) {
return end(
providerErrors.unauthorized({
message: `This method can only be used if the Snap has one of the following permissions: ${UI_PERMISSIONS.join(', ')}.`,
}),
);
}

const { params } = req;

try {
Expand Down
Loading