@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
263 lines • 10.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDialogImplementation = exports.dialogBuilder = exports.DIALOG_APPROVAL_TYPES = void 0;
const permission_controller_1 = require("@metamask/permission-controller");
const rpc_errors_1 = require("@metamask/rpc-errors");
const snaps_sdk_1 = require("@metamask/snaps-sdk");
const superstruct_1 = require("@metamask/superstruct");
const utils_1 = require("@metamask/utils");
const methodName = 'snap_dialog';
exports.DIALOG_APPROVAL_TYPES = {
[snaps_sdk_1.DialogType.Alert]: `${methodName}:alert`,
[snaps_sdk_1.DialogType.Confirmation]: `${methodName}:confirmation`,
[snaps_sdk_1.DialogType.Prompt]: `${methodName}:prompt`,
default: methodName,
};
const PlaceholderStruct = (0, superstruct_1.optional)((0, superstruct_1.size)((0, superstruct_1.string)(), 1, 40));
/**
* The specification builder for the `snap_dialog` permission. `snap_dialog`
* lets the Snap display one of the following dialogs to the user:
* - An alert, for displaying information.
* - A confirmation, for accepting or rejecting some action.
* - A prompt, for inputting some information.
*
* @param options - The specification builder options.
* @param options.allowedCaveats - The optional allowed caveats for the
* permission.
* @param options.messenger - The messenger.
* @returns The specification for the `snap_dialog` permission.
*/
const specificationBuilder = ({ allowedCaveats = null, messenger, }) => {
return {
permissionType: permission_controller_1.PermissionType.RestrictedMethod,
targetName: methodName,
allowedCaveats,
methodImplementation: getDialogImplementation({ messenger }),
subjectTypes: [permission_controller_1.SubjectType.Snap],
};
};
/* eslint-disable jsdoc/check-indentation */
/**
* Display a [dialog](https://docs.metamask.io/snaps/features/custom-ui/dialogs/)
* in the MetaMask UI.
*
* - `type` - The type of dialog. Not providing a type will create a fully
* [custom dialog](https://docs.metamask.io/snaps/features/custom-ui/dialogs/#display-a-custom-dialog).
* Possible values are:
* - `alert` - An alert that can only be acknowledged.
* - `confirmation` - A confirmation that can be accepted or rejected.
* - `prompt` - A prompt where the user can enter a text response.
*
* - One of:
* - `content` - The content of the alert, as a
* [custom UI](https://docs.metamask.io/snaps/features/custom-ui/) component.
* - `id` - The ID of an
* [interactive interface](https://docs.metamask.io/snaps/reference/snaps-api/snap_createinterface).
* - `placeholder` - An optional placeholder text to display in the dialog. Only
* applicable for the `prompt` dialog.
*
* @example
* ```ts
* import { Box, Heading, Text } from '@metamask/snaps-sdk/jsx';
*
* const walletAddress = await snap.request({
* method: 'snap_dialog',
* params: {
* type: 'prompt',
* content: (
* <Box>
* <Heading>What is the wallet address?</Heading>
* <Text>Please enter the wallet address to be monitored.</Text>
* </Box>
* ),
* placeholder: '0x123...',
* },
* });
*
* // `walletAddress` will be a string containing the address entered by the
* // user.
* ```
*/
exports.dialogBuilder = Object.freeze({
targetName: methodName,
specificationBuilder,
actionNames: [
'ApprovalController:addRequest',
'SnapInterfaceController:createInterface',
'SnapInterfaceController:getInterface',
'SnapInterfaceController:setInterfaceDisplayed',
],
});
/* eslint-enable jsdoc/check-indentation */
const AlertParametersWithContentStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Alert),
content: snaps_sdk_1.ComponentOrElementStruct,
});
const AlertParametersWithIdStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Alert),
id: (0, superstruct_1.string)(),
});
const AlertParametersStruct = (0, snaps_sdk_1.selectiveUnion)((value) => {
if ((0, utils_1.isPlainObject)(value) && (0, utils_1.hasProperty)(value, 'id')) {
return AlertParametersWithIdStruct;
}
return AlertParametersWithContentStruct;
});
const ConfirmationParametersWithContentStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Confirmation),
content: snaps_sdk_1.ComponentOrElementStruct,
});
const ConfirmationParametersWithIdStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Confirmation),
id: (0, superstruct_1.string)(),
});
const ConfirmationParametersStruct = (0, snaps_sdk_1.selectiveUnion)((value) => {
if ((0, utils_1.isPlainObject)(value) && (0, utils_1.hasProperty)(value, 'id')) {
return ConfirmationParametersWithIdStruct;
}
return ConfirmationParametersWithContentStruct;
});
const PromptParametersWithContentStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Prompt),
content: snaps_sdk_1.ComponentOrElementStruct,
placeholder: PlaceholderStruct,
});
const PromptParametersWithIdStruct = (0, superstruct_1.object)({
type: (0, snaps_sdk_1.enumValue)(snaps_sdk_1.DialogType.Prompt),
id: (0, superstruct_1.string)(),
placeholder: PlaceholderStruct,
});
const PromptParametersStruct = (0, snaps_sdk_1.selectiveUnion)((value) => {
if ((0, utils_1.isPlainObject)(value) && (0, utils_1.hasProperty)(value, 'id')) {
return PromptParametersWithIdStruct;
}
return PromptParametersWithContentStruct;
});
const DefaultParametersWithContentStruct = (0, superstruct_1.object)({
content: snaps_sdk_1.ComponentOrElementStruct,
});
const DefaultParametersWithIdStruct = (0, superstruct_1.object)({
id: (0, superstruct_1.string)(),
});
const DefaultParametersStruct = (0, snaps_sdk_1.selectiveUnion)((value) => {
if ((0, utils_1.isPlainObject)(value) && (0, utils_1.hasProperty)(value, 'id')) {
return DefaultParametersWithIdStruct;
}
return DefaultParametersWithContentStruct;
});
const DialogParametersStruct = (0, snaps_sdk_1.selectiveUnion)((value) => {
if ((0, utils_1.isPlainObject)(value) && (0, utils_1.hasProperty)(value, 'type')) {
switch (value.type) {
// We cannot use typedUnion here unfortunately.
case snaps_sdk_1.DialogType.Alert:
return AlertParametersStruct;
case snaps_sdk_1.DialogType.Confirmation:
return ConfirmationParametersStruct;
case snaps_sdk_1.DialogType.Prompt:
return PromptParametersStruct;
default:
throw new Error(`The "type" property must be one of: ${Object.values(snaps_sdk_1.DialogType).join(', ')}.`);
}
}
return DefaultParametersStruct;
});
/**
* Builds the method implementation for `snap_dialog`.
*
* @param options - The options.
* @param options.messenger - The messenger.
* @returns The method implementation which return value depends on the dialog
* type, valid return types are: string, boolean, null.
*/
function getDialogImplementation({ messenger, }) {
return async function dialogImplementation(args) {
const { params, context: { origin }, } = args;
if (!(0, utils_1.isObject)(params)) {
throw rpc_errors_1.rpcErrors.invalidParams({
message: 'Invalid params: Expected params to be a single object.',
});
}
const validatedParams = getValidatedParams(params);
const placeholder = isPromptDialog(validatedParams)
? validatedParams.placeholder
: undefined;
const validatedType = (0, utils_1.hasProperty)(validatedParams, 'type')
? validatedParams.type
: 'default';
const approvalType = exports.DIALOG_APPROVAL_TYPES[validatedType];
if ((0, utils_1.hasProperty)(validatedParams, 'content')) {
const id = messenger.call('SnapInterfaceController:createInterface', origin, validatedParams.content);
messenger.call('SnapInterfaceController:setInterfaceDisplayed', origin, id);
return messenger.call('ApprovalController:addRequest', {
id: approvalType === exports.DIALOG_APPROVAL_TYPES.default ? id : undefined,
origin,
type: approvalType,
requestData: { id, placeholder },
}, true);
}
validateInterface(origin, validatedParams.id, messenger);
messenger.call('SnapInterfaceController:setInterfaceDisplayed', origin, validatedParams.id);
return messenger.call('ApprovalController:addRequest', {
id: approvalType === exports.DIALOG_APPROVAL_TYPES.default
? validatedParams.id
: undefined,
origin,
type: approvalType,
requestData: { id: validatedParams.id, placeholder },
}, true);
};
}
exports.getDialogImplementation = getDialogImplementation;
/**
* Validate that the interface ID is valid.
*
* @param origin - The origin of the request.
* @param id - The interface ID.
* @param messenger - The messenger.
*/
function validateInterface(origin, id, messenger) {
try {
messenger.call('SnapInterfaceController:getInterface', origin, id);
}
catch (error) {
throw rpc_errors_1.rpcErrors.invalidParams({
message: `Invalid params: ${error.message}`,
});
}
}
/**
* Gets the dialog type from the dialog parameters.
*
* @param params - The dialog parameters.
* @returns The dialog type.
*/
function getDialogType(params) {
return (0, utils_1.hasProperty)(params, 'type') ? params.type : undefined;
}
/**
* Checks if the dialog parameters are for a prompt dialog.
*
* @param params - The dialog parameters.
* @returns `true` if the dialog parameters are for a prompt dialog, `false` otherwise.
*/
function isPromptDialog(params) {
return getDialogType(params) === snaps_sdk_1.DialogType.Prompt;
}
/**
* Validates the confirm method `params` and returns them cast to the correct
* type. Throws if validation fails.
*
* @param params - The unvalidated params object from the method request.
* @returns The validated confirm method parameter object.
*/
function getValidatedParams(params) {
try {
return (0, superstruct_1.create)(params, DialogParametersStruct);
}
catch (error) {
throw rpc_errors_1.rpcErrors.invalidParams({
message: `Invalid params: ${error.message}`,
});
}
}
//# sourceMappingURL=dialog.cjs.map