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
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
diff --git a/dist/types/restricted/getLocale.d.ts b/dist/types/restricted/getLocale.d.ts
index 2941d2733042664c341776c7bc840ba0813994ca..0188bbd4de0cd013159a36b736ad9baf94c18c92 100644
--- a/dist/types/restricted/getLocale.d.ts
+++ b/dist/types/restricted/getLocale.d.ts
@@ -1,6 +1,6 @@
import type { PermissionSpecificationBuilder, ValidPermissionSpecification, RestrictedMethodOptions } from '@metamask/permission-controller';
import { PermissionType } from '@metamask/permission-controller';
-import type { NonEmptyArray } from '@metamask/utils';
+import type { Json, NonEmptyArray } from '@metamask/utils';
import type { MethodHooksObject } from '../utils';
declare const methodName = "snap_getLocale";
export declare type GetLocaleMethodHooks = {
@@ -43,5 +43,5 @@ export declare const getLocaleBuilder: Readonly<{
* @param hooks.getLocale - A function that returns the user selected locale.
* @returns The user selected locale.
*/
-export declare function getImplementation({ getLocale }: GetLocaleMethodHooks): (_args: RestrictedMethodOptions<void>) => Promise<string>;
+export declare function getImplementation({ getLocale }: GetLocaleMethodHooks): (_args: RestrictedMethodOptions<Record<string, Json> | Json[]>) => Promise<string>;
export {};
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
"simple-git-hooks": {
"pre-push": "yarn lint"
},
"resolutions": {
"@metamask/rpc-methods@^0.38.1-flask.1": "patch:@metamask/rpc-methods@npm%3A0.38.1-flask.1#./.yarn/patches/@metamask-rpc-methods-npm-0.38.1-flask.1-081e1eb5b3.patch"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^2.3.1",
"@metamask/create-release-branch": "^1.1.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/permission-controller/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `@metamask/permission-controller`

Mediates access to JSON-RPC methods, used to interact with pieces of the MetaMask stack, via middleware for `json-rpc-engine`.
Mediates access to JSON-RPC methods, used to interact with pieces of the MetaMask stack, via middleware for `@metamask/json-rpc-engine`.

## Installation

Expand Down
4 changes: 2 additions & 2 deletions packages/permission-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
"@metamask/approval-controller": "^3.5.2",
"@metamask/base-controller": "^3.2.2",
"@metamask/controller-utils": "^5.0.1",
"@metamask/json-rpc-engine": "^7.1.1",
"@metamask/rpc-errors": "^6.0.0",
"@metamask/utils": "^8.1.0",
"@types/deep-freeze-strict": "^1.1.0",
"deep-freeze-strict": "^1.1.1",
"eth-rpc-errors": "^4.0.2",
"immer": "^9.0.6",
"json-rpc-engine": "^6.1.0",
"nanoid": "^3.1.31"
},
"devDependencies": {
Expand Down
15 changes: 8 additions & 7 deletions packages/permission-controller/src/Permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,19 +210,20 @@ type RestrictedMethodContext = Readonly<{
[key: string]: any;
}>;

export type RestrictedMethodParameters = Json[] | Record<string, Json> | void;
export type RestrictedMethodParameters = Json[] | Record<string, Json>;

/**
* The arguments passed to a restricted method implementation.
*
* @template Params - The JSON-RPC parameters of the restricted method.
*/
export type RestrictedMethodOptions<Params extends RestrictedMethodParameters> =
{
method: TargetName;
params?: Params;
context: RestrictedMethodContext;
};
export type RestrictedMethodOptions<
Params extends RestrictedMethodParameters | null,
> = {
method: TargetName;
params?: Params;
context: RestrictedMethodContext;
};

/**
* A synchronous restricted method implementation.
Expand Down
45 changes: 31 additions & 14 deletions packages/permission-controller/src/PermissionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import type {
} from '@metamask/approval-controller';
import { ControllerMessenger } from '@metamask/base-controller';
import { isPlainObject } from '@metamask/controller-utils';
import type { Json } from '@metamask/utils';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';
import type { Json, PendingJsonRpcResponse } from '@metamask/utils';
import { hasProperty } from '@metamask/utils';
import assert from 'assert';
import { JsonRpcEngine } from 'json-rpc-engine';
import type { PendingJsonRpcResponse } from 'json-rpc-engine';

import type {
AsyncRestrictedMethod,
Expand Down Expand Up @@ -251,7 +250,7 @@ function getDefaultPermissionSpecifications() {
CaveatTypes.filterArrayResponse,
CaveatTypes.reverseArrayResponse,
],
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<Json[]>) => {
return ['a', 'b', 'c'];
},
},
Expand All @@ -262,7 +261,9 @@ function getDefaultPermissionSpecifications() {
CaveatTypes.filterObjectResponse,
CaveatTypes.noopCaveat,
],
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (
_args: RestrictedMethodOptions<Record<string, Json>>,
) => {
return { a: 'x', b: 'y', c: 'z' };
},
validator: (permission: PermissionConstraint) => {
Expand Down Expand Up @@ -292,15 +293,15 @@ function getDefaultPermissionSpecifications() {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noop,
allowedCaveats: null,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
},
[PermissionKeys.wallet_noopWithPermittedAndFailureSideEffects]: {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithPermittedAndFailureSideEffects,
allowedCaveats: null,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
sideEffect: {
Expand All @@ -312,7 +313,7 @@ function getDefaultPermissionSpecifications() {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithPermittedAndFailureSideEffects2,
allowedCaveats: null,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
sideEffect: {
Expand All @@ -324,7 +325,7 @@ function getDefaultPermissionSpecifications() {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithPermittedSideEffects,
allowedCaveats: null,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
sideEffect: {
Expand All @@ -335,7 +336,7 @@ function getDefaultPermissionSpecifications() {
[PermissionKeys.wallet_noopWithValidator]: {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithValidator,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
allowedCaveats: [CaveatTypes.noopCaveat, CaveatTypes.filterArrayResponse],
Expand All @@ -352,7 +353,7 @@ function getDefaultPermissionSpecifications() {
[PermissionKeys.wallet_noopWithRequiredCaveat]: {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithRequiredCaveat,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
allowedCaveats: [CaveatTypes.noopCaveat],
Expand Down Expand Up @@ -388,7 +389,7 @@ function getDefaultPermissionSpecifications() {
[PermissionKeys.wallet_noopWithFactory]: {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.wallet_noopWithFactory,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
allowedCaveats: [CaveatTypes.filterArrayResponse],
Expand All @@ -415,7 +416,7 @@ function getDefaultPermissionSpecifications() {
permissionType: PermissionType.RestrictedMethod,
targetName: PermissionKeys.snap_foo,
allowedCaveats: null,
methodImplementation: (_args: RestrictedMethodOptions<void>) => {
methodImplementation: (_args: RestrictedMethodOptions<null>) => {
return null;
},
subjectTypes: [SubjectType.Snap],
Expand Down Expand Up @@ -1850,6 +1851,7 @@ describe('PermissionController', () => {
expect(() =>
controller.removeCaveat(
origin,
// @ts-expect-error - Testing invalid permission name.
PermissionNames.wallet_noopWithRequiredCaveat,
CaveatTypes.noopCaveat,
),
Expand Down Expand Up @@ -5270,7 +5272,13 @@ describe('PermissionController', () => {
};

const expectedError = errors.unauthorized({
data: { origin, method: PermissionNames.wallet_getSecretArray },
data: {
origin,
method: PermissionNames.wallet_getSecretArray,
cause: null,
},
message:
'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions',
});

const { error }: any = await engine.handle(request);
Expand All @@ -5293,6 +5301,11 @@ describe('PermissionController', () => {
const expectedError = errors.methodNotFound('wallet_foo', { origin });

const { error }: any = await engine.handle(request);

expect(error.message).toStrictEqual(expectedError.message);
expect(error.data.cause).toBeNull();
delete error.message;
delete error.data.cause;
expect(error).toMatchObject(expect.objectContaining(expectedError));
});

Expand Down Expand Up @@ -5334,6 +5347,10 @@ describe('PermissionController', () => {
);

const { error }: any = await engine.handle(request);
expect(error.message).toStrictEqual(expectedError.message);
expect(error.data.cause).toBeNull();
delete error.message;
delete error.data.cause;
expect(error).toMatchObject(expect.objectContaining(expectedError));
});
});
Expand Down
5 changes: 3 additions & 2 deletions packages/permission-controller/src/PermissionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ import {
isPlainObject,
isValidJson,
} from '@metamask/controller-utils';
import { JsonRpcError } from '@metamask/rpc-errors';
import { hasProperty } from '@metamask/utils';
import type { Json, Mutable } from '@metamask/utils';
import deepFreeze from 'deep-freeze-strict';
import { EthereumRpcError } from 'eth-rpc-errors';
import { castDraft } from 'immer';
import type { Draft, Patch } from 'immer';
import { nanoid } from 'nanoid';
Expand Down Expand Up @@ -1802,6 +1802,7 @@ export class PermissionController<
target: string,
): void {
if (!isPlainObject(caveat)) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw new InvalidCaveatError(caveat, origin, target);
}

Expand Down Expand Up @@ -2149,7 +2150,7 @@ export class PermissionController<
try {
this.validateRequestedPermissions(origin, permissions);
} catch (error) {
if (error instanceof EthereumRpcError) {
if (error instanceof JsonRpcError) {
// Re-throw as an internal error; we should never receive invalid approved
// permissions.
throw internalError(
Expand Down
33 changes: 21 additions & 12 deletions packages/permission-controller/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { errorCodes, ethErrors, EthereumRpcError } from 'eth-rpc-errors';
import type { DataWithOptionalCause } from '@metamask/rpc-errors';
import {
errorCodes,
providerErrors,
rpcErrors,
JsonRpcError,
} from '@metamask/rpc-errors';

import type { PermissionType } from './Permission';

type UnauthorizedArg = {
data?: Record<string, unknown>;
message?: string;
};

/**
Expand All @@ -13,7 +20,7 @@ type UnauthorizedArg = {
* @returns The built error
*/
export function unauthorized(opts: UnauthorizedArg) {
return ethErrors.provider.unauthorized({
return providerErrors.unauthorized({
message:
'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions',
data: opts.data,
Expand All @@ -27,19 +34,19 @@ export function unauthorized(opts: UnauthorizedArg) {
* @param data - Optional data for context.
* @returns The built error
*/
export function methodNotFound(method: string, data?: unknown) {
export function methodNotFound(method: string, data?: DataWithOptionalCause) {
const message = `The method "${method}" does not exist / is not available.`;

const opts: Parameters<typeof ethErrors.rpc.methodNotFound>[0] = { message };
const opts: Parameters<typeof rpcErrors.methodNotFound>[0] = { message };
if (data !== undefined) {
opts.data = data;
}
return ethErrors.rpc.methodNotFound(opts);
return rpcErrors.methodNotFound(opts);
}

type InvalidParamsArg = {
message?: string;
data?: unknown;
data?: DataWithOptionalCause;
};

/**
Expand All @@ -49,7 +56,7 @@ type InvalidParamsArg = {
* @returns The built error
*/
export function invalidParams(opts: InvalidParamsArg) {
return ethErrors.rpc.invalidParams({
return rpcErrors.invalidParams({
data: opts.data,
message: opts.message,
});
Expand All @@ -63,8 +70,8 @@ export function invalidParams(opts: InvalidParamsArg) {
*/
export function userRejectedRequest<Data extends Record<string, unknown>>(
data?: Data,
): EthereumRpcError<Data> {
return ethErrors.provider.userRejectedRequest({ data });
): JsonRpcError<Data> {
return providerErrors.userRejectedRequest({ data });
}

/**
Expand All @@ -77,8 +84,8 @@ export function userRejectedRequest<Data extends Record<string, unknown>>(
export function internalError<Data extends Record<string, unknown>>(
message: string,
data?: Data,
): EthereumRpcError<Data> {
return ethErrors.rpc.internal({ message, data });
): JsonRpcError<Data> {
return rpcErrors.internal({ message, data });
}

export class InvalidSubjectIdentifierError extends Error {
Expand Down Expand Up @@ -183,7 +190,9 @@ export class CaveatAlreadyExistsError extends Error {
}
}

export class InvalidCaveatError extends EthereumRpcError<unknown> {
export class InvalidCaveatError extends JsonRpcError<
DataWithOptionalCause | undefined
> {
public override data: { origin: string; target: string };

constructor(receivedCaveat: unknown, origin: string, target: string) {
Expand Down
8 changes: 5 additions & 3 deletions packages/permission-controller/src/permission-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import type { Json } from '@metamask/utils';
import { createAsyncMiddleware } from 'json-rpc-engine';
import { createAsyncMiddleware } from '@metamask/json-rpc-engine';
import type {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
JsonRpcEngine,
JsonRpcMiddleware,
AsyncJsonRpcEngineNextCallback,
} from '@metamask/json-rpc-engine';
import type {
Json,
PendingJsonRpcResponse,
JsonRpcRequest,
} from 'json-rpc-engine';
} from '@metamask/utils';

import type {
GenericPermissionController,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JsonRpcEngine } from 'json-rpc-engine';
import { JsonRpcEngine } from '@metamask/json-rpc-engine';

import { getPermissionsHandler } from './getPermissions';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine';
import type { PendingJsonRpcResponse } from '@metamask/utils';
import type { JsonRpcEngineEndCallback } from 'json-rpc-engine';

import type { PermissionConstraint } from '../Permission';
import type { SubjectPermissions } from '../PermissionController';
Expand Down
Loading