Skip to content

Commit b8aa317

Browse files
authored
feat: init Open Frame spec support (#285)
1 parent c7b0562 commit b8aa317

File tree

13 files changed

+185
-9
lines changed

13 files changed

+185
-9
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@coinbase/onchainkit': minor
3+
---
4+
5+
- **feat**: init Open Frame spec support. By @zizzamia @daria-github @neekolas #285

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
### Patch Changes
66

7-
- b795268: - **feat**: exposed the `getName` and `getAvatar` utilities to assist in retrieving name and avatar identity information. These utilities come in handy when working with Next.js or any Node.js backend. By @zizzamia #265 #283
7+
- **feat**: exposed the `getName` and `getAvatar` utilities to assist in retrieving name and avatar identity information. These utilities come in handy when working with Next.js or any Node.js backend. By @zizzamia #265 #283 b795268
88

99
## 0.11.2
1010

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<p align="center">
22
<a href="https://onchainkit.xyz">
33
<picture>
4-
<source media="(prefers-color-scheme: dark)" srcset="./site/docs/public/logo/v0-11.png">
5-
<img alt="OnchainKit logo vibes" src="./site/docs/public/logo/v0-11.png" width="auto">
4+
<source media="(prefers-color-scheme: dark)" srcset="./site/docs/public/logo/v0-12.png">
5+
<img alt="OnchainKit logo vibes" src="./site/docs/public/logo/v0-12.png" width="auto">
66
</picture>
77
</a>
88
</p>

site/docs/pages/frame/types.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,13 @@ type FrameMetadataReact = FrameMetadataType & {
8484

8585
```ts
8686
type FrameMetadataType = {
87+
accepts?: {
88+
[protocolIdentifier: string]: string;
89+
}; // The minimum client protocol version accepted for the given protocol identifier.
8790
buttons?: [FrameButtonMetadata, ...FrameButtonMetadata[]]; // A list of strings which are the label for the buttons in the frame (max 4 buttons).
8891
image: string | FrameImageMetadata; // An image which must be smaller than 10MB and should have an aspect ratio of 1.91:1
8992
input?: FrameInputMetadata; // The text input to use for the Frame.
93+
isOpenFrame?: boolean; // A boolean indicating if the frame uses the Open Frames standard.
9094
postUrl?: string; // A valid POST URL to send the Signature Packet to.
9195
refreshPeriod?: number; // A period in seconds at which the app should expect the image to update.
9296
state?: object; // A string containing serialized state (e.g. JSON) passed to the frame server.

site/docs/public/logo/v0-12.png

1.25 MB
Loading

src/frame/components/FrameMetadata.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,39 @@ describe('FrameMetadata', () => {
422422
expect(meta.container.querySelector('meta[property="og:title"]')).toBeNull();
423423
expect(meta.container.querySelectorAll('meta').length).toBe(3);
424424
});
425+
426+
describe('when using isOpenFrame true', () => {
427+
it('renders', () => {
428+
const meta = render(<FrameMetadata isOpenFrame image="https://example.com/image.png" />);
429+
expect(meta.container.querySelectorAll('meta').length).toBe(5);
430+
});
431+
432+
it('renders with accepts', () => {
433+
const meta = render(
434+
<FrameMetadata
435+
accepts={{ xmtp: '1.0.0' }}
436+
isOpenFrame
437+
image="https://example.com/image.png"
438+
/>,
439+
);
440+
expect(
441+
meta.container.querySelector('meta[property="of:accepts:xmtp"]')?.getAttribute('content'),
442+
).toBe('1.0.0');
443+
expect(meta.container.querySelectorAll('meta').length).toBe(6);
444+
});
445+
446+
it('renders with image', () => {
447+
const meta = render(
448+
<FrameMetadata
449+
accepts={{ xmtp: '1.0.0' }}
450+
isOpenFrame
451+
image="https://example.com/image.png"
452+
/>,
453+
);
454+
expect(
455+
meta.container.querySelector('meta[property="of:image"]')?.getAttribute('content'),
456+
).toBe('https://example.com/image.png');
457+
expect(meta.container.querySelectorAll('meta').length).toBe(6);
458+
});
459+
});
425460
});

src/frame/components/FrameMetadata.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ import type { FrameMetadataReact } from '../types';
3333
* ```
3434
*
3535
* @param {FrameMetadataReact} props - The metadata for the frame.
36+
* @param {{ [protocolIdentifier: string]: string; }} accepts - The types of protocol the frame accepts.
3637
* @param {Array<{ label: string, action?: string }>} props.buttons - The buttons.
3738
* @param {string | { src: string, aspectRatio?: string }} props.image - The image URL.
3839
* @param {string} props.input - The input text.
40+
* @param {boolean} props.isOpenFrame: Whether the frame uses the Open Frames standard.
3941
* @param {string} props.ogDescription - The Open Graph description.
4042
* @param {string} props.ogTitle - The Open Graph title.
4143
* @param {string} props.postUrl - The post URL.
@@ -45,9 +47,11 @@ import type { FrameMetadataReact } from '../types';
4547
* @returns {React.ReactElement} The FrameMetadata component.
4648
*/
4749
export function FrameMetadata({
50+
accepts = {},
4851
buttons,
4952
image,
5053
input,
54+
isOpenFrame = false,
5155
ogDescription,
5256
ogTitle,
5357
postUrl,
@@ -130,6 +134,14 @@ export function FrameMetadata({
130134
{!!refreshPeriodToUse && (
131135
<meta property="fc:frame:refresh_period" content={refreshPeriodToUse.toString()} />
132136
)}
137+
138+
{!!isOpenFrame && <meta property="of:version" content="vNext" />}
139+
140+
{!!isOpenFrame && accepts && accepts['xmtp'] && (
141+
<meta property={`of:accepts:xmtp`} content={accepts['xmtp']} />
142+
)}
143+
144+
{!!isOpenFrame && imageSrc && <meta property="of:image" content={imageSrc} />}
133145
</Wrapper>
134146
);
135147
}

src/frame/getFrameHtmlResponse.test.ts

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,59 @@ describe('getFrameHtmlResponse', () => {
417417
);
418418
expect(html).not.toContain('<script>alert("XSS")</script>');
419419
});
420-
});
421420

422-
export { getFrameHtmlResponse };
421+
describe('when using isOpenFrame true', () => {
422+
it('should return correct HTML with all parameters', () => {
423+
const html = getFrameHtmlResponse({
424+
accepts: { 'protocol-identifier': '1.0.0' },
425+
buttons: [
426+
{ label: 'button1', action: 'post' },
427+
{ label: 'button2', action: 'mint', target: 'https://example.com' },
428+
{ label: 'button3', action: 'post_redirect' },
429+
{ label: 'button4' },
430+
],
431+
image: {
432+
src: 'https://example.com/image.png',
433+
aspectRatio: '1.91:1',
434+
},
435+
input: {
436+
text: 'Enter a message...',
437+
},
438+
isOpenFrame: true,
439+
postUrl: 'https://example.com/api/frame',
440+
refreshPeriod: 10,
441+
state: {
442+
counter: 1,
443+
},
444+
});
445+
446+
expect(html).toBe(`<!DOCTYPE html>
447+
<html>
448+
<head>
449+
<meta property="og:description" content="Frame description" />
450+
<meta property="og:title" content="Frame title" />
451+
<meta property="fc:frame" content="vNext" />
452+
<meta property="fc:frame:button:1" content="button1" />
453+
<meta property="fc:frame:button:1:action" content="post" />
454+
<meta property="fc:frame:button:2" content="button2" />
455+
<meta property="fc:frame:button:2:action" content="mint" />
456+
<meta property="fc:frame:button:2:target" content="https://example.com" />
457+
<meta property="fc:frame:button:3" content="button3" />
458+
<meta property="fc:frame:button:3:action" content="post_redirect" />
459+
<meta property="fc:frame:button:4" content="button4" />
460+
<meta property="og:image" content="https://example.com/image.png" />
461+
<meta property="fc:frame:image" content="https://example.com/image.png" />
462+
<meta property="fc:frame:image:aspect_ratio" content="1.91:1" />
463+
<meta property="fc:frame:input:text" content="Enter a message..." />
464+
<meta property="fc:frame:post_url" content="https://example.com/api/frame" />
465+
<meta property="fc:frame:refresh_period" content="10" />
466+
<meta property="fc:frame:state" content="%7B%22counter%22%3A1%7D" />
467+
<meta property="of:version" content="vNext" />
468+
<meta property="of:accepts:protocol-identifier" content="1.0.0" />
469+
<meta property="of:image" content="https://example.com/image.png" />
470+
471+
</head>
472+
</html>`);
473+
});
474+
});
475+
});

src/frame/getFrameHtmlResponse.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ type FrameMetadataHTMLResponse = FrameMetadataType & {
88
/**
99
* Returns an HTML string containing metadata for a new valid frame.
1010
*
11+
* @param accepts: The types of protocol the frame accepts.
1112
* @param buttons: The buttons to use for the frame.
1213
* @param image: The image to use for the frame.
1314
* @param input: The text input to use for the frame.
15+
* @param isOpenFrame: Whether the frame uses the Open Frames standard.
1416
* @param ogDescription: The Open Graph description for the frame.
1517
* @param ogTitle: The Open Graph title for the frame.
1618
* @param postUrl: The URL to post the frame to.
@@ -19,9 +21,11 @@ type FrameMetadataHTMLResponse = FrameMetadataType & {
1921
* @returns An HTML string containing metadata for the frame.
2022
*/
2123
function getFrameHtmlResponse({
24+
accepts = {},
2225
buttons,
2326
image,
2427
input,
28+
isOpenFrame = false,
2529
ogDescription,
2630
ogTitle,
2731
postUrl,
@@ -79,14 +83,27 @@ function getFrameHtmlResponse({
7983
? ` <meta property="fc:frame:refresh_period" content="${refreshPeriodToUse.toString()}" />\n`
8084
: '';
8185

86+
let ofHtml = '';
87+
// Set the Open Frames metadata
88+
if (isOpenFrame) {
89+
ofHtml = ` <meta property="of:version" content="vNext" />\n`;
90+
const ofAcceptsHtml = Object.keys(accepts)
91+
.map((protocolIdentifier) => {
92+
return ` <meta property="of:accepts:${protocolIdentifier}" content="${accepts[protocolIdentifier]}" />\n`;
93+
})
94+
.join('');
95+
const ofImageHtml = ` <meta property="of:image" content="${imgSrc}" />\n`;
96+
ofHtml += ofAcceptsHtml + ofImageHtml;
97+
}
98+
8299
// Return the HTML string containing all the metadata.
83100
let html = `<!DOCTYPE html>
84101
<html>
85102
<head>
86103
<meta property="og:description" content="${ogDescription || 'Frame description'}" />
87104
<meta property="og:title" content="${ogTitle || 'Frame title'}" />
88105
<meta property="fc:frame" content="vNext" />
89-
${buttonsHtml}${ogImageHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}${stateHtml}
106+
${buttonsHtml}${ogImageHtml}${imageHtml}${inputHtml}${postUrlHtml}${refreshPeriodHtml}${stateHtml}${ofHtml}
90107
</head>
91108
</html>`;
92109

src/frame/getFrameMetadata.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,4 +282,35 @@ describe('getFrameMetadata', () => {
282282
'%7B%22counter%22%3A1%2C%22xss%22%3A%22%3Cscript%3Ealert(%5C%22XSS%5C%22)%3C%2Fscript%3E%22%7D',
283283
});
284284
});
285+
286+
describe('when using isOpenFrame true', () => {
287+
it('should return the correct metadata', () => {
288+
expect(
289+
getFrameMetadata({
290+
accepts: { 'protocol-identifier': '1.0.0' },
291+
buttons: [
292+
{ label: 'button1', action: 'post' },
293+
{ label: 'button2', action: 'post_redirect' },
294+
{ label: 'button3' },
295+
],
296+
image: { src: 'image', aspectRatio: '1.91:1' },
297+
isOpenFrame: true,
298+
postUrl: 'post_url',
299+
}),
300+
).toEqual({
301+
'fc:frame': 'vNext',
302+
'fc:frame:button:1': 'button1',
303+
'fc:frame:button:1:action': 'post',
304+
'fc:frame:button:2': 'button2',
305+
'fc:frame:button:2:action': 'post_redirect',
306+
'fc:frame:button:3': 'button3',
307+
'fc:frame:image': 'image',
308+
'fc:frame:image:aspect_ratio': '1.91:1',
309+
'fc:frame:post_url': 'post_url',
310+
'of:version': 'vNext',
311+
'of:accepts:protocol-identifier': '1.0.0',
312+
'of:image': 'image',
313+
});
314+
});
315+
});
285316
});

0 commit comments

Comments
 (0)