diff --git a/apps/web/app/frames/route.tsx b/apps/web/app/frames/route.tsx deleted file mode 100644 index b9481518e03..00000000000 --- a/apps/web/app/frames/route.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { GET as getHandler, POST as postHandler } from '@frames.js/render/next'; -import { ipSafe } from 'apps/web/src/middleware/ipSafe'; -import { NextRequest, NextResponse } from 'next/server'; -import ipaddr from 'ipaddr.js'; -import { URL } from 'url'; -import dns from 'dns/promises'; - -function withIPCheck(handler: (req: NextRequest) => Promise) { - return async function (req: NextRequest) { - const searchParams = req.nextUrl.searchParams; - const url = searchParams.get('url'); - - if (url) { - try { - const parsedUrl = new URL(url); - const hostname = parsedUrl.hostname; - const resolvedAddresses = await dns.resolve(hostname); - - let allSafe = true; - - for (const address of resolvedAddresses) { - if (ipaddr.isValid(address)) { - if (!ipSafe(address)) { - allSafe = false; - } - } else { - return NextResponse.json({ message: 'Invalid IP address resolution' }, { status: 400 }); - } - } - - if (!allSafe) { - return NextResponse.json({ message: 'Forbidden' }, { status: 403 }); - } - - return await handler(req); - } catch (error) { - return NextResponse.json({ message: 'Invalid URL format' }, { status: 400 }); - } - } - - return handler(req); - }; -} - -export const GET = withIPCheck(getHandler); -export const POST = withIPCheck(postHandler); diff --git a/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx b/apps/web/src/components/Basenames/UsernameProfileSectionFrames/FrameTheme.tsx index eec3af4f978..78cd55c3d17 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) + ? '' + : isDataUrl(src) + ? src + : `/frames/img-proxy?url=${encodeURIComponent(src)}`, + [isLoading, src], + ); + return (
{/* Loading Screen */} @@ -90,15 +116,12 @@ function TransitionWrapper({ {/* Image */}