Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Better Handling of FrameButton post actions #1053

Merged
merged 8 commits into from
Aug 15, 2024
1 change: 1 addition & 0 deletions src/frame/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type FrameButtonMetadata =
| {
action?: 'post' | 'post_redirect';
label: string;
postUrl?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of the Frame Spec? And where is this writtend?

Copy link
Contributor Author

@brendan-defi brendan-defi Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Per Button Actions, we are told that a post should send an HTTP request to the button's post_url.

But as currently configured, getFrameMetadata does not set a fc:frame:button:[id]:post_url property for post actions, only tx actions:

  if (buttons) {
    buttons.forEach((button, index) => {
      metadata[`fc:frame:button:${index + 1}`] = button.label;
      if (button.action) {
        metadata[`fc:frame:button:${index + 1}:action`] = button.action;
      }
      if (button.target) {
        metadata[`fc:frame:button:${index + 1}:target`] = button.target;
      }
      if (button.action && button.action === 'tx' && button.postUrl) {
        metadata[`fc:frame:button:${index + 1}:post_url`] = button.postUrl;
      }
    });
  }

The above change to FrameButtonMetadata is required for strict type-checking of the getFrameMetadata logic.

target?: string;
}
| {
Expand Down
33 changes: 33 additions & 0 deletions src/frame/utils/getFrameMetadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,39 @@ describe('getFrameMetadata', () => {
});
});

it('should return the correct metadata with action post and post_url', () => {
expect(
getFrameMetadata({
buttons: [
{
label: 'Button1',
action: 'post',
postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX',
},
{
label: 'Button2',
action: 'post',
postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY',
},
],
image: 'https://zizzamia.xyz/park-1.png',
postUrl: 'https://zizzamia.xyz/api/frame',
}),
).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'Button1',
'fc:frame:button:1:action': 'post',
'fc:frame:button:1:post_url':
'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX',
'fc:frame:button:2': 'Button2',
'fc:frame:button:2:action': 'post',
'fc:frame:button:2:post_url':
'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY',
'fc:frame:image': 'https://zizzamia.xyz/park-1.png',
'fc:frame:post_url': 'https://zizzamia.xyz/api/frame',
});
});

it('should not render action target if action is not link, mint or tx', () => {
expect(
getFrameMetadata({
Expand Down
14 changes: 2 additions & 12 deletions src/frame/utils/getFrameMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FrameMetadataResponse, FrameMetadataType } from '../types';
import { setFrameMetadataButtons } from './setFrameMetadataButtons';

/**
* This function generates the metadata for a Farcaster Frame.
Expand Down Expand Up @@ -36,18 +37,7 @@ export const getFrameMetadata = ({
metadata['fc:frame:input:text'] = input.text;
}
if (buttons) {
buttons.forEach((button, index) => {
metadata[`fc:frame:button:${index + 1}`] = button.label;
if (button.action) {
metadata[`fc:frame:button:${index + 1}:action`] = button.action;
}
if (button.target) {
metadata[`fc:frame:button:${index + 1}:target`] = button.target;
}
if (button.action && button.action === 'tx' && button.postUrl) {
metadata[`fc:frame:button:${index + 1}:post_url`] = button.postUrl;
}
});
setFrameMetadataButtons(metadata, buttons);
}
if (postUrlToUse) {
metadata['fc:frame:post_url'] = postUrlToUse;
Expand Down
126 changes: 126 additions & 0 deletions src/frame/utils/setFrameMetadataButtons.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, expect, it } from 'vitest';
import { setFrameMetadataButtons } from './setFrameMetadataButtons';

describe('setFrameMetadataButtons', () => {
it('should return no button metadata', () => {
const testMetadata = {
'fc:frame': 'vNext',
};
setFrameMetadataButtons(testMetadata, undefined);

expect(testMetadata).toEqual({
'fc:frame': 'vNext',
});
});

it('should return the correct metadata', () => {
const testMetadata = {
'fc:frame': 'vNext',
};
setFrameMetadataButtons(testMetadata, [
{ label: 'button1', action: 'post' },
{ label: 'button2', action: 'post_redirect' },
{ label: 'button3' },
]);

expect(testMetadata).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'button1',
'fc:frame:button:1:action': 'post',
'fc:frame:button:2': 'button2',
'fc:frame:button:2:action': 'post_redirect',
'fc:frame:button:3': 'button3',
});
});

it('should return the correct metadata for button with action tx and target', () => {
const testMetadata = { 'fc:frame': 'vNext' };
setFrameMetadataButtons(testMetadata, [
{
label: 'Button1',
action: 'tx',
target: 'https://zizzamia.xyz/api/frame/tx',
},
]);

expect(testMetadata).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'Button1',
'fc:frame:button:1:action': 'tx',
'fc:frame:button:1:target': 'https://zizzamia.xyz/api/frame/tx',
});
});

it('should return the correct metadata for button with action post and post_url', () => {
const testMetadata = { 'fc:frame': 'vNext' };
setFrameMetadataButtons(testMetadata, [
{
label: 'Button1',
action: 'post',
postUrl: 'https://zizzamia.xyz/api/frame/post_url',
},
]);

expect(testMetadata).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'Button1',
'fc:frame:button:1:action': 'post',
'fc:frame:button:1:post_url': 'https://zizzamia.xyz/api/frame/post_url',
});
});

it('should return the correct metadata for buttons with action tx and custom targets', () => {
const testMetadata = { 'fc:frame': 'vNext' };
setFrameMetadataButtons(testMetadata, [
{
label: 'Button1',
action: 'tx',
target: 'https://zizzamia.xyz/api/frame/tx?queryParam=XXX',
},
{
label: 'Button2',
action: 'tx',
target: 'https://zizzamia.xyz/api/frame/tx?queryParam=YYY',
},
]);

expect(testMetadata).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'Button1',
'fc:frame:button:1:action': 'tx',
'fc:frame:button:1:target':
'https://zizzamia.xyz/api/frame/tx?queryParam=XXX',
'fc:frame:button:2': 'Button2',
'fc:frame:button:2:action': 'tx',
'fc:frame:button:2:target':
'https://zizzamia.xyz/api/frame/tx?queryParam=YYY',
});
});

it('should return the correct metadata for buttons with action post and custom post_urls', () => {
const testMetadata = { 'fc:frame': 'vNext' };
setFrameMetadataButtons(testMetadata, [
{
label: 'Button1',
action: 'post',
postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX',
},
{
label: 'Button2',
action: 'post',
postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY',
},
]);
expect(testMetadata).toEqual({
'fc:frame': 'vNext',
'fc:frame:button:1': 'Button1',
'fc:frame:button:1:action': 'post',
'fc:frame:button:1:post_url':
'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX',
'fc:frame:button:2': 'Button2',
'fc:frame:button:2:action': 'post',
'fc:frame:button:2:post_url':
'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY',
});
});
});
27 changes: 27 additions & 0 deletions src/frame/utils/setFrameMetadataButtons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { FrameMetadataType } from '../types';

export function setFrameMetadataButtons(
metadata: Record<string, string>,
buttons: FrameMetadataType['buttons'],
) {
if (!buttons) {
return;
}

buttons.forEach((button, index) => {
metadata[`fc:frame:button:${index + 1}`] = button.label;
if (button.action) {
metadata[`fc:frame:button:${index + 1}:action`] = button.action;
}
if (button.target) {
metadata[`fc:frame:button:${index + 1}:target`] = button.target;
}
if (
button.action &&
(button.action === 'tx' || button.action === 'post') &&
button.postUrl
) {
metadata[`fc:frame:button:${index + 1}:post_url`] = button.postUrl;
}
});
}
Loading