Skip to content

Commit

Permalink
Add frame state
Browse files Browse the repository at this point in the history
  • Loading branch information
taycaldwell committed Feb 26, 2024
1 parent 1a3b607 commit a74518b
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 1 deletion.
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 a74518b

Please sign in to comment.