Skip to content
Open
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
2 changes: 1 addition & 1 deletion packages/examples/packages/get-file/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": "YEIejIfr+8rWTP0dSPc4HcLO1JwM/QQCwA/BItw5goE=",
"shasum": "HwhCRDjZxqIRT9rbHCNT11CoQ/YS8tO7ZFXrjxllpZ0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
2 changes: 2 additions & 0 deletions packages/examples/packages/get-file/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
MethodNotFoundError,
assert,
type OnRpcRequestHandler,
} from '@metamask/snaps-sdk';

Expand Down Expand Up @@ -28,6 +29,7 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
method: 'snap_getFile',
params: { path: './files/foo.json', encoding: 'utf8' },
});
assert(fileInPlaintext);
return JSON.parse(fileInPlaintext);
}

Expand Down
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 @@ -11,9 +11,9 @@ module.exports = deepmerge(baseConfig, {
coverageThreshold: {
global: {
branches: 97.28,
functions: 98.84,
lines: 99.14,
statements: 98.81,
functions: 98.87,
lines: 99.19,
statements: 98.91,
},
},
});
4 changes: 2 additions & 2 deletions packages/snaps-rpc-methods/scripts/generate-schema.mts
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,7 @@ async function processPermittedHandler(
}

/**
* Process the permitted handlers defined in `src/permitted/handlers.ts`,
* Process the permitted handlers defined in `src/permitted/middleware.ts`,
* extracting the method names, descriptions, parameters, return types, and
* subject types for each handler.
*
Expand All @@ -1303,7 +1303,7 @@ async function processPermittedHandler(
* @returns An array of method schemas extracted from the permitted handlers.
*/
async function processPermittedHandlers(project: Project) {
const handlersFile = project.getSourceFile('src/permitted/handlers.ts');
const handlersFile = project.getSourceFile('src/permitted/middleware.ts');
assert(handlersFile, 'Handlers file not found.');

const permittedHandlers =
Expand Down
6 changes: 1 addition & 5 deletions packages/snaps-rpc-methods/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
export {
handlers as permittedMethods,
createSnapsMethodMiddleware,
} from './permitted';
export { createSnapsMethodMiddleware } from './permitted';
export type { PermittedRpcMethodHooks } from './permitted';
export { SnapCaveatType } from '@metamask/snaps-utils';
export { selectHooks } from './utils';
export * from './endowments';
export * from './middleware';
export * from './permissions';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,74 @@
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import {
JsonRpcEngine,
createOriginMiddleware,
} from '@metamask/json-rpc-engine';
import type {
CancelBackgroundEventParams,
CancelBackgroundEventResult,
SnapId,
} from '@metamask/snaps-sdk';
import { MOCK_SNAP_ID } from '@metamask/snaps-utils/test-utils';
import {
MOCK_SNAP_ID,
MockControllerMessenger,
} from '@metamask/snaps-utils/test-utils';
import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils';

import type { CancelBackgroundEventMethodActions } from './cancelBackgroundEvent';
import { cancelBackgroundEventHandler } from './cancelBackgroundEvent';

describe('snap_cancelBackgroundEvent', () => {
describe('cancelBackgroundEventHandler', () => {
it('has the expected shape', () => {
expect(cancelBackgroundEventHandler).toMatchObject({
methodNames: ['snap_cancelBackgroundEvent'],
implementation: expect.any(Function),
hookNames: {
cancelBackgroundEvent: true,
},
actionNames: [
'PermissionController:hasPermission',
'CronjobController:cancel',
],
});
});
});

describe('implementation', () => {
const createOriginMiddleware =
(origin: string) =>
(request: any, _response: unknown, next: () => void, _end: unknown) => {
request.origin = origin;
next();
};

it('returns null after calling the `cancelBackgroundEvent` hook', async () => {
const { implementation } = cancelBackgroundEventHandler;
const getMessenger = () => {
const messenger = new MockControllerMessenger<
CancelBackgroundEventMethodActions,
never
>();

messenger.registerActionHandler(
'PermissionController:hasPermission',
() => true,
);

const cancelBackgroundEvent = jest.fn();
const hasPermission = jest.fn().mockImplementation(() => true);
messenger.registerActionHandler(
'CronjobController:cancel',
() => undefined,
);

const hooks = {
cancelBackgroundEvent,
hasPermission,
};
jest.spyOn(messenger, 'call');

return messenger;
};

it('returns null after calling the `CronjobController:cancel` action', async () => {
const { implementation } = cancelBackgroundEventHandler;

const messenger = getMessenger();

const engine = new JsonRpcEngine();

engine.push(createOriginMiddleware(MOCK_SNAP_ID));
engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<CancelBackgroundEventParams>,
request as JsonRpcRequest<CancelBackgroundEventParams> & {
origin: SnapId;
},
response as PendingJsonRpcResponse<CancelBackgroundEventResult>,
next,
end,
hooks,
{} as never,
messenger,
);

result?.catch(end);
Expand All @@ -70,24 +89,21 @@ describe('snap_cancelBackgroundEvent', () => {
it('cancels a background event', async () => {
const { implementation } = cancelBackgroundEventHandler;

const cancelBackgroundEvent = jest.fn();
const hasPermission = jest.fn().mockImplementation(() => true);

const hooks = {
cancelBackgroundEvent,
hasPermission,
};
const messenger = getMessenger();

const engine = new JsonRpcEngine();

engine.push(createOriginMiddleware(MOCK_SNAP_ID));
engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<CancelBackgroundEventParams>,
request as JsonRpcRequest<CancelBackgroundEventParams> & {
origin: SnapId;
},
response as PendingJsonRpcResponse<CancelBackgroundEventResult>,
next,
end,
hooks,
{} as never,
messenger,
);

result?.catch(end);
Expand All @@ -102,30 +118,36 @@ describe('snap_cancelBackgroundEvent', () => {
},
});

expect(cancelBackgroundEvent).toHaveBeenCalledWith('foo');
expect(messenger.call).toHaveBeenCalledWith(
'CronjobController:cancel',
MOCK_SNAP_ID,
'foo',
);
});

it('throws if a snap does not have the "endowment:cronjob" permission', async () => {
const { implementation } = cancelBackgroundEventHandler;

const cancelBackgroundEvent = jest.fn();
const hasPermission = jest.fn().mockImplementation(() => false);
const messenger = getMessenger();

const hooks = {
cancelBackgroundEvent,
hasPermission,
};
messenger.registerActionHandler(
'PermissionController:hasPermission',
() => false,
);

const engine = new JsonRpcEngine();

engine.push(createOriginMiddleware(MOCK_SNAP_ID));
engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<CancelBackgroundEventParams>,
request as JsonRpcRequest<CancelBackgroundEventParams> & {
origin: SnapId;
},
response as PendingJsonRpcResponse<CancelBackgroundEventResult>,
next,
end,
hooks,
{} as never,
messenger,
);

result?.catch(end);
Expand Down Expand Up @@ -155,24 +177,21 @@ describe('snap_cancelBackgroundEvent', () => {
it('throws on invalid params', async () => {
const { implementation } = cancelBackgroundEventHandler;

const cancelBackgroundEvent = jest.fn();
const hasPermission = jest.fn().mockImplementation(() => true);

const hooks = {
cancelBackgroundEvent,
hasPermission,
};
const messenger = getMessenger();

const engine = new JsonRpcEngine();

engine.push(createOriginMiddleware(MOCK_SNAP_ID));
engine.push((request, response, next, end) => {
const result = implementation(
request as JsonRpcRequest<CancelBackgroundEventParams>,
request as JsonRpcRequest<CancelBackgroundEventParams> & {
origin: SnapId;
},
response as PendingJsonRpcResponse<CancelBackgroundEventResult>,
next,
end,
hooks,
{} as never,
messenger,
);

result?.catch(end);
Expand Down
65 changes: 37 additions & 28 deletions packages/snaps-rpc-methods/src/permitted/cancelBackgroundEvent.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
import type {
JsonRpcEngineEndCallback,
MethodHandler,
} from '@metamask/json-rpc-engine';
import type { Messenger } from '@metamask/messenger';
import type { PermissionControllerHasPermissionAction } from '@metamask/permission-controller';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type {
JsonRpcRequest,
CancelBackgroundEventParams,
CancelBackgroundEventResult,
SnapId,
} from '@metamask/snaps-sdk';
import { type InferMatching } from '@metamask/snaps-utils';
import { StructError, create, object, string } from '@metamask/superstruct';
import { type PendingJsonRpcResponse } from '@metamask/utils';

import { SnapEndowments } from '../endowments';
import type { PermittedHandlerExport } from '../types';
import type { MethodHooksObject } from '../utils';

const methodName = 'snap_cancelBackgroundEvent';

const hookNames: MethodHooksObject<CancelBackgroundEventMethodHooks> = {
cancelBackgroundEvent: true,
hasPermission: true,
};
import type {
CronjobControllerCancelAction,
JsonRpcRequestWithOrigin,
} from '../types';

export type CancelBackgroundEventMethodHooks = {
cancelBackgroundEvent: (id: string) => void;
hasPermission: (permissionName: string) => boolean;
};
export type CancelBackgroundEventMethodActions =
| PermissionControllerHasPermissionAction
| CronjobControllerCancelAction;

/**
* Cancel a background event created by
Expand All @@ -46,13 +45,17 @@ export type CancelBackgroundEventMethodHooks = {
* ```
*/
export const cancelBackgroundEventHandler = {
methodNames: [methodName] as const,
implementation: getCancelBackgroundEventImplementation,
hookNames,
} satisfies PermittedHandlerExport<
CancelBackgroundEventMethodHooks,
actionNames: [
'PermissionController:hasPermission',
'CronjobController:cancel',
],
} satisfies MethodHandler<
never,
CancelBackgroundEventMethodActions,
CancelBackgroundEventParameters,
CancelBackgroundEventResult
CancelBackgroundEventResult,
{ origin: SnapId }
>;

const CancelBackgroundEventsParametersStruct = object({
Expand All @@ -72,21 +75,27 @@ export type CancelBackgroundEventParameters = InferMatching<
* @param _next - The `json-rpc-engine` "next" callback. Not used by this
* function.
* @param end - The `json-rpc-engine` "end" callback.
* @param hooks - The RPC method hooks.
* @param hooks.cancelBackgroundEvent - The function to cancel a background event.
* @param hooks.hasPermission - The function to check if a snap has the `endowment:cronjob` permission.
* @param _hooks - The RPC method hooks. Not used by this function.
* @param messenger - The messenger used to call controller actions.
* @returns Nothing.
*/
async function getCancelBackgroundEventImplementation(
req: JsonRpcRequest<CancelBackgroundEventParameters>,
req: JsonRpcRequestWithOrigin<CancelBackgroundEventParameters>,
res: PendingJsonRpcResponse<CancelBackgroundEventResult>,
_next: unknown,
end: JsonRpcEngineEndCallback,
{ cancelBackgroundEvent, hasPermission }: CancelBackgroundEventMethodHooks,
_hooks: never,
messenger: Messenger<string, CancelBackgroundEventMethodActions>,
): Promise<void> {
const { params } = req;
const { params, origin } = req;

if (!hasPermission(SnapEndowments.Cronjob)) {
if (
!messenger.call(
'PermissionController:hasPermission',
origin,
SnapEndowments.Cronjob,
)
) {
return end(providerErrors.unauthorized());
}

Expand All @@ -95,7 +104,7 @@ async function getCancelBackgroundEventImplementation(

const { id } = validatedParams;

cancelBackgroundEvent(id);
messenger.call('CronjobController:cancel', origin, id);
res.result = null;
} catch (error) {
return end(error);
Expand Down
Loading
Loading