Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
134 lines
4.1 KiB
TypeScript
134 lines
4.1 KiB
TypeScript
import type { IMessage } from '@rocket.chat/core-typings';
|
|
import { MessageBlock } from '@rocket.chat/fuselage';
|
|
|
|
import { useOembedLayout } from '../hooks/useOembedLayout';
|
|
import type { OEmbedPreviewMetadata } from './urlPreviews/OEmbedPreviewMetadata';
|
|
import OEmbedResolver from './urlPreviews/OEmbedResolver';
|
|
import UrlPreview from './urlPreviews/UrlPreview';
|
|
import type { UrlPreviewMetadata } from './urlPreviews/UrlPreviewMetadata';
|
|
import { buildImageURL } from './urlPreviews/buildImageURL';
|
|
|
|
type OembedUrlLegacy = Required<IMessage>['urls'][0];
|
|
|
|
type PreviewTypes = 'headers' | 'oembed';
|
|
|
|
type PreviewData = {
|
|
type: PreviewTypes;
|
|
data: OEmbedPreviewMetadata | UrlPreviewMetadata;
|
|
};
|
|
|
|
const normalizeMeta = ({ url, meta }: { url: string; meta: Record<string, string> }): OEmbedPreviewMetadata => {
|
|
const image = meta.ogImage || meta.twitterImage || meta.msapplicationTileImage || meta.oembedThumbnailUrl || meta.oembedThumbnailUrl;
|
|
|
|
const imageHeight = meta.ogImageHeight || meta.oembedHeight || meta.oembedThumbnailHeight;
|
|
const imageWidth = meta.ogImageWidth || meta.oembedWidth || meta.oembedThumbnailWidth;
|
|
|
|
return Object.fromEntries(
|
|
Object.entries({
|
|
siteName: meta.ogSiteName || meta.oembedProviderName,
|
|
siteUrl: meta.ogUrl || meta.oembedProviderUrl,
|
|
title: meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle,
|
|
description: meta.ogDescription || meta.twitterDescription || meta.description,
|
|
authorName: meta.oembedAuthorName,
|
|
authorUrl: meta.oembedAuthorUrl,
|
|
...(image && {
|
|
image: {
|
|
url: buildImageURL(url, image),
|
|
dimensions: {
|
|
...(imageHeight && { height: imageHeight }),
|
|
...(imageWidth && { width: imageWidth }),
|
|
},
|
|
},
|
|
}),
|
|
url: meta.oembedUrl || url,
|
|
type: meta.ogType || meta.oembedType,
|
|
...(meta.oembedHtml && { html: meta.oembedHtml }),
|
|
}).filter(([, value]) => value),
|
|
);
|
|
};
|
|
|
|
const hasContentType = (headers: OembedUrlLegacy['headers']): headers is { contentType: string } =>
|
|
headers ? 'contentType' in headers : false;
|
|
|
|
const getHeaderType = (headers: OembedUrlLegacy['headers']): UrlPreviewMetadata['type'] | undefined => {
|
|
if (!hasContentType(headers)) {
|
|
return;
|
|
}
|
|
if (headers.contentType.match(/image\/.*/)) {
|
|
return 'image';
|
|
}
|
|
if (headers.contentType.match(/audio\/.*/)) {
|
|
return 'audio';
|
|
}
|
|
if (headers.contentType.match(/video\/.*/)) {
|
|
return 'video';
|
|
}
|
|
};
|
|
|
|
const isValidPreviewMeta = ({
|
|
siteName,
|
|
siteUrl,
|
|
authorName,
|
|
authorUrl,
|
|
title,
|
|
description,
|
|
image,
|
|
html,
|
|
}: OEmbedPreviewMetadata): boolean =>
|
|
!((!siteName || !siteUrl) && (!authorName || !authorUrl) && !title && !description && !image && !html);
|
|
|
|
const hasMeta = (url: OembedUrlLegacy): url is { url: string; meta: Record<string, string> } => !!url.meta && !!Object.values(url.meta);
|
|
|
|
const processMetaAndHeaders = (url: OembedUrlLegacy): PreviewData | false => {
|
|
if (!url.headers && !url.meta) {
|
|
return false;
|
|
}
|
|
|
|
const data = hasMeta(url) ? normalizeMeta(url) : undefined;
|
|
if (data && isValidPreviewMeta(data)) {
|
|
return { type: 'oembed', data };
|
|
}
|
|
|
|
const type = getHeaderType(url.headers);
|
|
if (!type) {
|
|
return false;
|
|
}
|
|
|
|
return {
|
|
type: 'headers',
|
|
data: { url: url.url, type, originalType: hasContentType(url.headers) ? url.headers?.contentType : '' },
|
|
};
|
|
};
|
|
|
|
const isPreviewData = (data: PreviewData | false): data is PreviewData => !!data;
|
|
|
|
const isMetaPreview = (_data: PreviewData['data'], type: PreviewTypes): _data is OEmbedPreviewMetadata => type === 'oembed';
|
|
|
|
export type UrlPreviewsProps = { urls: OembedUrlLegacy[] };
|
|
|
|
const UrlPreviews = ({ urls }: UrlPreviewsProps) => {
|
|
const { maxWidth: oembedMaxWidth } = useOembedLayout();
|
|
const metaAndHeaders = urls.map(processMetaAndHeaders).filter(isPreviewData);
|
|
|
|
return (
|
|
<>
|
|
{metaAndHeaders.map(({ type, data }, index) => {
|
|
if (isMetaPreview(data, type)) {
|
|
return (
|
|
<MessageBlock width='100%' maxWidth={oembedMaxWidth} key={index}>
|
|
<OEmbedResolver meta={data} />
|
|
</MessageBlock>
|
|
);
|
|
}
|
|
return (
|
|
<MessageBlock width='100%' maxWidth={oembedMaxWidth} key={index}>
|
|
<UrlPreview {...data} />
|
|
</MessageBlock>
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default UrlPreviews;
|