Commit b6380f6f authored by tom's avatar tom

fetch media type via node.js

parent 370b1c00
import type { NextApiRequest, NextApiResponse } from 'next';
import nodeFetch from 'node-fetch';
import { httpLogger } from 'lib/api/logger';
import getQueryParamString from 'lib/router/getQueryParamString';
export default async function mediaTypeHandler(req: NextApiRequest, res: NextApiResponse) {
httpLogger(req, res);
try {
const url = getQueryParamString(req.query.url);
const response = await nodeFetch(url, { method: 'HEAD' });
if (response.status !== 200) {
throw new Error();
}
const contentType = response.headers.get('content-type');
const mediaType = contentType?.startsWith('video') ? 'video' : 'image';
res.status(200).json({ type: mediaType });
} catch (error) {
res.status(200).json({ type: undefined });
}
}
...@@ -15,6 +15,7 @@ declare module "nextjs-routes" { ...@@ -15,6 +15,7 @@ declare module "nextjs-routes" {
| DynamicRoute<"/address/[hash]/contract_verification", { "hash": string }> | DynamicRoute<"/address/[hash]/contract_verification", { "hash": string }>
| DynamicRoute<"/address/[hash]", { "hash": string }> | DynamicRoute<"/address/[hash]", { "hash": string }>
| StaticRoute<"/api/csrf"> | StaticRoute<"/api/csrf">
| StaticRoute<"/api/media-type">
| StaticRoute<"/api/proxy"> | StaticRoute<"/api/proxy">
| StaticRoute<"/api-docs"> | StaticRoute<"/api-docs">
| DynamicRoute<"/apps/[id]", { "id": string }> | DynamicRoute<"/apps/[id]", { "id": string }>
......
import { AspectRatio, chakra, Skeleton } from '@chakra-ui/react'; import { AspectRatio, chakra, Skeleton } from '@chakra-ui/react';
import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import NftImage from './NftImage'; import NftImage from './NftImage';
import NftVideo from './NftVideo'; import NftVideo from './NftVideo';
import type { MediaType } from './utils';
import { getPreliminaryMediaType } from './utils';
interface Props { interface Props {
imageUrl: string | null; imageUrl: string | null;
...@@ -11,7 +14,7 @@ interface Props { ...@@ -11,7 +14,7 @@ interface Props {
} }
const NftMedia = ({ imageUrl, animationUrl, className }: Props) => { const NftMedia = ({ imageUrl, animationUrl, className }: Props) => {
const [ type, setType ] = React.useState<'image' | 'video' | undefined>(!animationUrl ? 'image' : undefined); const [ type, setType ] = React.useState<MediaType | undefined>(!animationUrl ? 'image' : undefined);
React.useEffect(() => { React.useEffect(() => {
if (!animationUrl) { if (!animationUrl) {
...@@ -20,10 +23,26 @@ const NftMedia = ({ imageUrl, animationUrl, className }: Props) => { ...@@ -20,10 +23,26 @@ const NftMedia = ({ imageUrl, animationUrl, className }: Props) => {
// media could be either gif or video // media could be either gif or video
// so we pre-fetch the resources in order to get its content type // so we pre-fetch the resources in order to get its content type
fetch(animationUrl, { method: 'HEAD' }) // have to do it via Node.js due to strict CSP for connect-src
.then((response) => { // but in order not to abuse our server firstly we check file url extension
const contentType = response.headers.get('content-type'); // and if it is valid we will trust it and display corresponding media component
setType(contentType?.startsWith('video') ? 'video' : 'image');
const preliminaryType = getPreliminaryMediaType(animationUrl);
if (preliminaryType) {
setType(preliminaryType);
return;
}
const url = route({ pathname: '/api/media-type', query: { url: animationUrl } });
fetch(url)
.then((response) => response.json())
.then((_response) => {
const response = _response as { type: MediaType | undefined };
setType(response.type || 'image');
})
.catch(() => {
setType('image');
}); });
}, [ animationUrl ]); }, [ animationUrl ]);
......
export type MediaType = 'image' | 'video';
const IMAGE_EXTENSIONS = [
'.jpg', 'jpeg',
'.png',
'.gif',
'.svg',
];
const VIDEO_EXTENSIONS = [
'.mp4',
'.webm',
'.ogg',
];
export function getPreliminaryMediaType(url: string): MediaType | undefined {
if (IMAGE_EXTENSIONS.some((ext) => url.endsWith(ext))) {
return 'image';
}
if (url.startsWith('data:image')) {
return 'image';
}
if (VIDEO_EXTENSIONS.some((ext) => url.endsWith(ext))) {
return 'video';
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment