UNPKG

@metamask/snaps-rpc-methods

Version:
263 lines 10.7 kB
"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