|
| 1 | +import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; |
| 2 | +import React, { memo } from 'react'; |
| 3 | +import { custom, object, optional, pipe, readonly, safeParse, string, union, type InferInput } from 'valibot'; |
| 4 | + |
| 5 | +import readDataURIToBlob from '../Utils/readDataURIToBlob'; |
| 6 | +import ImageContent from './ImageContent'; |
| 7 | +import { type WebChatAttachment } from './private/types/WebChatAttachment'; |
| 8 | + |
| 9 | +const imageAttachmentPropsSchema = pipe( |
| 10 | + object({ |
| 11 | + attachment: custom<WebChatAttachment>( |
| 12 | + value => |
| 13 | + safeParse( |
| 14 | + union([ |
| 15 | + object({ |
| 16 | + contentUrl: string(), |
| 17 | + name: optional(string()), |
| 18 | + thumbnailUrl: optional(string()) |
| 19 | + }), |
| 20 | + object({ |
| 21 | + contentUrl: optional(string()), |
| 22 | + name: optional(string()), |
| 23 | + thumbnailUrl: string() |
| 24 | + }) |
| 25 | + ]), |
| 26 | + value |
| 27 | + ).success |
| 28 | + ) |
| 29 | + }), |
| 30 | + readonly() |
| 31 | +); |
| 32 | + |
| 33 | +type ImageAttachmentProps = InferInput<typeof imageAttachmentPropsSchema>; |
| 34 | + |
| 35 | +// React component is better with standard function than arrow function. |
| 36 | +// eslint-disable-next-line prefer-arrow-callback |
| 37 | +const ImageAttachment = memo(function ImageAttachment(props: ImageAttachmentProps) { |
| 38 | + const { attachment } = validateProps(imageAttachmentPropsSchema, props); |
| 39 | + |
| 40 | + let imageURL = attachment.thumbnailUrl || attachment.contentUrl; |
| 41 | + |
| 42 | + // To support Content Security Policy, data URI cannot be used. |
| 43 | + // We need to parse the data URI into a blob: URL. |
| 44 | + const blob = readDataURIToBlob(imageURL); |
| 45 | + |
| 46 | + if (blob) { |
| 47 | + // Only allow image/* for image, otherwise, treat it as binary. |
| 48 | + // eslint-disable-next-line no-restricted-properties |
| 49 | + imageURL = URL.createObjectURL( |
| 50 | + new Blob([blob], { |
| 51 | + type: blob.type.startsWith('image/') ? blob.type : 'application/octet-stream' |
| 52 | + }) |
| 53 | + ); |
| 54 | + } |
| 55 | + |
| 56 | + return <ImageContent alt={attachment.name} src={imageURL} />; |
| 57 | +}); |
| 58 | + |
| 59 | +export default ImageAttachment; |
0 commit comments