Skip to content

Commit d915569

Browse files
committed
Port to TypeScript
1 parent ebae59a commit d915569

3 files changed

Lines changed: 68 additions & 46 deletions

File tree

packages/component/src/Attachment/ImageAttachment.js

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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;

packages/component/src/Utils/readDataURIToBlob.js renamed to packages/component/src/Utils/readDataURIToBlob.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ const PATTERN = /^data:([^,]*?)(;(base64)){0,1},([A-Za-z0-9+/=]+)/u;
44

55
const DEFAULT_CONTENT_TYPE = 'text/plain;charset=US-ASCII';
66

7-
export function parse(dataURI) {
7+
export function parse(dataURI: string):
8+
| {
9+
base64: string;
10+
contentType: string;
11+
encoding: string;
12+
}
13+
| undefined {
814
const match = PATTERN.exec(dataURI);
915

1016
if (!match) {
@@ -20,12 +26,12 @@ export function parse(dataURI) {
2026
return { base64, contentType: contentType || DEFAULT_CONTENT_TYPE, encoding };
2127
}
2228

23-
export default function readDataURIToBlob(dataURI) {
29+
export default function readDataURIToBlob(dataURI: string): Blob | undefined {
2430
const parsed = parse(dataURI);
2531

2632
if (!parsed) {
2733
return;
2834
}
2935

30-
return new Blob([toByteArray(parsed.base64)], { type: parsed.contentType });
36+
return new Blob([toByteArray(parsed.base64).buffer as ArrayBuffer], { type: parsed.contentType });
3137
}

0 commit comments

Comments
 (0)