{"version":3,"file":"notify.mjs","sourceRoot":"","sources":["../../src/restricted/notify.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Messenger } from '@metamask/messenger';\nimport type {\n  PermissionSpecificationBuilder,\n  RestrictedMethodOptions,\n  ValidPermissionSpecification,\n} from '@metamask/permission-controller';\nimport { PermissionType, SubjectType } from '@metamask/permission-controller';\nimport { rpcErrors } from '@metamask/rpc-errors';\nimport type {\n  NotifyParams,\n  NotifyResult,\n  ComponentOrElement,\n} from '@metamask/snaps-sdk';\nimport {\n  enumValue,\n  NotificationType,\n  union,\n  ContentType,\n  getErrorMessage,\n  ComponentOrElementStruct,\n} from '@metamask/snaps-sdk';\nimport {\n  createUnion,\n  validateLink,\n  validateTextLinks,\n} from '@metamask/snaps-utils';\nimport type { InferMatching } from '@metamask/snaps-utils';\nimport { object, string, optional } from '@metamask/superstruct';\nimport type { NonEmptyArray } from '@metamask/utils';\nimport { assertExhaustive, hasProperty, isObject } from '@metamask/utils';\n\nimport type {\n  RateLimitControllerCallAction,\n  SnapControllerGetSnapAction,\n  SnapInterfaceControllerCreateInterfaceAction,\n} from '../types';\nimport { type MethodHooksObject } from '../utils';\n\nconst methodName = 'snap_notify';\n\nconst NativeNotificationStruct = object({\n  type: enumValue(NotificationType.Native),\n  message: string(),\n});\n\nconst InAppNotificationStruct = object({\n  type: enumValue(NotificationType.InApp),\n  message: string(),\n});\n\nconst InAppNotificationWithDetailsStruct = object({\n  type: enumValue(NotificationType.InApp),\n  message: string(),\n  content: ComponentOrElementStruct,\n  title: string(),\n  footerLink: optional(\n    object({\n      href: string(),\n      text: string(),\n    }),\n  ),\n});\n\nconst NotificationParametersStruct = union([\n  InAppNotificationStruct,\n  InAppNotificationWithDetailsStruct,\n  NativeNotificationStruct,\n]);\n\nexport type NotificationArgs = InferMatching<\n  typeof NotificationParametersStruct,\n  NotifyParams\n>;\n\nexport type NotifyMethodHooks = {\n  isOnPhishingList: (url: string) => boolean;\n\n  maybeUpdatePhishingList: () => Promise<void>;\n};\n\nexport type NotifyMessengerActions =\n  | RateLimitControllerCallAction\n  | SnapControllerGetSnapAction\n  | SnapInterfaceControllerCreateInterfaceAction;\n\ntype SpecificationBuilderOptions = {\n  allowedCaveats?: Readonly<NonEmptyArray<string>> | null;\n  methodHooks: NotifyMethodHooks;\n  messenger: Messenger<string, NotifyMessengerActions>;\n};\n\ntype Specification = ValidPermissionSpecification<{\n  permissionType: PermissionType.RestrictedMethod;\n  targetName: typeof methodName;\n  methodImplementation: ReturnType<typeof getImplementation>;\n  allowedCaveats: Readonly<NonEmptyArray<string>> | null;\n}>;\n\n/**\n * The specification builder for the `snap_notify` permission.\n * `snap_notify` allows snaps to send multiple types of notifications to its users.\n *\n * @param options - The specification builder options.\n * @param options.allowedCaveats - The optional allowed caveats for the permission.\n * @param options.messenger - The messenger.\n * @param options.methodHooks - The RPC method hooks needed by the method implementation.\n * @returns The specification for the `snap_notify` permission.\n */\nexport const specificationBuilder: PermissionSpecificationBuilder<\n  PermissionType.RestrictedMethod,\n  SpecificationBuilderOptions,\n  Specification\n> = ({\n  allowedCaveats = null,\n  methodHooks,\n  messenger,\n}: SpecificationBuilderOptions) => {\n  return {\n    permissionType: PermissionType.RestrictedMethod,\n    targetName: methodName,\n    allowedCaveats,\n    methodImplementation: getImplementation({ methodHooks, messenger }),\n    subjectTypes: [SubjectType.Snap],\n  };\n};\n\nconst methodHooks: MethodHooksObject<NotifyMethodHooks> = {\n  isOnPhishingList: true,\n  maybeUpdatePhishingList: true,\n};\n\n/**\n * Display a\n * [notification](https://docs.metamask.io/snaps/features/notifications/) in\n * MetaMask or natively in the OS. Snaps can trigger a short (up to 80\n * characters) notification message for actionable or time sensitive\n * information. `inApp` notifications can also include an optional\n * [expanded view](https://docs.metamask.io/snaps/features/notifications/#expanded-view).\n * The expanded view has a title, content, and optional footer link shown when\n * a user clicks on the notification.\n *\n * @example Basic in app notification\n * ```json name=\"Manifest\"\n * {\n *   \"initialPermissions\": {\n *     \"snap_notify\": {}\n *   }\n * }\n * ```\n * ```ts name=\"Usage\"\n * snap.request({\n *   method: 'snap_notify',\n *   params: {\n *     type: 'inApp',\n *     message: 'This is an in-app notification',\n *   },\n * });\n * ```\n *\n * @example Expandable in app notification\n * ```json name=\"Manifest\"\n * {\n *   \"initialPermissions\": {\n *     \"snap_notify\": {}\n *   }\n * }\n * ```\n * ```ts name=\"Usage\"\n * snap.request({\n *   method: 'snap_notify',\n *   params: {\n *     type: 'inApp',\n *     message: 'This is an in-app notification',\n *     title: 'Notification Title',\n *     content: (\n *       <Box>\n *         <Text>This is the expanded content of the notification.</Text>\n *       </Box>\n *     ),\n *     footerLink: {\n *       href: 'https://example.com',\n *       text: 'Click here for more info',\n *     },\n *   },\n * });\n * ```\n *\n * @example Native notification\n * ```json name=\"Manifest\"\n * {\n *   \"initialPermissions\": {\n *     \"snap_notify\": {}\n *   }\n * }\n * ```\n * ```ts name=\"Usage\"\n * snap.request({\n *   method: 'snap_notify',\n *   params: {\n *     type: 'native',\n *     message: 'This is a native notification',\n *   },\n * });\n * ```\n */\nexport const notifyBuilder = Object.freeze({\n  targetName: methodName,\n  specificationBuilder,\n  methodHooks,\n  actionNames: [\n    'RateLimitController:call',\n    'SnapController:getSnap',\n    'SnapInterfaceController:createInterface',\n  ],\n} as const);\n\n/**\n * Builds the method implementation for `snap_notify`.\n *\n * @param options - The options.\n * @param options.messenger - The messenger.\n * @param options.methodHooks - The RPC method hooks.\n * @param options.methodHooks.isOnPhishingList - A function that checks for links against the phishing list.\n * @param options.methodHooks.maybeUpdatePhishingList - A function that updates the phishing list if needed.\n * @returns The method implementation which returns `null` on success.\n * @throws If the params are invalid.\n */\nexport function getImplementation({\n  methodHooks: { isOnPhishingList, maybeUpdatePhishingList },\n  messenger,\n}: SpecificationBuilderOptions) {\n  return async function implementation(\n    args: RestrictedMethodOptions<NotifyParams>,\n  ): Promise<NotifyResult> {\n    const {\n      params,\n      context: { origin },\n    } = args;\n\n    await maybeUpdatePhishingList();\n\n    const validatedParams = getValidatedParams(\n      params,\n      isOnPhishingList,\n      messenger,\n    );\n\n    if (hasProperty(validatedParams, 'content')) {\n      const id = messenger.call(\n        'SnapInterfaceController:createInterface',\n        origin,\n        validatedParams.content as ComponentOrElement,\n        undefined,\n        ContentType.Notification,\n      );\n      validatedParams.content = id;\n    }\n\n    switch (validatedParams.type) {\n      case NotificationType.Native:\n        return (await messenger.call(\n          'RateLimitController:call',\n          origin,\n          'showNativeNotification',\n          origin,\n          validatedParams.message,\n        )) as NotifyResult;\n      case NotificationType.InApp: {\n        const { content, message, title, footerLink } =\n          validatedParams as NotificationArgs & {\n            content?: string;\n            title?: string;\n            footerLink?: { href: string; text: string };\n          };\n        return (await messenger.call(\n          'RateLimitController:call',\n          origin,\n          'showInAppNotification',\n          origin,\n          {\n            interfaceId: content,\n            message,\n            title,\n            footerLink,\n          },\n        )) as NotifyResult;\n      }\n      /* istanbul ignore next */\n      default:\n        return assertExhaustive(validatedParams.type as never);\n    }\n  };\n}\n\n/**\n * Validates the notify method `params` and returns them cast to the correct\n * type. Throws if validation fails.\n *\n * @param params - The unvalidated params object from the method request.\n * @param isOnPhishingList - The function that checks for links against the phishing list.\n * @param messenger - The messenger.\n * @returns The validated method parameter object.\n * @throws If the params are invalid.\n */\nexport function getValidatedParams(\n  params: unknown,\n  isOnPhishingList: NotifyMethodHooks['isOnPhishingList'],\n  messenger: Messenger<string, NotifyMessengerActions>,\n): NotifyParams {\n  if (!isObject(params)) {\n    throw rpcErrors.invalidParams({\n      message: 'Expected params to be a single object.',\n    });\n  }\n\n  const { type, message } = params;\n\n  if (\n    !type ||\n    typeof type !== 'string' ||\n    !Object.values(NotificationType).includes(type as NotificationType)\n  ) {\n    throw rpcErrors.invalidParams({\n      message: 'Must specify a valid notification \"type\".',\n    });\n  }\n\n  const isNotString = !message || typeof message !== 'string';\n  // Set to the max message length on a Mac notification for now.\n  if (\n    type === NotificationType.Native &&\n    (isNotString || message.length >= 50)\n  ) {\n    throw rpcErrors.invalidParams({\n      message:\n        'Must specify a non-empty string \"message\" less than 50 characters long.',\n    });\n  }\n\n  if (\n    type === NotificationType.InApp &&\n    (isNotString || message.length >= 500)\n  ) {\n    throw rpcErrors.invalidParams({\n      message:\n        'Must specify a non-empty string \"message\" less than 500 characters long.',\n    });\n  }\n\n  try {\n    const validatedParams = createUnion(\n      params,\n      NotificationParametersStruct,\n      'type',\n    );\n\n    const getSnap = (snapId: string) =>\n      messenger.call('SnapController:getSnap', snapId);\n\n    validateTextLinks(validatedParams.message, isOnPhishingList, getSnap);\n\n    if (hasProperty(validatedParams, 'footerLink')) {\n      validateLink(validatedParams.footerLink.href, isOnPhishingList, getSnap);\n    }\n\n    return validatedParams;\n  } catch (error) {\n    throw rpcErrors.invalidParams({\n      message: `Invalid params: ${getErrorMessage(error)}`,\n    });\n  }\n}\n"]}