From a144c145146c00a82f4b69bbc2c04f3be8d6830a Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Thu, 15 Feb 2024 14:41:19 -0800 Subject: [PATCH 1/5] Use latest version of FramesClient --- package-lock.json | 8 ++++---- package.json | 2 +- src/controllers/FullMessageController.tsx | 5 +++-- src/helpers/openFrames.ts | 6 ++++++ 4 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 src/helpers/openFrames.ts diff --git a/package-lock.json b/package-lock.json index 0e71fd43..9e978ed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@xmtp/content-type-remote-attachment": "^1.1.4", "@xmtp/content-type-reply": "^1.1.5", "@xmtp/experimental-content-type-screen-effect": "^1.0.2", - "@xmtp/frames-client": "0.2.0", + "@xmtp/frames-client": "0.3.2", "@xmtp/react-sdk": "^5.0.1", "buffer": "^6.0.3", "date-fns": "^2.29.3", @@ -14602,9 +14602,9 @@ } }, "node_modules/@xmtp/frames-client": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@xmtp/frames-client/-/frames-client-0.2.0.tgz", - "integrity": "sha512-ezjhMd+8lvGD7RW7wd1yGqdWwpH9ngk4XooAmZMS56KvME4952OaoFatPf55ZQBq/4tZOzGjAgSlAYGWDbizqA==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@xmtp/frames-client/-/frames-client-0.3.2.tgz", + "integrity": "sha512-61rxA7YcNUUKndQ9e5X44LNVwWJCrrZR6sBGOWLckfMK00LqRasoiLgom1PlR34c17a59PPZmagxoNQ2QCto7A==", "dependencies": { "@noble/hashes": "^1.3.3", "@xmtp/proto": "3.41.0-beta.5", diff --git a/package.json b/package.json index 82d57031..02fe1e81 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@xmtp/content-type-remote-attachment": "^1.1.4", "@xmtp/content-type-reply": "^1.1.5", "@xmtp/experimental-content-type-screen-effect": "^1.0.2", - "@xmtp/frames-client": "0.2.0", + "@xmtp/frames-client": "0.3.2", "@xmtp/react-sdk": "^5.0.1", "buffer": "^6.0.3", "date-fns": "^2.29.3", diff --git a/src/controllers/FullMessageController.tsx b/src/controllers/FullMessageController.tsx index 49a666e4..6e26687e 100644 --- a/src/controllers/FullMessageController.tsx +++ b/src/controllers/FullMessageController.tsx @@ -11,6 +11,7 @@ import MessageContentController from "./MessageContentController"; import { useXmtpStore } from "../store/xmtp"; import { Frame } from "../component-library/components/Frame/Frame"; import { getFrameInfo } from "../helpers/getFrameInfo"; +import { readMetadata } from "../helpers/openFrames"; interface FullMessageControllerProps { message: CachedMessageWithId; @@ -65,7 +66,7 @@ export const FullMessageController = ({ ], }); - const updatedFrameMetadata = await FramesClient.postToFrame( + const updatedFrameMetadata = await framesClient.proxy.post( frameInfo.postUrl, payload, ); @@ -86,7 +87,7 @@ export const FullMessageController = ({ const isUrl = !!word.match(urlRegex)?.[0]; if (isUrl) { - const metadata = await FramesClient.readMetadata(word); + const metadata = await readMetadata(word); if (metadata) { const info = getFrameInfo(metadata.extractedTags); setFrameInfo(info); diff --git a/src/helpers/openFrames.ts b/src/helpers/openFrames.ts new file mode 100644 index 00000000..bdce26eb --- /dev/null +++ b/src/helpers/openFrames.ts @@ -0,0 +1,6 @@ +import { OpenFramesProxy, type FramesApiResponse } from "@xmtp/frames-client"; + +const proxy = new OpenFramesProxy(); + +export const readMetadata = async (url: string): Promise => + proxy.readMetadata(url); From 5d01a10f9008f62e58db7ddc3ab7fcc9ab1fc323 Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:15:39 -0800 Subject: [PATCH 2/5] Use latest frames capabilities --- .../components/Frame/Frame.tsx | 14 +++--- src/controllers/FullMessageController.tsx | 36 +++++++++++----- src/helpers/getFrameInfo.ts | 43 +++++++++++++++++-- src/helpers/openFrames.ts | 11 +++++ 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/component-library/components/Frame/Frame.tsx b/src/component-library/components/Frame/Frame.tsx index 8a45474b..151771d5 100644 --- a/src/component-library/components/Frame/Frame.tsx +++ b/src/component-library/components/Frame/Frame.tsx @@ -1,10 +1,14 @@ +import type { FrameButton } from "../../../helpers/getFrameInfo"; import { GhostButton } from "../GhostButton/GhostButton"; type FrameProps = { image: string; title: string; - buttons: string[]; - handleClick: (buttonNumber: number) => Promise; + buttons: FrameButton[]; + handleClick: ( + buttonNumber: number, + action: FrameButton["action"], + ) => Promise; frameButtonUpdating: number; }; @@ -23,12 +27,12 @@ export const Frame = ({ return null; } const handlePress = () => { - void handleClick(index + 1); + void handleClick(index + 1, button.action); }; return ( 0} diff --git a/src/controllers/FullMessageController.tsx b/src/controllers/FullMessageController.tsx index 6e26687e..7b6c3906 100644 --- a/src/controllers/FullMessageController.tsx +++ b/src/controllers/FullMessageController.tsx @@ -10,6 +10,7 @@ import { classNames, shortAddress } from "../helpers"; import MessageContentController from "./MessageContentController"; import { useXmtpStore } from "../store/xmtp"; import { Frame } from "../component-library/components/Frame/Frame"; +import type { FrameButton } from "../helpers/getFrameInfo"; import { getFrameInfo } from "../helpers/getFrameInfo"; import { readMetadata } from "../helpers/openFrames"; @@ -22,7 +23,7 @@ interface FullMessageControllerProps { export type FrameInfo = { image: string; title: string; - buttons: string[]; + buttons: FrameButton[]; postUrl: string; }; @@ -39,11 +40,15 @@ export const FullMessageController = ({ const [frameInfo, setFrameInfo] = useState(undefined); const [frameButtonUpdating, setFrameButtonUpdating] = useState(0); - const handleFrameButtonClick = async (buttonIndex: number) => { + const handleFrameButtonClick = async ( + buttonIndex: number, + action: FrameButton["action"], + ) => { if (!frameInfo) { return; } const frameUrl = frameInfo.image; + const button = frameInfo.buttons[buttonIndex]; setFrameButtonUpdating(buttonIndex); @@ -65,15 +70,24 @@ export const FullMessageController = ({ conversation.peerAddress, ], }); - - const updatedFrameMetadata = await framesClient.proxy.post( - frameInfo.postUrl, - payload, - ); - const updatedFrameInfo = getFrameInfo(updatedFrameMetadata.extractedTags); - - setFrameInfo(updatedFrameInfo); - setFrameButtonUpdating(0); + if (action === "post") { + const updatedFrameMetadata = await framesClient.proxy.post( + button?.target || frameInfo.postUrl, + payload, + ); + const updatedFrameInfo = getFrameInfo(updatedFrameMetadata.extractedTags); + + setFrameInfo(updatedFrameInfo); + setFrameButtonUpdating(0); + } else if (action === "post_redirect") { + const { redirectedTo } = await framesClient.proxy.postRedirect( + button?.target || frameInfo.postUrl, + payload, + ); + window.open(redirectedTo, "_blank"); + } else if (action === "link" && button?.target) { + window.open(button.target, "_blank"); + } }; useEffect(() => { diff --git a/src/helpers/getFrameInfo.ts b/src/helpers/getFrameInfo.ts index d09b8d3f..f3511f66 100644 --- a/src/helpers/getFrameInfo.ts +++ b/src/helpers/getFrameInfo.ts @@ -1,21 +1,56 @@ +import { getButtonIndex, mediaUrl } from "./openFrames"; + const BUTTON_PREFIX = "fc:frame:button:"; const IMAGE_PREFIX = "fc:frame:image"; const POST_URL_PREFIX = "fc:frame:post_url"; const TITLE_PREFIX = "og:title"; +const ALLOWED_ACTIONS = ["post", "post_redirect", "link", "mint"] as const; + +export type FrameButton = { + text?: string; + action?: (typeof ALLOWED_ACTIONS)[number]; + target?: string; +}; + +function addButtonTag( + existingButtons: FrameButton[], + key: string, + value: string, +): FrameButton[] { + const buttons = existingButtons; + const buttonIndex = getButtonIndex(key); + if (buttonIndex) { + buttons[buttonIndex] = buttons[buttonIndex] || {}; + if ( + key.endsWith(":action") && + ALLOWED_ACTIONS.includes(value as (typeof ALLOWED_ACTIONS)[number]) + ) { + buttons[buttonIndex].action = value as (typeof ALLOWED_ACTIONS)[number]; + } else if (key.endsWith(":target")) { + buttons[buttonIndex].target = value; + } else if (key.endsWith(buttonIndex.toString())) { + buttons[buttonIndex].text = value; + } + } + return buttons; +} + export function getFrameInfo(extractedTags: Record) { - const buttons: string[] = []; + let buttons: FrameButton[] = []; let image = ""; let postUrl = ""; let title = ""; for (const key in extractedTags) { if (key) { if (key.startsWith(BUTTON_PREFIX)) { - const buttonIndex = parseInt(key.replace(BUTTON_PREFIX, ""), 10); - buttons[buttonIndex] = extractedTags[key]; + buttons = addButtonTag(buttons, key, extractedTags[key]); } if (key.startsWith(IMAGE_PREFIX)) { - image = extractedTags[key]; + const imageUrl = extractedTags[key]; + image = imageUrl.startsWith("data:") + ? extractedTags[key] + : mediaUrl(imageUrl); } if (key.startsWith(POST_URL_PREFIX)) { postUrl = extractedTags[key]; diff --git a/src/helpers/openFrames.ts b/src/helpers/openFrames.ts index bdce26eb..e274d354 100644 --- a/src/helpers/openFrames.ts +++ b/src/helpers/openFrames.ts @@ -1,6 +1,17 @@ import { OpenFramesProxy, type FramesApiResponse } from "@xmtp/frames-client"; const proxy = new OpenFramesProxy(); +const BUTTON_INDEX_REGEX = /fc:frame:button:(\d)(?:$|:).*/; export const readMetadata = async (url: string): Promise => proxy.readMetadata(url); + +export const mediaUrl = (url: string): string => proxy.mediaUrl(url); + +export const getButtonIndex = (property: string): number | undefined => { + const matches = property.match(BUTTON_INDEX_REGEX); + if (matches?.length === 2) { + return parseInt(matches[1], 10); + } + return undefined; +}; From 40f0dd4b6117cf93bafb9ddface655de272951fa Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:24:56 -0800 Subject: [PATCH 3/5] Remove useWalletClient stuff. Just use the client --- src/controllers/FullMessageController.tsx | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/controllers/FullMessageController.tsx b/src/controllers/FullMessageController.tsx index 7b6c3906..09031561 100644 --- a/src/controllers/FullMessageController.tsx +++ b/src/controllers/FullMessageController.tsx @@ -1,9 +1,7 @@ import type { CachedConversation, CachedMessageWithId } from "@xmtp/react-sdk"; -import { Client, useClient } from "@xmtp/react-sdk"; +import { useClient } from "@xmtp/react-sdk"; import { FramesClient } from "@xmtp/frames-client"; import { useEffect, useState } from "react"; -import type { PrivateKeyAccount, Transport, WalletClient } from "viem"; -import type { mainnet } from "wagmi"; import { useWalletClient } from "wagmi"; import { FullMessage } from "../component-library/components/FullMessage/FullMessage"; import { classNames, shortAddress } from "../helpers"; @@ -33,7 +31,6 @@ export const FullMessageController = ({ isReply, }: FullMessageControllerProps) => { const { client } = useClient(); - const { data: walletClient } = useWalletClient(); const conversationTopic = useXmtpStore((state) => state.conversationTopic); @@ -44,7 +41,7 @@ export const FullMessageController = ({ buttonIndex: number, action: FrameButton["action"], ) => { - if (!frameInfo) { + if (!frameInfo || !client) { return; } const frameUrl = frameInfo.image; @@ -52,23 +49,13 @@ export const FullMessageController = ({ setFrameButtonUpdating(buttonIndex); - const xmtpClient = await Client.create( - walletClient as WalletClient< - Transport, - typeof mainnet, - PrivateKeyAccount - >, - ); - const framesClient = new FramesClient(xmtpClient); + const framesClient = new FramesClient(client); const payload = await framesClient.signFrameAction({ frameUrl, buttonIndex, conversationTopic: conversationTopic as string, - participantAccountAddresses: [ - client?.address as string, - conversation.peerAddress, - ], + participantAccountAddresses: [client.address, conversation.peerAddress], }); if (action === "post") { const updatedFrameMetadata = await framesClient.proxy.post( From b2dec762d1e17b745ef2302ed4afd890ca723b05 Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:28:10 -0800 Subject: [PATCH 4/5] Remove unused import --- src/controllers/FullMessageController.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/FullMessageController.tsx b/src/controllers/FullMessageController.tsx index 09031561..30eef374 100644 --- a/src/controllers/FullMessageController.tsx +++ b/src/controllers/FullMessageController.tsx @@ -2,7 +2,6 @@ import type { CachedConversation, CachedMessageWithId } from "@xmtp/react-sdk"; import { useClient } from "@xmtp/react-sdk"; import { FramesClient } from "@xmtp/frames-client"; import { useEffect, useState } from "react"; -import { useWalletClient } from "wagmi"; import { FullMessage } from "../component-library/components/FullMessage/FullMessage"; import { classNames, shortAddress } from "../helpers"; import MessageContentController from "./MessageContentController"; From 0e3564174ca426c623a8895fbebcdc0976758525 Mon Sep 17 00:00:00 2001 From: Nicholas Molnar <65710+neekolas@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:24:42 -0800 Subject: [PATCH 5/5] Fix button indexing issue --- src/controllers/FullMessageController.tsx | 5 ++--- src/helpers/getFrameInfo.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/controllers/FullMessageController.tsx b/src/controllers/FullMessageController.tsx index 30eef374..e7bdee78 100644 --- a/src/controllers/FullMessageController.tsx +++ b/src/controllers/FullMessageController.tsx @@ -56,15 +56,13 @@ export const FullMessageController = ({ conversationTopic: conversationTopic as string, participantAccountAddresses: [client.address, conversation.peerAddress], }); - if (action === "post") { + if (action === "post" || !action) { const updatedFrameMetadata = await framesClient.proxy.post( button?.target || frameInfo.postUrl, payload, ); const updatedFrameInfo = getFrameInfo(updatedFrameMetadata.extractedTags); - setFrameInfo(updatedFrameInfo); - setFrameButtonUpdating(0); } else if (action === "post_redirect") { const { redirectedTo } = await framesClient.proxy.postRedirect( button?.target || frameInfo.postUrl, @@ -74,6 +72,7 @@ export const FullMessageController = ({ } else if (action === "link" && button?.target) { window.open(button.target, "_blank"); } + setFrameButtonUpdating(0); }; useEffect(() => { diff --git a/src/helpers/getFrameInfo.ts b/src/helpers/getFrameInfo.ts index f3511f66..139ae563 100644 --- a/src/helpers/getFrameInfo.ts +++ b/src/helpers/getFrameInfo.ts @@ -61,7 +61,7 @@ export function getFrameInfo(extractedTags: Record) { } } return { - buttons, + buttons: buttons.filter((b) => b), image, postUrl, title,