Skip to content

Commit

Permalink
feat: transform raw data into FrameMetadata (framegear) (#205)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasc authored Mar 7, 2024
1 parent 0628ecb commit 6ef37ea
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 35 deletions.
2 changes: 1 addition & 1 deletion framegear/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test": "jest"
},
"dependencies": {
"@coinbase/onchainkit": "0.9.4",
"@coinbase/onchainkit": "0.10.0",
"@radix-ui/react-icons": "^1.3.0",
"jotai": "^2.6.4",
"next": "14.1.0",
Expand Down
57 changes: 57 additions & 0 deletions framegear/utils/frameResultToFrameMetadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { frameResultToFrameMetadata } from './frameResultToFrameMetadata';

describe('frameResultToFrameMetadata', () => {
const baseResult = {
'fc:frame:button:1': 'Button 1',
'fc:frame:button:1:action': 'Action 1',
'fc:frame:button:1:target': 'Target 1',
'fc:frame:image': 'Image URL',
'fc:frame:input': 'Input Text',
'fc:frame:post_url': 'Post URL',
'fc:frame:state': JSON.stringify({ key: 'value' }),
'fc:frame:refresh_period': '10',
};

it('should correctly map frame result to frame metadata', () => {
const metadata = frameResultToFrameMetadata(baseResult);

expect(metadata).toEqual({
buttons: [
{
action: 'Action 1',
label: 'Button 1',
target: 'Target 1',
},
undefined,
undefined,
undefined,
],
image: 'Image URL',
input: { text: 'Input Text' },
postUrl: 'Post URL',
state: { key: 'value' },
refreshPeriod: 10,
});
});

it('should handle missing optional fields', () => {
const result = { ...baseResult };
delete (result as any)['fc:frame:button:1'];
delete (result as any)['fc:frame:image'];
delete (result as any)['fc:frame:input'];
delete (result as any)['fc:frame:post_url'];
delete (result as any)['fc:frame:state'];
delete (result as any)['fc:frame:refresh_period'];

const metadata = frameResultToFrameMetadata(result);

expect(metadata).toEqual({
buttons: [undefined, undefined, undefined, undefined],
image: undefined,
input: undefined,
postUrl: undefined,
state: undefined,
refreshPeriod: undefined,
});
});
});
23 changes: 23 additions & 0 deletions framegear/utils/frameResultToFrameMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FrameMetadataType } from '@coinbase/onchainkit';

export function frameResultToFrameMetadata(result: Record<string, string>): FrameMetadataType {
const buttons = [1, 2, 3, 4].map((idx) =>
result[`fc:frame:button:${idx}`]
? {
action: result[`fc:frame:button:${idx}:action`],
label: result[`fc:frame:button:${idx}`],
target: result[`fc:frame:button:${idx}:target`],
}
: undefined,
);
const image = result['fc:frame:image'];
const inputText = result['fc:frame:input'];
const input = inputText ? { text: inputText } : undefined;
const postUrl = result['fc:frame:post_url'];
const rawState = result['fc:frame:state'];
const rawRefreshPeriod = result['fc:frame:refresh_period'];
const refreshPeriod = rawRefreshPeriod ? parseInt(rawRefreshPeriod, 10) : undefined;
const state = rawState ? JSON.parse(result['fc:frame:state']) : undefined;

return { buttons: buttons as any, image, input, postUrl, state, refreshPeriod };
}
83 changes: 55 additions & 28 deletions framegear/utils/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('schema validation', () => {
).toBe(true);
});

it('fails when button action is not "post" or "post_url"', () => {
it('fails when button action is not "post" or "post_redirect"', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
Expand All @@ -80,20 +80,20 @@ describe('schema validation', () => {
).toBe(false);
});

it('succeeds when button action is "post", "post_url", "mint", or "link"', () => {
it('succeeds when button action is "post", "post_redirect", "mint", or "link"', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:button:1': 'henlo',
'fc:frame:button:1:action': 'post',
'fc:frame:button:2': 'henlo',
'fc:frame:button:2:action': 'post_url',
'fc:frame:button:2:action': 'post_redirect',
'fc:frame:button:3': 'henlo',
'fc:frame:button:3:action': 'mint',
'fc:frame:button:4': 'henlo',
'fc:frame:button:4:action': 'link',
}),
).toBe(false);
).toBe(true);
});
});

Expand Down Expand Up @@ -150,32 +150,59 @@ describe('schema validation', () => {
).toBe(false);
});
});
});

describe('aspect_ratio', () => {
it('succeeds when 1:1', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1:1',
}),
).toBe(true);
});
it('succeeds when 1:1', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1.91:1',
}),
).toBe(true);
describe('state', () => {
it('succeeds when state is less than 4096 bytes', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:state': '{"hello": "goodbye"}',
}),
).toBe(true);
});
it('succeeds when state is exactly 4096 bytes', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:state': new Array(4096).fill('a').join(''),
}),
).toBe(true);
});
it('fails when state exceeds 4096 bytes', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:state': new Array(4097).fill('a').join(''),
}),
).toBe(false);
});
});
it('fails when some other value', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1.618:1',
}),
).toBe(false);

describe('aspect_ratio', () => {
it('succeeds when 1:1', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1:1',
}),
).toBe(true);
});
it('succeeds when 1.91:1', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1.91:1',
}),
).toBe(true);
});
it('fails when some other value', () => {
expect(
vNextSchema.isValidSync({
...baseGoodDefinition,
'fc:frame:image:aspect_ratio': '1.618:1',
}),
).toBe(false);
});
});
});
});
9 changes: 8 additions & 1 deletion framegear/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const vNextSchema = yup.object({
.optional()
.matches(
/^post$|^post_redirect$|^mint$|^link$/,
`button action must be "post" or "post_url". Failed on index: ${index}`,
`button action must be "post" or "post_redirect". Failed on index: ${index}`,
),
}),
{},
Expand Down Expand Up @@ -73,6 +73,13 @@ export const vNextSchema = yup.object({
.string()
.optional()
.matches(/^1:1$|^1.91:1$/),
'fc:frame:state': yup
.string()
.optional()
.test('state-has-valid-size', 'frame:state has maximum size of 4096 bytes', (value) => {
// test only fires when `value` is defined
return new Blob([value!]).size <= 4096;
}),
});

// This interface doesn't fully encapsulate the dynamically defined types. Do we even need it?
Expand Down
11 changes: 6 additions & 5 deletions framegear/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -438,17 +438,18 @@ __metadata:
languageName: node
linkType: hard

"@coinbase/onchainkit@npm:0.9.4":
version: 0.9.4
resolution: "@coinbase/onchainkit@npm:0.9.4"
"@coinbase/onchainkit@npm:0.10.0":
version: 0.10.0
resolution: "@coinbase/onchainkit@npm:0.10.0"
peerDependencies:
"@tanstack/react-query": ^5
"@xmtp/frames-validator": ^0.5.0
graphql: ^14
graphql-request: ^6
react: ^18
react-dom: ^18
viem: ^2.7.0
checksum: 0e7ea8c3eda0d46f3eb690dc04f02db13fd0304b89150961f4047b33ef218182a2e04b4306889647becc679bdc681e2bc5cf0f604f968bd5af97328241305398
checksum: f1b3bb3d805703c3fcb5e28821971efbaa098cd7729995ac8a8502cdec38395cbcbcac81442f8ce45a30c333ecafaaeaa368fce77ca69732f045c88ec93342e8
languageName: node
linkType: hard

Expand Down Expand Up @@ -3193,7 +3194,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "framegear@workspace:."
dependencies:
"@coinbase/onchainkit": "npm:0.9.4"
"@coinbase/onchainkit": "npm:0.10.0"
"@radix-ui/react-icons": "npm:^1.3.0"
"@testing-library/jest-dom": "npm:^6.4.2"
"@testing-library/react": "npm:^14.2.1"
Expand Down

0 comments on commit 6ef37ea

Please sign in to comment.