UNPKG

@metamask/snaps-rpc-methods

Version:
148 lines 6.21 kB
import { PermissionType, SubjectType } from "@metamask/permission-controller"; import { rpcErrors } from "@metamask/rpc-errors"; import { enumValue, NotificationType, union, ContentType, getErrorMessage, ComponentOrElementStruct } from "@metamask/snaps-sdk"; import { createUnion, validateLink, validateTextLinks } from "@metamask/snaps-utils"; import { object, string, optional } from "@metamask/superstruct"; import { hasProperty, isObject } from "@metamask/utils"; const methodName = 'snap_notify'; const NativeNotificationStruct = object({ type: enumValue(NotificationType.Native), message: string(), }); const InAppNotificationStruct = object({ type: enumValue(NotificationType.InApp), message: string(), }); const InAppNotificationWithDetailsStruct = object({ type: enumValue(NotificationType.InApp), message: string(), content: ComponentOrElementStruct, title: string(), footerLink: optional(object({ href: string(), text: string(), })), }); const NotificationParametersStruct = union([ InAppNotificationStruct, InAppNotificationWithDetailsStruct, NativeNotificationStruct, ]); /** * The specification builder for the `snap_notify` permission. * `snap_notify` allows snaps to send multiple types of notifications to its users. * * @param options - The specification builder options. * @param options.allowedCaveats - The optional allowed caveats for the permission. * @param options.methodHooks - The RPC method hooks needed by the method implementation. * @returns The specification for the `snap_notify` permission. */ export const specificationBuilder = ({ allowedCaveats = null, methodHooks }) => { return { permissionType: PermissionType.RestrictedMethod, targetName: methodName, allowedCaveats, methodImplementation: getImplementation(methodHooks), subjectTypes: [SubjectType.Snap], }; }; const methodHooks = { showNativeNotification: true, showInAppNotification: true, isOnPhishingList: true, maybeUpdatePhishingList: true, createInterface: true, getSnap: true, }; export const notifyBuilder = Object.freeze({ targetName: methodName, specificationBuilder, methodHooks, }); /** * Builds the method implementation for `snap_notify`. * * @param hooks - The RPC method hooks. * @param hooks.showNativeNotification - A function that shows a native browser notification. * @param hooks.showInAppNotification - A function that shows a notification in the MetaMask UI. * @param hooks.isOnPhishingList - A function that checks for links against the phishing list. * @param hooks.maybeUpdatePhishingList - A function that updates the phishing list if needed. * @param hooks.createInterface - A function that creates the interface in SnapInterfaceController. * @param hooks.getSnap - A function that checks if a snap is installed. * @returns The method implementation which returns `null` on success. * @throws If the params are invalid. */ export function getImplementation({ showNativeNotification, showInAppNotification, isOnPhishingList, maybeUpdatePhishingList, createInterface, getSnap, }) { return async function implementation(args) { const { params, context: { origin }, } = args; await maybeUpdatePhishingList(); const validatedParams = getValidatedParams(params, isOnPhishingList, getSnap); if (hasProperty(validatedParams, 'content')) { const id = await createInterface(origin, validatedParams.content, undefined, ContentType.Notification); validatedParams.content = id; } switch (validatedParams.type) { case NotificationType.Native: return await showNativeNotification(origin, validatedParams); case NotificationType.InApp: return await showInAppNotification(origin, validatedParams); default: throw rpcErrors.invalidParams({ message: 'Must specify a valid notification "type".', }); } }; } /** * Validates the notify method `params` and returns them cast to the correct * type. Throws if validation fails. * * @param params - The unvalidated params object from the method request. * @param isOnPhishingList - The function that checks for links against the phishing list. * @param getSnap - A function that checks if a snap is installed. * @returns The validated method parameter object. * @throws If the params are invalid. */ export function getValidatedParams(params, isOnPhishingList, getSnap) { if (!isObject(params)) { throw rpcErrors.invalidParams({ message: 'Expected params to be a single object.', }); } const { type, message } = params; if (!type || typeof type !== 'string' || !Object.values(NotificationType).includes(type)) { throw rpcErrors.invalidParams({ message: 'Must specify a valid notification "type".', }); } const isNotString = !message || typeof message !== 'string'; // Set to the max message length on a Mac notification for now. if (type === NotificationType.Native && (isNotString || message.length >= 50)) { throw rpcErrors.invalidParams({ message: 'Must specify a non-empty string "message" less than 50 characters long.', }); } if (type === NotificationType.InApp && (isNotString || message.length >= 500)) { throw rpcErrors.invalidParams({ message: 'Must specify a non-empty string "message" less than 500 characters long.', }); } try { const validatedParams = createUnion(params, NotificationParametersStruct, 'type'); validateTextLinks(validatedParams.message, isOnPhishingList, getSnap); if (hasProperty(validatedParams, 'footerLink')) { validateLink(validatedParams.footerLink.href, isOnPhishingList, getSnap); } return validatedParams; } catch (error) { throw rpcErrors.invalidParams({ message: `Invalid params: ${getErrorMessage(error)}`, }); } } //# sourceMappingURL=notify.mjs.map