From cea73a70e36602592298a32dc2651c7c53c5188f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Kvasnic=CC=8Ca=CC=81k?= Date: Fri, 25 Oct 2024 09:08:15 +0200 Subject: [PATCH] feat: support new unstable api in debugger --- .changeset/twenty-pugs-roll.md | 7 + .../app/components/action-debugger.tsx | 13 +- .../debugger/app/components/cast-composer.tsx | 18 +- .../app/components/frame-debugger.tsx | 13 +- packages/debugger/app/debugger-page.tsx | 176 ++++++++++-------- 5 files changed, 118 insertions(+), 109 deletions(-) create mode 100644 .changeset/twenty-pugs-roll.md diff --git a/.changeset/twenty-pugs-roll.md b/.changeset/twenty-pugs-roll.md new file mode 100644 index 000000000..41a0bfcb9 --- /dev/null +++ b/.changeset/twenty-pugs-roll.md @@ -0,0 +1,7 @@ +--- +"frames.js": patch +"@frames.js/debugger": patch +"@frames.js/render": patch +--- + +feat: multi specification support diff --git a/packages/debugger/app/components/action-debugger.tsx b/packages/debugger/app/components/action-debugger.tsx index c6835584b..4e87c98d6 100644 --- a/packages/debugger/app/components/action-debugger.tsx +++ b/packages/debugger/app/components/action-debugger.tsx @@ -7,8 +7,6 @@ import { } from "@/components/ui/hover-card"; import { cn } from "@/lib/utils"; import { - type FarcasterFrameContext, - type FrameActionBodyPayload, OnComposeFormActionFuncReturnType, defaultTheme, } from "@frames.js/render"; @@ -33,7 +31,6 @@ import { Button } from "../../@/components/ui/button"; import { FrameDebugger } from "./frame-debugger"; import IconByName from "./octicons"; import { MockHubActionContext } from "../utils/mock-hub-utils"; -import { useFrame } from "@frames.js/render/use-frame"; import { WithTooltip } from "./with-tooltip"; import { useToast } from "@/components/ui/use-toast"; import type { CastActionDefinitionResponse } from "../frames/route"; @@ -42,7 +39,7 @@ import { AwaitableController } from "../lib/awaitable-controller"; import type { ComposerActionFormResponse } from "frames.js/types"; import { CastComposer, CastComposerRef } from "./cast-composer"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import type { FarcasterSigner } from "@frames.js/render/identity/farcaster"; +import { useFrame } from "@frames.js/render/unstable-use-frame"; type FrameDebuggerFramePropertiesTableRowsProps = { actionMetadataItem: CastActionDefinitionResponse; @@ -177,13 +174,7 @@ function ShortenedText({ type ActionDebuggerProps = { actionMetadataItem: CastActionDefinitionResponse; - farcasterFrameConfig: Parameters< - typeof useFrame< - FarcasterSigner | null, - FrameActionBodyPayload, - FarcasterFrameContext - > - >[0]; + farcasterFrameConfig: Parameters[0]; refreshUrl: (arg0?: string) => void; mockHubContext?: Partial; setMockHubContext?: Dispatch>>; diff --git a/packages/debugger/app/components/cast-composer.tsx b/packages/debugger/app/components/cast-composer.tsx index 8452fa809..3ee76200f 100644 --- a/packages/debugger/app/components/cast-composer.tsx +++ b/packages/debugger/app/components/cast-composer.tsx @@ -11,7 +11,6 @@ import { ExternalLinkIcon, } from "lucide-react"; import IconByName from "./octicons"; -import { useFrame } from "@frames.js/render/use-frame"; import { WithTooltip } from "./with-tooltip"; import type { FarcasterFrameContext, @@ -23,17 +22,12 @@ import { useToast } from "@/components/ui/use-toast"; import { ToastAction } from "@radix-ui/react-toast"; import Link from "next/link"; import type { FarcasterSigner } from "@frames.js/render/identity/farcaster"; +import { useFrame } from "@frames.js/render/unstable-use-frame"; type CastComposerProps = { composerAction: Partial; onComposerActionClick: (state: ComposerActionState) => any; - farcasterFrameConfig: Parameters< - typeof useFrame< - FarcasterSigner | null, - FrameActionBodyPayload, - FarcasterFrameContext - > - >[0]; + farcasterFrameConfig: Parameters[0]; }; export type CastComposerRef = { @@ -119,13 +113,7 @@ export const CastComposer = React.forwardRef< CastComposer.displayName = "CastComposer"; type CastEmbedPreviewProps = { - farcasterFrameConfig: Parameters< - typeof useFrame< - FarcasterSigner | null, - FrameActionBodyPayload, - FarcasterFrameContext - > - >[0]; + farcasterFrameConfig: Parameters[0]; url: string; onRemove: () => void; }; diff --git a/packages/debugger/app/components/frame-debugger.tsx b/packages/debugger/app/components/frame-debugger.tsx index dc0018f66..04f8a3041 100644 --- a/packages/debugger/app/components/frame-debugger.tsx +++ b/packages/debugger/app/components/frame-debugger.tsx @@ -63,6 +63,7 @@ import type { AnonymousSigner } from "@frames.js/render/identity/anonymous"; import type { LensSigner } from "@frames.js/render/identity/lens"; import type { FarcasterSigner } from "@frames.js/render/identity/farcaster"; import type { XmtpSigner } from "@frames.js/render/identity/xmtp"; +import type { UseFrameReturnValue } from "@frames.js/render/unstable-use-frame"; type FrameDiagnosticsProps = { stackItem: FramesStackItem; @@ -160,8 +161,8 @@ function FrameDiagnostics({ stackItem }: FrameDiagnosticsProps) { {stackItem.speed > 5 ? `Request took more than 5s (${stackItem.speed} seconds). This may be normal: first request will take longer in development (as next.js builds), but in production, clients will timeout requests after 5s` : stackItem.speed > 4 - ? `Warning: Request took more than 4s (${stackItem.speed} seconds). Requests will fail at 5s. This may be normal: first request will take longer in development (as next.js builds), but in production, if there's variance here, requests could fail in production if over 5s` - : `${stackItem.speed} seconds`} + ? `Warning: Request took more than 4s (${stackItem.speed} seconds). Requests will fail at 5s. This may be normal: first request will take longer in development (as next.js builds), but in production, if there's variance here, requests could fail in production if over 5s` + : `${stackItem.speed} seconds`} {properties.validProperties.map(([propertyKey, value]) => { @@ -282,9 +283,7 @@ const FramesRequestCardContentIcon: React.FC<{ const FramesRequestCardContent: React.FC<{ stack: FramesStack; - fetchFrame: FrameState< - FarcasterSigner | XmtpSigner | LensSigner | AnonymousSigner | null - >["fetchFrame"]; + fetchFrame: UseFrameReturnValue["fetchFrame"]; }> = ({ fetchFrame, stack }) => { return stack.map((frameStackItem, i) => { return ( @@ -369,10 +368,10 @@ type FrameDebuggerSharedProps = { type FrameDebuggerProps = FrameDebuggerSharedProps & ( | { - useFrameHook: () => FrameState; + useFrameHook: () => UseFrameReturnValue; } | { - frameState: FrameState; + frameState: UseFrameReturnValue; } ); diff --git a/packages/debugger/app/debugger-page.tsx b/packages/debugger/app/debugger-page.tsx index 6b1b60bed..7d7fca085 100644 --- a/packages/debugger/app/debugger-page.tsx +++ b/packages/debugger/app/debugger-page.tsx @@ -4,16 +4,12 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { - type UseFrameOptions, fallbackFrameContext, type OnTransactionFunc, type OnSignatureFunc, - type FrameActionBodyPayload, type OnConnectWalletFunc, - type FarcasterFrameContext, } from "@frames.js/render"; import { attribution } from "@frames.js/render/farcaster"; -import { useFrame } from "@frames.js/render/use-frame"; import { ConnectButton, useConnectModal } from "@rainbow-me/rainbowkit"; import { sendTransaction, signTypedData, switchChain } from "@wagmi/core"; import { useRouter } from "next/navigation"; @@ -39,7 +35,10 @@ import { ActionDebugger, ActionDebuggerRef, } from "./components/action-debugger"; -import type { ParseResult } from "frames.js/frame-parsers"; +import type { + ParseFramesWithReportsResult, + ParseResult, +} from "frames.js/frame-parsers"; import { Loader2 } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; import { ToastAction } from "@/components/ui/toast"; @@ -66,6 +65,10 @@ import { useXmtpFrameContext, useXmtpIdentity, } from "@frames.js/render/identity/xmtp"; +import { + useFrame, + type UseFrameOptions, +} from "@frames.js/render/unstable-use-frame"; const FALLBACK_URL = process.env.NEXT_PUBLIC_DEBUGGER_DEFAULT_URL || "http://localhost:3000"; @@ -121,7 +124,8 @@ export default function DebuggerPage({ return undefined; } }, [searchParams.url]); - const [initialFrame, setInitialFrame] = useState(); + const [initialFrame, setInitialFrame] = + useState(); const [initialAction, setInitialAction] = useState(); const [mockHubContext, setMockHubContext] = useState< @@ -175,7 +179,6 @@ export default function DebuggerPage({ const searchParams = new URLSearchParams({ url: newUrl || url, - specification: protocolConfiguration.specification, actions: "true", }); const proxiedUrl = `/frames?${searchParams.toString()}`; @@ -197,7 +200,7 @@ export default function DebuggerPage({ setInitialAction(json); setInitialFrame(undefined); } else if (json.type === "frame") { - setInitialFrame(json[protocolConfiguration.specification]); + setInitialFrame(json); setInitialAction(undefined); } }) @@ -299,8 +302,6 @@ export default function DebuggerPage({ }, }); - const anonymousFrameContext = {}; - const onConnectWallet: OnConnectWalletFunc = useCallback(async () => { if (!openConnectModal) { throw new Error(`openConnectModal not implemented`); @@ -425,18 +426,65 @@ export default function DebuggerPage({ [account.address, currentChainId, config, openConnectModal, toast] ); - const useFrameConfig: Omit< - UseFrameOptions, FrameActionBodyPayload>, - "signerState" | "specification" - > = useMemo( + const useFrameConfig: UseFrameOptions = useMemo( () => ({ homeframeUrl: url, frame: initialFrame, frameActionProxy: "/frames", frameGetProxy: "/frames", - frameContext: { - ...fallbackFrameContext, - address: account.address || fallbackFrameContext.address, + resolveCastOrComposerActionSigner() { + console.log("resolve cast or composer action signer"); + + return { + signerState: farcasterSignerState, + frameContext: { + ...farcasterFrameContext.frameContext, + address: + account.address || farcasterFrameContext.frameContext.address, + }, + }; + }, + resolveSpecification() { + console.log("resolve spec"); + if ( + !protocolConfiguration?.protocol || + protocolConfiguration.protocol === "farcaster" + ) { + return { + frameContext: { + ...fallbackFrameContext, + address: account.address || fallbackFrameContext.address, + }, + signerState: farcasterSignerState, + specification: "farcaster", + }; + } + + switch (protocolConfiguration.protocol) { + case "anonymous": { + return { + specification: "openframes", + frameContext: {}, + signerState: anonymousSignerState, + }; + } + case "lens": { + return { + specification: "openframes", + frameContext: lensFrameContext.frameContext, + signerState: lensSignerState, + }; + } + case "xmtp": { + return { + specification: "openframes", + frameContext: xmtpFrameContext.frameContext, + signerState: xmtpSignerState, + }; + } + default: + throw new Error("Unknown protocol"); + } }, connectedAddress: account.address, extraButtonRequestPayload: { mockData: mockHubContext }, @@ -561,32 +609,53 @@ export default function DebuggerPage({ }), [ account.address, + anonymousSignerState, + farcasterFrameContext.frameContext, + farcasterSignerState, initialFrame, + lensFrameContext.frameContext, + lensSignerState, mockHubContext, onConnectWallet, onSignature, onTransaction, openConnectModal, + protocolConfiguration?.protocol, toast, url, + xmtpFrameContext.frameContext, + xmtpSignerState, ] ); - const farcasterFrameConfig: UseFrameOptions< - FarcasterSigner | null, - FrameActionBodyPayload, - FarcasterFrameContext - > = useMemo(() => { + const farcasterFrameConfig: UseFrameOptions = useMemo(() => { const attributionData = process.env.NEXT_PUBLIC_FARCASTER_ATTRIBUTION_FID ? attribution(parseInt(process.env.NEXT_PUBLIC_FARCASTER_ATTRIBUTION_FID)) : undefined; return { ...useFrameConfig, - signerState: farcasterSignerState, - specification: "farcaster", - frameContext: { - ...farcasterFrameContext.frameContext, - address: account.address || farcasterFrameContext.frameContext.address, + resolveCastOrComposerActionSigner() { + console.log("resolve cast or composer signer"); + return { + signerState: farcasterSignerState, + frameContext: { + ...farcasterFrameContext.frameContext, + address: + account.address || farcasterFrameContext.frameContext.address, + }, + }; + }, + resolveSpecification() { + console.log("resolve farcaster spec"); + return { + frameContext: { + ...farcasterFrameContext.frameContext, + address: + account.address || farcasterFrameContext.frameContext.address, + }, + signerState: farcasterSignerState, + specification: "farcaster", + }; }, transactionDataSuffix: attributionData, }; @@ -597,55 +666,10 @@ export default function DebuggerPage({ useFrameConfig, ]); - const useFrameHook = useMemo(() => { - return () => { - const selectedProtocol = protocolConfiguration?.protocol ?? "farcaster"; - - switch (selectedProtocol) { - case "anonymous": { - // eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger - return useFrame({ - ...useFrameConfig, - signerState: anonymousSignerState, - specification: "openframes", - frameContext: anonymousFrameContext, - }); - } - case "lens": { - // eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger - return useFrame({ - ...useFrameConfig, - signerState: lensSignerState, - specification: "openframes", - frameContext: lensFrameContext.frameContext, - }); - } - case "xmtp": { - // eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger - return useFrame({ - ...useFrameConfig, - signerState: xmtpSignerState, - specification: "openframes", - frameContext: xmtpFrameContext.frameContext, - }); - } - default: { - // eslint-disable-next-line react-hooks/rules-of-hooks -- this is used as a hook in FrameDebugger - return useFrame(farcasterFrameConfig); - } - } - }; - }, [ - anonymousSignerState, - anonymousFrameContext, - farcasterFrameConfig, - lensFrameContext.frameContext, - lensSignerState, - protocolConfiguration?.protocol, - useFrameConfig, - xmtpFrameContext.frameContext, - xmtpSignerState, - ]); + const useFrameHook = useCallback(() => { + // eslint-disable-next-line react-hooks/rules-of-hooks -- hook is called in component + return useFrame(useFrameConfig); + }, [useFrameConfig]); return (