diff --git a/apps/web/app/frames/img-proxy/route.ts b/apps/web/app/frames/img-proxy/route.ts new file mode 100644 index 0000000000..e7e1d47ea4 --- /dev/null +++ b/apps/web/app/frames/img-proxy/route.ts @@ -0,0 +1,29 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const url = searchParams.get('url'); + + if (!url) { + return NextResponse.json({ error: 'Missing url' }, { status: 400 }); + } + + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); + } + const contentType = response.headers.get('content-type'); + const imageBuffer = await response.arrayBuffer(); + return new NextResponse(imageBuffer, { + status: 200, + headers: { + 'Content-Type': contentType ?? 'application/octet-stream', + 'Cache-Control': 'public, max-age=86400', + }, + }); + } catch (error) { + console.error('Error fetching image:', error); + return NextResponse.json({ error: 'Failed to fetch image' }, { status: 500 }); + } +} diff --git a/apps/web/app/frames/route.tsx b/apps/web/app/frames/route.ts similarity index 100% rename from apps/web/app/frames/route.tsx rename to apps/web/app/frames/route.ts diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx index eec3af4f97..0727ce8c97 100644 --- a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx +++ b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx @@ -3,7 +3,7 @@ import type { FrameUIComponents, FrameUITheme } from '@frames.js/render/ui'; import classNames from 'classnames'; import Image from 'next/image'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import baseLoading from './base-loading.gif'; type StylingProps = { @@ -40,6 +40,14 @@ export const theme: FrameUITheme = { }, }; +function isDataUrl(url: string) { + return /^data:image\/[a-zA-Z]+;base64,/.test(url); +} + +function isSvgDataUrl(url: string) { + return url.startsWith('data:image/svg+xml'); +} + type TransitionWrapperProps = { aspectRatio: '1:1' | '1.91:1'; src: string; @@ -75,6 +83,24 @@ function TransitionWrapper({ const ar = aspectRatio.replace(':', '/'); + const style = useMemo( + () => ({ + '--frame-image-aspect-ratio': ar, + ...(isCssProperties(stylingProps.style) && stylingProps.style), + }), + [ar, stylingProps.style], + ); + + const assetSrc = useMemo( + () => + isLoading || isSvgDataUrl(src) + ? '' // todo: in the svg case, add an error state instead + : isDataUrl(src) + ? src + : `/frames/img-proxy?url=${encodeURIComponent(src)}`, + [isLoading, src], + ); + return (
{/* Loading Screen */} @@ -90,15 +116,12 @@ function TransitionWrapper({ {/* Image */}