@metamask/snaps-rpc-methods
Version:
MetaMask Snaps JSON-RPC method implementations
148 lines • 6.21 kB
JavaScript
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