Skip to content

Commit

Permalink
feat: automating og:image for getFrameHtmlResponse and `FrameMeta…
Browse files Browse the repository at this point in the history
…data`. (#109)
  • Loading branch information
Zizzamia authored Feb 10, 2024
1 parent d16d516 commit c5ee76d
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-bottles-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

- **feat**: automated the `og:image` and `og:title` properties for `getFrameHtmlResponse` and `FrameMetadata`. By @zizzamia #109
54 changes: 42 additions & 12 deletions src/components/FrameMetadata.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ describe('FrameMetadata', () => {
expect(
meta.container.querySelector('meta[property="fc:frame:image"]')?.getAttribute('content'),
).toBe('https://example.com/image.png');
expect(meta.container.querySelectorAll('meta').length).toBe(2);
expect(meta.container.querySelectorAll('meta').length).toBe(3);
});

it('renders with image src', () => {
const meta = render(<FrameMetadata image={{ src: 'https://example.com/image.png' }} />);
expect(
meta.container.querySelector('meta[property="fc:frame:image"]')?.getAttribute('content'),
).toBe('https://example.com/image.png');
expect(meta.container.querySelectorAll('meta').length).toBe(2);
expect(meta.container.querySelectorAll('meta').length).toBe(3);
});

it('renders with image aspect ratio', () => {
Expand All @@ -38,7 +38,7 @@ describe('FrameMetadata', () => {
.querySelector('meta[property="fc:frame:image:aspect_ratio"]')
?.getAttribute('content'),
).toBe('1:1');
expect(meta.container.querySelectorAll('meta').length).toBe(3);
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with input', () => {
Expand All @@ -49,7 +49,7 @@ describe('FrameMetadata', () => {
expect(
meta.container.querySelector('meta[property="fc:frame:input:text"]')?.getAttribute('content'),
).toBe('test');
expect(meta.container.querySelectorAll('meta').length).toBe(3);
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with two basic buttons', () => {
Expand Down Expand Up @@ -79,7 +79,7 @@ describe('FrameMetadata', () => {
?.getAttribute('content'),
).toBe('post_redirect');
// Length
expect(meta.container.querySelectorAll('meta').length).toBe(5);
expect(meta.container.querySelectorAll('meta').length).toBe(6);
});

it('renders with all buttons', () => {
Expand Down Expand Up @@ -156,7 +156,7 @@ describe('FrameMetadata', () => {
?.getAttribute('content'),
).toBe('https://zizzamia.xyz/api/frame/link');
// Length
expect(meta.container.querySelectorAll('meta').length).toBe(11);
expect(meta.container.querySelectorAll('meta').length).toBe(12);
});

it('renders with post_url', () => {
Expand All @@ -167,7 +167,7 @@ describe('FrameMetadata', () => {
expect(
meta.container.querySelector('meta[property="fc:frame:post_url"]')?.getAttribute('content'),
).toBe('https://example.com');
expect(meta.container.querySelectorAll('meta').length).toBe(3);
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with refresh_period', () => {
Expand All @@ -178,7 +178,7 @@ describe('FrameMetadata', () => {
.querySelector('meta[property="fc:frame:refresh_period"]')
?.getAttribute('content'),
).toBe('10');
expect(meta.container.querySelectorAll('meta').length).toBe(3);
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with wrapper', () => {
Expand All @@ -191,7 +191,7 @@ describe('FrameMetadata', () => {

expect(meta.container.querySelector('#wrapper')).not.toBeNull();
expect(meta.container.querySelector('meta[property="fc:frame:image"]')).not.toBeNull();
expect(meta.container.querySelectorAll('meta').length).toBe(2);
expect(meta.container.querySelectorAll('meta').length).toBe(3);
});

it('renders with action mint', () => {
Expand Down Expand Up @@ -223,7 +223,7 @@ describe('FrameMetadata', () => {
.querySelector('meta[property="fc:frame:button:1:target"]')
?.getAttribute('content'),
).toBe('https://zizzamia.xyz/api/frame/mint');
expect(meta.container.querySelectorAll('meta').length).toBe(5);
expect(meta.container.querySelectorAll('meta').length).toBe(6);
});

it('renders with action link', () => {
Expand Down Expand Up @@ -255,7 +255,7 @@ describe('FrameMetadata', () => {
.querySelector('meta[property="fc:frame:button:1:target"]')
?.getAttribute('content'),
).toBe('https://zizzamia.xyz/api/frame/link');
expect(meta.container.querySelectorAll('meta').length).toBe(5);
expect(meta.container.querySelectorAll('meta').length).toBe(6);
});

it('should not render action target if action is not link or mint', () => {
Expand All @@ -267,6 +267,36 @@ describe('FrameMetadata', () => {
/>,
);
expect(meta.container.querySelector('meta[property="fc:frame:button:1:target"')).toBeNull();
expect(meta.container.querySelectorAll('meta').length).toBe(5);
expect(meta.container.querySelectorAll('meta').length).toBe(6);
});

it('should set og:description', () => {
const meta = render(
<FrameMetadata
image="https://example.com/image.png"
ogDescription="This is the description"
/>,
);
expect(
meta.container.querySelector('meta[property="og:description"]')?.getAttribute('content'),
).toBe('This is the description');
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('should set og:title', () => {
const meta = render(
<FrameMetadata image="https://example.com/image.png" ogTitle="This is the title" />,
);
expect(meta.container.querySelector('meta[property="og:title"]')?.getAttribute('content')).toBe(
'This is the title',
);
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('should not render og:description and og:title if not provided', () => {
const meta = render(<FrameMetadata image="https://example.com/image.png" />);
expect(meta.container.querySelector('meta[property="og:description"]')).toBeNull();
expect(meta.container.querySelector('meta[property="og:title"]')).toBeNull();
expect(meta.container.querySelectorAll('meta').length).toBe(3);
});
});
10 changes: 9 additions & 1 deletion src/components/FrameMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Fragment } from 'react';
import type { FrameMetadataType, FrameImageMetadata } from '../core/types';

type FrameMetadataReact = FrameMetadataType & {
ogDescription?: string;
ogTitle?: string;
wrapper?: React.ComponentType<any>;
};

Expand Down Expand Up @@ -40,6 +42,8 @@ type FrameMetadataReact = FrameMetadataType & {
* @param {Array<{ label: string, action?: string }>} props.buttons - The buttons.
* @param {string | { src: string, aspectRatio?: string }} props.image - The image URL.
* @param {string} props.input - The input text.
* @param {string} props.ogDescription - The Open Graph description.
* @param {string} props.ogTitle - The Open Graph title.
* @param {string} props.postUrl - The post URL.
* @param {number} props.refreshPeriod - The refresh period.
* @param {React.ComponentType<any> | undefined} props.wrapper - The wrapper component meta tags are rendered in.
Expand All @@ -49,6 +53,8 @@ export function FrameMetadata({
buttons,
image,
input,
ogDescription,
ogTitle,
postUrl,
post_url,
refreshPeriod,
Expand All @@ -68,10 +74,12 @@ export function FrameMetadata({
// with Helmet as a wrapper component, it is crucial to flatten the Buttons loop.
return (
<Wrapper>
{!!ogDescription && <meta property="og:description" content={ogDescription} />}
{!!ogTitle && <meta property="og:title" content={ogTitle} />}
<meta property="fc:frame" content="vNext" />
<meta property="og:image" content={imageSrc} />
{!!imageSrc && <meta property="fc:frame:image" content={imageSrc} />}
{!!aspectRatio && <meta property="fc:frame:image:aspect_ratio" content={aspectRatio} />}

{!!input && <meta property="fc:frame:input:text" content={input.text} />}

{!!button1 && <meta property="fc:frame:button:1" content={button1.label} />}
Expand Down
42 changes: 42 additions & 0 deletions src/core/getFrameHtmlResponse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ describe('getFrameHtmlResponse', () => {
expect(html).toBe(`<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="Frame description" />
<meta property="og:title" content="Frame title" />
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:button:1" content="button1" />
<meta property="fc:frame:button:1:action" content="post" />
Expand All @@ -32,6 +34,7 @@ describe('getFrameHtmlResponse', () => {
<meta property="fc:frame:button:3" content="button3" />
<meta property="fc:frame:button:3:action" content="post_redirect" />
<meta property="fc:frame:button:4" content="button4" />
<meta property="og:image" content="https://example.com/image.png" />
<meta property="fc:frame:image" content="https://example.com/image.png" />
<meta property="fc:frame:image:aspect_ratio" content="1.91:1" />
<meta property="fc:frame:input:text" content="Enter a message..." />
Expand All @@ -53,10 +56,13 @@ describe('getFrameHtmlResponse', () => {
expect(html).toBe(`<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="Frame description" />
<meta property="og:title" content="Frame title" />
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:button:1" content="Mint" />
<meta property="fc:frame:button:1:action" content="mint" />
<meta property="fc:frame:button:1:target" content="https://zizzamia.xyz/api/frame/mint" />
<meta property="og:image" content="https://zizzamia.xyz/park-1.png" />
<meta property="fc:frame:image" content="https://zizzamia.xyz/park-1.png" />
</head>
Expand All @@ -75,10 +81,13 @@ describe('getFrameHtmlResponse', () => {
expect(html).toBe(`<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="Frame description" />
<meta property="og:title" content="Frame title" />
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:button:1" content="Mint" />
<meta property="fc:frame:button:1:action" content="mint" />
<meta property="fc:frame:button:1:target" content="https://zizzamia.xyz/api/frame/mint" />
<meta property="og:image" content="https://zizzamia.xyz/park-1.png" />
<meta property="fc:frame:image" content="https://zizzamia.xyz/park-1.png" />
<meta property="fc:frame:image:aspect_ratio" content="1:1" />
Expand All @@ -95,10 +104,13 @@ describe('getFrameHtmlResponse', () => {
expect(html).toBe(`<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="Frame description" />
<meta property="og:title" content="Frame title" />
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:button:1" content="Mint" />
<meta property="fc:frame:button:1:action" content="mint" />
<meta property="fc:frame:button:1:target" content="https://zizzamia.xyz/api/frame/mint" />
<meta property="og:image" content="https://zizzamia.xyz/park-1.png" />
<meta property="fc:frame:image" content="https://zizzamia.xyz/park-1.png" />
</head>
Expand All @@ -114,10 +126,13 @@ describe('getFrameHtmlResponse', () => {
expect(html).toBe(`<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="Frame description" />
<meta property="og:title" content="Frame title" />
<meta property="fc:frame" content="vNext" />
<meta property="fc:frame:button:1" content="Link" />
<meta property="fc:frame:button:1:action" content="link" />
<meta property="fc:frame:button:1:target" content="https://zizzamia.xyz/api/frame/link" />
<meta property="og:image" content="https://zizzamia.xyz/park-1.png" />
<meta property="fc:frame:image" content="https://zizzamia.xyz/park-1.png" />
</head>
Expand All @@ -136,6 +151,7 @@ describe('getFrameHtmlResponse', () => {
expect(html).toContain(
'<meta property="fc:frame:image" content="https://example.com/image.png" />',
);
expect(html).toContain('<meta property="og:image" content="https://example.com/image.png" />');
expect(html).toContain(
'<meta property="fc:frame:post_url" content="https://example.com/api/frame" />',
);
Expand All @@ -152,6 +168,7 @@ describe('getFrameHtmlResponse', () => {
expect(html).toContain(
'<meta property="fc:frame:image" content="https://example.com/image.png" />',
);
expect(html).toContain('<meta property="og:image" content="https://example.com/image.png" />');
expect(html).toContain(
'<meta property="fc:frame:post_url" content="https://example.com/api/frame" />',
);
Expand All @@ -168,6 +185,7 @@ describe('getFrameHtmlResponse', () => {
expect(html).toContain(
'<meta property="fc:frame:image" content="https://example.com/image.png" />',
);
expect(html).toContain('<meta property="og:image" content="https://example.com/image.png" />');
expect(html).toContain('<meta property="fc:frame:button:1" content="button1" />');
expect(html).not.toContain('fc:frame:post_url');
});
Expand Down Expand Up @@ -204,6 +222,30 @@ describe('getFrameHtmlResponse', () => {
expect(html).toContain('<meta property="fc:frame:post_url" content="post_url" />');
expect(html).not.toContain('fc:frame:button:1:target');
});

it('should set og:description and og:title to default values if not provided', () => {
const html = getFrameHtmlResponse({
buttons: [{ label: 'button1' }],
image: 'image',
postUrl: 'post_url',
});

expect(html).toContain('<meta property="og:description" content="Frame description" />');
expect(html).toContain('<meta property="og:title" content="Frame title" />');
});

it('should set og:description and og:title to provided values', () => {
const html = getFrameHtmlResponse({
buttons: [{ label: 'button1' }],
image: 'image',
postUrl: 'post_url',
ogDescription: 'description',
ogTitle: 'title',
});

expect(html).toContain('<meta property="og:description" content="description" />');
expect(html).toContain('<meta property="og:title" content="title" />');
});
});

export { getFrameHtmlResponse };
30 changes: 19 additions & 11 deletions src/core/getFrameHtmlResponse.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { FrameMetadataType, FrameImageMetadata } from './types';

type FrameMetadataHTMLResponse = FrameMetadataType & {
ogDescription?: string;
ogTitle?: string;
};

/**
* Returns an HTML string containing metadata for a new valid frame.
*
* @param buttons: The buttons to use for the frame.
* @param image: The image to use for the frame.
* @param input: The text input to use for the frame.
* @param ogDescription: The Open Graph description for the frame.
* @param ogTitle: The Open Graph title for the frame.
* @param postUrl: The URL to post the frame to.
* @param refreshPeriod: The refresh period for the image used.
* @returns An HTML string containing metadata for the frame.
Expand All @@ -14,21 +21,20 @@ function getFrameHtmlResponse({
buttons,
image,
input,
ogDescription,
ogTitle,
postUrl,
post_url,
refreshPeriod,
refresh_period,
}: FrameMetadataType): string {
// Set the image metadata if it exists.
let imageHtml = '';
if (typeof image === 'string') {
imageHtml = ` <meta property="fc:frame:image" content="${image}" />\n`;
} else {
imageHtml = ` <meta property="fc:frame:image" content="${image.src}" />\n`;
if (image.aspectRatio) {
imageHtml += ` <meta property="fc:frame:image:aspect_ratio" content="${image.aspectRatio}" />\n`;
}
}: FrameMetadataHTMLResponse): string {
const imgSrc = typeof image === 'string' ? image : image.src;
const ogImageHtml = ` <meta property="og:image" content="${imgSrc}" />\n`;
let imageHtml = ` <meta property="fc:frame:image" content="${imgSrc}" />\n`;
if (typeof image !== 'string' && image.aspectRatio) {
imageHtml += ` <meta property="fc:frame:image:aspect_ratio" content="${image.aspectRatio}" />\n`;
}

// Set the input metadata if it exists.
const inputHtml = input
? ` <meta property="fc:frame:input:text" content="${input.text}" />\n`
Expand Down Expand Up @@ -67,8 +73,10 @@ function getFrameHtmlResponse({
let html = `<!DOCTYPE html>
<html>
<head>
<meta property="og:description" content="${ogDescription || 'Frame description'}" />
<meta property="og:title" content="${ogTitle || 'Frame title'}" />
<meta property="fc:frame" content="vNext" />
${buttonsHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}
${buttonsHtml}${ogImageHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}
</head>
</html>`;

Expand Down
2 changes: 1 addition & 1 deletion src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const version = '0.6.0';
export const version = '0.6.1';

0 comments on commit c5ee76d

Please sign in to comment.