Skip to content

Commit

Permalink
feat: Added frame state (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
taycaldwell authored Feb 27, 2024
1 parent 1a3b607 commit 4410ad0
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/three-doors-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@coinbase/onchainkit": minor
---

**feat**: add support for passing `state` to frame server. By @taycaldwell #197
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog


## 0.10.0

### Minor Changes

- **feat**: add support for passing `state` to frame server. By @taycaldwell #197

## 0.9.4

### Patch Changes
Expand Down
4 changes: 4 additions & 0 deletions site/docs/pages/frame/frame-metadata.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export default function HomePage() {
input={{
text: 'Tell me a boat story',
}}
state={{
counter: 1,
}}
postUrl="https://zizzamia.xyz/api/frame"
/>
...
Expand All @@ -54,6 +57,7 @@ export default function HomePage() {
<meta name="fc:frame:image" content="https://zizzamia.xyz/park-3.png" />
<meta name="fc:frame:image:aspect_ratio" content="1:1" />
<meta name="fc:frame:input:text" content="Tell me a boat story" />
<meta name="fc:frame:state" content="%7B%22counter%22%3A1%7D" />
<meta name="fc:frame:post_url" content="https://zizzamia.xyz/api/frame" />
```

Expand Down
2 changes: 2 additions & 0 deletions site/docs/pages/frame/types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ type FrameMetadataType = {
postUrl?: string;
// A period in seconds at which the app should expect the image to update.
refreshPeriod?: number;
// A string containing serialized state (e.g. JSON) passed to the frame server.
state?: object;
};
```

Expand Down
11 changes: 11 additions & 0 deletions src/frame/components/FrameMetadata.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ describe('FrameMetadata', () => {
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with input', () => {
const meta = render(
<FrameMetadata image="https://example.com/image.png" state={{ counter: 1 }} />,
);
expect(meta.container.querySelector('meta[property="fc:frame:state"]')).not.toBeNull();
expect(
meta.container.querySelector('meta[property="fc:frame:state"]')?.getAttribute('content'),
).toBe('%7B%22counter%22%3A1%7D');
expect(meta.container.querySelectorAll('meta').length).toBe(4);
});

it('renders with two basic buttons', () => {
const meta = render(
<FrameMetadata
Expand Down
3 changes: 3 additions & 0 deletions src/frame/components/FrameMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type { FrameMetadataReact } from '../types';
* @param {string} props.ogTitle - The Open Graph title.
* @param {string} props.postUrl - The post URL.
* @param {number} props.refreshPeriod - The refresh period.
* @param {object} props.state - The serialized state (e.g. JSON) for the frame.
* @param {React.ComponentType<any> | undefined} props.wrapper - The wrapper component meta tags are rendered in.
* @returns {React.ReactElement} The FrameMetadata component.
*/
Expand All @@ -53,6 +54,7 @@ export function FrameMetadata({
post_url,
refreshPeriod,
refresh_period,
state,
wrapper: Wrapper = Fragment,
}: FrameMetadataReact) {
const button1 = buttons && buttons[0];
Expand All @@ -75,6 +77,7 @@ export function FrameMetadata({
{!!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} />}
{!!state && <meta property="fc:frame:state" content={encodeURIComponent(JSON.stringify(state))} />}

{!!button1 && <meta property="fc:frame:button:1" content={button1.label} />}
{!!(button1 && !!button1.action) && (
Expand Down
23 changes: 23 additions & 0 deletions src/frame/getFrameHtmlResponse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe('getFrameHtmlResponse', () => {
},
postUrl: 'https://example.com/api/frame',
refreshPeriod: 10,
state: {
counter: 1,
},
});

expect(html).toBe(`<!DOCTYPE html>
Expand All @@ -40,6 +43,7 @@ describe('getFrameHtmlResponse', () => {
<meta property="fc:frame:input:text" content="Enter a message..." />
<meta property="fc:frame:post_url" content="https://example.com/api/frame" />
<meta property="fc:frame:refresh_period" content="10" />
<meta property="fc:frame:state" content="%7B%22counter%22%3A1%7D" />
</head>
</html>`);
Expand Down Expand Up @@ -291,6 +295,25 @@ describe('getFrameHtmlResponse', () => {
expect(html).not.toContain('fc:frame:button:4:action');
expect(html).not.toContain('fc:frame:button:4:target');
});

it('should handle no state', () => {
const html = getFrameHtmlResponse({
buttons: [{ label: 'button1' }],
image: 'https://example.com/image.png',
postUrl: 'https://example.com/api/frame',
});

expect(html).toContain('<meta property="fc:frame" content="vNext" />');
expect(html).toContain('<meta property="fc:frame:button:1" content="button1" />');
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" />',
);
expect(html).not.toContain('fc:frame:state');
});
});

export { getFrameHtmlResponse };
9 changes: 8 additions & 1 deletion src/frame/getFrameHtmlResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type FrameMetadataHTMLResponse = FrameMetadataType & {
* @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.
* @param state: The serialized state (e.g. JSON) for the frame.
* @returns An HTML string containing metadata for the frame.
*/
function getFrameHtmlResponse({
Expand All @@ -27,6 +28,7 @@ function getFrameHtmlResponse({
post_url,
refreshPeriod,
refresh_period,
state,
}: FrameMetadataHTMLResponse): string {
const imgSrc = typeof image === 'string' ? image : image.src;
const ogImageHtml = ` <meta property="og:image" content="${imgSrc}" />\n`;
Expand All @@ -40,6 +42,11 @@ function getFrameHtmlResponse({
? ` <meta property="fc:frame:input:text" content="${input.text}" />\n`
: '';

// Set the state metadata if it exists.
const stateHtml = state
? ` <meta property="fc:frame:state" content="${encodeURIComponent(JSON.stringify(state))}" />\n`
: '';

// Set the button metadata if it exists.
let buttonsHtml = '';
if (buttons) {
Expand Down Expand Up @@ -76,7 +83,7 @@ function getFrameHtmlResponse({
<meta property="og:description" content="${ogDescription || 'Frame description'}" />
<meta property="og:title" content="${ogTitle || 'Frame title'}" />
<meta property="fc:frame" content="vNext" />
${buttonsHtml}${ogImageHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}
${buttonsHtml}${ogImageHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}${stateHtml}
</head>
</html>`;

Expand Down
21 changes: 21 additions & 0 deletions src/frame/getFrameMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,25 @@ describe('getFrameMetadata', () => {
'fc:frame:image': 'image',
});
});

it('should return the correct metadata with state', () => {
expect(
getFrameMetadata({
buttons: [{ label: 'button1' }],
image: 'image',
postUrl: 'post_url',
refreshPeriod: 10,
state: {
counter: 1,
},
}),
).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'button1',
'fc:frame:image': 'image',
'fc:frame:post_url': 'post_url',
'fc:frame:refresh_period': '10',
'fc:frame:state': '%7B%22counter%22%3A1%7D',
});
});
});
5 changes: 5 additions & 0 deletions src/frame/getFrameMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FrameMetadataResponse, FrameMetadataType } from './types';
* @param input: The text input to use for the frame.
* @param postUrl: The URL to post the frame to.
* @param refreshPeriod: The refresh period for the image used.
* @param state: The serialized state (e.g. JSON) for the frame.
* @returns The metadata for the frame.
*/
export const getFrameMetadata = function ({
Expand All @@ -17,6 +18,7 @@ export const getFrameMetadata = function ({
post_url,
refreshPeriod,
refresh_period,
state,
}: FrameMetadataType): FrameMetadataResponse {
const postUrlToUse = postUrl || post_url;
const refreshPeriodToUse = refreshPeriod || refresh_period;
Expand Down Expand Up @@ -52,5 +54,8 @@ export const getFrameMetadata = function ({
if (refreshPeriodToUse) {
metadata['fc:frame:refresh_period'] = refreshPeriodToUse.toString();
}
if (state) {
metadata['fc:frame:state'] = encodeURIComponent(JSON.stringify(state));
}
return metadata;
};
2 changes: 2 additions & 0 deletions src/frame/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ export type FrameMetadataType = {
refresh_period?: number;
// A period in seconds at which the app should expect the image to update.
refreshPeriod?: number;
// A string containing serialized state (e.g. JSON) passed to the frame server.
state?: object;
};

/**
Expand Down

0 comments on commit 4410ad0

Please sign in to comment.