import { omit, pick } from 'lodash'; import { ApprovalType } from '@metamask/controller-utils'; import { rejectPendingApproval, resolvePendingApproval, setNewNetworkAdded, upsertNetworkConfiguration, } from '../../../store/actions'; import addEthereumChain from './add-ethereum-chain'; import switchEthereumChain from './switch-ethereum-chain'; import success from './success'; import error from './error'; ///: BEGIN:ONLY_INCLUDE_IN(snaps) import snapAlert from './snaps/snap-alert/snap-alert'; import snapConfirmation from './snaps/snap-confirmation/snap-confirmation'; import snapPrompt from './snaps/snap-prompt/snap-prompt'; ///: END:ONLY_INCLUDE_IN const APPROVAL_TEMPLATES = { [ApprovalType.AddEthereumChain]: addEthereumChain, [ApprovalType.SwitchEthereumChain]: switchEthereumChain, // Use ApprovalType from utils controller [ApprovalType.ResultSuccess]: success, [ApprovalType.ResultError]: error, ///: BEGIN:ONLY_INCLUDE_IN(snaps) [ApprovalType.SnapDialogAlert]: snapAlert, [ApprovalType.SnapDialogConfirmation]: snapConfirmation, [ApprovalType.SnapDialogPrompt]: snapPrompt, ///: END:ONLY_INCLUDE_IN }; export const TEMPLATED_CONFIRMATION_APPROVAL_TYPES = Object.keys(APPROVAL_TEMPLATES); const ALLOWED_TEMPLATE_KEYS = [ 'cancelText', 'content', 'onCancel', 'onSubmit', 'networkDisplay', 'submitText', 'loadingText', ]; /** * @typedef {object} PendingApproval * @property {string} id - The randomly generated id of the approval * @property {string} origin - The origin of the site requesting this approval * @property {number} time - The time the approval was requested * @property {string} type - The type of approval being requested * @property {object} requestData - The data submitted with the request */ /** * getTemplateAlerts calls the getAlerts function exported by the template if * it exists, and then returns the result of that function. In the confirmation * page the alerts returned from the getAlerts method will be set into the * alertState state object. * * @param {object} pendingApproval - the object representing the confirmation * @param {object} state - The state object consist of required info to determine alerts. */ export async function getTemplateAlerts(pendingApproval, state) { const fn = APPROVAL_TEMPLATES[pendingApproval.type]?.getAlerts; const results = fn ? await fn(pendingApproval, state) : []; if (!Array.isArray(results)) { throw new Error(`Template alerts must be an array, received: ${results}`); } if (results.some((result) => result?.id === undefined)) { throw new Error( `Template alert entries must be objects with an id key. Received: ${results}`, ); } return results; } /** * The function call to return state must be a promise returning function * this "NOOP" is here to conform to the requirements for templates without * state. */ async function emptyState() { return {}; } /** * getTemplateState calls the getState function exported by the template if * it exists, and then returns the result of that function. In the confirmation * page the object returned from the getState method will be set into the * confirmationState state object. Note, this state is not consumed by the page * itself. * * @param {object} pendingApproval - the object representing the confirmation */ export async function getTemplateState(pendingApproval) { const fn = APPROVAL_TEMPLATES[pendingApproval.type]?.getState ?? emptyState; const result = await fn(pendingApproval); if (typeof result !== 'object' || Array.isArray(result)) { throw new Error(`Template state must be an object, received: ${result}`); } else if (result === null || result === undefined) { return {}; } return result; } /** * We do not want to pass the entire dispatch function to the template. * This function should return an object of actions that we generally consider * to be safe for templates to invoke. In the future we could put these behind * permission sets so that snaps that wish to manipulate state must ask for * explicit permission to do so. * * @param {Function} dispatch - Redux dispatch function */ function getAttenuatedDispatch(dispatch) { return { rejectPendingApproval: (...args) => dispatch(rejectPendingApproval(...args)), resolvePendingApproval: (...args) => dispatch(resolvePendingApproval(...args)), upsertNetworkConfiguration: (...args) => dispatch(upsertNetworkConfiguration(...args)), setNewNetworkAdded: (...args) => dispatch(setNewNetworkAdded(...args)), }; } /** * Returns the templated values to be consumed in the confirmation page * * @param {object} pendingApproval - The pending confirmation object. * @param {Function} t - Translation function. * @param {Function} dispatch - Redux dispatch function. * @param {object} history - The application's history object. * @param {Function} setInputState - A function that can be used to record the * state of input fields in the templated component. */ export function getTemplateValues( pendingApproval, t, dispatch, history, setInputState, ) { const fn = APPROVAL_TEMPLATES[pendingApproval.type]?.getValues; if (!fn) { throw new Error( `MESSAGE_TYPE: '${pendingApproval.type}' is not specified in approval templates`, ); } const safeActions = getAttenuatedDispatch(dispatch); const values = fn(pendingApproval, t, safeActions, history, setInputState); const extraneousKeys = omit(values, ALLOWED_TEMPLATE_KEYS); const safeValues = pick(values, ALLOWED_TEMPLATE_KEYS); if (extraneousKeys.length > 0) { throw new Error( `Received extraneous keys from ${ pendingApproval.type }.getValues. These keys are not passed to the confirmation page: ${Object.keys( extraneousKeys, )}`, ); } return safeValues; }